BigW Consortium Gitlab

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

3
describe Ci::API::API do
4 5
  include ApiHelpers

6
  let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) }
7
  let(:project) { FactoryGirl.create(:empty_project) }
8

9 10 11 12
  before do
    stub_ci_commit_to_return_yaml_file
  end

13
  describe "Builds API for runners" do
14
    let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") }
15
    let(:shared_project) { FactoryGirl.create(:empty_project, name: "SharedProject") }
16 17

    before do
18
      FactoryGirl.create :ci_runner_project, project: project, runner: runner
19 20 21 22
    end

    describe "POST /builds/register" do
      it "should start a build" do
23
        commit = FactoryGirl.create(:ci_commit, project: project)
24
        commit.create_builds('master', false, nil)
25 26
        build = commit.builds.first

Valery Sizov committed
27
        post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
28

29 30 31
        expect(response.status).to eq(201)
        expect(json_response['sha']).to eq(build.sha)
        expect(runner.reload.platform).to eq("darwin")
32 33 34
      end

      it "should return 404 error if no pending build found" do
Valery Sizov committed
35
        post ci_api("/builds/register"), token: runner.token
36

37
        expect(response.status).to eq(404)
38 39 40
      end

      it "should return 404 error if no builds for specific runner" do
41
        commit = FactoryGirl.create(:ci_commit, project: shared_project)
42
        FactoryGirl.create(:ci_build, commit: commit, status: 'pending')
43

Valery Sizov committed
44
        post ci_api("/builds/register"), token: runner.token
45

46
        expect(response.status).to eq(404)
47 48 49
      end

      it "should return 404 error if no builds for shared runner" do
50
        commit = FactoryGirl.create(:ci_commit, project: project)
51
        FactoryGirl.create(:ci_build, commit: commit, status: 'pending')
52

Valery Sizov committed
53
        post ci_api("/builds/register"), token: shared_runner.token
54

55
        expect(response.status).to eq(404)
56 57 58
      end

      it "returns options" do
59
        commit = FactoryGirl.create(:ci_commit, project: project)
60
        commit.create_builds('master', false, nil)
61

Valery Sizov committed
62
        post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
63

64
        expect(response.status).to eq(201)
Valery Sizov committed
65
        expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] })
66 67 68
      end

      it "returns variables" do
69
        commit = FactoryGirl.create(:ci_commit, project: project)
70
        commit.create_builds('master', false, nil)
71
        project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
72

Valery Sizov committed
73
        post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
74

75 76
        expect(response.status).to eq(201)
        expect(json_response["variables"]).to eq([
77 78
          { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
          { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
Valery Sizov committed
79
          { "key" => "DB_NAME", "value" => "postgres", "public" => true },
80
          { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }
81
        ])
82 83 84
      end

      it "returns variables for triggers" do
85
        trigger = FactoryGirl.create(:ci_trigger, project: project)
86
        commit = FactoryGirl.create(:ci_commit, project: project)
87

88
        trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger)
89
        commit.create_builds('master', false, nil, trigger_request)
90
        project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
91

Valery Sizov committed
92
        post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
93

94 95
        expect(response.status).to eq(201)
        expect(json_response["variables"]).to eq([
96 97 98
          { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
          { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
          { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
Valery Sizov committed
99 100 101
          { "key" => "DB_NAME", "value" => "postgres", "public" => true },
          { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
          { "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false },
102
        ])
103
      end
104 105 106 107 108 109 110 111 112

      it "returns dependent builds" do
        commit = FactoryGirl.create(:ci_commit, project: project)
        commit.create_builds('master', false, nil, nil)
        commit.builds.where(stage: 'test').each(&:success)

        post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }

        expect(response.status).to eq(201)
113 114
        expect(json_response["depends_on_builds"].count).to eq(2)
        expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec")
115
      end
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

      %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
            post ci_api("/builds/register"), token: runner.token, info: { param => value }
            expect(response.status).to eq(404)
            runner.reload
            is_expected.to eq(value)
          end
        end
      end
131 132 133
    end

    describe "PUT /builds/:id" do
134
      let(:commit) {create(:ci_commit, project: project)}
135
      let(:build) { create(:ci_build, :trace, commit: commit, runner_id: runner.id) }
136

137
      before do
138
        build.run!
Valery Sizov committed
139
        put ci_api("/builds/#{build.id}"), token: runner.token
140 141 142
      end

      it "should update a running build" do
143
        expect(response.status).to eq(200)
144 145
      end

146 147 148 149 150 151 152 153 154 155
      it 'should not override trace information when no trace is given' do
        expect(build.reload.trace).to eq 'BUILD TRACE'
      end

      context 'build has been erased' do
        let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }

        it 'should respond with forbidden' do
          expect(response.status).to eq 403
        end
156 157
      end
    end
158 159 160 161

    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') }
162 163
      let(:commit) { create(:ci_commit, project: project) }
      let(:build) { create(:ci_build, commit: commit, runner_id: runner.id) }
164 165 166 167
      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") }
168
      let(:headers) { { "GitLab-Workhorse" => "1.0" } }
169
      let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
170

171 172
      before { build.run! }

173 174 175
      describe "POST /builds/:id/artifacts/authorize" do
        context "should authorize posting artifact to running build" do
          it "using token as parameter" do
176
            post authorize_url, { token: build.token }, headers
177
            expect(response.status).to eq(200)
Kamil Trzcinski committed
178
            expect(json_response["TempPath"]).to_not be_nil
179 180 181 182 183
          end

          it "using token as header" do
            post authorize_url, {}, headers_with_token
            expect(response.status).to eq(200)
Kamil Trzcinski committed
184
            expect(json_response["TempPath"]).to_not be_nil
185 186 187 188 189
          end
        end

        context "should fail to post too large artifact" do
          it "using token as parameter" do
190
            stub_application_setting(max_artifacts_size: 0)
191
            post authorize_url, { token: build.token, filesize: 100 }, headers
192 193 194 195
            expect(response.status).to eq(413)
          end

          it "using token as header" do
196
            stub_application_setting(max_artifacts_size: 0)
197 198 199 200 201
            post authorize_url, { filesize: 100 }, headers_with_token
            expect(response.status).to eq(413)
          end
        end

202 203 204 205
        context 'authorization token is invalid' do
          before { post authorize_url, { token: 'invalid', filesize: 100 } }

          it 'should respond with forbidden' do
206 207 208 209 210 211
            expect(response.status).to eq(403)
          end
        end
      end

      describe "POST /builds/:id/artifacts" do
212
        context "disable sanitizer" do
213 214 215 216 217
          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

218 219 220 221 222 223 224 225 226
          context 'build has been erased' do
            let(:build) { create(:ci_build, erased_at: Time.now) }
            before { upload_artifacts(file_upload, headers_with_token) }

            it 'should respond with forbidden' do
              expect(response.status).to eq 403
            end
          end

227
          context "should post artifact to running build" do
228 229 230 231 232 233 234 235
            it "uses regual file post" do
              upload_artifacts(file_upload, headers_with_token, false)
              expect(response.status).to eq(201)
              expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename)
            end

            it "uses accelerated file post" do
              upload_artifacts(file_upload, headers_with_token, true)
236 237 238 239 240 241 242 243 244 245 246 247
              expect(response.status).to eq(201)
              expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename)
            end

            it "updates artifact" do
              upload_artifacts(file_upload, headers_with_token)
              upload_artifacts(file_upload2, headers_with_token)
              expect(response.status).to eq(201)
              expect(json_response["artifacts_file"]["filename"]).to eq(file_upload2.original_filename)
            end
          end

248
          context 'should post artifacts file and metadata file' do
249 250 251
            let!(:artifacts) { file_upload }
            let!(:metadata) { file_upload2 }

252 253 254
            let(:stored_artifacts_file) { build.reload.artifacts_file.file }
            let(:stored_metadata_file) { build.reload.artifacts_metadata.file }

255 256 257
            before do
              post(post_url, post_data, headers_with_token)
            end
258

259 260 261 262 263 264 265 266 267
            context 'post data accelerated by workhorse is correct' do
              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
268
                expect(response.status).to eq(201)
269 270 271
                expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
                expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
              end
272 273
            end

274
            context 'no artifacts file in post data' do
275
              let(:post_data) do
276
                { 'metadata' => metadata }
277 278
              end

279 280
              it 'is expected to respond with bad request' do
                expect(response.status).to eq(400)
281 282
              end

283
              it 'does not store metadata' do
284 285
                expect(stored_metadata_file).to be_nil
              end
286 287 288
            end
          end

289 290
          context "artifacts file is too large" do
            it "should fail to post too large artifact" do
291
              stub_application_setting(max_artifacts_size: 0)
292 293 294 295 296
              upload_artifacts(file_upload, headers_with_token)
              expect(response.status).to eq(413)
            end
          end

297 298
          context "artifacts post request does not contain file" do
            it "should fail to post artifacts without file" do
299 300 301 302 303
              post post_url, {}, headers_with_token
              expect(response.status).to eq(400)
            end
          end

304 305
          context 'GitLab Workhorse is not configured' do
            it "should fail to post artifacts without GitLab-Workhorse" do
306
              post post_url, { token: build.token }, {}
307 308 309 310 311
              expect(response.status).to eq(403)
            end
          end
        end

312
        context "artifacts are being stored outside of tmp path" do
313 314 315 316 317 318 319 320 321 322 323
          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

324
          it "should fail to post artifacts for outside of tmp path" do
325 326 327 328 329
            upload_artifacts(file_upload, headers_with_token)
            expect(response.status).to eq(400)
          end
        end

330 331 332 333 334 335 336 337 338
        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
339 340 341
        end
      end

342 343 344
      describe 'DELETE /builds/:id/artifacts' do
        let(:build) { create(:ci_build, :artifacts) }
        before { delete delete_url, token: build.token }
345

346
        it 'should remove build artifacts' do
347
          expect(response.status).to eq(200)
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
          expect(build.artifacts_file.exists?).to be_falsy
          expect(build.artifacts_metadata.exists?).to be_falsy
        end
      end

      describe 'GET /builds/:id/artifacts' do
        before { get get_url, token: build.token }

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

          it 'should download artifact' do
364
            expect(response.status).to eq(200)
365 366
            expect(response.headers).to include download_headers
          end
367 368
        end

369 370 371 372
        context 'build does not has artifacts' do
          it 'should respond with not found' do
            expect(response.status).to eq(404)
          end
373 374 375
        end
      end
    end
376 377
  end
end