builds_spec.rb 27.8 KB
Newer Older
1 2
require 'spec_helper'

3
describe Ci::API::Builds do
Douwe Maan's avatar
Douwe Maan committed
4
  let(:runner) { FactoryGirl.create(:ci_runner, tag_list: %w(mysql ruby)) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
5
  let(:project) { FactoryGirl.create(:empty_project, shared_runners_enabled: false) }
6
  let(:last_update) { nil }
7 8

  describe "Builds API for runners" do
9
    let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') }
10 11

    before do
12
      project.runners << runner
13 14 15
    end

    describe "POST /builds/register" do
16
      let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
17
      let(:user_agent) { 'gitlab-ci-multi-runner 1.5.2 (1-5-stable; go1.6.3; linux/amd64)' }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
18 19
      let!(:last_update) { }
      let!(:new_update) { }
20

21 22 23 24
      before do
        stub_container_registry_config(enabled: false)
      end

25 26 27
      shared_examples 'no builds available' do
        context 'when runner sends version in User-Agent' do
          context 'for stable version' do
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
            it 'gives 204 and set X-GitLab-Last-Update' do
              expect(response).to have_http_status(204)
              expect(response.header).to have_key('X-GitLab-Last-Update')
            end
          end

          context 'when last_update is up-to-date' do
            let(:last_update) { runner.ensure_runner_queue_value }

            it 'gives 204 and set the same X-GitLab-Last-Update' do
              expect(response).to have_http_status(204)
              expect(response.header['X-GitLab-Last-Update'])
                .to eq(last_update)
            end
          end

          context 'when last_update is outdated' do
            let(:last_update) { runner.ensure_runner_queue_value }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
46
            let(:new_update) { runner.tick_runner_queue }
47 48 49 50 51 52

            it 'gives 204 and set a new X-GitLab-Last-Update' do
              expect(response).to have_http_status(204)
              expect(response.header['X-GitLab-Last-Update'])
                .to eq(new_update)
            end
53 54 55 56 57 58 59 60 61 62 63 64
          end

          context 'for beta version' do
            let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (1-5-stable; go1.6.3; linux/amd64)' }
            it { expect(response).to have_http_status(204) }
          end
        end

        context "when runner doesn't send version in User-Agent" do
          let(:user_agent) { 'Go-http-client/1.1' }
          it { expect(response).to have_http_status(404) }
        end
65 66 67 68 69

        context "when runner doesn't have a User-Agent" do
          let(:user_agent) { nil }
          it { expect(response).to have_http_status(404) }
        end
70
      end
71

72 73 74 75 76
      context 'when there is a pending build' do
        it 'starts a build' do
          register_builds info: { platform: :darwin }

          expect(response).to have_http_status(201)
77
          expect(response.headers).not_to have_key('X-GitLab-Last-Update')
78 79 80 81
          expect(json_response['sha']).to eq(build.sha)
          expect(runner.reload.platform).to eq("darwin")
          expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] })
          expect(json_response["variables"]).to include(
82 83
            { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true },
            { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true },
84 85 86 87 88 89 90
            { "key" => "DB_NAME", "value" => "postgres", "public" => true }
          )
        end

        it 'updates runner info' do
          expect { register_builds }.to change { runner.reload.contacted_at }
        end
91

Kamil Trzcinski's avatar
Kamil Trzcinski committed
92 93 94 95 96 97 98 99 100 101 102 103 104 105
        context 'when concurrently updating build' do
          before do
            expect_any_instance_of(Ci::Build).to receive(:run!).
              and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
          end

          it 'returns a conflict' do
            register_builds info: { platform: :darwin }

            expect(response).to have_http_status(409)
            expect(response.headers).not_to have_key('X-GitLab-Last-Update')
          end
        end

106 107
        context 'registry credentials' do
          let(:registry_credentials) do
108
            { 'type' => 'registry',
109 110 111
              'url' => 'registry.example.com:5005',
              'username' => 'gitlab-ci-token',
              'password' => build.token }
112 113
          end

114 115 116 117
          context 'when registry is enabled' do
            before do
              stub_container_registry_config(enabled: true, host_port: 'registry.example.com:5005')
            end
118

119 120
            it 'sends registry credentials key' do
              register_builds info: { platform: :darwin }
121

122 123 124
              expect(json_response).to have_key('credentials')
              expect(json_response['credentials']).to include(registry_credentials)
            end
125 126
          end

127 128 129 130 131 132 133
          context 'when registry is disabled' do
            before do
              stub_container_registry_config(enabled: false, host_port: 'registry.example.com:5005')
            end

            it 'does not send registry credentials' do
              register_builds info: { platform: :darwin }
134

135 136 137
              expect(json_response).to have_key('credentials')
              expect(json_response['credentials']).not_to include(registry_credentials)
            end
138 139
          end
        end
140 141
      end

142 143 144 145 146
      context 'when builds are finished' do
        before do
          build.success
          register_builds
        end
147 148

        it_behaves_like 'no builds available'
149 150
      end

151 152 153 154 155 156
      context 'for other project with builds' do
        before do
          build.success
          create(:ci_build, :pending)
          register_builds
        end
157 158

        it_behaves_like 'no builds available'
159 160
      end

161
      context 'for shared runner' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
162
        let!(:runner) { create(:ci_runner, :shared, token: "SharedRunner") }
163

164
        before do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
165
          register_builds(runner.token)
166
        end
167 168

        it_behaves_like 'no builds available'
169 170
      end

171 172 173 174 175 176
      context 'for triggered build' do
        before do
          trigger = create(:ci_trigger, project: project)
          create(:ci_trigger_request_with_variables, pipeline: pipeline, builds: [build], trigger: trigger)
          project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
        end
177

178 179 180 181 182
        it "returns variables for triggers" do
          register_builds info: { platform: :darwin }

          expect(response).to have_http_status(201)
          expect(json_response["variables"]).to include(
183 184 185
            { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true },
            { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true },
            { "key" => "CI_PIPELINE_TRIGGERED", "value" => "true", "public" => true },
186 187
            { "key" => "DB_NAME", "value" => "postgres", "public" => true },
            { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
188
            { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false }
189 190
          )
        end
191 192
      end

193 194 195 196
      context 'with multiple builds' do
        before do
          build.success
        end
197

198
        let!(:test_build) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
199

200 201
        it "returns dependent builds" do
          register_builds info: { platform: :darwin }
202

203 204 205 206 207
          expect(response).to have_http_status(201)
          expect(json_response["id"]).to eq(test_build.id)
          expect(json_response["depends_on_builds"].count).to eq(1)
          expect(json_response["depends_on_builds"][0]).to include('id' => build.id, 'name' => 'spinach')
        end
208
      end
209 210 211 212 213 214 215 216

      %w(name version revision platform architecture).each do |param|
        context "updates runner #{param}" do
          let(:value) { "#{param}_value" }

          subject { runner.read_attribute(param.to_sym) }

          it do
217 218 219
            register_builds info: { param => value }

            expect(response).to have_http_status(201)
220 221 222 223 224
            runner.reload
            is_expected.to eq(value)
          end
        end
      end
225 226 227

      context 'when build has no tags' do
        before do
228
          build.update(tags: [])
229 230 231 232 233 234 235 236 237 238 239 240 241
        end

        context 'when runner is allowed to pick untagged builds' do
          before { runner.update_column(:run_untagged, true) }

          it 'picks build' do
            register_builds

            expect(response).to have_http_status 201
          end
        end

        context 'when runner is not allowed to pick untagged builds' do
242 243
          before do
            runner.update_column(:run_untagged, false)
244 245
            register_builds
          end
246 247

          it_behaves_like 'no builds available'
248
        end
249
      end
250

251
      context 'when runner is paused' do
252
        let(:runner) { create(:ci_runner, :inactive, token: 'InactiveRunner') }
253

254 255 256 257
        it 'responds with 404' do
          register_builds

          expect(response).to have_http_status 404
258 259
        end

260 261 262 263
        it 'does not update runner info' do
          expect { register_builds }
            .not_to change { runner.reload.contacted_at }
        end
264 265
      end

266
      def register_builds(token = runner.token, **params)
267 268 269
        new_params = params.merge(token: token, last_update: last_update)

        post ci_api("/builds/register"), new_params, { 'User-Agent' => user_agent }
270
      end
271 272 273
    end

    describe "PUT /builds/:id" do
274
      let(:build) { create(:ci_build, :pending, :trace, pipeline: pipeline, runner_id: runner.id) }
275

276
      before do
277
        build.run!
Valery Sizov's avatar
Valery Sizov committed
278
        put ci_api("/builds/#{build.id}"), token: runner.token
279 280
      end

281
      it "updates a running build" do
282
        expect(response).to have_http_status(200)
283 284
      end

285
      it 'does not override trace information when no trace is given' do
286
        expect(build.reload.trace.raw).to eq 'BUILD TRACE'
287 288
      end

Filipa Lacerda's avatar
Filipa Lacerda committed
289
      context 'job has been erased' do
290 291
        let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }

292
        it 'responds with forbidden' do
293 294
          expect(response.status).to eq 403
        end
295 296
      end
    end
297

298
    describe 'PATCH /builds/:id/trace.txt' do
299
      let(:build) do
300
        attributes = { runner_id: runner.id, pipeline: pipeline }
301
        create(:ci_build, :running, :trace, attributes)
302 303
      end

Tomasz Maczukin's avatar
Tomasz Maczukin committed
304 305
      let(:headers) { { Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token, 'Content-Type' => 'text/plain' } }
      let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
306 307 308 309
      let(:update_interval) { 10.seconds.to_i }

      def patch_the_trace(content = ' appended', request_headers = nil)
        unless request_headers
310 311 312 313 314
          build.trace.read do |stream|
            offset = stream.size
            limit = offset + content.length - 1
            request_headers = headers.merge({ 'Content-Range' => "#{offset}-#{limit}" })
          end
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
        end

        Timecop.travel(build.updated_at + update_interval) do
          patch ci_api("/builds/#{build.id}/trace.txt"), content, request_headers
          build.reload
        end
      end

      def initial_patch_the_trace
        patch_the_trace(' appended', headers_with_range)
      end

      def force_patch_the_trace
        2.times { patch_the_trace('') }
      end
330 331

      before do
332
        initial_patch_the_trace
333 334
      end

Tomasz Maczukin's avatar
Tomasz Maczukin committed
335
      context 'when request is valid' do
Valery Sizov's avatar
Valery Sizov committed
336 337
        it 'gets correct response' do
          expect(response.status).to eq 202
338
          expect(build.reload.trace.raw).to eq 'BUILD TRACE appended'
Valery Sizov's avatar
Valery Sizov committed
339 340 341 342
          expect(response.header).to have_key 'Range'
          expect(response.header).to have_key 'Build-Status'
        end

343 344 345 346 347 348
        context 'when build has been updated recently' do
          it { expect{ patch_the_trace }.not_to change { build.updated_at }}

          it 'changes the build trace' do
            patch_the_trace

349
            expect(build.reload.trace.raw).to eq 'BUILD TRACE appended appended'
350 351 352 353 354 355 356 357
          end

          context 'when Runner makes a force-patch' do
            it { expect{ force_patch_the_trace }.not_to change { build.updated_at }}

            it "doesn't change the build.trace" do
              force_patch_the_trace

358
              expect(build.reload.trace.raw).to eq 'BUILD TRACE appended'
359 360 361 362 363 364 365 366 367 368 369 370
            end
          end
        end

        context 'when build was not updated recently' do
          let(:update_interval) { 15.minutes.to_i }

          it { expect { patch_the_trace }.to change { build.updated_at } }

          it 'changes the build.trace' do
            patch_the_trace

371
            expect(build.reload.trace.raw).to eq 'BUILD TRACE appended appended'
372 373 374 375 376 377 378 379
          end

          context 'when Runner makes a force-patch' do
            it { expect { force_patch_the_trace }.to change { build.updated_at } }

            it "doesn't change the build.trace" do
              force_patch_the_trace

380
              expect(build.reload.trace.raw).to eq 'BUILD TRACE appended'
381 382 383
            end
          end
        end
384 385 386

        context 'when project for the build has been deleted' do
          let(:build) do
387
            attributes = { runner_id: runner.id, pipeline: pipeline }
388
            create(:ci_build, :running, :trace, attributes) do |build|
389 390
              build.project.update(pending_delete: true)
            end
391 392 393
          end

          it 'responds with forbidden' do
394
            expect(response.status).to eq(403)
395 396
          end
        end
397 398 399 400 401 402 403 404 405
      end

      context 'when Runner makes a force-patch' do
        before do
          force_patch_the_trace
        end

        it 'gets correct response' do
          expect(response.status).to eq 202
406
          expect(build.reload.trace.raw).to eq 'BUILD TRACE appended'
407 408 409
          expect(response.header).to have_key 'Range'
          expect(response.header).to have_key 'Build-Status'
        end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
410 411 412 413 414
      end

      context 'when content-range start is too big' do
        let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) }

415
        it 'gets 416 error response with range headers' do
Valery Sizov's avatar
Valery Sizov committed
416 417 418 419
          expect(response.status).to eq 416
          expect(response.header).to have_key 'Range'
          expect(response.header['Range']).to eq '0-11'
        end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
420 421 422 423 424
      end

      context 'when content-range start is too small' do
        let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) }

425
        it 'gets 416 error response with range headers' do
Valery Sizov's avatar
Valery Sizov committed
426 427 428 429
          expect(response.status).to eq 416
          expect(response.header).to have_key 'Range'
          expect(response.header['Range']).to eq '0-11'
        end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
430 431 432
      end

      context 'when Content-Range header is missing' do
433
        let(:headers_with_range) { headers }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
434 435

        it { expect(response.status).to eq 400 }
436 437
      end

438
      context 'when build has been errased' do
439 440
        let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }

Tomasz Maczukin's avatar
Tomasz Maczukin committed
441
        it { expect(response.status).to eq 403 }
442 443 444
      end
    end

445 446 447
    context "Artifacts" do
      let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
      let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
448
      let(:build) { create(:ci_build, :pending, pipeline: pipeline, runner_id: runner.id) }
449 450 451 452
      let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
      let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
      let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
      let(:get_url) { ci_api("/builds/#{build.id}/artifacts") }
453 454
      let(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
      let(:headers) { { "GitLab-Workhorse" => "1.0", Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } }
455 456
      let(:token) { build.token }
      let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => token) }
457

458 459
      before { build.run! }

460
      describe "POST /builds/:id/artifacts/authorize" do
461
        context "authorizes posting artifact to running build" do
462
          it "using token as parameter" do
463
            post authorize_url, { token: build.token }, headers
464

465
            expect(response).to have_http_status(200)
466
            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
467
            expect(json_response["TempPath"]).not_to be_nil
468 469 470 471
          end

          it "using token as header" do
            post authorize_url, {}, headers_with_token
472

473
            expect(response).to have_http_status(200)
474
            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
475
            expect(json_response["TempPath"]).not_to be_nil
476
          end
477

478 479
          it "using runners token" do
            post authorize_url, { token: build.project.runners_token }, headers
480

481 482 483 484 485
            expect(response).to have_http_status(200)
            expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
            expect(json_response["TempPath"]).not_to be_nil
          end

486
          it "reject requests that did not go through gitlab-workhorse" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
487
            headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
488

489
            post authorize_url, { token: build.token }, headers
490

491 492
            expect(response).to have_http_status(500)
          end
493 494
        end

495
        context "fails to post too large artifact" do
496
          it "using token as parameter" do
497
            stub_application_setting(max_artifacts_size: 0)
498

499
            post authorize_url, { token: build.token, filesize: 100 }, headers
500

501
            expect(response).to have_http_status(413)
502 503 504
          end

          it "using token as header" do
505
            stub_application_setting(max_artifacts_size: 0)
506

507
            post authorize_url, { filesize: 100 }, headers_with_token
508

509
            expect(response).to have_http_status(413)
510 511 512
          end
        end

513 514 515
        context 'authorization token is invalid' do
          before { post authorize_url, { token: 'invalid', filesize: 100 } }

516
          it 'responds with forbidden' do
517
            expect(response).to have_http_status(403)
518 519 520 521 522
          end
        end
      end

      describe "POST /builds/:id/artifacts" do
523
        context "disable sanitizer" do
524 525 526 527 528
          before do
            # by configuring this path we allow to pass temp file from any path
            allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
          end

529
          describe 'build has been erased' do
530
            let(:build) { create(:ci_build, erased_at: Time.now) }
531 532 533 534

            before do
              upload_artifacts(file_upload, headers_with_token)
            end
535

536
            it 'responds with forbidden' do
537 538 539 540
              expect(response.status).to eq 403
            end
          end

541
          describe 'uploading artifacts for a running build' do
542
            shared_examples 'successful artifacts upload' do
543 544
              it 'updates successfully' do
                response_filename =
545
                  json_response['artifacts_file']['filename']
546 547 548 549

                expect(response).to have_http_status(201)
                expect(response_filename).to eq(file_upload.original_filename)
              end
550 551
            end

552 553 554 555 556
            context 'uses regular file post' do
              before do
                upload_artifacts(file_upload, headers_with_token, false)
              end

557
              it_behaves_like 'successful artifacts upload'
558 559
            end

560 561 562 563 564
            context 'uses accelerated file post' do
              before do
                upload_artifacts(file_upload, headers_with_token, true)
              end

565
              it_behaves_like 'successful artifacts upload'
566 567 568 569 570 571 572 573
            end

            context 'updates artifact' do
              before do
                upload_artifacts(file_upload2, headers_with_token)
                upload_artifacts(file_upload, headers_with_token)
              end

574
              it_behaves_like 'successful artifacts upload'
575
            end
576 577 578 579 580 581 582 583 584 585

            context 'when using runners token' do
              let(:token) { build.project.runners_token }

              before do
                upload_artifacts(file_upload, headers_with_token)
              end

              it_behaves_like 'successful artifacts upload'
            end
586 587
          end

588
          context 'posts artifacts file and metadata file' do
589
            let!(:artifacts) { file_upload }
590
            let!(:metadata) { file_upload2 }
591

592 593
            let(:stored_artifacts_file) { build.reload.artifacts_file.file }
            let(:stored_metadata_file) { build.reload.artifacts_metadata.file }
594
            let(:stored_artifacts_size) { build.reload.artifacts_size }
595

596 597 598
            before do
              post(post_url, post_data, headers_with_token)
            end
599

600
            context 'posts data accelerated by workhorse is correct' do
601 602 603 604 605 606 607 608
              let(:post_data) do
                { 'file.path' => artifacts.path,
                  'file.name' => artifacts.original_filename,
                  'metadata.path' => metadata.path,
                  'metadata.name' => metadata.original_filename }
              end

              it 'stores artifacts and artifacts metadata' do
609
                expect(response).to have_http_status(201)
610 611
                expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
                expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
612
                expect(stored_artifacts_size).to eq(71759)
613
              end
614 615
            end

616
            context 'no artifacts file in post data' do
617
              let(:post_data) do
618
                { 'metadata' => metadata }
619 620
              end

621
              it 'is expected to respond with bad request' do
622
                expect(response).to have_http_status(400)
623 624
              end

625
              it 'does not store metadata' do
626 627
                expect(stored_metadata_file).to be_nil
              end
628 629 630
            end
          end

631
          context 'with an expire date' do
632
            let!(:artifacts) { file_upload }
633
            let(:default_artifacts_expire_in) {}
634 635 636 637 638 639 640 641

            let(:post_data) do
              { 'file.path' => artifacts.path,
                'file.name' => artifacts.original_filename,
                'expire_in' => expire_in }
            end

            before do
642
              stub_application_setting(
643
                default_artifacts_expire_in: default_artifacts_expire_in)
644

645 646 647
              post(post_url, post_data, headers_with_token)
            end

648
            context 'with an expire_in given' do
649 650
              let(:expire_in) { '7 days' }

Kamil Trzcinski's avatar
Kamil Trzcinski committed
651
              it 'updates when specified' do
652
                build.reload
653
                expect(response).to have_http_status(201)
654
                expect(json_response['artifacts_expire_at']).not_to be_empty
655 656
                expect(build.artifacts_expire_at).
                  to be_within(5.minutes).of(7.days.from_now)
657 658 659
              end
            end

660
            context 'with no expire_in given' do
661 662
              let(:expire_in) { nil }

Kamil Trzcinski's avatar
Kamil Trzcinski committed
663
              it 'ignores if not specified' do
664
                build.reload
665
                expect(response).to have_http_status(201)
666 667 668
                expect(json_response['artifacts_expire_at']).to be_nil
                expect(build.artifacts_expire_at).to be_nil
              end
669 670

              context 'with application default' do
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
                context 'default to 5 days' do
                  let(:default_artifacts_expire_in) { '5 days' }

                  it 'sets to application default' do
                    build.reload
                    expect(response).to have_http_status(201)
                    expect(json_response['artifacts_expire_at'])
                      .not_to be_empty
                    expect(build.artifacts_expire_at)
                      .to be_within(5.minutes).of(5.days.from_now)
                  end
                end

                context 'default to 0' do
                  let(:default_artifacts_expire_in) { '0' }

                  it 'does not set expire_in' do
                    build.reload
                    expect(response).to have_http_status(201)
                    expect(json_response['artifacts_expire_at']).to be_nil
                    expect(build.artifacts_expire_at).to be_nil
                  end
693 694
                end
              end
695 696 697
            end
          end

698
          context "artifacts file is too large" do
699
            it "fails to post too large artifact" do
700
              stub_application_setting(max_artifacts_size: 0)
701
              upload_artifacts(file_upload, headers_with_token)
702
              expect(response).to have_http_status(413)
703 704 705
            end
          end

706
          context "artifacts post request does not contain file" do
707
            it "fails to post artifacts without file" do
708
              post post_url, {}, headers_with_token
709
              expect(response).to have_http_status(400)
710 711 712
            end
          end

713
          context 'GitLab Workhorse is not configured' do
714
            it "fails to post artifacts without GitLab-Workhorse" do
715
              post post_url, { token: build.token }, {}
716
              expect(response).to have_http_status(403)
717 718 719 720
            end
          end
        end

721
        context "artifacts are being stored outside of tmp path" do
722 723 724 725 726 727 728 729 730 731 732
          before do
            # by configuring this path we allow to pass file from @tmpdir only
            # but all temporary files are stored in system tmp directory
            @tmpdir = Dir.mktmpdir
            allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir)
          end

          after do
            FileUtils.remove_entry @tmpdir
          end

733
          it "fails to post artifacts for outside of tmp path" do
734
            upload_artifacts(file_upload, headers_with_token)
735
            expect(response).to have_http_status(400)
736 737 738
          end
        end

739 740 741 742 743 744 745 746 747
        def upload_artifacts(file, headers = {}, accelerated = true)
          if accelerated
            post post_url, {
              'file.path' => file.path,
              'file.name' => file.original_filename
            }, headers
          else
            post post_url, { file: file }, headers
          end
748 749 750
        end
      end

751 752
      describe 'DELETE /builds/:id/artifacts' do
        let(:build) { create(:ci_build, :artifacts) }
753 754 755 756

        before do
          delete delete_url, token: build.token
        end
757

758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
        shared_examples 'having removable artifacts' do
          it 'removes build artifacts' do
            build.reload

            expect(response).to have_http_status(200)
            expect(build.artifacts_file.exists?).to be_falsy
            expect(build.artifacts_metadata.exists?).to be_falsy
            expect(build.artifacts_size).to be_nil
          end
        end

        context 'when using build token' do
          before do
            delete delete_url, token: build.token
          end

          it_behaves_like 'having removable artifacts'
        end

        context 'when using runnners token' do
          before do
            delete delete_url, token: build.project.runners_token
          end

          it_behaves_like 'having removable artifacts'
783 784 785 786
        end
      end

      describe 'GET /builds/:id/artifacts' do
787 788 789
        before do
          get get_url, token: token
        end
790 791 792 793

        context 'build has artifacts' do
          let(:build) { create(:ci_build, :artifacts) }
          let(:download_headers) do
794 795
            { 'Content-Transfer-Encoding' => 'binary',
              'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
796 797
          end

798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
          shared_examples 'having downloadable artifacts' do
            it 'download artifacts' do
              expect(response).to have_http_status(200)
              expect(response.headers).to include download_headers
            end
          end

          context 'when using build token' do
            let(:token) { build.token }

            it_behaves_like 'having downloadable artifacts'
          end

          context 'when using runnners token' do
            let(:token) { build.project.runners_token }

            it_behaves_like 'having downloadable artifacts'
815
          end
816 817
        end

818
        context 'build does not has artifacts' do
819 820
          let(:token) { build.token }

821
          it 'responds with not found' do
822
            expect(response).to have_http_status(404)
823
          end
824 825 826
        end
      end
    end
827 828
  end
end