environments_controller_spec.rb 21.0 KB
Newer Older
G
gfyoung 已提交
1 2
# frozen_string_literal: true

3 4 5
require 'spec_helper'

describe Projects::EnvironmentsController do
Z
Z.J. van de Weg 已提交
6
  set(:user) { create(:user) }
7
  set(:project) { create(:project) }
8

Z
Z.J. van de Weg 已提交
9
  set(:environment) do
10
    create(:environment, name: 'production', project: project)
11
  end
12 13

  before do
14
    project.add_maintainer(user)
15 16 17 18

    sign_in(user)
  end

19
  describe 'GET index' do
Z
Z.J. van de Weg 已提交
20
    context 'when a request for the HTML is made' do
21
      it 'responds with status code 200' do
B
blackst0ne 已提交
22
        get :index, params: environment_params
23

24
        expect(response).to have_gitlab_http_status(:ok)
25
      end
26 27 28 29 30

      it 'expires etag cache to force reload environments list' do
        expect_any_instance_of(Gitlab::EtagCaching::Store)
          .to receive(:touch).with(project_environments_path(project, format: :json))

B
blackst0ne 已提交
31
        get :index, params: environment_params
32
      end
33 34
    end

35 36
    context 'when requesting JSON response for folders' do
      before do
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
        create(:environment, project: project,
                             name: 'staging/review-1',
                             state: :available)

        create(:environment, project: project,
                             name: 'staging/review-2',
                             state: :available)

        create(:environment, project: project,
                             name: 'staging/review-3',
                             state: :stopped)
      end

      let(:environments) { json_response['environments'] }

52 53 54 55 56 57 58
      context 'with default parameters' do
        before do
          get :index, params: environment_params(format: :json)
        end

        it 'responds with a flat payload describing available environments' do
          expect(environments.count).to eq 3
59 60 61
          expect(environments.first).to include('name' => 'production', 'name_without_type' => 'production')
          expect(environments.second).to include('name' => 'staging/review-1', 'name_without_type' => 'review-1')
          expect(environments.third).to include('name' => 'staging/review-2', 'name_without_type' => 'review-2')
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
          expect(json_response['available_count']).to eq 3
          expect(json_response['stopped_count']).to eq 1
        end

        it 'sets the polling interval header' do
          expect(response).to have_gitlab_http_status(:ok)
          expect(response.headers['Poll-Interval']).to eq("3000")
        end
      end

      context 'when a folder-based nested structure is requested' do
        before do
          get :index, params: environment_params(format: :json, nested: true)
        end

        it 'responds with a payload containing the latest environment for each folder' do
          expect(environments.count).to eq 2
          expect(environments.first['name']).to eq 'production'
          expect(environments.second['name']).to eq 'staging'
          expect(environments.second['size']).to eq 2
          expect(environments.second['latest']['name']).to eq 'staging/review-2'
        end
      end

86 87
      context 'when requesting available environments scope' do
        before do
88
          get :index, params: environment_params(format: :json, nested: true, scope: :available)
89 90 91 92 93 94 95 96 97 98 99 100 101 102
        end

        it 'responds with a payload describing available environments' do
          expect(environments.count).to eq 2
          expect(environments.first['name']).to eq 'production'
          expect(environments.second['name']).to eq 'staging'
          expect(environments.second['size']).to eq 2
          expect(environments.second['latest']['name']).to eq 'staging/review-2'
        end

        it 'contains values describing environment scopes sizes' do
          expect(json_response['available_count']).to eq 3
          expect(json_response['stopped_count']).to eq 1
        end
103 104
      end

105 106
      context 'when requesting stopped environments scope' do
        before do
107
          get :index, params: environment_params(format: :json, nested: true, scope: :stopped)
108 109 110 111 112 113 114 115
        end

        it 'responds with a payload describing stopped environments' do
          expect(environments.count).to eq 1
          expect(environments.first['name']).to eq 'staging'
          expect(environments.first['size']).to eq 1
          expect(environments.first['latest']['name']).to eq 'staging/review-3'
        end
116

117 118 119 120
        it 'contains values describing environment scopes sizes' do
          expect(json_response['available_count']).to eq 3
          expect(json_response['stopped_count']).to eq 1
        end
121 122 123 124
      end
    end
  end

125 126 127 128 129
  describe 'GET folder' do
    before do
      create(:environment, project: project,
                           name: 'staging-1.0/review',
                           state: :available)
130
      create(:environment, project: project,
131
                           name: 'staging-1.0/zzz',
132
                           state: :available)
133 134 135 136
    end

    context 'when using default format' do
      it 'responds with HTML' do
B
blackst0ne 已提交
137 138 139 140 141
        get :folder, params: {
                       namespace_id: project.namespace,
                       project_id: project,
                       id: 'staging-1.0'
                     }
142 143 144 145 146 147 148

        expect(response).to be_ok
        expect(response).to render_template 'folder'
      end
    end

    context 'when using JSON format' do
Z
Z.J. van de Weg 已提交
149
      it 'sorts the subfolders lexicographically' do
B
blackst0ne 已提交
150 151 152 153 154
        get :folder, params: {
                       namespace_id: project.namespace,
                       project_id: project,
                       id: 'staging-1.0'
                     },
155 156 157 158 159
                     format: :json

        expect(response).to be_ok
        expect(response).not_to render_template 'folder'
        expect(json_response['environments'][0])
160
          .to include('name' => 'staging-1.0/review', 'name_without_type' => 'review')
161
        expect(json_response['environments'][1])
162
          .to include('name' => 'staging-1.0/zzz', 'name_without_type' => 'zzz')
163 164 165 166
      end
    end
  end

167 168 169
  describe 'GET show' do
    context 'with valid id' do
      it 'responds with a status code 200' do
B
blackst0ne 已提交
170
        get :show, params: environment_params
171 172 173 174 175 176 177

        expect(response).to be_ok
      end
    end

    context 'with invalid id' do
      it 'responds with a status code 404' do
Z
Z.J. van de Weg 已提交
178 179
        params = environment_params
        params[:id] = 12345
B
blackst0ne 已提交
180
        get :show, params: params
181

182
        expect(response).to have_gitlab_http_status(404)
183 184 185 186 187 188
      end
    end
  end

  describe 'GET edit' do
    it 'responds with a status code 200' do
B
blackst0ne 已提交
189
      get :edit, params: environment_params
190 191 192 193 194 195 196

      expect(response).to be_ok
    end
  end

  describe 'PATCH #update' do
    it 'responds with a 302' do
Z
Z.J. van de Weg 已提交
197
      patch_params = environment_params.merge(environment: { external_url: 'https://git.gitlab.com' })
B
blackst0ne 已提交
198
      patch :update, params: patch_params
199

200
      expect(response).to have_gitlab_http_status(302)
201 202
    end
  end
Z
Z.J. van de Weg 已提交
203

F
Fatih Acet 已提交
204 205 206 207 208
  describe 'PATCH #stop' do
    context 'when env not available' do
      it 'returns 404' do
        allow_any_instance_of(Environment).to receive(:available?) { false }

B
blackst0ne 已提交
209
        patch :stop, params: environment_params(format: :json)
F
Fatih Acet 已提交
210

211
        expect(response).to have_gitlab_http_status(404)
F
Fatih Acet 已提交
212 213 214 215 216 217 218 219 220 221
      end
    end

    context 'when stop action' do
      it 'returns action url' do
        action = create(:ci_build, :manual)

        allow_any_instance_of(Environment)
          .to receive_messages(available?: true, stop_with_action!: action)

B
blackst0ne 已提交
222
        patch :stop, params: environment_params(format: :json)
F
Fatih Acet 已提交
223

224
        expect(response).to have_gitlab_http_status(200)
F
Fatih Acet 已提交
225 226
        expect(json_response).to eq(
          { 'redirect_url' =>
227
              project_job_url(project, action) })
F
Fatih Acet 已提交
228 229 230 231 232 233 234 235
      end
    end

    context 'when no stop action' do
      it 'returns env url' do
        allow_any_instance_of(Environment)
          .to receive_messages(available?: true, stop_with_action!: nil)

B
blackst0ne 已提交
236
        patch :stop, params: environment_params(format: :json)
F
Fatih Acet 已提交
237

238
        expect(response).to have_gitlab_http_status(200)
F
Fatih Acet 已提交
239 240
        expect(json_response).to eq(
          { 'redirect_url' =>
241
              project_environment_url(project, environment) })
F
Fatih Acet 已提交
242 243 244 245
      end
    end
  end

246 247 248
  describe 'GET #terminal' do
    context 'with valid id' do
      it 'responds with a status code 200' do
B
blackst0ne 已提交
249
        get :terminal, params: environment_params
250

251
        expect(response).to have_gitlab_http_status(200)
252 253
      end

G
George Tsiolis 已提交
254
      it 'loads the terminals for the environment' do
255 256 257 258
        # In EE we have to stub EE::Environment since it overwrites the
        # "terminals" method.
        expect_any_instance_of(defined?(EE) ? EE::Environment : Environment)
          .to receive(:terminals)
259

B
blackst0ne 已提交
260
        get :terminal, params: environment_params
261 262 263 264 265
      end
    end

    context 'with invalid id' do
      it 'responds with a status code 404' do
B
blackst0ne 已提交
266
        get :terminal, params: environment_params(id: 666)
267

268
        expect(response).to have_gitlab_http_status(404)
269 270 271 272 273 274 275 276 277 278 279 280
      end
    end
  end

  describe 'GET #terminal_websocket_authorize' do
    context 'with valid workhorse signature' do
      before do
        allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_return(nil)
      end

      context 'and valid id' do
        it 'returns the first terminal for the environment' do
281 282 283
          # In EE we have to stub EE::Environment since it overwrites the
          # "terminals" method.
          expect_any_instance_of(defined?(EE) ? EE::Environment : Environment)
284 285 286 287
            .to receive(:terminals)
            .and_return([:fake_terminal])

          expect(Gitlab::Workhorse)
288
            .to receive(:channel_websocket)
289 290
            .with(:fake_terminal)
            .and_return(workhorse: :response)
291

B
blackst0ne 已提交
292
          get :terminal_websocket_authorize, params: environment_params
293

294
          expect(response).to have_gitlab_http_status(200)
295 296 297 298 299 300 301
          expect(response.headers["Content-Type"]).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
          expect(response.body).to eq('{"workhorse":"response"}')
        end
      end

      context 'and invalid id' do
        it 'returns 404' do
B
blackst0ne 已提交
302
          get :terminal_websocket_authorize, params: environment_params(id: 666)
303

304
          expect(response).to have_gitlab_http_status(404)
305 306 307 308 309 310 311 312
        end
      end
    end

    context 'with invalid workhorse signature' do
      it 'aborts with an exception' do
        allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_raise(JWT::DecodeError)

B
blackst0ne 已提交
313
        expect { get :terminal_websocket_authorize, params: environment_params }.to raise_error(JWT::DecodeError)
314 315 316 317 318 319
        # controller tests don't set the response status correctly. It's enough
        # to check that the action raised an exception
      end
    end
  end

320 321 322 323 324 325
  describe 'GET #metrics_redirect' do
    let(:project) { create(:project) }

    it 'redirects to environment if it exists' do
      environment = create(:environment, name: 'production', project: project)

B
blackst0ne 已提交
326
      get :metrics_redirect, params: { namespace_id: project.namespace, project_id: project }
327 328 329 330 331

      expect(response).to redirect_to(environment_metrics_path(environment))
    end

    it 'redirects to empty page if no environment exists' do
B
blackst0ne 已提交
332
      get :metrics_redirect, params: { namespace_id: project.namespace, project_id: project }
333

334 335
      expect(response).to be_ok
      expect(response).to render_template 'empty'
336 337 338
    end
  end

339 340 341 342 343 344 345
  describe 'GET #metrics' do
    before do
      allow(controller).to receive(:environment).and_return(environment)
    end

    context 'when environment has no metrics' do
      it 'returns a metrics page' do
346 347
        expect(environment).not_to receive(:metrics)

B
blackst0ne 已提交
348
        get :metrics, params: environment_params
349 350 351 352 353 354

        expect(response).to be_ok
      end

      context 'when requesting metrics as JSON' do
        it 'returns a metrics JSON document' do
355 356
          expect(environment).to receive(:metrics).and_return(nil)

B
blackst0ne 已提交
357
          get :metrics, params: environment_params(format: :json)
358

359
          expect(response).to have_gitlab_http_status(204)
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
          expect(json_response).to eq({})
        end
      end
    end

    context 'when environment has some metrics' do
      before do
        expect(environment).to receive(:metrics).and_return({
          success: true,
          metrics: {},
          last_update: 42
        })
      end

      it 'returns a metrics JSON document' do
B
blackst0ne 已提交
375
        get :metrics, params: environment_params(format: :json)
376 377 378 379 380 381 382 383 384

        expect(response).to be_ok
        expect(json_response['success']).to be(true)
        expect(json_response['metrics']).to eq({})
        expect(json_response['last_update']).to eq(42)
      end
    end
  end

385
  describe 'GET #additional_metrics' do
386 387
    let(:window_params) { { start: '1554702993.5398998', end: '1554717396.996232' } }

388 389 390 391 392 393 394 395 396 397 398
    before do
      allow(controller).to receive(:environment).and_return(environment)
    end

    context 'when environment has no metrics' do
      before do
        expect(environment).to receive(:additional_metrics).and_return(nil)
      end

      context 'when requesting metrics as JSON' do
        it 'returns a metrics JSON document' do
399
          additional_metrics(window_params)
400

401
          expect(response).to have_gitlab_http_status(204)
402 403 404 405 406 407 408
          expect(json_response).to eq({})
        end
      end
    end

    context 'when environment has some metrics' do
      before do
P
Pawel Chojnacki 已提交
409 410 411 412 413 414 415
        expect(environment)
          .to receive(:additional_metrics)
                .and_return({
                              success: true,
                              data: {},
                              last_update: 42
                            })
416 417 418
      end

      it 'returns a metrics JSON document' do
419
        additional_metrics(window_params)
420 421 422 423 424 425

        expect(response).to be_ok
        expect(json_response['success']).to be(true)
        expect(json_response['data']).to eq({})
        expect(json_response['last_update']).to eq(42)
      end
426
    end
427

428 429 430 431
    context 'when time params are missing' do
      it 'raises an error when window params are missing' do
        expect { additional_metrics }
        .to raise_error(ActionController::ParameterMissing)
432
      end
433
    end
434 435 436

    context 'when only one time param is provided' do
      it 'raises an error when start is missing' do
437
        expect { additional_metrics(end: '1552647300.651094') }
438 439 440 441 442 443 444 445
          .to raise_error(ActionController::ParameterMissing)
      end

      it 'raises an error when end is missing' do
        expect { additional_metrics(start: '1552647300.651094') }
          .to raise_error(ActionController::ParameterMissing)
      end
    end
446 447
  end

448 449 450 451 452 453 454 455 456 457 458 459 460
  describe 'metrics_dashboard' do
    context 'when prometheus endpoint is disabled' do
      before do
        stub_feature_flags(environment_metrics_use_prometheus_endpoint: false)
      end

      it 'responds with status code 403' do
        get :metrics_dashboard, params: environment_params(format: :json)

        expect(response).to have_gitlab_http_status(:forbidden)
      end
    end

461 462 463 464 465 466 467
    shared_examples_for '200 response' do |contains_all_dashboards: false|
      let(:expected_keys) { %w(dashboard status) }

      before do
        expected_keys << 'all_dashboards' if contains_all_dashboards
      end

468
      it 'returns a json representation of the environment dashboard' do
469
        get :metrics_dashboard, params: environment_params(dashboard_params)
470 471

        expect(response).to have_gitlab_http_status(:ok)
472
        expect(json_response.keys).to contain_exactly(*expected_keys)
R
rpereira2 已提交
473
        expect(json_response['dashboard']).to be_an_instance_of(Hash)
474
      end
475 476 477 478 479 480 481 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
    end

    shared_examples_for 'error response' do |status_code, contains_all_dashboards: false|
      let(:expected_keys) { %w(message status) }

      before do
        expected_keys << 'all_dashboards' if contains_all_dashboards
      end

      it 'returns an error response' do
        get :metrics_dashboard, params: environment_params(dashboard_params)

        expect(response).to have_gitlab_http_status(status_code)
        expect(json_response.keys).to contain_exactly(*expected_keys)
      end
    end

    shared_examples_for 'has all dashboards' do
      it 'includes an index of all available dashboards' do
        get :metrics_dashboard, params: environment_params(dashboard_params)

        expect(json_response.keys).to include('all_dashboards')
        expect(json_response['all_dashboards']).to be_an_instance_of(Array)
        expect(json_response['all_dashboards']).to all( include('path', 'default') )
      end
    end

    context 'when multiple dashboards is disabled' do
      before do
        stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
      end

      let(:dashboard_params) { { format: :json } }

      it_behaves_like '200 response'
510 511 512

      context 'when the dashboard could not be provided' do
        before do
513
          allow(YAML).to receive(:safe_load).and_return({})
514 515
        end

516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
        it_behaves_like 'error response', :unprocessable_entity
      end

      context 'when a dashboard param is specified' do
        let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/not_there_dashboard.yml' } }

        it_behaves_like '200 response'
      end
    end

    context 'when multiple dashboards is enabled' do
      let(:dashboard_params) { { format: :json } }

      it_behaves_like '200 response', contains_all_dashboards: true
      it_behaves_like 'has all dashboards'

      context 'when a dashboard could not be provided' do
        before do
          allow(YAML).to receive(:safe_load).and_return({})
        end

        it_behaves_like 'error response', :unprocessable_entity, contains_all_dashboards: true
        it_behaves_like 'has all dashboards'
      end

      context 'when a dashboard param is specified' do
        let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } }

        context 'when the dashboard is available' do
          let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
          let(:dashboard_file) { { '.gitlab/dashboards/test.yml' => dashboard_yml } }
          let(:project) { create(:project, :custom_repo, files: dashboard_file) }
          let(:environment) { create(:environment, name: 'production', project: project) }

          it_behaves_like '200 response', contains_all_dashboards: true
          it_behaves_like 'has all dashboards'
        end
553

554 555 556
        context 'when the dashboard does not exist' do
          it_behaves_like 'error response', :not_found, contains_all_dashboards: true
          it_behaves_like 'has all dashboards'
557 558
        end
      end
559 560 561 562 563 564 565 566 567 568 569 570 571

      context 'when the dashboard is intended for embedding' do
        let(:dashboard_params) { { format: :json, embedded: true } }

        it_behaves_like '200 response'

        context 'when a dashboard path is provided' do
          let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml', embedded: true } }

          # The dashboard path should simple be ignored.
          it_behaves_like '200 response'
        end
      end
572 573 574
    end
  end

575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
  describe 'GET #search' do
    before do
      create(:environment, name: 'staging', project: project)
      create(:environment, name: 'review/patch-1', project: project)
      create(:environment, name: 'review/patch-2', project: project)
    end

    let(:query) { 'pro' }

    it 'responds with status code 200' do
      get :search, params: environment_params(format: :json, query: query)

      expect(response).to have_gitlab_http_status(:ok)
    end

    it 'returns matched results' do
      get :search, params: environment_params(format: :json, query: query)

      expect(json_response).to contain_exactly('production')
    end

    context 'when query is review' do
      let(:query) { 'review' }

      it 'returns matched results' do
        get :search, params: environment_params(format: :json, query: query)

        expect(json_response).to contain_exactly('review/patch-1', 'review/patch-2')
      end
    end

    context 'when query is empty' do
      let(:query) { '' }

      it 'returns matched results' do
        get :search, params: environment_params(format: :json, query: query)

        expect(json_response)
          .to contain_exactly('production', 'staging', 'review/patch-1', 'review/patch-2')
      end
    end

    context 'when query is review/patch-3' do
      let(:query) { 'review/patch-3' }

      it 'responds with status code 204' do
        get :search, params: environment_params(format: :json, query: query)

        expect(response).to have_gitlab_http_status(:no_content)
      end
    end

    context 'when query is partially matched in the middle of environment name' do
      let(:query) { 'patch' }

      it 'responds with status code 204' do
        get :search, params: environment_params(format: :json, query: query)

        expect(response).to have_gitlab_http_status(:no_content)
      end
    end

    context 'when query contains a wildcard character' do
      let(:query) { 'review%' }

      it 'prevents wildcard injection' do
        get :search, params: environment_params(format: :json, query: query)

        expect(response).to have_gitlab_http_status(:no_content)
      end
    end
  end

648 649 650 651
  def environment_params(opts = {})
    opts.reverse_merge(namespace_id: project.namespace,
                       project_id: project,
                       id: environment.id)
Z
Z.J. van de Weg 已提交
652
  end
653 654 655 656

  def additional_metrics(opts = {})
    get :additional_metrics, params: environment_params(format: :json, **opts)
  end
657
end