create_pipeline_service_spec.rb 12.5 KB
Newer Older
1 2 3
require 'spec_helper'

describe Ci::CreatePipelineService, services: true do
4
  let(:project) { create(:project, :repository) }
5
  let(:user) { create(:admin) }
6
  let(:ref_name) { 'refs/heads/master' }
7 8 9 10 11 12

  before do
    stub_ci_pipeline_to_return_yaml_file
  end

  describe '#execute' do
13 14 15 16 17 18
    def execute_service(
      source: :push,
      after: project.commit.id,
      message: 'Message',
      ref: ref_name,
      trigger_request: nil)
19 20 21 22 23
      params = { ref: ref,
                 before: '00000000',
                 after: after,
                 commits: [{ message: message }] }

24 25
      described_class.new(project, user, params).execute(
        source, trigger_request: trigger_request)
26 27
    end

28 29 30
    context 'valid params' do
      let(:pipeline) { execute_service }

31 32 33 34 35 36
      let(:pipeline_on_previous_commit) do
        execute_service(
          after: previous_commit_sha_from_ref('master')
        )
      end

37 38 39
      it 'creates a pipeline' do
        expect(pipeline).to be_kind_of(Ci::Pipeline)
        expect(pipeline).to be_valid
40
        expect(pipeline).to be_push
41 42 43 44 45
        expect(pipeline).to eq(project.pipelines.last)
        expect(pipeline).to have_attributes(user: user)
        expect(pipeline).to have_attributes(status: 'pending')
        expect(pipeline.builds.first).to be_kind_of(Ci::Build)
      end
46

47
      context 'when merge requests already exist for this source branch' do
48 49 50
        it 'updates head pipeline of each merge request' do
          merge_request_1 = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
          merge_request_2 = create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
51

52
          head_pipeline = pipeline
53

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
          expect(merge_request_1.reload.head_pipeline).to eq(head_pipeline)
          expect(merge_request_2.reload.head_pipeline).to eq(head_pipeline)
        end

        context 'when there is no pipeline for source branch' do
          it "does not update merge request head pipeline" do
            merge_request = create(:merge_request, source_branch: 'other_branch', target_branch: "branch_1", source_project: project)

            head_pipeline = pipeline

            expect(merge_request.reload.head_pipeline).not_to eq(head_pipeline)
          end
        end

        context 'when merge request target project is different from source project' do
69
          let!(:target_project) { create(:project) }
70 71 72 73 74 75 76 77 78 79 80
          let!(:forked_project_link) { create(:forked_project_link, forked_to_project: project, forked_from_project: target_project) }

          it 'updates head pipeline for merge request' do
            merge_request =
              create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project, target_project: target_project)

            head_pipeline = pipeline

            expect(merge_request.reload.head_pipeline).to eq(head_pipeline)
          end
        end
81

82
        context 'when the pipeline is not the latest for the branch' do
83 84
          it 'does not update merge request head pipeline' do
            merge_request = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
85 86

            allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false)
87 88 89 90 91 92

            pipeline

            expect(merge_request.reload.head_pipeline).to be_nil
          end
        end
93 94
      end

95 96 97 98 99
      context 'auto-cancel enabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'enabled')
        end

100 101
        it 'does not cancel HEAD pipeline' do
          pipeline
R
Rydkin Maxim 已提交
102
          pipeline_on_previous_commit
103

R
Rydkin Maxim 已提交
104
          expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
105
        end
106 107

        it 'auto cancel pending non-HEAD pipelines' do
R
Rydkin Maxim 已提交
108
          pipeline_on_previous_commit
109 110
          pipeline

R
Rydkin Maxim 已提交
111
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
112 113 114
        end

        it 'does not cancel running outdated pipelines' do
R
Rydkin Maxim 已提交
115
          pipeline_on_previous_commit.run
116 117
          execute_service

R
Rydkin Maxim 已提交
118
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'running', auto_canceled_by_id: nil)
119 120 121
        end

        it 'cancel created outdated pipelines' do
R
Rydkin Maxim 已提交
122
          pipeline_on_previous_commit.update(status: 'created')
123 124
          pipeline

R
Rydkin Maxim 已提交
125
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
126 127 128 129 130 131 132 133 134
        end

        it 'does not cancel pipelines from the other branches' do
          pending_pipeline = execute_service(
            ref: 'refs/heads/feature',
            after: previous_commit_sha_from_ref('feature')
          )
          pipeline

135
          expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
136 137
        end
      end
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

      context 'auto-cancel disabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'disabled')
        end

        it 'does not auto cancel pending non-HEAD pipelines' do
          pipeline_on_previous_commit
          pipeline

          expect(pipeline_on_previous_commit.reload)
            .to have_attributes(status: 'pending', auto_canceled_by_id: nil)
        end
      end

      def previous_commit_sha_from_ref(ref)
        project.commit(ref).parent.sha
      end
156 157
    end

158 159
    context "skip tag if there is no build for it" do
      it "creates commit if there is appropriate job" do
160
        expect(execute_service).to be_persisted
161 162 163 164 165 166
      end

      it "creates commit if there is no appropriate job but deploy job has right ref setting" do
        config = YAML.dump({ deploy: { script: "ls", only: ["master"] } })
        stub_ci_pipeline_yaml_file(config)

167
        expect(execute_service).to be_persisted
168 169 170 171 172 173
      end
    end

    it 'skips creating pipeline for refs without .gitlab-ci.yml' do
      stub_ci_pipeline_yaml_file(nil)

174
      expect(execute_service).not_to be_persisted
175 176 177
      expect(Ci::Pipeline.count).to eq(0)
    end

178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
    shared_examples 'a failed pipeline' do
      it 'creates failed pipeline' do
        stub_ci_pipeline_yaml_file(ci_yaml)

        pipeline = execute_service(message: message)

        expect(pipeline).to be_persisted
        expect(pipeline.builds.any?).to be false
        expect(pipeline.status).to eq('failed')
        expect(pipeline.yaml_errors).not_to be_nil
      end
    end

    context 'when yaml is invalid' do
      let(:ci_yaml) { 'invalid: file: fiile' }
      let(:message) { 'Message' }

      it_behaves_like 'a failed pipeline'

      context 'when receive git commit' do
        before do
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
        end

        it_behaves_like 'a failed pipeline'
      end
204 205 206 207
    end

    context 'when commit contains a [ci skip] directive' do
      let(:message) { "some message[ci skip]" }
208 209 210 211 212 213 214 215 216 217 218

      ci_messages = [
        "some message[ci skip]",
        "some message[skip ci]",
        "some message[CI SKIP]",
        "some message[SKIP CI]",
        "some message[ci_skip]",
        "some message[skip_ci]",
        "some message[ci-skip]",
        "some message[skip-ci]"
      ]
219 220 221 222 223

      before do
        allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
      end

224 225
      ci_messages.each do |ci_message|
        it "skips builds creation if the commit message is #{ci_message}" do
226
          pipeline = execute_service(message: ci_message)
227 228 229 230 231

          expect(pipeline).to be_persisted
          expect(pipeline.builds.any?).to be false
          expect(pipeline.status).to eq("skipped")
        end
232 233
      end

234
      shared_examples 'creating a pipeline' do
R
Rydkin Maxim 已提交
235
        it 'does not skip pipeline creation' do
236
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
237

238
          pipeline = execute_service(message: commit_message)
239

240 241 242
          expect(pipeline).to be_persisted
          expect(pipeline.builds.first.name).to eq("rspec")
        end
243 244
      end

245 246
      context 'when commit message does not contain [ci skip] nor [skip ci]' do
        let(:commit_message) { 'some message' }
247

248
        it_behaves_like 'creating a pipeline'
249 250
      end

251 252
      context 'when commit message is nil' do
        let(:commit_message) { nil }
253

254
        it_behaves_like 'creating a pipeline'
255 256
      end

257 258 259 260 261
      context 'when there is [ci skip] tag in commit message and yaml is invalid' do
        let(:ci_yaml) { 'invalid: file: fiile' }

        it_behaves_like 'a failed pipeline'
      end
262 263 264 265 266 267 268 269 270
    end

    context 'when there are no jobs for this pipeline' do
      before do
        config = YAML.dump({ test: { script: 'ls', only: ['feature'] } })
        stub_ci_pipeline_yaml_file(config)
      end

      it 'does not create a new pipeline' do
271
        result = execute_service
272 273 274 275 276 277 278 279 280 281 282 283 284 285

        expect(result).not_to be_persisted
        expect(Ci::Build.all).to be_empty
        expect(Ci::Pipeline.count).to eq(0)
      end
    end

    context 'with manual actions' do
      before do
        config = YAML.dump({ deploy: { script: 'ls', when: 'manual' } })
        stub_ci_pipeline_yaml_file(config)
      end

      it 'does not create a new pipeline' do
286
        result = execute_service
287 288 289 290 291

        expect(result).to be_persisted
        expect(result.manual_actions).not_to be_empty
      end
    end
292 293 294

    context 'with environment' do
      before do
Z
Z.J. van de Weg 已提交
295
        config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
296 297 298 299
        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates the environment' do
300
        result = execute_service
301 302 303 304 305

        expect(result).to be_persisted
        expect(Environment.find_by(name: "review/master")).not_to be_nil
      end
    end
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

    context 'when environment with invalid name' do
      before do
        config = YAML.dump(deploy: { environment: { name: 'name,with,commas' }, script: 'ls' })
        stub_ci_pipeline_yaml_file(config)
      end

      it 'does not create an environment' do
        expect do
          result = execute_service

          expect(result).to be_persisted
        end.not_to change { Environment.count }
      end
    end
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345

    shared_examples 'when ref is protected' do
      let(:user) { create(:user) }

      context 'when user is developer' do
        before do
          project.add_developer(user)
        end

        it 'does not create a pipeline' do
          expect(execute_service).not_to be_persisted
          expect(Ci::Pipeline.count).to eq(0)
        end
      end

      context 'when user is master' do
        before do
          project.add_master(user)
        end

        it 'creates a pipeline' do
          expect(execute_service).to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392

      context 'when trigger belongs to no one' do
        let(:user) {}
        let(:trigger_request) { create(:ci_trigger_request) }

        it 'does not create a pipeline' do
          expect(execute_service(trigger_request: trigger_request))
            .not_to be_persisted
          expect(Ci::Pipeline.count).to eq(0)
        end
      end

      context 'when trigger belongs to a developer' do
        let(:user) {}

        let(:trigger_request) do
          create(:ci_trigger_request).tap do |request|
            user = create(:user)
            project.add_developer(user)
            request.trigger.update(owner: user)
          end
        end

        it 'does not create a pipeline' do
          expect(execute_service(trigger_request: trigger_request))
            .not_to be_persisted
          expect(Ci::Pipeline.count).to eq(0)
        end
      end

      context 'when trigger belongs to a master' do
        let(:user) {}

        let(:trigger_request) do
          create(:ci_trigger_request).tap do |request|
            user = create(:user)
            project.add_master(user)
            request.trigger.update(owner: user)
          end
        end

        it 'does not create a pipeline' do
          expect(execute_service(trigger_request: trigger_request))
            .to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
    end

    context 'when ref is a protected branch' do
      before do
        create(:protected_branch, project: project, name: 'master')
      end

      it_behaves_like 'when ref is protected'
    end

    context 'when ref is a protected tag' do
      let(:ref_name) { 'refs/tags/v1.0.0' }

      before do
        create(:protected_tag, project: project, name: '*')
      end

      it_behaves_like 'when ref is protected'
    end
412 413
  end
end