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

3
describe API::Builds, api: true do
4 5 6
  include ApiHelpers

  let(:user) { create(:user) }
Kamil Trzciński's avatar
Kamil Trzciński committed
7
  let(:api_user) { user }
8 9
  let!(:project) { create(:project, creator_id: user.id) }
  let!(:developer) { create(:project_member, :developer, user: user, project: project) }
10 11
  let(:reporter) { create(:project_member, :reporter, project: project) }
  let(:guest) { create(:project_member, :guest, project: project) }
12
  let!(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
13
  let!(:build) { create(:ci_build, pipeline: pipeline) }
14 15

  describe 'GET /projects/:id/builds ' do
Kamil Trzciński's avatar
Kamil Trzciński committed
16 17
    let(:query) { '' }

Lin Jen-Shin's avatar
Lin Jen-Shin committed
18 19 20
    before do
      get api("/projects/#{project.id}/builds?#{query}", api_user)
    end
Kamil Trzciński's avatar
Kamil Trzciński committed
21

22
    context 'authorized user' do
23
      it 'returns project builds' do
24
        expect(response).to have_http_status(200)
25 26 27
        expect(json_response).to be_an Array
      end

28 29 30 31 32
      it 'returns correct values' do
        expect(json_response).not_to be_empty
        expect(json_response.first['commit']['id']).to eq project.commit.id
      end

33 34 35 36 37 38 39 40 41
      it 'returns pipeline data' do
        json_build = json_response.first
        expect(json_build['pipeline']).not_to be_empty
        expect(json_build['pipeline']['id']).to eq build.pipeline.id
        expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
        expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
        expect(json_build['pipeline']['status']).to eq build.pipeline.status
      end

Kamil Trzciński's avatar
Kamil Trzciński committed
42 43
      context 'filter project with one scope element' do
        let(:query) { 'scope=pending' }
44

Kamil Trzciński's avatar
Kamil Trzciński committed
45
        it do
46
          expect(response).to have_http_status(200)
Kamil Trzciński's avatar
Kamil Trzciński committed
47 48
          expect(json_response).to be_an Array
        end
49 50
      end

Kamil Trzciński's avatar
Kamil Trzciński committed
51 52
      context 'filter project with array of scope elements' do
        let(:query) { 'scope[0]=pending&scope[1]=running' }
53

Kamil Trzciński's avatar
Kamil Trzciński committed
54
        it do
55
          expect(response).to have_http_status(200)
Kamil Trzciński's avatar
Kamil Trzciński committed
56 57
          expect(json_response).to be_an Array
        end
58
      end
59

Kamil Trzciński's avatar
Kamil Trzciński committed
60 61
      context 'respond 400 when scope contains invalid state' do
        let(:query) { 'scope[0]=pending&scope[1]=unknown_status' }
62

63
        it { expect(response).to have_http_status(400) }
64
      end
65 66 67
    end

    context 'unauthorized user' do
Kamil Trzciński's avatar
Kamil Trzciński committed
68
      let(:api_user) { nil }
69

Kamil Trzciński's avatar
Kamil Trzciński committed
70
      it 'should not return project builds' do
71
        expect(response).to have_http_status(401)
72 73 74 75
      end
    end
  end

76
  describe 'GET /projects/:id/repository/commits/:sha/builds' do
77 78 79 80
    context 'when commit does not exist in repository' do
      before do
        get api("/projects/#{project.id}/repository/commits/1a271fd1/builds", api_user)
      end
81

82 83 84
      it 'responds with 404' do
        expect(response).to have_http_status(404)
      end
Kamil Trzciński's avatar
Kamil Trzciński committed
85 86
    end

87 88 89 90
    context 'when commit exists in repository' do
      context 'when user is authorized' do
        context 'when pipeline has builds' do
          before do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
91
            create(:ci_pipeline, project: project, sha: project.commit.id)
92
            create(:ci_build, pipeline: pipeline)
Lin Jen-Shin's avatar
Lin Jen-Shin committed
93
            create(:ci_build)
94 95 96 97

            get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user)
          end

98
          it 'returns project builds for specific commit' do
99 100 101 102
            expect(response).to have_http_status(200)
            expect(json_response).to be_an Array
            expect(json_response.size).to eq 2
          end
103 104 105 106 107 108 109 110 111

          it 'returns pipeline data' do
            json_build = json_response.first
            expect(json_build['pipeline']).not_to be_empty
            expect(json_build['pipeline']['id']).to eq build.pipeline.id
            expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
            expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
            expect(json_build['pipeline']['status']).to eq build.pipeline.status
          end
112 113 114 115 116 117 118 119 120 121 122 123 124 125
        end

        context 'when pipeline has no builds' do
          before do
            branch_head = project.commit('feature').id
            get api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user)
          end

          it 'returns an empty array' do
            expect(response).to have_http_status(200)
            expect(json_response).to be_an Array
            expect(json_response).to be_empty
          end
        end
126 127
      end

128 129
      context 'when user is not authorized' do
        before do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
130 131
          create(:ci_pipeline, project: project, sha: project.commit.id)
          create(:ci_build, pipeline: pipeline)
132

133 134 135
          get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil)
        end

136
        it 'does not return project builds' do
137 138 139
          expect(response).to have_http_status(401)
          expect(json_response.except('message')).to be_empty
        end
140 141 142
      end
    end
  end
143

144
  describe 'GET /projects/:id/builds/:build_id' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
145 146 147
    before do
      get api("/projects/#{project.id}/builds/#{build.id}", api_user)
    end
Kamil Trzciński's avatar
Kamil Trzciński committed
148

149
    context 'authorized user' do
150
      it 'returns specific build data' do
151
        expect(response).to have_http_status(200)
152 153
        expect(json_response['name']).to eq('test')
      end
154 155 156 157 158 159 160 161 162

      it 'returns pipeline data' do
        json_build = json_response
        expect(json_build['pipeline']).not_to be_empty
        expect(json_build['pipeline']['id']).to eq build.pipeline.id
        expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
        expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
        expect(json_build['pipeline']['status']).to eq build.pipeline.status
      end
163 164 165
    end

    context 'unauthorized user' do
Kamil Trzciński's avatar
Kamil Trzciński committed
166
      let(:api_user) { nil }
167

168
      it 'does not return specific build data' do
169
        expect(response).to have_http_status(401)
170 171 172 173
      end
    end
  end

174
  describe 'GET /projects/:id/builds/:build_id/artifacts' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
175 176 177
    before do
      get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
    end
178

Kamil Trzciński's avatar
Kamil Trzciński committed
179
    context 'build with artifacts' do
180
      let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
181

Kamil Trzciński's avatar
Kamil Trzciński committed
182 183
      context 'authorized user' do
        let(:download_headers) do
184 185
          { 'Content-Transfer-Encoding' => 'binary',
            'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
Kamil Trzciński's avatar
Kamil Trzciński committed
186
        end
187

188
        it 'returns specific build artifacts' do
189
          expect(response).to have_http_status(200)
Kamil Trzciński's avatar
Kamil Trzciński committed
190 191
          expect(response.headers).to include(download_headers)
        end
192 193
      end

Kamil Trzciński's avatar
Kamil Trzciński committed
194 195
      context 'unauthorized user' do
        let(:api_user) { nil }
196

197
        it 'does not return specific build artifacts' do
198
          expect(response).to have_http_status(401)
Kamil Trzciński's avatar
Kamil Trzciński committed
199
        end
200 201
      end
    end
Kamil Trzciński's avatar
Kamil Trzciński committed
202

203
    it 'does not return build artifacts if not uploaded' do
204
      expect(response).to have_http_status(404)
Kamil Trzciński's avatar
Kamil Trzciński committed
205
    end
206 207
  end

208
  describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
209
    let(:api_user) { reporter.user }
210 211 212 213 214
    let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }

    before do
      build.success
    end
215

216
    def path_for_ref(ref = pipeline.ref, job = build.name)
217
      api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", api_user)
218 219
    end

220
    context 'when not logged in' do
221
      let(:api_user) { nil }
222 223

      before do
224
        get path_for_ref
225 226
      end

227
      it 'gives 401' do
228 229 230 231
        expect(response).to have_http_status(401)
      end
    end

232
    context 'when logging as guest' do
233
      let(:api_user) { guest.user }
234 235 236 237 238 239 240 241 242 243

      before do
        get path_for_ref
      end

      it 'gives 403' do
        expect(response).to have_http_status(403)
      end
    end

244
    context 'non-existing build' do
245 246
      shared_examples 'not found' do
        it { expect(response).to have_http_status(:not_found) }
247 248
      end

249 250
      context 'has no such ref' do
        before do
251
          get path_for_ref('TAIL', build.name)
252 253
        end

254
        it_behaves_like 'not found'
255 256 257 258
      end

      context 'has no such build' do
        before do
259
          get path_for_ref(pipeline.ref, 'NOBUILD')
260 261
        end

262
        it_behaves_like 'not found'
263
      end
264 265
    end

266
    context 'find proper build' do
267
      shared_examples 'a valid file' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
268
        let(:download_headers) do
269 270 271
          { 'Content-Transfer-Encoding' => 'binary',
            'Content-Disposition' =>
              "attachment; filename=#{build.artifacts_file.filename}" }
Lin Jen-Shin's avatar
Lin Jen-Shin committed
272
        end
273

274 275
        it { expect(response).to have_http_status(200) }
        it { expect(response.headers).to include(download_headers) }
276 277 278 279
      end

      context 'with regular branch' do
        before do
Kamil Trzciński's avatar
Kamil Trzciński committed
280
          pipeline.reload
281 282 283
          pipeline.update(ref: 'master',
                          sha: project.commit('master').sha)

284
          get path_for_ref('master')
285 286
        end

287
        it_behaves_like 'a valid file'
288 289
      end

290 291
      context 'with branch name containing slash' do
        before do
Kamil Trzciński's avatar
Kamil Trzciński committed
292
          pipeline.reload
293 294 295 296 297
          pipeline.update(ref: 'improve/awesome',
                          sha: project.commit('improve/awesome').sha)
        end

        before do
298
          get path_for_ref('improve/awesome')
299 300
        end

301
        it_behaves_like 'a valid file'
302
      end
303 304 305
    end
  end

306
  describe 'GET /projects/:id/builds/:build_id/trace' do
307
    let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
308

Lin Jen-Shin's avatar
Lin Jen-Shin committed
309 310 311
    before do
      get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user)
    end
Kamil Trzciński's avatar
Kamil Trzciński committed
312

313
    context 'authorized user' do
314
      it 'returns specific build trace' do
315
        expect(response).to have_http_status(200)
Kamil Trzciński's avatar
Kamil Trzciński committed
316
        expect(response.body).to eq(build.trace)
317 318 319 320
      end
    end

    context 'unauthorized user' do
Kamil Trzciński's avatar
Kamil Trzciński committed
321
      let(:api_user) { nil }
322

323
      it 'does not return specific build trace' do
324
        expect(response).to have_http_status(401)
325 326 327
      end
    end
  end
328

329
  describe 'POST /projects/:id/builds/:build_id/cancel' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
330 331 332
    before do
      post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user)
    end
Kamil Trzciński's avatar
Kamil Trzciński committed
333

334
    context 'authorized user' do
335
      context 'user with :update_build persmission' do
336
        it 'cancels running or pending build' do
337
          expect(response).to have_http_status(201)
338 339 340 341
          expect(project.builds.first.status).to eq('canceled')
        end
      end

342
      context 'user without :update_build permission' do
343
        let(:api_user) { reporter.user }
344

345
        it 'does not cancel build' do
346
          expect(response).to have_http_status(403)
347 348 349 350 351
        end
      end
    end

    context 'unauthorized user' do
Kamil Trzciński's avatar
Kamil Trzciński committed
352
      let(:api_user) { nil }
353

354
      it 'does not cancel build' do
355
        expect(response).to have_http_status(401)
356 357 358 359
      end
    end
  end

360
  describe 'POST /projects/:id/builds/:build_id/retry' do
361
    let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
Kamil Trzciński's avatar
Kamil Trzciński committed
362

Lin Jen-Shin's avatar
Lin Jen-Shin committed
363 364 365
    before do
      post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user)
    end
Kamil Trzciński's avatar
Kamil Trzciński committed
366

367
    context 'authorized user' do
Kamil Trzciński's avatar
Kamil Trzciński committed
368
      context 'user with :update_build permission' do
369
        it 'retries non-running build' do
370
          expect(response).to have_http_status(201)
371 372 373 374 375
          expect(project.builds.first.status).to eq('canceled')
          expect(json_response['status']).to eq('pending')
        end
      end

376
      context 'user without :update_build permission' do
377
        let(:api_user) { reporter.user }
378

379
        it 'does not retry build' do
380
          expect(response).to have_http_status(403)
381 382 383 384 385
        end
      end
    end

    context 'unauthorized user' do
Kamil Trzciński's avatar
Kamil Trzciński committed
386
      let(:api_user) { nil }
387

388
      it 'does not retry build' do
389
        expect(response).to have_http_status(401)
390 391 392
      end
    end
  end
393

394
  describe 'POST /projects/:id/builds/:build_id/erase' do
395
    before do
396
      post api("/projects/#{project.id}/builds/#{build.id}/erase", user)
397 398
    end

399
    context 'build is erasable' do
400
      let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) }
401

402
      it 'erases build content' do
403
        expect(response.status).to eq 201
404 405 406 407
        expect(build.trace).to be_empty
        expect(build.artifacts_file.exists?).to be_falsy
        expect(build.artifacts_metadata.exists?).to be_falsy
      end
408

409
      it 'updates build' do
410 411 412
        expect(build.reload.erased_at).to be_truthy
        expect(build.reload.erased_by).to eq user
      end
413 414
    end

415
    context 'build is not erasable' do
416
      let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) }
417

418
      it 'responds with forbidden' do
419 420 421 422
        expect(response.status).to eq 403
      end
    end
  end
423 424 425 426 427 428 429 430 431 432 433 434

  describe 'POST /projects/:id/builds/:build_id/artifacts/keep' do
    before do
      post api("/projects/#{project.id}/builds/#{build.id}/artifacts/keep", user)
    end

    context 'artifacts did not expire' do
      let(:build) do
        create(:ci_build, :trace, :artifacts, :success,
               project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days)
      end

435
      it 'keeps artifacts' do
436
        expect(response.status).to eq 200
437
        expect(build.reload.artifacts_expire_at).to be_nil
438 439 440 441 442 443
      end
    end

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

444
      it 'responds with not found' do
445 446 447 448
        expect(response.status).to eq 404
      end
    end
  end
449 450 451 452 453 454 455 456 457 458 459 460

  describe 'POST /projects/:id/builds/:build_id/play' do
    before do
      post api("/projects/#{project.id}/builds/#{build.id}/play", user)
    end

    context 'on an playable build' do
      let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) }

      it 'plays the build' do
        expect(response).to have_http_status 200
        expect(json_response['user']['id']).to eq(user.id)
Z.J. van de Weg's avatar
Z.J. van de Weg committed
461
        expect(json_response['id']).to eq(build.id)
462 463 464 465 466 467 468 469 470 471
      end
    end

    context 'on a non-playable build' do
      it 'returns a status code 400, Bad Request' do
        expect(response).to have_http_status 400
        expect(response.body).to match("Unplayable Build")
      end
    end
  end
472
end