environment_spec.rb 20.7 KB
Newer Older
1 2
require 'spec_helper'

3
describe Environment do
4
  let(:project) { create(:project) }
5
  subject(:environment) { create(:environment, project: project) }
6 7 8 9

  it { is_expected.to belong_to(:project) }
  it { is_expected.to have_many(:deployments) }

Kamil Trzcinski's avatar
Kamil Trzcinski committed
10
  it { is_expected.to delegate_method(:stop_action).to(:last_deployment) }
11
  it { is_expected.to delegate_method(:manual_actions).to(:last_deployment) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
12

13 14
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) }
15
  it { is_expected.to validate_length_of(:name).is_at_most(255) }
16

Nick Thomas's avatar
Nick Thomas committed
17 18
  it { is_expected.to validate_uniqueness_of(:slug).scoped_to(:project_id) }
  it { is_expected.to validate_length_of(:slug).is_at_most(24) }
19

Nick Thomas's avatar
Nick Thomas committed
20
  it { is_expected.to validate_length_of(:external_url).is_at_most(255) }
Z.J. van de Weg's avatar
Z.J. van de Weg committed
21

22
  describe '.order_by_last_deployed_at' do
23
    let(:project) { create(:project, :repository) }
Douwe Maan's avatar
Douwe Maan committed
24 25 26
    let!(:environment1) { create(:environment, project: project) }
    let!(:environment2) { create(:environment, project: project) }
    let!(:environment3) { create(:environment, project: project) }
27
    let!(:deployment1) { create(:deployment, environment: environment1) }
28 29
    let!(:deployment2) { create(:deployment, environment: environment2) }
    let!(:deployment3) { create(:deployment, environment: environment1) }
Douwe Maan's avatar
Douwe Maan committed
30

31 32
    it 'returns the environments in order of having been last deployed' do
      expect(project.environments.order_by_last_deployed_at.to_a).to eq([environment3, environment2, environment1])
Douwe Maan's avatar
Douwe Maan committed
33 34 35
    end
  end

36 37 38 39 40 41 42 43
  describe 'state machine' do
    it 'invalidates the cache after a change' do
      expect(environment).to receive(:expire_etag_cache)

      environment.stop
    end
  end

44 45 46 47 48 49 50 51 52 53 54 55
  describe '#expire_etag_cache' do
    let(:store) { Gitlab::EtagCaching::Store.new }

    it 'changes the cached value' do
      old_value = store.get(environment.etag_cache_key)

      environment.stop

      expect(store.get(environment.etag_cache_key)).not_to eq(old_value)
    end
  end

56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
  describe '#folder_name' do
    context 'when it is inside a folder' do
      subject(:environment) do
        create(:environment, name: 'staging/review-1')
      end

      it 'returns a top-level folder name' do
        expect(environment.folder_name).to eq 'staging'
      end
    end

    context 'when the environment if a top-level item itself' do
      subject(:environment) do
        create(:environment, name: 'production')
      end

      it 'returns an environment name' do
        expect(environment.folder_name).to eq 'production'
      end
    end
  end

Z.J. van de Weg's avatar
Z.J. van de Weg committed
78 79 80 81 82
  describe '#nullify_external_url' do
    it 'replaces a blank url with nil' do
      env = build(:environment, external_url: "")

      expect(env.save).to be true
83
      expect(env.external_url).to be_nil
Z.J. van de Weg's avatar
Z.J. van de Weg committed
84 85
    end
  end
Z.J. van de Weg's avatar
Z.J. van de Weg committed
86

87
  describe '#includes_commit?' do
88
    let(:project) { create(:project, :repository) }
89

Z.J. van de Weg's avatar
Z.J. van de Weg committed
90 91
    context 'without a last deployment' do
      it "returns false" do
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
        expect(environment.includes_commit?('HEAD')).to be false
      end
    end

    context 'with a last deployment' do
      let!(:deployment) do
        create(:deployment, environment: environment, sha: project.commit('master').id)
      end

      context 'in the same branch' do
        it 'returns true' do
          expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be true
        end
      end

      context 'not in the same branch' do
        before do
          deployment.update(sha: project.commit('feature').id)
        end

        it 'returns false' do
          expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be false
        end
Z.J. van de Weg's avatar
Z.J. van de Weg committed
115 116 117
      end
    end
  end
118

119
  describe '#update_merge_request_metrics?' do
120 121
    {
      'production' => true,
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
      'production/eu' => true,
      'production/www.gitlab.com' => true,
      'productioneu' => false,
      'Production' => false,
      'Production/eu' => false,
      'test-production' => false
    }.each do |name, expected_value|
      it "returns #{expected_value} for #{name}" do
        env = create(:environment, name: name)

        expect(env.update_merge_request_metrics?).to eq(expected_value)
      end
    end
  end

Z.J. van de Weg's avatar
Z.J. van de Weg committed
137
  describe '#first_deployment_for' do
138
    let(:project)       { create(:project, :repository) }
139 140 141 142 143
    let!(:deployment)   { create(:deployment, environment: environment, ref: commit.parent.id) }
    let!(:deployment1)  { create(:deployment, environment: environment, ref: commit.id) }
    let(:head_commit)   { project.commit }
    let(:commit)        { project.commit.parent }

144
    it 'returns deployment id for the environment' do
145
      expect(environment.first_deployment_for(commit.id)).to eq deployment1
146
    end
147

148
    it 'return nil when no deployment is found' do
149
      expect(environment.first_deployment_for(head_commit.id)).to eq nil
150 151
    end

152
    it 'returns a UTF-8 ref' do
153
      expect(environment.first_deployment_for(commit.id).ref).to be_utf8
154
    end
155 156
  end

157 158 159 160
  describe '#environment_type' do
    subject { environment.environment_type }

    it 'sets a environment type if name has multiple segments' do
161
      environment.update!(name: 'production/worker.gitlab.com')
162 163 164 165 166

      is_expected.to eq('production')
    end

    it 'nullifies a type if it\'s a simple name' do
167
      environment.update!(name: 'production')
168 169 170 171

      is_expected.to be_nil
    end
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
172

Kamil Trzcinski's avatar
Kamil Trzcinski committed
173 174
  describe '#stop_action?' do
    subject { environment.stop_action? }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
175 176 177 178 179 180 181 182

    context 'when no other actions' do
      it { is_expected.to be_falsey }
    end

    context 'when matching action is defined' do
      let(:build) { create(:ci_build) }
      let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') }
183
      let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201

      context 'when environment is available' do
        before do
          environment.start
        end

        it { is_expected.to be_truthy }
      end

      context 'when environment is stopped' do
        before do
          environment.stop
        end

        it { is_expected.to be_falsey }
      end
    end
  end
202

Kamil Trzcinski's avatar
Kamil Trzcinski committed
203
  describe '#stop_with_action!' do
204
    let(:user) { create(:admin) }
205

Kamil Trzcinski's avatar
Kamil Trzcinski committed
206
    subject { environment.stop_with_action!(user) }
207 208

    before do
209
      expect(environment).to receive(:available?).and_call_original
210 211 212
    end

    context 'when no other actions' do
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
      context 'environment is available' do
        before do
          environment.update(state: :available)
        end

        it do
          subject

          expect(environment).to be_stopped
        end
      end

      context 'environment is already stopped' do
        before do
          environment.update(state: :stopped)
        end

        it do
          subject

          expect(environment).to be_stopped
        end
      end
236 237 238
    end

    context 'when matching action is defined' do
239 240 241 242 243 244 245 246
      let(:pipeline) { create(:ci_pipeline, project: project) }
      let(:build) { create(:ci_build, pipeline: pipeline) }

      let!(:deployment) do
        create(:deployment, environment: environment,
                            deployable: build,
                            on_stop: 'close_app')
      end
247

248 249 250 251
      context 'when user is not allowed to stop environment' do
        let!(:close_action) do
          create(:ci_build, :manual, pipeline: pipeline, name: 'close_app')
        end
252

253 254
        it 'raises an exception' do
          expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
255 256 257
        end
      end

258 259
      context 'when user is allowed to stop environment' do
        before do
260 261 262 263
          project.add_developer(user)

          create(:protected_branch, :developers_can_merge,
                 name: 'master', project: project)
264 265 266 267 268 269 270 271 272 273 274 275
        end

        context 'when action did not yet finish' do
          let!(:close_action) do
            create(:ci_build, :manual, pipeline: pipeline, name: 'close_app')
          end

          it 'returns the same action' do
            expect(subject).to eq(close_action)
            expect(subject.user).to eq(user)
          end
        end
276

277 278 279 280 281 282 283 284 285 286 287
        context 'if action did finish' do
          let!(:close_action) do
            create(:ci_build, :manual, :success,
                   pipeline: pipeline, name: 'close_app')
          end

          it 'returns a new action of the same type' do
            expect(subject).to be_persisted
            expect(subject.name).to eq(close_action.name)
            expect(subject.user).to eq(user)
          end
288 289 290 291
        end
      end
    end
  end
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312

  describe 'recently_updated_on_branch?' do
    subject { environment.recently_updated_on_branch?('feature') }

    context 'when last deployment to environment is the most recent one' do
      before do
        create(:deployment, environment: environment, ref: 'feature')
      end

      it { is_expected.to be true }
    end

    context 'when last deployment to environment is not the most recent' do
      before do
        create(:deployment, environment: environment, ref: 'feature')
        create(:deployment, environment: environment, ref: 'master')
      end

      it { is_expected.to be false }
    end
  end
313 314 315 316

  describe '#actions_for' do
    let(:deployment) { create(:deployment, environment: environment) }
    let(:pipeline) { deployment.deployable.pipeline }
317
    let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_COMMIT_REF_NAME' )}
318 319 320 321 322 323
    let!(:production_action) { create(:ci_build, :manual, name: 'production', pipeline: pipeline, environment: 'production' )}

    it 'returns a list of actions with matching environment' do
      expect(environment.actions_for('review/master')).to contain_exactly(review_action)
    end
  end
Nick Thomas's avatar
Nick Thomas committed
324

325 326
  describe '#has_terminals?' do
    subject { environment.has_terminals? }
327 328 329

    context 'when the enviroment is available' do
      context 'with a deployment service' do
330 331 332 333 334 335 336 337 338 339 340 341 342
        shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
          context 'and a deployment' do
            let!(:deployment) { create(:deployment, environment: environment) }
            it { is_expected.to be_truthy }
          end

          context 'but no deployments' do
            it { is_expected.to be_falsy }
          end
        end

        context 'when user configured kubernetes from Integration > Kubernetes' do
          let(:project) { create(:kubernetes_project) }
343

344
          it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
345 346
        end

347 348 349 350 351
        context 'when user configured kubernetes from CI/CD > Clusters' do
          let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
          let(:project) { cluster.project }

          it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
352 353 354 355 356 357 358 359 360 361
        end
      end

      context 'without a deployment service' do
        it { is_expected.to be_falsy }
      end
    end

    context 'when the environment is unavailable' do
      let(:project) { create(:kubernetes_project) }
362 363 364 365 366

      before do
        environment.stop
      end

367 368 369 370
      it { is_expected.to be_falsy }
    end
  end

371 372 373 374 375 376 377
  describe '#deployment_platform' do
    before do
      stub_licensed_features(multiple_clusters: true)
    end

    context 'when there is a deployment platform for environment' do
      let!(:cluster) do
378
        create(:cluster, :provided_by_gcp, projects: [project])
379 380 381 382 383 384 385 386 387 388 389 390
      end

      it 'finds a deployment platform' do
        expect(environment.deployment_platform).to eq cluster.platform
      end
    end

    context 'when there is no deployment platform for environment' do
      it 'returns nil' do
        expect(environment.deployment_platform).to be_nil
      end
    end
391 392 393 394 395 396 397

    it 'checks deployment platforms associated with a project' do
      expect(project).to receive(:deployment_platform)
        .with(environment: environment.name)

      environment.deployment_platform
    end
398 399
  end

400 401 402 403
  describe '#terminals' do
    subject { environment.terminals }

    context 'when the environment has terminals' do
404
      before do
405
        allow(environment).to receive(:has_terminals?).and_return(true)
406
      end
407

408 409
      shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
        it 'returns the terminals from the deployment service' do
410
          expect(environment.deployment_platform)
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
            .to receive(:terminals).with(environment)
            .and_return(:fake_terminals)

          is_expected.to eq(:fake_terminals)
        end
      end

      context 'when user configured kubernetes from Integration > Kubernetes' do
        let(:project) { create(:kubernetes_project) }

        it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
      end

      context 'when user configured kubernetes from CI/CD > Clusters' do
        let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
        let(:project) { cluster.project }
427

428
        it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
429 430 431 432
      end
    end

    context 'when the environment does not have terminals' do
433
      before do
434
        allow(environment).to receive(:has_terminals?).and_return(false)
435 436 437
      end

      it { is_expected.to be_nil }
438 439 440 441
    end
  end

  describe '#rollout_status' do
442 443
    shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
      subject { environment.rollout_status }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
444

445 446 447 448
      context 'when the environment has rollout status' do
        before do
          allow(environment).to receive(:has_terminals?).and_return(true)
        end
449

450
        it 'returns the rollout status from the deployment service' do
451
          expect(environment.deployment_platform)
452 453 454 455 456
            .to receive(:rollout_status).with(environment)
            .and_return(:fake_rollout_status)

          is_expected.to eq(:fake_rollout_status)
        end
457
      end
458

459 460 461 462
      context 'when the environment does not have rollout status' do
        before do
          allow(environment).to receive(:has_terminals?).and_return(false)
        end
463

464
        it { is_expected.to eq(nil) }
465 466 467
      end
    end

468 469 470 471 472 473 474 475 476
    context 'when user configured kubernetes from Integration > Kubernetes' do
      let(:project) { create(:kubernetes_project) }

      it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
    end

    context 'when user configured kubernetes from CI/CD > Clusters' do
      let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
      let(:project) { cluster.project }
477

478
      it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
Nick Thomas's avatar
Nick Thomas committed
479 480 481
    end
  end

482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
  describe '#has_metrics?' do
    subject { environment.has_metrics? }

    context 'when the enviroment is available' do
      context 'with a deployment service' do
        let(:project) { create(:prometheus_project) }

        context 'and a deployment' do
          let!(:deployment) { create(:deployment, environment: environment) }
          it { is_expected.to be_truthy }
        end

        context 'but no deployments' do
          it { is_expected.to be_falsy }
        end
      end

      context 'without a monitoring service' do
        it { is_expected.to be_falsy }
      end
    end

    context 'when the environment is unavailable' do
      let(:project) { create(:prometheus_project) }

      before do
        environment.stop
      end

      it { is_expected.to be_falsy }
    end
  end

  describe '#metrics' do
    let(:project) { create(:prometheus_project) }
    subject { environment.metrics }

    context 'when the environment has metrics' do
      before do
        allow(environment).to receive(:has_metrics?).and_return(true)
      end

      it 'returns the metrics from the deployment service' do
Pawel Chojnacki's avatar
Pawel Chojnacki committed
525 526
        expect(environment.prometheus_adapter)
          .to receive(:query).with(:environment, environment)
527
          .and_return(:fake_metrics)
528 529 530 531 532 533 534 535 536 537 538

        is_expected.to eq(:fake_metrics)
      end
    end

    context 'when the environment does not have metrics' do
      before do
        allow(environment).to receive(:has_metrics?).and_return(false)
      end

      it { is_expected.to be_nil }
539 540 541
    end
  end

542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
  describe '#has_metrics?' do
    subject { environment.has_metrics? }

    context 'when the enviroment is available' do
      context 'with a deployment service' do
        let(:project) { create(:prometheus_project) }

        context 'and a deployment' do
          let!(:deployment) { create(:deployment, environment: environment) }
          it { is_expected.to be_truthy }
        end

        context 'but no deployments' do
          it { is_expected.to be_falsy }
        end
      end

      context 'without a monitoring service' do
        it { is_expected.to be_falsy }
      end
    end

    context 'when the environment is unavailable' do
      let(:project) { create(:prometheus_project) }

      before do
        environment.stop
      end

      it { is_expected.to be_falsy }
    end
  end

Pawel Chojnacki's avatar
Pawel Chojnacki committed
575
  describe '#additional_metrics' do
576
    let(:project) { create(:prometheus_project) }
Pawel Chojnacki's avatar
Pawel Chojnacki committed
577
    subject { environment.additional_metrics }
578 579 580 581 582 583

    context 'when the environment has metrics' do
      before do
        allow(environment).to receive(:has_metrics?).and_return(true)
      end

584
      it 'returns the additional metrics from the deployment service' do
585 586
        expect(environment.prometheus_adapter).to receive(:query)
                                                .with(:additional_metrics_environment, environment)
587 588 589 590 591 592 593 594
                                                .and_return(:fake_metrics)

        is_expected.to eq(:fake_metrics)
      end
    end

    context 'when the environment does not have metrics' do
      before do
595
        allow(environment).to receive(:has_metrics?).and_return(false)
596 597 598 599 600 601
      end

      it { is_expected.to be_nil }
    end
  end

Nick Thomas's avatar
Nick Thomas committed
602 603 604 605 606 607 608 609 610 611 612
  describe '#slug' do
    it "is automatically generated" do
      expect(environment.slug).not_to be_nil
    end

    it "is not regenerated if name changes" do
      original_slug = environment.slug
      environment.update_attributes!(name: environment.name.reverse)

      expect(environment.slug).to eq(original_slug)
    end
613 614 615 616 617 618 619 620 621

    it "regenerates the slug if nil" do
      environment = build(:environment, slug: nil)

      new_slug = environment.slug

      expect(new_slug).not_to be_nil
      expect(environment.slug).to eq(new_slug)
    end
Nick Thomas's avatar
Nick Thomas committed
622 623 624
  end

  describe '#generate_slug' do
Douwe Maan's avatar
Douwe Maan committed
625
    SUFFIX = "-[a-z0-9]{6}".freeze
Nick Thomas's avatar
Nick Thomas committed
626 627 628 629 630 631 632 633 634 635
    {
      "staging-12345678901234567" => "staging-123456789" + SUFFIX,
      "9-staging-123456789012345" => "env-9-staging-123" + SUFFIX,
      "staging-1234567890123456"  => "staging-1234567890123456",
      "production"                => "production",
      "PRODUCTION"                => "production" + SUFFIX,
      "review/1-foo"              => "review-1-foo" + SUFFIX,
      "1-foo"                     => "env-1-foo" + SUFFIX,
      "1/foo"                     => "env-1-foo" + SUFFIX,
      "foo-"                      => "foo" + SUFFIX,
636 637 638 639
      "foo--bar"                  => "foo-bar" + SUFFIX,
      "foo**bar"                  => "foo-bar" + SUFFIX,
      "*-foo"                     => "env-foo" + SUFFIX,
      "staging-12345678-"         => "staging-12345678" + SUFFIX,
640
      "staging-12345678-01234567" => "staging-12345678" + SUFFIX
Nick Thomas's avatar
Nick Thomas committed
641 642
    }.each do |name, matcher|
      it "returns a slug matching #{matcher}, given #{name}" do
643
        slug = described_class.new(name: name).generate_slug
Nick Thomas's avatar
Nick Thomas committed
644 645 646 647 648

        expect(slug).to match(/\A#{matcher}\z/)
      end
    end
  end
Douwe Maan's avatar
Douwe Maan committed
649

650 651 652 653 654 655 656 657
  describe '#ref_path' do
    subject(:environment) do
      create(:environment, name: 'staging / review-1')
    end

    it 'returns a path that uses the slug and does not have spaces' do
      expect(environment.ref_path).to start_with('refs/environments/staging-review-1-')
    end
658 659 660 661 662 663

    it "doesn't change when the slug is nil initially" do
      environment.slug = nil

      expect(environment.ref_path).to eq(environment.ref_path)
    end
664 665
  end

Douwe Maan's avatar
Douwe Maan committed
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
  describe '#external_url_for' do
    let(:source_path) { 'source/file.html' }
    let(:sha) { RepoHelpers.sample_commit.id }

    before do
      environment.external_url = 'http://example.com'
    end

    context 'when the public path is not known' do
      before do
        allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return(nil)
      end

      it 'returns nil' do
        expect(environment.external_url_for(source_path, sha)).to be_nil
      end
    end

    context 'when the public path is known' do
      before do
        allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return('file.html')
      end

      it 'returns the full external URL' do
        expect(environment.external_url_for(source_path, sha)).to eq('http://example.com/file.html')
      end
    end
  end
694 695

  describe '#prometheus_adapter' do
696 697
    it 'calls prometheus adapter service' do
      expect_any_instance_of(Prometheus::AdapterService).to receive(:prometheus_adapter)
698

699
      subject.prometheus_adapter
700 701
    end
  end
702
end