jobs_spec.rb 26.9 KB
Newer Older
1 2
require 'spec_helper'

3
describe API::Jobs do
4 5
  include HttpIOHelpers

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
  shared_examples 'a job with artifacts and trace' do |result_is_array: true|
    context 'with artifacts and trace' do
      let!(:second_job) { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) }

      it 'returns artifacts and trace data', :skip_before_request do
        get api(api_endpoint, api_user)
        json_job = result_is_array ? json_response.select { |job| job['id'] == second_job.id }.first : json_response

        expect(json_job['artifacts_file']).not_to be_nil
        expect(json_job['artifacts_file']).not_to be_empty
        expect(json_job['artifacts_file']['filename']).to eq(second_job.artifacts_file.filename)
        expect(json_job['artifacts_file']['size']).to eq(second_job.artifacts_file.size)
        expect(json_job['artifacts']).not_to be_nil
        expect(json_job['artifacts']).to be_an Array
        expect(json_job['artifacts'].size).to eq(second_job.job_artifacts.length)
        json_job['artifacts'].each do |artifact|
          expect(artifact).not_to be_nil
          file_type = Ci::JobArtifact.file_types[artifact['file_type']]
          expect(artifact['size']).to eq(second_job.job_artifacts.where(file_type: file_type).first.size)
          expect(artifact['filename']).to eq(second_job.job_artifacts.where(file_type: file_type).first.filename)
          expect(artifact['file_format']).to eq(second_job.job_artifacts.where(file_type: file_type).first.file_format)
        end
      end
    end
  end

32
  set(:project) do
33 34 35
    create(:project, :repository, public_builds: false)
  end

36
  set(:pipeline) do
37 38 39 40 41
    create(:ci_empty_pipeline, project: project,
                               sha: project.commit.id,
                               ref: project.default_branch)
  end

42 43 44 45
  let!(:job) do
    create(:ci_build, :success, pipeline: pipeline,
                                artifacts_expire_at: 1.day.since)
  end
46 47

  let(:user) { create(:user) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
48
  let(:api_user) { user }
49 50 51 52 53 54
  let(:reporter) { create(:project_member, :reporter, project: project).user }
  let(:guest) { create(:project_member, :guest, project: project).user }

  before do
    project.add_developer(user)
  end
55

56 57
  describe 'GET /projects/:id/jobs' do
    let(:query) { Hash.new }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
58

59 60 61 62
    before do |example|
      unless example.metadata[:skip_before_request]
        get api("/projects/#{project.id}/jobs", api_user), query
      end
Lin Jen-Shin's avatar
Lin Jen-Shin committed
63
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
64

65
    context 'authorized user' do
66
      it 'returns project jobs' do
67
        expect(response).to have_gitlab_http_status(200)
68
        expect(response).to include_pagination_headers
69 70 71
        expect(json_response).to be_an Array
      end

72 73 74
      it 'returns correct values' do
        expect(json_response).not_to be_empty
        expect(json_response.first['commit']['id']).to eq project.commit.id
75
        expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at)
76 77
      end

78 79 80 81 82 83 84 85 86 87 88 89 90 91
      context 'without artifacts and trace' do
        it 'returns no artifacts nor trace data' do
          json_job = json_response.first

          expect(json_job['artifacts_file']).to be_nil
          expect(json_job['artifacts']).to be_an Array
          expect(json_job['artifacts']).to be_empty
        end
      end

      it_behaves_like 'a job with artifacts and trace' do
        let(:api_endpoint) { "/projects/#{project.id}/jobs" }
      end

92
      it 'returns pipeline data' do
93
        json_job = json_response.first
94

95 96 97 98 99
        expect(json_job['pipeline']).not_to be_empty
        expect(json_job['pipeline']['id']).to eq job.pipeline.id
        expect(json_job['pipeline']['ref']).to eq job.pipeline.ref
        expect(json_job['pipeline']['sha']).to eq job.pipeline.sha
        expect(json_job['pipeline']['status']).to eq job.pipeline.status
100 101
      end

102
      it 'avoids N+1 queries', :skip_before_request do
103
        first_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline)
104 105 106 107
        first_build.runner = create(:ci_runner)
        first_build.user = create(:user)
        first_build.save

108
        control_count = ActiveRecord::QueryRecorder.new { go }.count
109 110

        second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
111
        second_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: second_pipeline)
112 113 114 115
        second_build.runner = create(:ci_runner)
        second_build.user = create(:user)
        second_build.save

116 117 118
        expect { go }.not_to exceed_query_limit(control_count)
      end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
119
      context 'filter project with one scope element' do
120
        let(:query) { { 'scope' => 'pending' } }
121

Kamil Trzcinski's avatar
Kamil Trzcinski committed
122
        it do
123
          expect(response).to have_gitlab_http_status(200)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
124 125
          expect(json_response).to be_an Array
        end
126 127
      end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
128
      context 'filter project with array of scope elements' do
129
        let(:query) { { scope: %w(pending running) } }
130

Kamil Trzcinski's avatar
Kamil Trzcinski committed
131
        it do
132
          expect(response).to have_gitlab_http_status(200)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
133 134
          expect(json_response).to be_an Array
        end
135
      end
136

Kamil Trzcinski's avatar
Kamil Trzcinski committed
137
      context 'respond 400 when scope contains invalid state' do
138
        let(:query) { { scope: %w(unknown running) } }
139

140
        it { expect(response).to have_gitlab_http_status(400) }
141
      end
142 143 144
    end

    context 'unauthorized user' do
145 146
      context 'when user is not logged in' do
        let(:api_user) { nil }
147

148 149 150 151 152 153 154 155 156 157 158
        it 'does not return project jobs' do
          expect(response).to have_gitlab_http_status(401)
        end
      end

      context 'when user is guest' do
        let(:api_user) { guest }

        it 'does not return project jobs' do
          expect(response).to have_gitlab_http_status(403)
        end
159 160
      end
    end
161

162 163 164
    def go
      get api("/projects/#{project.id}/jobs", api_user), query
    end
165 166
  end

167 168 169
  describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do
    let(:query) { Hash.new }

170 171 172 173 174
    before do |example|
      unless example.metadata[:skip_before_request]
        job
        get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
      end
175 176 177 178
    end

    context 'authorized user' do
      it 'returns pipeline jobs' do
179
        expect(response).to have_gitlab_http_status(200)
180 181 182 183 184 185 186
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
      end

      it 'returns correct values' do
        expect(json_response).not_to be_empty
        expect(json_response.first['commit']['id']).to eq project.commit.id
187
        expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at)
188 189 190 191 192 193 194
        expect(json_response.first['artifacts_file']).to be_nil
        expect(json_response.first['artifacts']).to be_an Array
        expect(json_response.first['artifacts']).to be_empty
      end

      it_behaves_like 'a job with artifacts and trace' do
        let(:api_endpoint) { "/projects/#{project.id}/pipelines/#{pipeline.id}/jobs" }
195 196 197
      end

      it 'returns pipeline data' do
198
        json_job = json_response.first
199

200 201 202 203 204
        expect(json_job['pipeline']).not_to be_empty
        expect(json_job['pipeline']['id']).to eq job.pipeline.id
        expect(json_job['pipeline']['ref']).to eq job.pipeline.ref
        expect(json_job['pipeline']['sha']).to eq job.pipeline.sha
        expect(json_job['pipeline']['status']).to eq job.pipeline.status
205 206 207 208 209 210
      end

      context 'filter jobs with one scope element' do
        let(:query) { { 'scope' => 'pending' } }

        it do
211
          expect(response).to have_gitlab_http_status(200)
212 213 214 215 216
          expect(json_response).to be_an Array
        end
      end

      context 'filter jobs with array of scope elements' do
217
        let(:query) { { scope: %w(pending running) } }
218 219

        it do
220
          expect(response).to have_gitlab_http_status(200)
221 222 223 224 225
          expect(json_response).to be_an Array
        end
      end

      context 'respond 400 when scope contains invalid state' do
226
        let(:query) { { scope: %w(unknown running) } }
227

228
        it { expect(response).to have_gitlab_http_status(400) }
229 230 231 232
      end

      context 'jobs in different pipelines' do
        let!(:pipeline2) { create(:ci_empty_pipeline, project: project) }
233
        let!(:job2) { create(:ci_build, pipeline: pipeline2) }
234 235 236 237 238

        it 'excludes jobs from other pipelines' do
          json_response.each { |job| expect(job['pipeline']['id']).to eq(pipeline.id) }
        end
      end
239 240 241 242 243 244

      it 'avoids N+1 queries' do
        control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
          get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
        end.count

245
        3.times { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) }
246 247 248 249 250

        expect do
          get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
        end.not_to exceed_all_query_limit(control_count)
      end
251 252 253
    end

    context 'unauthorized user' do
254 255 256 257 258 259 260
      context 'when user is not logged in' do
        let(:api_user) { nil }

        it 'does not return jobs' do
          expect(response).to have_gitlab_http_status(401)
        end
      end
261

262 263 264 265 266 267
      context 'when user is guest' do
        let(:api_user) { guest }

        it 'does not return jobs' do
          expect(response).to have_gitlab_http_status(403)
        end
268 269 270 271
      end
    end
  end

272
  describe 'GET /projects/:id/jobs/:job_id' do
273 274 275 276
    before do |example|
      unless example.metadata[:skip_before_request]
        get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
      end
Lin Jen-Shin's avatar
Lin Jen-Shin committed
277
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
278

279
    context 'authorized user' do
Filipa Lacerda's avatar
Filipa Lacerda committed
280
      it 'returns specific job data' do
281
        expect(response).to have_gitlab_http_status(200)
282 283 284 285 286 287 288 289 290 291
        expect(json_response['id']).to eq(job.id)
        expect(json_response['status']).to eq(job.status)
        expect(json_response['stage']).to eq(job.stage)
        expect(json_response['name']).to eq(job.name)
        expect(json_response['ref']).to eq(job.ref)
        expect(json_response['tag']).to eq(job.tag)
        expect(json_response['coverage']).to eq(job.coverage)
        expect(Time.parse(json_response['created_at'])).to be_like_time(job.created_at)
        expect(Time.parse(json_response['started_at'])).to be_like_time(job.started_at)
        expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at)
292
        expect(Time.parse(json_response['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at)
293 294 295
        expect(json_response['artifacts_file']).to be_nil
        expect(json_response['artifacts']).to be_an Array
        expect(json_response['artifacts']).to be_empty
296
        expect(json_response['duration']).to eq(job.duration)
297
        expect(json_response['web_url']).to be_present
298
      end
299

300 301 302 303
      it_behaves_like 'a job with artifacts and trace', result_is_array: false do
        let(:api_endpoint) { "/projects/#{project.id}/jobs/#{second_job.id}" }
      end

304
      it 'returns pipeline data' do
305 306 307 308 309 310 311
        json_job = json_response

        expect(json_job['pipeline']).not_to be_empty
        expect(json_job['pipeline']['id']).to eq job.pipeline.id
        expect(json_job['pipeline']['ref']).to eq job.pipeline.ref
        expect(json_job['pipeline']['sha']).to eq job.pipeline.sha
        expect(json_job['pipeline']['status']).to eq job.pipeline.status
312
      end
313 314 315
    end

    context 'unauthorized user' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
316
      let(:api_user) { nil }
317

Filipa Lacerda's avatar
Filipa Lacerda committed
318
      it 'does not return specific job data' do
319
        expect(response).to have_gitlab_http_status(401)
320 321 322 323
      end
    end
  end

324 325 326 327 328 329 330 331
  describe 'GET /projects/:id/jobs/:job_id/artifacts/:artifact_path' do
    context 'when job has artifacts' do
      let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }

      let(:artifact) do
        'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif'
      end

332
      context 'when user is anonymous' do
333 334
        let(:api_user) { nil }

335 336 337 338 339 340 341 342
        context 'when project is public' do
          it 'allows to access artifacts' do
            project.update_column(:visibility_level,
                                  Gitlab::VisibilityLevel::PUBLIC)
            project.update_column(:public_builds, true)

            get_artifact_file(artifact)

343
            expect(response).to have_gitlab_http_status(200)
344 345 346 347 348 349 350 351 352 353 354
          end
        end

        context 'when project is public with builds access disabled' do
          it 'rejects access to artifacts' do
            project.update_column(:visibility_level,
                                  Gitlab::VisibilityLevel::PUBLIC)
            project.update_column(:public_builds, false)

            get_artifact_file(artifact)

355
            expect(response).to have_gitlab_http_status(403)
356 357 358 359 360 361 362 363 364 365
          end
        end

        context 'when project is private' do
          it 'rejects access and hides existence of artifacts' do
            project.update_column(:visibility_level,
                                  Gitlab::VisibilityLevel::PRIVATE)
            project.update_column(:public_builds, true)

            get_artifact_file(artifact)
366

367
            expect(response).to have_gitlab_http_status(404)
368
          end
369 370 371 372 373 374 375 376 377 378 379
        end
      end

      context 'when user is authorized' do
        it 'returns a specific artifact file for a valid path' do
          expect(Gitlab::Workhorse)
            .to receive(:send_artifacts_entry)
            .and_call_original

          get_artifact_file(artifact)

380
          expect(response).to have_gitlab_http_status(200)
381
          expect(response.headers.to_h)
382 383
            .to include('Content-Type' => 'application/json',
                        'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
384 385 386 387 388 389 390 391
        end
      end
    end

    context 'when job does not have artifacts' do
      it 'does not return job artifact file' do
        get_artifact_file('some/artifact')

392
        expect(response).to have_gitlab_http_status(404)
393 394 395 396 397 398 399 400 401
      end
    end

    def get_artifact_file(artifact_path)
      get api("/projects/#{project.id}/jobs/#{job.id}/" \
              "artifacts/#{artifact_path}", api_user)
    end
  end

402
  describe 'GET /projects/:id/jobs/:job_id/artifacts' do
Micaël Bergeron's avatar
Micaël Bergeron committed
403 404 405 406 407 408 409 410
    shared_examples 'downloads artifact' do
      let(:download_headers) do
        { 'Content-Transfer-Encoding' => 'binary',
          'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
      end

      it 'returns specific job artifacts' do
        expect(response).to have_gitlab_http_status(200)
411
        expect(response.headers.to_h).to include(download_headers)
Micaël Bergeron's avatar
Micaël Bergeron committed
412 413 414 415
        expect(response.body).to match_file(job.artifacts_file.file.file)
      end
    end

416 417 418 419 420 421 422 423 424 425 426
    context 'normal authentication' do
      context 'job with artifacts' do
        context 'when artifacts are stored locally' do
          let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }

          before do
            get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
          end

          context 'authorized user' do
            it_behaves_like 'downloads artifact'
427 428
          end

Micaël Bergeron's avatar
Micaël Bergeron committed
429 430 431 432 433 434
          context 'unauthorized user' do
            let(:api_user) { nil }

            it 'does not return specific job artifacts' do
              expect(response).to have_gitlab_http_status(404)
            end
435
          end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
436
        end
437

438
        context 'when artifacts are stored remotely' do
439 440
          let(:proxy_download) { false }

Micaël Bergeron's avatar
Micaël Bergeron committed
441
          before do
442
            stub_artifacts_object_storage(proxy_download: proxy_download)
Micaël Bergeron's avatar
Micaël Bergeron committed
443 444
          end

445 446 447 448 449 450 451 452
          let(:job) { create(:ci_build, pipeline: pipeline) }
          let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }

          before do
            job.reload

            get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
          end
453

454 455 456 457 458 459 460 461 462 463 464 465 466 467
          context 'when proxy download is enabled' do
            let(:proxy_download) { true }

            it 'responds with the workhorse send-url' do
              expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
            end
          end

          context 'when proxy download is disabled' do
            it 'returns location redirect' do
              expect(response).to have_gitlab_http_status(302)
            end
          end

Micaël Bergeron's avatar
Micaël Bergeron committed
468 469 470 471 472 473 474 475 476 477 478 479
          context 'authorized user' do
            it 'returns the file remote URL' do
              expect(response).to redirect_to(artifact.file.url)
            end
          end

          context 'unauthorized user' do
            let(:api_user) { nil }

            it 'does not return specific job artifacts' do
              expect(response).to have_gitlab_http_status(404)
            end
480
          end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
481
        end
482 483 484 485

        it 'does not return job artifacts if not uploaded' do
          get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)

Micaël Bergeron's avatar
Micaël Bergeron committed
486
          expect(response).to have_gitlab_http_status(:not_found)
487 488 489
        end
      end
    end
490 491
  end

492
  describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
493
    let(:api_user) { reporter }
494
    let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
495 496

    before do
Micaël Bergeron's avatar
Micaël Bergeron committed
497
      stub_artifacts_object_storage
498
      job.success
499
    end
500

501 502
    def get_for_ref(ref = pipeline.ref, job_name = job.name)
      get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), job: job_name
503 504
    end

505
    context 'when not logged in' do
506
      let(:api_user) { nil }
507 508

      before do
509
        get_for_ref
510 511
      end

512 513
      it 'does not find a resource in a private project' do
        expect(project).to be_private
514
        expect(response).to have_gitlab_http_status(404)
515 516 517
      end
    end

518
    context 'when logging as guest' do
519
      let(:api_user) { guest }
520 521

      before do
522
        get_for_ref
523 524 525
      end

      it 'gives 403' do
526
        expect(response).to have_gitlab_http_status(403)
527 528 529
      end
    end

Filipa Lacerda's avatar
Filipa Lacerda committed
530
    context 'non-existing job' do
531
      shared_examples 'not found' do
532
        it { expect(response).to have_gitlab_http_status(:not_found) }
533 534
      end

535 536
      context 'has no such ref' do
        before do
537
          get_for_ref('TAIL')
538 539
        end

540
        it_behaves_like 'not found'
541 542
      end

Filipa Lacerda's avatar
Filipa Lacerda committed
543
      context 'has no such job' do
544
        before do
545
          get_for_ref(pipeline.ref, 'NOBUILD')
546 547
        end

548
        it_behaves_like 'not found'
549
      end
550 551
    end

Filipa Lacerda's avatar
Filipa Lacerda committed
552
    context 'find proper job' do
553
      shared_examples 'a valid file' do
554 555 556 557 558 559 560
        context 'when artifacts are stored locally' do
          let(:download_headers) do
            { 'Content-Transfer-Encoding' => 'binary',
              'Content-Disposition' =>
                "attachment; filename=#{job.artifacts_file.filename}" }
          end

Micaël Bergeron's avatar
Micaël Bergeron committed
561
          it { expect(response).to have_http_status(:ok) }
562
          it { expect(response.headers.to_h).to include(download_headers) }
563
        end
564

565
        context 'when artifacts are stored remotely' do
566 567 568 569 570 571 572 573
          let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
          let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }

          before do
            job.reload

            get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
          end
574 575

          it 'returns location redirect' do
Micaël Bergeron's avatar
Micaël Bergeron committed
576
            expect(response).to have_http_status(:found)
577 578
          end
        end
579 580 581 582
      end

      context 'with regular branch' do
        before do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
583
          pipeline.reload
584 585 586
          pipeline.update(ref: 'master',
                          sha: project.commit('master').sha)

587
          get_for_ref('master')
588 589
        end

590
        it_behaves_like 'a valid file'
591 592
      end

593 594
      context 'with branch name containing slash' do
        before do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
595
          pipeline.reload
596 597 598 599 600
          pipeline.update(ref: 'improve/awesome',
                          sha: project.commit('improve/awesome').sha)
        end

        before do
601
          get_for_ref('improve/awesome')
602 603
        end

604
        it_behaves_like 'a valid file'
605
      end
606 607 608
    end
  end

609
  describe 'GET /projects/:id/jobs/:job_id/trace' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
610
    before do
611
      get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user)
Lin Jen-Shin's avatar
Lin Jen-Shin committed
612
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
613

614
    context 'authorized user' do
615 616
      context 'when trace is in ObjectStorage' do
        let!(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
617
        let(:url) { 'http://object-storage/trace' }
Kamil Trzciński's avatar
Kamil Trzciński committed
618
        let(:file_path) { expand_fixture_path('trace/sample_trace') }
619 620

        before do
621
          stub_remote_url_206(url, file_path)
622
          allow_any_instance_of(JobArtifactUploader).to receive(:file_storage?) { false }
623
          allow_any_instance_of(JobArtifactUploader).to receive(:url) { url }
Kamil Trzciński's avatar
Kamil Trzciński committed
624
          allow_any_instance_of(JobArtifactUploader).to receive(:size) { File.size(file_path) }
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
        end

        it 'returns specific job trace' do
          expect(response).to have_gitlab_http_status(200)
          expect(response.body).to eq(job.trace.raw)
        end
      end

      context 'when trace is artifact' do
        let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }

        it 'returns specific job trace' do
          expect(response).to have_gitlab_http_status(200)
          expect(response.body).to eq(job.trace.raw)
        end
      end

      context 'when trace is file' do
        let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }

        it 'returns specific job trace' do
          expect(response).to have_gitlab_http_status(200)
          expect(response.body).to eq(job.trace.raw)
        end
649 650 651 652
      end
    end

    context 'unauthorized user' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
653
      let(:api_user) { nil }
654

Filipa Lacerda's avatar
Filipa Lacerda committed
655
      it 'does not return specific job trace' do
656
        expect(response).to have_gitlab_http_status(401)
657 658 659
      end
    end
  end
660

661
  describe 'POST /projects/:id/jobs/:job_id/cancel' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
662
    before do
663
      post api("/projects/#{project.id}/jobs/#{job.id}/cancel", api_user)
Lin Jen-Shin's avatar
Lin Jen-Shin committed
664
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
665

666
    context 'authorized user' do
667
      context 'user with :update_build persmission' do
Filipa Lacerda's avatar
Filipa Lacerda committed
668
        it 'cancels running or pending job' do
669
          expect(response).to have_gitlab_http_status(201)
Kamil Trzciński's avatar
Kamil Trzciński committed
670
          expect(project.builds.first.status).to eq('success')
671 672 673
        end
      end

674
      context 'user without :update_build permission' do
675
        let(:api_user) { reporter }
676

Filipa Lacerda's avatar
Filipa Lacerda committed
677
        it 'does not cancel job' do
678
          expect(response).to have_gitlab_http_status(403)
679 680 681 682 683
        end
      end
    end

    context 'unauthorized user' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
684
      let(:api_user) { nil }
685

Filipa Lacerda's avatar
Filipa Lacerda committed
686
      it 'does not cancel job' do
687
        expect(response).to have_gitlab_http_status(401)
688 689 690 691
      end
    end
  end

692
  describe 'POST /projects/:id/jobs/:job_id/retry' do
693
    let(:job) { create(:ci_build, :canceled, pipeline: pipeline) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
694

Lin Jen-Shin's avatar
Lin Jen-Shin committed
695
    before do
696
      post api("/projects/#{project.id}/jobs/#{job.id}/retry", api_user)
Lin Jen-Shin's avatar
Lin Jen-Shin committed
697
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
698

699
    context 'authorized user' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
700
      context 'user with :update_build permission' do
Filipa Lacerda's avatar
Filipa Lacerda committed
701
        it 'retries non-running job' do
702
          expect(response).to have_gitlab_http_status(201)
703 704 705 706 707
          expect(project.builds.first.status).to eq('canceled')
          expect(json_response['status']).to eq('pending')
        end
      end

708
      context 'user without :update_build permission' do
709
        let(:api_user) { reporter }
710

Filipa Lacerda's avatar
Filipa Lacerda committed
711
        it 'does not retry job' do
712
          expect(response).to have_gitlab_http_status(403)
713 714 715 716 717
        end
      end
    end

    context 'unauthorized user' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
718
      let(:api_user) { nil }
719

Filipa Lacerda's avatar
Filipa Lacerda committed
720
      it 'does not retry job' do
721
        expect(response).to have_gitlab_http_status(401)
722 723 724
      end
    end
  end
725

726
  describe 'POST /projects/:id/jobs/:job_id/erase' do
727
    let(:role) { :maintainer }
Shinya Maeda's avatar
Shinya Maeda committed
728

729
    before do
730
      project.add_role(user, role)
731

732
      post api("/projects/#{project.id}/jobs/#{job.id}/erase", user)
733 734
    end

Filipa Lacerda's avatar
Filipa Lacerda committed
735
    context 'job is erasable' do
736
      let(:job) { create(:ci_build, :trace_artifact, :artifacts, :test_reports, :success, project: project, pipeline: pipeline) }
737

Filipa Lacerda's avatar
Filipa Lacerda committed
738
      it 'erases job content' do
739
        expect(response).to have_gitlab_http_status(201)
740
        expect(job.job_artifacts.count).to eq(0)
741
        expect(job.trace.exist?).to be_falsy
742 743
        expect(job.artifacts_file.exists?).to be_falsy
        expect(job.artifacts_metadata.exists?).to be_falsy
744
        expect(job.has_job_artifacts?).to be_falsy
745
      end
746

Filipa Lacerda's avatar
Filipa Lacerda committed
747
      it 'updates job' do
748 749 750 751
        job.reload

        expect(job.erased_at).to be_truthy
        expect(job.erased_by).to eq(user)
752
      end
753 754
    end

Filipa Lacerda's avatar
Filipa Lacerda committed
755
    context 'job is not erasable' do
756
      let(:job) { create(:ci_build, :trace_live, project: project, pipeline: pipeline) }
757

758
      it 'responds with forbidden' do
759
        expect(response).to have_gitlab_http_status(403)
760 761
      end
    end
Shinya Maeda's avatar
Shinya Maeda committed
762

Shinya Maeda's avatar
Shinya Maeda committed
763 764
    context 'when a developer erases a build' do
      let(:role) { :developer }
765
      let(:job) { create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline, user: owner) }
Shinya Maeda's avatar
Shinya Maeda committed
766 767 768 769 770 771 772 773 774 775 776

      context 'when the build was created by the developer' do
        let(:owner) { user }

        it { expect(response).to have_gitlab_http_status(201) }
      end

      context 'when the build was created by the other' do
        let(:owner) { create(:user) }

        it { expect(response).to have_gitlab_http_status(403) }
777 778 779
      end
    end
  end
780

781
  describe 'POST /projects/:id/jobs/:job_id/artifacts/keep' do
782
    before do
783
      post api("/projects/#{project.id}/jobs/#{job.id}/artifacts/keep", user)
784 785 786
    end

    context 'artifacts did not expire' do
787
      let(:job) do
788
        create(:ci_build, :trace_artifact, :artifacts, :success,
789 790 791
               project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days)
      end

792
      it 'keeps artifacts' do
793
        expect(response).to have_gitlab_http_status(200)
794
        expect(job.reload.artifacts_expire_at).to be_nil
795 796 797 798
      end
    end

    context 'no artifacts' do
799
      let(:job) { create(:ci_build, project: project, pipeline: pipeline) }
800

801
      it 'responds with not found' do
802
        expect(response).to have_gitlab_http_status(404)
803 804 805
      end
    end
  end
806

807
  describe 'POST /projects/:id/jobs/:job_id/play' do
808
    before do
809
      post api("/projects/#{project.id}/jobs/#{job.id}/play", api_user)
810 811
    end

Filipa Lacerda's avatar
Filipa Lacerda committed
812
    context 'on an playable job' do
813
      let(:job) { create(:ci_build, :manual, project: project, pipeline: pipeline) }
814

815 816
      context 'when user is authorized to trigger a manual action' do
        it 'plays the job' do
817
          expect(response).to have_gitlab_http_status(200)
818
          expect(json_response['user']['id']).to eq(user.id)
819 820
          expect(json_response['id']).to eq(job.id)
          expect(job.reload).to be_pending
821 822 823 824 825 826 827 828
        end
      end

      context 'when user is not authorized to trigger a manual action' do
        context 'when user does not have access to the project' do
          let(:api_user) { create(:user) }

          it 'does not trigger a manual action' do
829
            expect(job.reload).to be_manual
830
            expect(response).to have_gitlab_http_status(404)
831 832 833 834 835 836 837
          end
        end

        context 'when user is not allowed to trigger the manual action' do
          let(:api_user) { reporter }

          it 'does not trigger a manual action' do
838
            expect(job.reload).to be_manual
839
            expect(response).to have_gitlab_http_status(403)
840 841
          end
        end
842 843 844
      end
    end

Filipa Lacerda's avatar
Filipa Lacerda committed
845
    context 'on a non-playable job' do
846
      it 'returns a status code 400, Bad Request' do
847
        expect(response).to have_gitlab_http_status 400
Filipa Lacerda's avatar
Filipa Lacerda committed
848
        expect(response.body).to match("Unplayable Job")
849 850 851
      end
    end
  end
852
end