create_pipeline_service_spec.rb 11.0 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
    def execute_service(source: :push, after: project.commit.id, message: 'Message', ref: ref_name)
14 15 16 17 18
      params = { ref: ref,
                 before: '00000000',
                 after: after,
                 commits: [{ message: message }] }

19
      described_class.new(project, user, params).execute(source)
20 21
    end

22 23 24
    context 'valid params' do
      let(:pipeline) { execute_service }

25 26 27 28 29 30
      let(:pipeline_on_previous_commit) do
        execute_service(
          after: previous_commit_sha_from_ref('master')
        )
      end

31 32 33
      it 'creates a pipeline' do
        expect(pipeline).to be_kind_of(Ci::Pipeline)
        expect(pipeline).to be_valid
34
        expect(pipeline).to be_push
35 36 37 38 39
        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
40

41
      context 'when merge requests already exist for this source branch' do
42 43 44
        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)
45

46
          head_pipeline = pipeline
47

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
          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
63
          let!(:target_project) { create(:project) }
64 65 66 67 68 69 70 71 72 73 74
          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
75

76
        context 'when the pipeline is not the latest for the branch' do
77 78
          it 'does not update merge request head pipeline' do
            merge_request = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
79 80

            allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false)
81 82 83 84 85 86

            pipeline

            expect(merge_request.reload.head_pipeline).to be_nil
          end
        end
87 88
      end

89 90 91 92 93
      context 'auto-cancel enabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'enabled')
        end

94 95
        it 'does not cancel HEAD pipeline' do
          pipeline
R
Rydkin Maxim 已提交
96
          pipeline_on_previous_commit
97

R
Rydkin Maxim 已提交
98
          expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
99
        end
100 101

        it 'auto cancel pending non-HEAD pipelines' do
R
Rydkin Maxim 已提交
102
          pipeline_on_previous_commit
103 104
          pipeline

R
Rydkin Maxim 已提交
105
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
106 107 108
        end

        it 'does not cancel running outdated pipelines' do
R
Rydkin Maxim 已提交
109
          pipeline_on_previous_commit.run
110 111
          execute_service

R
Rydkin Maxim 已提交
112
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'running', auto_canceled_by_id: nil)
113 114 115
        end

        it 'cancel created outdated pipelines' do
R
Rydkin Maxim 已提交
116
          pipeline_on_previous_commit.update(status: 'created')
117 118
          pipeline

R
Rydkin Maxim 已提交
119
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
120 121 122 123 124 125 126 127 128
        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

129
          expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
130 131
        end
      end
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149

      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
150 151
    end

152 153
    context "skip tag if there is no build for it" do
      it "creates commit if there is appropriate job" do
154
        expect(execute_service).to be_persisted
155 156 157 158 159 160
      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)

161
        expect(execute_service).to be_persisted
162 163 164 165 166 167
      end
    end

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

168
      expect(execute_service).not_to be_persisted
169 170 171
      expect(Ci::Pipeline.count).to eq(0)
    end

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    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
198 199 200 201
    end

    context 'when commit contains a [ci skip] directive' do
      let(:message) { "some message[ci skip]" }
202 203 204 205 206 207 208 209 210 211 212

      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]"
      ]
213 214 215 216 217

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

218 219
      ci_messages.each do |ci_message|
        it "skips builds creation if the commit message is #{ci_message}" do
220
          pipeline = execute_service(message: ci_message)
221 222 223 224 225

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

228
      shared_examples 'creating a pipeline' do
R
Rydkin Maxim 已提交
229
        it 'does not skip pipeline creation' do
230
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
231

232
          pipeline = execute_service(message: commit_message)
233

234 235 236
          expect(pipeline).to be_persisted
          expect(pipeline.builds.first.name).to eq("rspec")
        end
237 238
      end

239 240
      context 'when commit message does not contain [ci skip] nor [skip ci]' do
        let(:commit_message) { 'some message' }
241

242
        it_behaves_like 'creating a pipeline'
243 244
      end

245 246
      context 'when commit message is nil' do
        let(:commit_message) { nil }
247

248
        it_behaves_like 'creating a pipeline'
249 250
      end

251 252 253 254 255
      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
256 257 258 259 260 261 262 263 264
    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
265
        result = execute_service
266 267 268 269 270 271 272 273 274 275 276 277 278 279

        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
280
        result = execute_service
281 282 283 284 285

        expect(result).to be_persisted
        expect(result.manual_actions).not_to be_empty
      end
    end
286 287 288

    context 'with environment' do
      before do
Z
Z.J. van de Weg 已提交
289
        config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
290 291 292 293
        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates the environment' do
294
        result = execute_service
295 296 297 298 299

        expect(result).to be_persisted
        expect(Environment.find_by(name: "review/master")).not_to be_nil
      end
    end
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314

    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
315 316 317 318 319 320 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 346 347 348 349 350 351 352 353 354 355 356 357 358

    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
    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
359 360
  end
end