pages_domain_spec.rb 9.9 KB
Newer Older
1 2
# frozen_string_literal: true

K
Kamil Trzcinski 已提交
3 4
require 'spec_helper'

5
describe PagesDomain do
6 7 8 9
  using RSpec::Parameterized::TableSyntax

  subject(:pages_domain) { described_class.new }

K
Kamil Trzcinski 已提交
10 11 12
  describe 'associations' do
    it { is_expected.to belong_to(:project) }
  end
13

14
  describe 'validate domain' do
D
Drew Blessing 已提交
15
    subject(:pages_domain) { build(:pages_domain, domain: domain) }
K
Kamil Trzcinski 已提交
16 17 18 19

    context 'is unique' do
      let(:domain) { 'my.domain.com' }

20
      it { is_expected.to validate_uniqueness_of(:domain).case_insensitive }
K
Kamil Trzcinski 已提交
21 22
    end

R
Rob Watson 已提交
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
    describe "hostname" do
      {
        'my.domain.com'    => true,
        '123.456.789'      => true,
        '0x12345.com'      => true,
        '0123123'          => true,
        '_foo.com'         => false,
        'reserved.com'     => false,
        'a.reserved.com'   => false,
        nil                => false
      }.each do |value, validity|
        context "domain #{value.inspect} validity" do
          before do
            allow(Settings.pages).to receive(:host).and_return('reserved.com')
          end

          let(:domain) { value }

          it { expect(pages_domain.valid?).to eq(validity) }
        end
      end
    end

    describe "HTTPS-only" do
      using RSpec::Parameterized::TableSyntax

      let(:domain) { 'my.domain.com' }

      let(:project) do
        instance_double(Project, pages_https_only?: pages_https_only)
      end

      let(:pages_domain) do
        build(:pages_domain, certificate: certificate, key: key).tap do |pd|
          allow(pd).to receive(:project).and_return(project)
          pd.valid?
D
Drew Blessing 已提交
59
        end
R
Rob Watson 已提交
60
      end
D
Drew Blessing 已提交
61

R
Rob Watson 已提交
62 63 64 65 66 67 68 69 70 71 72 73 74
      where(:pages_https_only, :certificate, :key, :errors_on) do
        attributes = attributes_for(:pages_domain)
        cert, key = attributes.fetch_values(:certificate, :key)

        true  | nil  | nil | %i(certificate key)
        true  | cert | nil | %i(key)
        true  | nil  | key | %i(certificate key)
        true  | cert | key | []
        false | nil  | nil | []
        false | cert | nil | %i(key)
        false | nil  | key | %i(key)
        false | cert | key | []
      end
D
Drew Blessing 已提交
75

R
Rob Watson 已提交
76 77 78 79
      with_them do
        it "is adds the expected errors" do
          expect(pages_domain.errors.keys).to eq errors_on
        end
D
Drew Blessing 已提交
80
      end
K
Kamil Trzcinski 已提交
81 82 83 84 85 86
    end
  end

  describe 'validate certificate' do
    subject { domain }

R
Rob Watson 已提交
87 88
    context 'with matching key' do
      let(:domain) { build(:pages_domain) }
K
Kamil Trzcinski 已提交
89

R
Rob Watson 已提交
90
      it { is_expected.to be_valid }
K
Kamil Trzcinski 已提交
91 92
    end

R
Rob Watson 已提交
93 94
    context 'when no certificate is specified' do
      let(:domain) { build(:pages_domain, :without_certificate) }
K
Kamil Trzcinski 已提交
95

G
Grzegorz Bizon 已提交
96
      it { is_expected.not_to be_valid }
K
Kamil Trzcinski 已提交
97 98
    end

R
Rob Watson 已提交
99 100
    context 'when no key is specified' do
      let(:domain) { build(:pages_domain, :without_key) }
K
Kamil Trzcinski 已提交
101

R
Rob Watson 已提交
102
      it { is_expected.not_to be_valid }
K
Kamil Trzcinski 已提交
103 104 105
    end

    context 'for not matching key' do
R
Rob Watson 已提交
106
      let(:domain) { build(:pages_domain, :with_missing_chain) }
K
Kamil Trzcinski 已提交
107

G
Grzegorz Bizon 已提交
108
      it { is_expected.not_to be_valid }
K
Kamil Trzcinski 已提交
109 110 111
    end
  end

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
  describe 'validations' do
    it { is_expected.to validate_presence_of(:verification_code) }
  end

  describe '#verification_code' do
    subject { pages_domain.verification_code }

    it 'is set automatically with 128 bits of SecureRandom data' do
      expect(SecureRandom).to receive(:hex).with(16) { 'verification code' }

      is_expected.to eq('verification code')
    end
  end

  describe '#keyed_verification_code' do
    subject { pages_domain.keyed_verification_code }

    it { is_expected.to eq("gitlab-pages-verification-code=#{pages_domain.verification_code}") }
  end

  describe '#verification_domain' do
    subject { pages_domain.verification_domain }

    it { is_expected.to be_nil }

    it 'is a well-known subdomain if the domain is present' do
      pages_domain.domain = 'example.com'

      is_expected.to eq('_gitlab-pages-verification-code.example.com')
    end
  end

144
  describe '#url' do
K
Kamil Trzcinski 已提交
145 146
    subject { domain.url }

R
Rob Watson 已提交
147
    let(:domain) { build(:pages_domain) }
K
Kamil Trzcinski 已提交
148

R
Rob Watson 已提交
149
    it { is_expected.to eq("https://#{domain.domain}") }
K
Kamil Trzcinski 已提交
150

R
Rob Watson 已提交
151 152
    context 'without the certificate' do
      let(:domain) { build(:pages_domain, :without_certificate) }
K
Kamil Trzcinski 已提交
153

R
Rob Watson 已提交
154
      it { is_expected.to eq("http://#{domain.domain}") }
K
Kamil Trzcinski 已提交
155 156 157
    end
  end

158
  describe '#has_matching_key?' do
K
Kamil Trzcinski 已提交
159 160
    subject { domain.has_matching_key? }

R
Rob Watson 已提交
161
    let(:domain) { build(:pages_domain) }
K
Kamil Trzcinski 已提交
162

R
Rob Watson 已提交
163
    it { is_expected.to be_truthy }
K
Kamil Trzcinski 已提交
164 165

    context 'for invalid key' do
R
Rob Watson 已提交
166
      let(:domain) { build(:pages_domain, :with_missing_chain) }
K
Kamil Trzcinski 已提交
167 168 169 170 171

      it { is_expected.to be_falsey }
    end
  end

172
  describe '#has_intermediates?' do
K
Kamil Trzcinski 已提交
173 174 175
    subject { domain.has_intermediates? }

    context 'for self signed' do
R
Rob Watson 已提交
176
      let(:domain) { build(:pages_domain) }
K
Kamil Trzcinski 已提交
177 178 179 180

      it { is_expected.to be_truthy }
    end

181 182
    context 'for missing certificate chain' do
      let(:domain) { build(:pages_domain, :with_missing_chain) }
K
Kamil Trzcinski 已提交
183 184 185

      it { is_expected.to be_falsey }
    end
186 187

    context 'for trusted certificate chain' do
K
Kamil Trzcinski 已提交
188 189 190 191
      # We only validate that we can to rebuild the trust chain, for certificates
      # We assume that 'AddTrustExternalCARoot' needed to validate the chain is in trusted store.
      # It will be if ca-certificates is installed on Debian/Ubuntu/Alpine

192 193 194 195
      let(:domain) { build(:pages_domain, :with_trusted_chain) }

      it { is_expected.to be_truthy }
    end
K
Kamil Trzcinski 已提交
196 197
  end

198
  describe '#expired?' do
K
Kamil Trzcinski 已提交
199 200 201
    subject { domain.expired? }

    context 'for valid' do
R
Rob Watson 已提交
202
      let(:domain) { build(:pages_domain) }
K
Kamil Trzcinski 已提交
203 204 205 206 207 208 209 210 211 212 213

      it { is_expected.to be_falsey }
    end

    context 'for expired' do
      let(:domain) { build(:pages_domain, :with_expired_certificate) }

      it { is_expected.to be_truthy }
    end
  end

214
  describe '#subject' do
R
Rob Watson 已提交
215
    let(:domain) { build(:pages_domain) }
K
Kamil Trzcinski 已提交
216 217 218 219 220 221

    subject { domain.subject }

    it { is_expected.to eq('/CN=test-certificate') }
  end

222
  describe '#certificate_text' do
R
Rob Watson 已提交
223
    let(:domain) { build(:pages_domain) }
K
Kamil Trzcinski 已提交
224 225 226 227

    subject { domain.certificate_text }

    # We test only existence of output, since the output is long
G
Grzegorz Bizon 已提交
228
    it { is_expected.not_to be_empty }
K
Kamil Trzcinski 已提交
229
  end
230

R
Rob Watson 已提交
231 232 233 234 235 236 237 238 239 240 241 242
  describe "#https?" do
    context "when a certificate is present" do
      subject { build(:pages_domain) }
      it { is_expected.to be_https }
    end

    context "when no certificate is present" do
      subject { build(:pages_domain, :without_certificate) }
      it { is_expected.not_to be_https }
    end
  end

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
  describe '#update_daemon' do
    it 'runs when the domain is created' do
      domain = build(:pages_domain)

      expect(domain).to receive(:update_daemon)

      domain.save!
    end

    it 'runs when the domain is destroyed' do
      domain = create(:pages_domain)

      expect(domain).to receive(:update_daemon)

      domain.destroy!
    end

    it 'delegates to Projects::UpdatePagesConfigurationService' do
      service = instance_double('Projects::UpdatePagesConfigurationService')
      expect(Projects::UpdatePagesConfigurationService).to receive(:new) { service }
      expect(service).to receive(:execute)

      create(:pages_domain)
    end

    context 'configuration updates when attributes change' do
      set(:project1) { create(:project) }
      set(:project2) { create(:project) }
      set(:domain) { create(:pages_domain) }

      where(:attribute, :old_value, :new_value, :update_expected) do
        now = Time.now
        future = now + 1.day

        :project | nil       | :project1 | true
        :project | :project1 | :project1 | false
        :project | :project1 | :project2 | true
        :project | :project1 | nil       | true

        # domain can't be set to nil
        :domain | 'a.com' | 'a.com' | false
        :domain | 'a.com' | 'b.com' | true

        # verification_code can't be set to nil
        :verification_code | 'foo' | 'foo'  | false
        :verification_code | 'foo' | 'bar'  | false

        :verified_at | nil | now    | false
        :verified_at | now | now    | false
        :verified_at | now | future | false
        :verified_at | now | nil    | false

        :enabled_until | nil | now    | true
        :enabled_until | now | now    | false
        :enabled_until | now | future | false
        :enabled_until | now | nil    | true
      end

      with_them do
        it 'runs if a relevant attribute has changed' do
          a = old_value.is_a?(Symbol) ? send(old_value) : old_value
          b = new_value.is_a?(Symbol) ? send(new_value) : new_value

          domain.update!(attribute => a)

          if update_expected
            expect(domain).to receive(:update_daemon)
          else
            expect(domain).not_to receive(:update_daemon)
          end

          domain.update!(attribute => b)
        end
      end

      context 'TLS configuration' do
R
Rob Watson 已提交
319 320
        set(:domain_without_tls) { create(:pages_domain, :without_certificate, :without_key) }
        set(:domain) { create(:pages_domain) }
321

R
Rob Watson 已提交
322
        let(:cert1) { domain.certificate }
323
        let(:cert2) { cert1 + ' ' }
R
Rob Watson 已提交
324
        let(:key1) { domain.key }
325 326 327
        let(:key2) { key1 + ' ' }

        it 'updates when added' do
R
Rob Watson 已提交
328
          expect(domain_without_tls).to receive(:update_daemon)
329

R
Rob Watson 已提交
330
          domain_without_tls.update!(key: key1, certificate: cert1)
331 332 333
        end

        it 'updates when changed' do
R
Rob Watson 已提交
334
          expect(domain).to receive(:update_daemon)
335

R
Rob Watson 已提交
336
          domain.update!(key: key2, certificate: cert2)
337 338 339
        end

        it 'updates when removed' do
R
Rob Watson 已提交
340
          expect(domain).to receive(:update_daemon)
341

R
Rob Watson 已提交
342
          domain.update!(key: nil, certificate: nil)
343 344 345 346
        end
      end
    end
  end
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374

  describe '.for_removal' do
    subject { described_class.for_removal }

    context 'when domain is not schedule for removal' do
      let!(:domain) { create :pages_domain }

      it 'does not return domain' do
        is_expected.to be_empty
      end
    end

    context 'when domain is scheduled for removal yesterday' do
      let!(:domain) { create :pages_domain, remove_at: 1.day.ago }

      it 'returns domain' do
        is_expected.to eq([domain])
      end
    end

    context 'when domain is scheduled for removal tomorrow' do
      let!(:domain) { create :pages_domain, remove_at: 1.day.from_now }

      it 'does not return domain' do
        is_expected.to be_empty
      end
    end
  end
K
Kamil Trzcinski 已提交
375
end