Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/services/ci')
-rw-r--r--spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/rules_spec.rb1028
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb815
-rw-r--r--spec/services/ci/deployments/destroy_service_spec.rb65
-rw-r--r--spec/services/ci/destroy_pipeline_service_spec.rb20
-rw-r--r--spec/services/ci/job_artifacts/create_service_spec.rb8
-rw-r--r--spec/services/ci/job_artifacts/destroy_batch_service_spec.rb9
-rw-r--r--spec/services/ci/list_config_variables_service_spec.rb4
-rw-r--r--spec/services/ci/parse_dotenv_artifact_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb2
-rw-r--r--spec/services/ci/process_build_service_spec.rb2
-rw-r--r--spec/services/ci/register_job_service_spec.rb68
-rw-r--r--spec/services/ci/retry_job_service_spec.rb61
-rw-r--r--spec/services/ci/runners/assign_runner_service_spec.rb18
-rw-r--r--spec/services/ci/runners/bulk_delete_runners_service_spec.rb83
-rw-r--r--spec/services/ci/runners/process_runner_version_update_service_spec.rb80
-rw-r--r--spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb77
-rw-r--r--spec/services/ci/runners/register_runner_service_spec.rb150
-rw-r--r--spec/services/ci/runners/reset_registration_token_service_spec.rb13
-rw-r--r--spec/services/ci/runners/unassign_runner_service_spec.rb28
-rw-r--r--spec/services/ci/runners/unregister_runner_service_spec.rb7
-rw-r--r--spec/services/ci/runners/update_runner_service_spec.rb2
-rw-r--r--spec/services/ci/stuck_builds/drop_pending_service_spec.rb4
-rw-r--r--spec/services/ci/stuck_builds/drop_scheduled_service_spec.rb2
-rw-r--r--spec/services/ci/track_failed_build_service_spec.rb56
-rw-r--r--spec/services/ci/update_build_state_service_spec.rb18
27 files changed, 1640 insertions, 986 deletions
diff --git a/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb b/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb
index 9add096d782..7c698242921 100644
--- a/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb
+++ b/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:group) { create(:group, :private) }
- let_it_be(:group_variable) { create(:ci_group_variable, group: group, key: 'RUNNER_TAG', value: 'group')}
+ let_it_be(:group_variable) { create(:ci_group_variable, group: group, key: 'RUNNER_TAG', value: 'group') }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
index 4326fa5533f..cc808b7e61c 100644
--- a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
+++ b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Ci::CreatePipelineService, '#execute' do
expect(pipeline.statuses).to match_array [test, bridge]
expect(bridge.options).to eq(expected_bridge_options)
expect(bridge.yaml_variables)
- .to include(key: 'CROSS', value: 'downstream', public: true)
+ .to include(key: 'CROSS', value: 'downstream')
end
end
diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb
index d0ce1c5aba8..6e48141226d 100644
--- a/spec/services/ci/create_pipeline_service/rules_spec.rb
+++ b/spec/services/ci/create_pipeline_service/rules_spec.rb
@@ -7,10 +7,38 @@ RSpec.describe Ci::CreatePipelineService do
let(:ref) { 'refs/heads/master' }
let(:source) { :push }
let(:service) { described_class.new(project, user, { ref: ref }) }
- let(:pipeline) { service.execute(source).payload }
+ let(:response) { execute_service }
+ let(:pipeline) { response.payload }
let(:build_names) { pipeline.builds.pluck(:name) }
+ def execute_service(before: '00000000', variables_attributes: nil)
+ params = { ref: ref, before: before, after: project.commit(ref).sha, variables_attributes: variables_attributes }
+
+ described_class
+ .new(project, user, params)
+ .execute(source) do |pipeline|
+ yield(pipeline) if block_given?
+ end
+ end
+
context 'job:rules' do
+ let(:regular_job) { find_job('regular-job') }
+ let(:rules_job) { find_job('rules-job') }
+ let(:delayed_job) { find_job('delayed-job') }
+
+ def find_job(name)
+ pipeline.builds.find_by(name: name)
+ end
+
+ shared_examples 'rules jobs are excluded' do
+ it 'only persists the job without rules' do
+ expect(pipeline).to be_persisted
+ expect(regular_job).to be_persisted
+ expect(rules_job).to be_nil
+ expect(delayed_job).to be_nil
+ end
+ end
+
before do
stub_ci_pipeline_yaml_file(config)
allow_next_instance_of(Ci::BuildScheduleWorker) do |instance|
@@ -95,10 +123,6 @@ RSpec.describe Ci::CreatePipelineService do
end
context 'with allow_failure and exit_codes', :aggregate_failures do
- def find_job(name)
- pipeline.builds.find_by(name: name)
- end
-
let(:config) do
<<-EOY
job-1:
@@ -280,6 +304,773 @@ RSpec.describe Ci::CreatePipelineService do
end
end
end
+
+ context 'with simple if: clauses' do
+ let(:config) do
+ <<-EOY
+ regular-job:
+ script: 'echo Hello, World!'
+
+ master-job:
+ script: "echo hello world, $CI_COMMIT_REF_NAME"
+ rules:
+ - if: $CI_COMMIT_REF_NAME == "nonexistant-branch"
+ when: never
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ when: manual
+
+ negligible-job:
+ script: "exit 1"
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ allow_failure: true
+
+ delayed-job:
+ script: "echo See you later, World!"
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ when: delayed
+ start_in: 1 hour
+
+ never-job:
+ script: "echo Goodbye, World!"
+ rules:
+ - if: $CI_COMMIT_REF_NAME
+ when: never
+ EOY
+ end
+
+ context 'with matches' do
+ it 'creates a pipeline with the vanilla and manual jobs' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly(
+ 'regular-job', 'delayed-job', 'master-job', 'negligible-job'
+ )
+ end
+
+ it 'assigns job:when values to the builds' do
+ expect(find_job('regular-job').when).to eq('on_success')
+ expect(find_job('master-job').when).to eq('manual')
+ expect(find_job('negligible-job').when).to eq('on_success')
+ expect(find_job('delayed-job').when).to eq('delayed')
+ end
+
+ it 'assigns job:allow_failure values to the builds' do
+ expect(find_job('regular-job').allow_failure).to eq(false)
+ expect(find_job('master-job').allow_failure).to eq(false)
+ expect(find_job('negligible-job').allow_failure).to eq(true)
+ expect(find_job('delayed-job').allow_failure).to eq(false)
+ end
+
+ it 'assigns start_in for delayed jobs' do
+ expect(delayed_job.options[:start_in]).to eq('1 hour')
+ end
+ end
+
+ context 'with no matches' do
+ let(:ref) { 'refs/heads/feature' }
+
+ it_behaves_like 'rules jobs are excluded'
+ end
+ end
+
+ context 'with complex if: clauses' do
+ let(:config) do
+ <<-EOY
+ regular-job:
+ script: 'echo Hello, World!'
+ rules:
+ - if: $VAR == 'present' && $OTHER || $CI_COMMIT_REF_NAME
+ when: manual
+ allow_failure: true
+ EOY
+ end
+
+ it 'matches the first rule' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('regular-job')
+ expect(regular_job.when).to eq('manual')
+ expect(regular_job.allow_failure).to eq(true)
+ end
+ end
+ end
+
+ context 'changes:' do
+ let(:config) do
+ <<-EOY
+ regular-job:
+ script: 'echo Hello, World!'
+
+ rules-job:
+ script: "echo hello world, $CI_COMMIT_REF_NAME"
+ rules:
+ - changes:
+ - README.md
+ when: manual
+ - changes:
+ - app.rb
+ when: on_success
+
+ delayed-job:
+ script: "echo See you later, World!"
+ rules:
+ - changes:
+ - README.md
+ when: delayed
+ start_in: 4 hours
+
+ negligible-job:
+ script: "can be failed sometimes"
+ rules:
+ - changes:
+ - README.md
+ allow_failure: true
+
+ README:
+ script: "I use variables for changes!"
+ rules:
+ - changes:
+ - $CI_JOB_NAME*
+
+ changes-paths:
+ script: "I am using a new syntax!"
+ rules:
+ - changes:
+ paths: [README.md]
+ EOY
+ end
+
+ context 'and matches' do
+ before do
+ allow_next_instance_of(Ci::Pipeline) do |pipeline|
+ allow(pipeline).to receive(:modified_paths).and_return(%w[README.md])
+ end
+ end
+
+ it 'creates five jobs' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly(
+ 'regular-job', 'rules-job', 'delayed-job', 'negligible-job', 'README', 'changes-paths'
+ )
+ end
+
+ it 'sets when: for all jobs' do
+ expect(regular_job.when).to eq('on_success')
+ expect(rules_job.when).to eq('manual')
+ expect(delayed_job.when).to eq('delayed')
+ expect(delayed_job.options[:start_in]).to eq('4 hours')
+ end
+
+ it 'sets allow_failure: for negligible job' do
+ expect(find_job('negligible-job').allow_failure).to eq(true)
+ end
+ end
+
+ context 'and matches the second rule' do
+ before do
+ allow_next_instance_of(Ci::Pipeline) do |pipeline|
+ allow(pipeline).to receive(:modified_paths).and_return(%w[app.rb])
+ end
+ end
+
+ it 'includes both jobs' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('regular-job', 'rules-job')
+ end
+
+ it 'sets when: for the created rules job based on the second clause' do
+ expect(regular_job.when).to eq('on_success')
+ expect(rules_job.when).to eq('on_success')
+ end
+ end
+
+ context 'and does not match' do
+ before do
+ allow_next_instance_of(Ci::Pipeline) do |pipeline|
+ allow(pipeline).to receive(:modified_paths).and_return(%w[useless_script.rb])
+ end
+ end
+
+ it_behaves_like 'rules jobs are excluded'
+
+ it 'sets when: for the created job' do
+ expect(regular_job.when).to eq('on_success')
+ end
+ end
+
+ context 'with paths and compare_to' do
+ let_it_be(:project) { create(:project, :empty_repo) }
+ let_it_be(:user) { project.first_owner }
+
+ before_all do
+ project.repository.add_branch(user, 'feature_1', 'master')
+
+ project.repository.create_file(
+ user, 'file1.txt', 'file 1', message: 'Create file1.txt', branch_name: 'feature_1'
+ )
+
+ project.repository.add_branch(user, 'feature_2', 'feature_1')
+
+ project.repository.create_file(
+ user, 'file2.txt', 'file 2', message: 'Create file2.txt', branch_name: 'feature_2'
+ )
+ end
+
+ let(:changed_file) { 'file2.txt' }
+ let(:ref) { 'feature_2' }
+
+ let(:response) { execute_service(before: nil) }
+
+ context 'for jobs rules' do
+ let(:config) do
+ <<-EOY
+ job1:
+ script: exit 0
+ rules:
+ - changes:
+ paths: [#{changed_file}]
+ compare_to: #{compare_to}
+
+ job2:
+ script: exit 0
+ EOY
+ end
+
+ context 'when there is no such compare_to ref' do
+ let(:compare_to) { 'invalid-branch' }
+
+ it 'returns an error' do
+ expect(pipeline.errors.full_messages).to eq([
+ 'Failed to parse rule for job1: rules:changes:compare_to is not a valid ref'
+ ])
+ end
+
+ context 'when the FF ci_rules_changes_compare is not enabled' do
+ before do
+ stub_feature_flags(ci_rules_changes_compare: false)
+ end
+
+ it 'ignores compare_to and changes is always true' do
+ expect(build_names).to contain_exactly('job1', 'job2')
+ end
+ end
+ end
+
+ context 'when the compare_to ref exists' do
+ let(:compare_to) { 'feature_1' }
+
+ context 'when the rule matches' do
+ it 'creates job1 and job2' do
+ expect(build_names).to contain_exactly('job1', 'job2')
+ end
+
+ context 'when the FF ci_rules_changes_compare is not enabled' do
+ before do
+ stub_feature_flags(ci_rules_changes_compare: false)
+ end
+
+ it 'ignores compare_to and changes is always true' do
+ expect(build_names).to contain_exactly('job1', 'job2')
+ end
+ end
+ end
+
+ context 'when the rule does not match' do
+ let(:changed_file) { 'file1.txt' }
+
+ it 'does not create job1' do
+ expect(build_names).to contain_exactly('job2')
+ end
+
+ context 'when the FF ci_rules_changes_compare is not enabled' do
+ before do
+ stub_feature_flags(ci_rules_changes_compare: false)
+ end
+
+ it 'ignores compare_to and changes is always true' do
+ expect(build_names).to contain_exactly('job1', 'job2')
+ end
+ end
+ end
+ end
+ end
+
+ context 'for workflow rules' do
+ let(:config) do
+ <<-EOY
+ workflow:
+ rules:
+ - changes:
+ paths: [#{changed_file}]
+ compare_to: #{compare_to}
+
+ job1:
+ script: exit 0
+ EOY
+ end
+
+ let(:compare_to) { 'feature_1' }
+
+ context 'when the rule matches' do
+ it 'creates job1' do
+ expect(pipeline).to be_created_successfully
+ expect(build_names).to contain_exactly('job1')
+ end
+
+ context 'when the FF ci_rules_changes_compare is not enabled' do
+ before do
+ stub_feature_flags(ci_rules_changes_compare: false)
+ end
+
+ it 'ignores compare_to and changes is always true' do
+ expect(pipeline).to be_created_successfully
+ expect(build_names).to contain_exactly('job1')
+ end
+ end
+ end
+
+ context 'when the rule does not match' do
+ let(:changed_file) { 'file1.txt' }
+
+ it 'does not create job1' do
+ expect(pipeline).not_to be_created_successfully
+ expect(build_names).to be_empty
+ end
+ end
+ end
+ end
+ end
+
+ context 'mixed if: and changes: rules' do
+ let(:config) do
+ <<-EOY
+ regular-job:
+ script: 'echo Hello, World!'
+
+ rules-job:
+ script: "echo hello world, $CI_COMMIT_REF_NAME"
+ allow_failure: true
+ rules:
+ - changes:
+ - README.md
+ when: manual
+ - if: $CI_COMMIT_REF_NAME == "master"
+ when: on_success
+ allow_failure: false
+
+ delayed-job:
+ script: "echo See you later, World!"
+ rules:
+ - changes:
+ - README.md
+ when: delayed
+ start_in: 4 hours
+ allow_failure: true
+ - if: $CI_COMMIT_REF_NAME == "master"
+ when: delayed
+ start_in: 1 hour
+ EOY
+ end
+
+ context 'and changes: matches before if' do
+ before do
+ allow_next_instance_of(Ci::Pipeline) do |pipeline|
+ allow(pipeline).to receive(:modified_paths).and_return(%w[README.md])
+ end
+ end
+
+ it 'creates two jobs' do
+ expect(pipeline).to be_persisted
+ expect(build_names)
+ .to contain_exactly('regular-job', 'rules-job', 'delayed-job')
+ end
+
+ it 'sets when: for all jobs' do
+ expect(regular_job.when).to eq('on_success')
+ expect(rules_job.when).to eq('manual')
+ expect(delayed_job.when).to eq('delayed')
+ expect(delayed_job.options[:start_in]).to eq('4 hours')
+ end
+
+ it 'sets allow_failure: for all jobs' do
+ expect(regular_job.allow_failure).to eq(false)
+ expect(rules_job.allow_failure).to eq(true)
+ expect(delayed_job.allow_failure).to eq(true)
+ end
+ end
+
+ context 'and if: matches after changes' do
+ it 'includes both jobs' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('regular-job', 'rules-job', 'delayed-job')
+ end
+
+ it 'sets when: for the created rules job based on the second clause' do
+ expect(regular_job.when).to eq('on_success')
+ expect(rules_job.when).to eq('on_success')
+ expect(delayed_job.when).to eq('delayed')
+ expect(delayed_job.options[:start_in]).to eq('1 hour')
+ end
+ end
+
+ context 'and does not match' do
+ let(:ref) { 'refs/heads/wip' }
+
+ it_behaves_like 'rules jobs are excluded'
+
+ it 'sets when: for the created job' do
+ expect(regular_job.when).to eq('on_success')
+ end
+ end
+ end
+
+ context 'mixed if: and changes: clauses' do
+ let(:config) do
+ <<-EOY
+ regular-job:
+ script: 'echo Hello, World!'
+
+ rules-job:
+ script: "echo hello world, $CI_COMMIT_REF_NAME"
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ changes: [README.md]
+ when: on_success
+ allow_failure: true
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ changes: [app.rb]
+ when: manual
+ EOY
+ end
+
+ context 'with if matches and changes matches' do
+ before do
+ allow_next_instance_of(Ci::Pipeline) do |pipeline|
+ allow(pipeline).to receive(:modified_paths).and_return(%w[app.rb])
+ end
+ end
+
+ it 'persists all jobs' do
+ expect(pipeline).to be_persisted
+ expect(regular_job).to be_persisted
+ expect(rules_job).to be_persisted
+ expect(rules_job.when).to eq('manual')
+ expect(rules_job.allow_failure).to eq(false)
+ end
+ end
+
+ context 'with if matches and no change matches' do
+ it_behaves_like 'rules jobs are excluded'
+ end
+
+ context 'with change matches and no if matches' do
+ let(:ref) { 'refs/heads/feature' }
+
+ before do
+ allow_next_instance_of(Ci::Pipeline) do |pipeline|
+ allow(pipeline).to receive(:modified_paths).and_return(%w[README.md])
+ end
+ end
+
+ it_behaves_like 'rules jobs are excluded'
+ end
+
+ context 'and no matches' do
+ let(:ref) { 'refs/heads/feature' }
+
+ it_behaves_like 'rules jobs are excluded'
+ end
+ end
+
+ context 'complex if: allow_failure usages' do
+ let(:config) do
+ <<-EOY
+ job-1:
+ script: "exit 1"
+ allow_failure: true
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ allow_failure: false
+
+ job-2:
+ script: "exit 1"
+ allow_failure: true
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
+ allow_failure: false
+
+ job-3:
+ script: "exit 1"
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
+ allow_failure: true
+
+ job-4:
+ script: "exit 1"
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ allow_failure: false
+
+ job-5:
+ script: "exit 1"
+ allow_failure: false
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ allow_failure: true
+
+ job-6:
+ script: "exit 1"
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
+ allow_failure: false
+ - allow_failure: true
+ EOY
+ end
+
+ it 'creates a pipeline' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('job-1', 'job-4', 'job-5', 'job-6')
+ end
+
+ it 'assigns job:allow_failure values to the builds' do
+ expect(find_job('job-1').allow_failure).to eq(false)
+ expect(find_job('job-4').allow_failure).to eq(false)
+ expect(find_job('job-5').allow_failure).to eq(true)
+ expect(find_job('job-6').allow_failure).to eq(true)
+ end
+ end
+
+ context 'complex if: allow_failure & when usages' do
+ let(:config) do
+ <<-EOY
+ job-1:
+ script: "exit 1"
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ when: manual
+
+ job-2:
+ script: "exit 1"
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ when: manual
+ allow_failure: true
+
+ job-3:
+ script: "exit 1"
+ allow_failure: true
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ when: manual
+
+ job-4:
+ script: "exit 1"
+ allow_failure: true
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ when: manual
+ allow_failure: false
+
+ job-5:
+ script: "exit 1"
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
+ when: manual
+ allow_failure: false
+ - when: always
+ allow_failure: true
+
+ job-6:
+ script: "exit 1"
+ allow_failure: false
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ when: manual
+
+ job-7:
+ script: "exit 1"
+ allow_failure: false
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
+ when: manual
+ - when: :on_failure
+ allow_failure: true
+ EOY
+ end
+
+ it 'creates a pipeline' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly(
+ 'job-1', 'job-2', 'job-3', 'job-4', 'job-5', 'job-6', 'job-7'
+ )
+ end
+
+ it 'assigns job:allow_failure values to the builds' do
+ expect(find_job('job-1').allow_failure).to eq(false)
+ expect(find_job('job-2').allow_failure).to eq(true)
+ expect(find_job('job-3').allow_failure).to eq(true)
+ expect(find_job('job-4').allow_failure).to eq(false)
+ expect(find_job('job-5').allow_failure).to eq(true)
+ expect(find_job('job-6').allow_failure).to eq(false)
+ expect(find_job('job-7').allow_failure).to eq(true)
+ end
+
+ it 'assigns job:when values to the builds' do
+ expect(find_job('job-1').when).to eq('manual')
+ expect(find_job('job-2').when).to eq('manual')
+ expect(find_job('job-3').when).to eq('manual')
+ expect(find_job('job-4').when).to eq('manual')
+ expect(find_job('job-5').when).to eq('always')
+ expect(find_job('job-6').when).to eq('manual')
+ expect(find_job('job-7').when).to eq('on_failure')
+ end
+ end
+
+ context 'deploy freeze period `if:` clause' do
+ # '0 23 * * 5' == "At 23:00 on Friday."", '0 7 * * 1' == "At 07:00 on Monday.""
+ let!(:freeze_period) { create(:ci_freeze_period, project: project, freeze_start: '0 23 * * 5', freeze_end: '0 7 * * 1') }
+
+ context 'with 2 jobs' do
+ let(:config) do
+ <<-EOY
+ stages:
+ - test
+ - deploy
+
+ test-job:
+ script:
+ - echo 'running TEST stage'
+
+ deploy-job:
+ stage: deploy
+ script:
+ - echo 'running DEPLOY stage'
+ rules:
+ - if: $CI_DEPLOY_FREEZE == null
+ EOY
+ end
+
+ context 'when outside freeze period' do
+ it 'creates two jobs' do
+ Timecop.freeze(2020, 4, 10, 22, 59) do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('test-job', 'deploy-job')
+ end
+ end
+ end
+
+ context 'when inside freeze period' do
+ it 'creates one job' do
+ Timecop.freeze(2020, 4, 10, 23, 1) do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('test-job')
+ end
+ end
+ end
+ end
+
+ context 'with 1 job' do
+ let(:config) do
+ <<-EOY
+ stages:
+ - deploy
+
+ deploy-job:
+ stage: deploy
+ script:
+ - echo 'running DEPLOY stage'
+ rules:
+ - if: $CI_DEPLOY_FREEZE == null
+ EOY
+ end
+
+ context 'when outside freeze period' do
+ it 'creates two jobs' do
+ Timecop.freeze(2020, 4, 10, 22, 59) do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('deploy-job')
+ end
+ end
+ end
+
+ context 'when inside freeze period' do
+ it 'does not create the pipeline', :aggregate_failures do
+ Timecop.freeze(2020, 4, 10, 23, 1) do
+ expect(response).to be_error
+ expect(pipeline).not_to be_persisted
+ end
+ end
+ end
+ end
+ end
+
+ context 'with when:manual' do
+ let(:config) do
+ <<-EOY
+ job-with-rules:
+ script: 'echo hey'
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+
+ job-when-with-rules:
+ script: 'echo hey'
+ when: manual
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+
+ job-when-with-rules-when:
+ script: 'echo hey'
+ when: manual
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ when: on_success
+
+ job-with-rules-when:
+ script: 'echo hey'
+ rules:
+ - if: $CI_COMMIT_REF_NAME =~ /master/
+ when: manual
+
+ job-without-rules:
+ script: 'echo this is a job with NO rules'
+ EOY
+ end
+
+ let(:job_with_rules) { find_job('job-with-rules') }
+ let(:job_when_with_rules) { find_job('job-when-with-rules') }
+ let(:job_when_with_rules_when) { find_job('job-when-with-rules-when') }
+ let(:job_with_rules_when) { find_job('job-with-rules-when') }
+ let(:job_without_rules) { find_job('job-without-rules') }
+
+ context 'when matching the rules' do
+ let(:ref) { 'refs/heads/master' }
+
+ it 'adds the job-with-rules with a when:manual' do
+ expect(job_with_rules).to be_persisted
+ expect(job_when_with_rules).to be_persisted
+ expect(job_when_with_rules_when).to be_persisted
+ expect(job_with_rules_when).to be_persisted
+ expect(job_without_rules).to be_persisted
+
+ expect(job_with_rules.when).to eq('on_success')
+ expect(job_when_with_rules.when).to eq('manual')
+ expect(job_when_with_rules_when.when).to eq('on_success')
+ expect(job_with_rules_when.when).to eq('manual')
+ expect(job_without_rules.when).to eq('on_success')
+ end
+ end
+
+ context 'when there is no match to the rule' do
+ let(:ref) { 'refs/heads/wip' }
+
+ it 'does not add job_with_rules' do
+ expect(job_with_rules).to be_nil
+ expect(job_when_with_rules).to be_nil
+ expect(job_when_with_rules_when).to be_nil
+ expect(job_with_rules_when).to be_nil
+ expect(job_without_rules).to be_persisted
+ end
+ end
end
end
@@ -447,5 +1238,232 @@ RSpec.describe Ci::CreatePipelineService do
end
end
end
+
+ context 'with persisted variables' do
+ let(:config) do
+ <<-EOY
+ workflow:
+ rules:
+ - if: $CI_COMMIT_REF_NAME == "master"
+
+ regular-job:
+ script: 'echo Hello, World!'
+ EOY
+ end
+
+ context 'with matches' do
+ it 'creates a pipeline' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('regular-job')
+ end
+ end
+
+ context 'with no matches' do
+ let(:ref) { 'refs/heads/feature' }
+
+ it 'does not create a pipeline', :aggregate_failures do
+ expect(response).to be_error
+ expect(pipeline).not_to be_persisted
+ end
+ end
+ end
+
+ context 'with pipeline variables' do
+ let(:pipeline) do
+ execute_service(variables_attributes: variables_attributes).payload
+ end
+
+ let(:config) do
+ <<-EOY
+ workflow:
+ rules:
+ - if: $SOME_VARIABLE
+
+ regular-job:
+ script: 'echo Hello, World!'
+ EOY
+ end
+
+ context 'with matches' do
+ let(:variables_attributes) do
+ [{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }]
+ end
+
+ it 'creates a pipeline' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('regular-job')
+ end
+ end
+
+ context 'with no matches' do
+ let(:variables_attributes) { {} }
+
+ it 'does not create a pipeline', :aggregate_failures do
+ expect(response).to be_error
+ expect(pipeline).not_to be_persisted
+ end
+ end
+ end
+
+ context 'with trigger variables' do
+ let(:pipeline) do
+ execute_service do |pipeline|
+ pipeline.variables.build(variables)
+ end.payload
+ end
+
+ let(:config) do
+ <<-EOY
+ workflow:
+ rules:
+ - if: $SOME_VARIABLE
+
+ regular-job:
+ script: 'echo Hello, World!'
+ EOY
+ end
+
+ context 'with matches' do
+ let(:variables) do
+ [{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }]
+ end
+
+ it 'creates a pipeline' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('regular-job')
+ end
+
+ context 'when a job requires the same variable' do
+ let(:config) do
+ <<-EOY
+ workflow:
+ rules:
+ - if: $SOME_VARIABLE
+
+ build:
+ stage: build
+ script: 'echo build'
+ rules:
+ - if: $SOME_VARIABLE
+
+ test1:
+ stage: test
+ script: 'echo test1'
+ needs: [build]
+
+ test2:
+ stage: test
+ script: 'echo test2'
+ EOY
+ end
+
+ it 'creates a pipeline' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('build', 'test1', 'test2')
+ end
+ end
+ end
+
+ context 'with no matches' do
+ let(:variables) { {} }
+
+ it 'does not create a pipeline', :aggregate_failures do
+ expect(response).to be_error
+ expect(pipeline).not_to be_persisted
+ end
+
+ context 'when a job requires the same variable' do
+ let(:config) do
+ <<-EOY
+ workflow:
+ rules:
+ - if: $SOME_VARIABLE
+
+ build:
+ stage: build
+ script: 'echo build'
+ rules:
+ - if: $SOME_VARIABLE
+
+ test1:
+ stage: test
+ script: 'echo test1'
+ needs: [build]
+
+ test2:
+ stage: test
+ script: 'echo test2'
+ EOY
+ end
+
+ it 'does not create a pipeline', :aggregate_failures do
+ expect(response).to be_error
+ expect(pipeline).not_to be_persisted
+ end
+ end
+ end
+ end
+
+ context 'changes' do
+ shared_examples 'comparing file changes with workflow rules' do
+ context 'when matches' do
+ before do
+ allow_next_instance_of(Ci::Pipeline) do |pipeline|
+ allow(pipeline).to receive(:modified_paths).and_return(%w[file1.md])
+ end
+ end
+
+ it 'creates the pipeline with a job' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('job')
+ end
+ end
+
+ context 'when does not match' do
+ before do
+ allow_next_instance_of(Ci::Pipeline) do |pipeline|
+ allow(pipeline).to receive(:modified_paths).and_return(%w[unknown])
+ end
+ end
+
+ it 'creates the pipeline with a job' do
+ expect(pipeline.errors.full_messages).to eq(['Pipeline filtered out by workflow rules.'])
+ expect(response).to be_error
+ expect(pipeline).not_to be_persisted
+ end
+ end
+ end
+
+ context 'changes is an array' do
+ let(:config) do
+ <<-EOY
+ workflow:
+ rules:
+ - changes: [file1.md]
+
+ job:
+ script: exit 0
+ EOY
+ end
+
+ it_behaves_like 'comparing file changes with workflow rules'
+ end
+
+ context 'changes:paths is an array' do
+ let(:config) do
+ <<-EOY
+ workflow:
+ rules:
+ - changes:
+ paths: [file1.md]
+
+ job:
+ script: exit 0
+ EOY
+ end
+
+ it_behaves_like 'comparing file changes with workflow rules'
+ end
+ end
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 9cef7f7dadb..a9442b0dc68 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe Ci::CreatePipelineService do
# rubocop:disable Metrics/ParameterLists
def execute_service(
source: :push,
+ before: '00000000',
after: project.commit.id,
ref: ref_name,
trigger_request: nil,
@@ -29,7 +30,7 @@ RSpec.describe Ci::CreatePipelineService do
target_sha: nil,
save_on_errors: true)
params = { ref: ref,
- before: '00000000',
+ before: before,
after: after,
variables_attributes: variables_attributes,
push_options: push_options,
@@ -1865,818 +1866,6 @@ RSpec.describe Ci::CreatePipelineService do
end
end
end
-
- context 'when rules are used' do
- let(:ref_name) { 'refs/heads/master' }
- let(:response) { execute_service }
- let(:pipeline) { response.payload }
- let(:build_names) { pipeline.builds.pluck(:name) }
- let(:regular_job) { find_job('regular-job') }
- let(:rules_job) { find_job('rules-job') }
- let(:delayed_job) { find_job('delayed-job') }
-
- context 'with when:manual' do
- let(:config) do
- <<-EOY
- job-with-rules:
- script: 'echo hey'
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
-
- job-when-with-rules:
- script: 'echo hey'
- when: manual
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
-
- job-when-with-rules-when:
- script: 'echo hey'
- when: manual
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- when: on_success
-
- job-with-rules-when:
- script: 'echo hey'
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- when: manual
-
- job-without-rules:
- script: 'echo this is a job with NO rules'
- EOY
- end
-
- let(:job_with_rules) { find_job('job-with-rules') }
- let(:job_when_with_rules) { find_job('job-when-with-rules') }
- let(:job_when_with_rules_when) { find_job('job-when-with-rules-when') }
- let(:job_with_rules_when) { find_job('job-with-rules-when') }
- let(:job_without_rules) { find_job('job-without-rules') }
-
- context 'when matching the rules' do
- let(:ref_name) { 'refs/heads/master' }
-
- it 'adds the job-with-rules with a when:manual' do
- expect(job_with_rules).to be_persisted
- expect(job_when_with_rules).to be_persisted
- expect(job_when_with_rules_when).to be_persisted
- expect(job_with_rules_when).to be_persisted
- expect(job_without_rules).to be_persisted
-
- expect(job_with_rules.when).to eq('on_success')
- expect(job_when_with_rules.when).to eq('manual')
- expect(job_when_with_rules_when.when).to eq('on_success')
- expect(job_with_rules_when.when).to eq('manual')
- expect(job_without_rules.when).to eq('on_success')
- end
- end
-
- context 'when there is no match to the rule' do
- let(:ref_name) { 'refs/heads/wip' }
-
- it 'does not add job_with_rules' do
- expect(job_with_rules).to be_nil
- expect(job_when_with_rules).to be_nil
- expect(job_when_with_rules_when).to be_nil
- expect(job_with_rules_when).to be_nil
- expect(job_without_rules).to be_persisted
- end
- end
- end
-
- shared_examples 'rules jobs are excluded' do
- it 'only persists the job without rules' do
- expect(pipeline).to be_persisted
- expect(regular_job).to be_persisted
- expect(rules_job).to be_nil
- expect(delayed_job).to be_nil
- end
- end
-
- def find_job(name)
- pipeline.builds.find_by(name: name)
- end
-
- before do
- stub_ci_pipeline_yaml_file(config)
- allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
- end
-
- context 'with simple if: clauses' do
- let(:config) do
- <<-EOY
- regular-job:
- script: 'echo Hello, World!'
-
- master-job:
- script: "echo hello world, $CI_COMMIT_REF_NAME"
- rules:
- - if: $CI_COMMIT_REF_NAME == "nonexistant-branch"
- when: never
- - if: $CI_COMMIT_REF_NAME =~ /master/
- when: manual
-
- negligible-job:
- script: "exit 1"
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- allow_failure: true
-
- delayed-job:
- script: "echo See you later, World!"
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- when: delayed
- start_in: 1 hour
-
- never-job:
- script: "echo Goodbye, World!"
- rules:
- - if: $CI_COMMIT_REF_NAME
- when: never
- EOY
- end
-
- context 'with matches' do
- it 'creates a pipeline with the vanilla and manual jobs' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly(
- 'regular-job', 'delayed-job', 'master-job', 'negligible-job'
- )
- end
-
- it 'assigns job:when values to the builds' do
- expect(find_job('regular-job').when).to eq('on_success')
- expect(find_job('master-job').when).to eq('manual')
- expect(find_job('negligible-job').when).to eq('on_success')
- expect(find_job('delayed-job').when).to eq('delayed')
- end
-
- it 'assigns job:allow_failure values to the builds' do
- expect(find_job('regular-job').allow_failure).to eq(false)
- expect(find_job('master-job').allow_failure).to eq(false)
- expect(find_job('negligible-job').allow_failure).to eq(true)
- expect(find_job('delayed-job').allow_failure).to eq(false)
- end
-
- it 'assigns start_in for delayed jobs' do
- expect(delayed_job.options[:start_in]).to eq('1 hour')
- end
- end
-
- context 'with no matches' do
- let(:ref_name) { 'refs/heads/feature' }
-
- it_behaves_like 'rules jobs are excluded'
- end
- end
-
- context 'with complex if: clauses' do
- let(:config) do
- <<-EOY
- regular-job:
- script: 'echo Hello, World!'
- rules:
- - if: $VAR == 'present' && $OTHER || $CI_COMMIT_REF_NAME
- when: manual
- allow_failure: true
- EOY
- end
-
- it 'matches the first rule' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('regular-job')
- expect(regular_job.when).to eq('manual')
- expect(regular_job.allow_failure).to eq(true)
- end
- end
-
- context 'with changes:' do
- let(:config) do
- <<-EOY
- regular-job:
- script: 'echo Hello, World!'
-
- rules-job:
- script: "echo hello world, $CI_COMMIT_REF_NAME"
- rules:
- - changes:
- - README.md
- when: manual
- - changes:
- - app.rb
- when: on_success
-
- delayed-job:
- script: "echo See you later, World!"
- rules:
- - changes:
- - README.md
- when: delayed
- start_in: 4 hours
-
- negligible-job:
- script: "can be failed sometimes"
- rules:
- - changes:
- - README.md
- allow_failure: true
-
- README:
- script: "I use variables for changes!"
- rules:
- - changes:
- - $CI_JOB_NAME*
-
- changes-paths:
- script: "I am using a new syntax!"
- rules:
- - changes:
- paths: [README.md]
- EOY
- end
-
- context 'and matches' do
- before do
- allow_any_instance_of(Ci::Pipeline)
- .to receive(:modified_paths).and_return(%w[README.md])
- end
-
- it 'creates five jobs' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly(
- 'regular-job', 'rules-job', 'delayed-job', 'negligible-job', 'README', 'changes-paths'
- )
- end
-
- it 'sets when: for all jobs' do
- expect(regular_job.when).to eq('on_success')
- expect(rules_job.when).to eq('manual')
- expect(delayed_job.when).to eq('delayed')
- expect(delayed_job.options[:start_in]).to eq('4 hours')
- end
-
- it 'sets allow_failure: for negligible job' do
- expect(find_job('negligible-job').allow_failure).to eq(true)
- end
- end
-
- context 'and matches the second rule' do
- before do
- allow_any_instance_of(Ci::Pipeline)
- .to receive(:modified_paths).and_return(%w[app.rb])
- end
-
- it 'includes both jobs' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('regular-job', 'rules-job')
- end
-
- it 'sets when: for the created rules job based on the second clause' do
- expect(regular_job.when).to eq('on_success')
- expect(rules_job.when).to eq('on_success')
- end
- end
-
- context 'and does not match' do
- before do
- allow_any_instance_of(Ci::Pipeline)
- .to receive(:modified_paths).and_return(%w[useless_script.rb])
- end
-
- it_behaves_like 'rules jobs are excluded'
-
- it 'sets when: for the created job' do
- expect(regular_job.when).to eq('on_success')
- end
- end
- end
-
- context 'with mixed if: and changes: rules' do
- let(:config) do
- <<-EOY
- regular-job:
- script: 'echo Hello, World!'
-
- rules-job:
- script: "echo hello world, $CI_COMMIT_REF_NAME"
- allow_failure: true
- rules:
- - changes:
- - README.md
- when: manual
- - if: $CI_COMMIT_REF_NAME == "master"
- when: on_success
- allow_failure: false
-
- delayed-job:
- script: "echo See you later, World!"
- rules:
- - changes:
- - README.md
- when: delayed
- start_in: 4 hours
- allow_failure: true
- - if: $CI_COMMIT_REF_NAME == "master"
- when: delayed
- start_in: 1 hour
- EOY
- end
-
- context 'and changes: matches before if' do
- before do
- allow_any_instance_of(Ci::Pipeline)
- .to receive(:modified_paths).and_return(%w[README.md])
- end
-
- it 'creates two jobs' do
- expect(pipeline).to be_persisted
- expect(build_names)
- .to contain_exactly('regular-job', 'rules-job', 'delayed-job')
- end
-
- it 'sets when: for all jobs' do
- expect(regular_job.when).to eq('on_success')
- expect(rules_job.when).to eq('manual')
- expect(delayed_job.when).to eq('delayed')
- expect(delayed_job.options[:start_in]).to eq('4 hours')
- end
-
- it 'sets allow_failure: for all jobs' do
- expect(regular_job.allow_failure).to eq(false)
- expect(rules_job.allow_failure).to eq(true)
- expect(delayed_job.allow_failure).to eq(true)
- end
- end
-
- context 'and if: matches after changes' do
- it 'includes both jobs' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('regular-job', 'rules-job', 'delayed-job')
- end
-
- it 'sets when: for the created rules job based on the second clause' do
- expect(regular_job.when).to eq('on_success')
- expect(rules_job.when).to eq('on_success')
- expect(delayed_job.when).to eq('delayed')
- expect(delayed_job.options[:start_in]).to eq('1 hour')
- end
- end
-
- context 'and does not match' do
- let(:ref_name) { 'refs/heads/wip' }
-
- it_behaves_like 'rules jobs are excluded'
-
- it 'sets when: for the created job' do
- expect(regular_job.when).to eq('on_success')
- end
- end
- end
-
- context 'with mixed if: and changes: clauses' do
- let(:config) do
- <<-EOY
- regular-job:
- script: 'echo Hello, World!'
-
- rules-job:
- script: "echo hello world, $CI_COMMIT_REF_NAME"
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- changes: [README.md]
- when: on_success
- allow_failure: true
- - if: $CI_COMMIT_REF_NAME =~ /master/
- changes: [app.rb]
- when: manual
- EOY
- end
-
- context 'with if matches and changes matches' do
- before do
- allow_any_instance_of(Ci::Pipeline)
- .to receive(:modified_paths).and_return(%w[app.rb])
- end
-
- it 'persists all jobs' do
- expect(pipeline).to be_persisted
- expect(regular_job).to be_persisted
- expect(rules_job).to be_persisted
- expect(rules_job.when).to eq('manual')
- expect(rules_job.allow_failure).to eq(false)
- end
- end
-
- context 'with if matches and no change matches' do
- it_behaves_like 'rules jobs are excluded'
- end
-
- context 'with change matches and no if matches' do
- let(:ref_name) { 'refs/heads/feature' }
-
- before do
- allow_any_instance_of(Ci::Pipeline)
- .to receive(:modified_paths).and_return(%w[README.md])
- end
-
- it_behaves_like 'rules jobs are excluded'
- end
-
- context 'and no matches' do
- let(:ref_name) { 'refs/heads/feature' }
-
- it_behaves_like 'rules jobs are excluded'
- end
- end
-
- context 'with complex if: allow_failure usages' do
- let(:config) do
- <<-EOY
- job-1:
- script: "exit 1"
- allow_failure: true
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- allow_failure: false
-
- job-2:
- script: "exit 1"
- allow_failure: true
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
- allow_failure: false
-
- job-3:
- script: "exit 1"
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
- allow_failure: true
-
- job-4:
- script: "exit 1"
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- allow_failure: false
-
- job-5:
- script: "exit 1"
- allow_failure: false
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- allow_failure: true
-
- job-6:
- script: "exit 1"
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
- allow_failure: false
- - allow_failure: true
- EOY
- end
-
- it 'creates a pipeline' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('job-1', 'job-4', 'job-5', 'job-6')
- end
-
- it 'assigns job:allow_failure values to the builds' do
- expect(find_job('job-1').allow_failure).to eq(false)
- expect(find_job('job-4').allow_failure).to eq(false)
- expect(find_job('job-5').allow_failure).to eq(true)
- expect(find_job('job-6').allow_failure).to eq(true)
- end
- end
-
- context 'with complex if: allow_failure & when usages' do
- let(:config) do
- <<-EOY
- job-1:
- script: "exit 1"
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- when: manual
-
- job-2:
- script: "exit 1"
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- when: manual
- allow_failure: true
-
- job-3:
- script: "exit 1"
- allow_failure: true
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- when: manual
-
- job-4:
- script: "exit 1"
- allow_failure: true
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- when: manual
- allow_failure: false
-
- job-5:
- script: "exit 1"
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
- when: manual
- allow_failure: false
- - when: always
- allow_failure: true
-
- job-6:
- script: "exit 1"
- allow_failure: false
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /master/
- when: manual
-
- job-7:
- script: "exit 1"
- allow_failure: false
- rules:
- - if: $CI_COMMIT_REF_NAME =~ /nonexistant-branch/
- when: manual
- - when: :on_failure
- allow_failure: true
- EOY
- end
-
- it 'creates a pipeline' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly(
- 'job-1', 'job-2', 'job-3', 'job-4', 'job-5', 'job-6', 'job-7'
- )
- end
-
- it 'assigns job:allow_failure values to the builds' do
- expect(find_job('job-1').allow_failure).to eq(false)
- expect(find_job('job-2').allow_failure).to eq(true)
- expect(find_job('job-3').allow_failure).to eq(true)
- expect(find_job('job-4').allow_failure).to eq(false)
- expect(find_job('job-5').allow_failure).to eq(true)
- expect(find_job('job-6').allow_failure).to eq(false)
- expect(find_job('job-7').allow_failure).to eq(true)
- end
-
- it 'assigns job:when values to the builds' do
- expect(find_job('job-1').when).to eq('manual')
- expect(find_job('job-2').when).to eq('manual')
- expect(find_job('job-3').when).to eq('manual')
- expect(find_job('job-4').when).to eq('manual')
- expect(find_job('job-5').when).to eq('always')
- expect(find_job('job-6').when).to eq('manual')
- expect(find_job('job-7').when).to eq('on_failure')
- end
- end
-
- context 'with deploy freeze period `if:` clause' do
- # '0 23 * * 5' == "At 23:00 on Friday."", '0 7 * * 1' == "At 07:00 on Monday.""
- let!(:freeze_period) { create(:ci_freeze_period, project: project, freeze_start: '0 23 * * 5', freeze_end: '0 7 * * 1') }
-
- context 'with 2 jobs' do
- let(:config) do
- <<-EOY
- stages:
- - test
- - deploy
-
- test-job:
- script:
- - echo 'running TEST stage'
-
- deploy-job:
- stage: deploy
- script:
- - echo 'running DEPLOY stage'
- rules:
- - if: $CI_DEPLOY_FREEZE == null
- EOY
- end
-
- context 'when outside freeze period' do
- it 'creates two jobs' do
- Timecop.freeze(2020, 4, 10, 22, 59) do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('test-job', 'deploy-job')
- end
- end
- end
-
- context 'when inside freeze period' do
- it 'creates one job' do
- Timecop.freeze(2020, 4, 10, 23, 1) do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('test-job')
- end
- end
- end
- end
-
- context 'with 1 job' do
- let(:config) do
- <<-EOY
- stages:
- - deploy
-
- deploy-job:
- stage: deploy
- script:
- - echo 'running DEPLOY stage'
- rules:
- - if: $CI_DEPLOY_FREEZE == null
- EOY
- end
-
- context 'when outside freeze period' do
- it 'creates two jobs' do
- Timecop.freeze(2020, 4, 10, 22, 59) do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('deploy-job')
- end
- end
- end
-
- context 'when inside freeze period' do
- it 'does not create the pipeline', :aggregate_failures do
- Timecop.freeze(2020, 4, 10, 23, 1) do
- expect(response).to be_error
- expect(pipeline).not_to be_persisted
- end
- end
- end
- end
- end
-
- context 'with workflow rules with persisted variables' do
- let(:config) do
- <<-EOY
- workflow:
- rules:
- - if: $CI_COMMIT_REF_NAME == "master"
-
- regular-job:
- script: 'echo Hello, World!'
- EOY
- end
-
- context 'with matches' do
- it 'creates a pipeline' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('regular-job')
- end
- end
-
- context 'with no matches' do
- let(:ref_name) { 'refs/heads/feature' }
-
- it 'does not create a pipeline', :aggregate_failures do
- expect(response).to be_error
- expect(pipeline).not_to be_persisted
- end
- end
- end
-
- context 'with workflow rules with pipeline variables' do
- let(:pipeline) do
- execute_service(variables_attributes: variables_attributes).payload
- end
-
- let(:config) do
- <<-EOY
- workflow:
- rules:
- - if: $SOME_VARIABLE
-
- regular-job:
- script: 'echo Hello, World!'
- EOY
- end
-
- context 'with matches' do
- let(:variables_attributes) do
- [{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }]
- end
-
- it 'creates a pipeline' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('regular-job')
- end
- end
-
- context 'with no matches' do
- let(:variables_attributes) { {} }
-
- it 'does not create a pipeline', :aggregate_failures do
- expect(response).to be_error
- expect(pipeline).not_to be_persisted
- end
- end
- end
-
- context 'with workflow rules with trigger variables' do
- let(:pipeline) do
- execute_service do |pipeline|
- pipeline.variables.build(variables)
- end.payload
- end
-
- let(:config) do
- <<-EOY
- workflow:
- rules:
- - if: $SOME_VARIABLE
-
- regular-job:
- script: 'echo Hello, World!'
- EOY
- end
-
- context 'with matches' do
- let(:variables) do
- [{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }]
- end
-
- it 'creates a pipeline' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('regular-job')
- end
-
- context 'when a job requires the same variable' do
- let(:config) do
- <<-EOY
- workflow:
- rules:
- - if: $SOME_VARIABLE
-
- build:
- stage: build
- script: 'echo build'
- rules:
- - if: $SOME_VARIABLE
-
- test1:
- stage: test
- script: 'echo test1'
- needs: [build]
-
- test2:
- stage: test
- script: 'echo test2'
- EOY
- end
-
- it 'creates a pipeline' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('build', 'test1', 'test2')
- end
- end
- end
-
- context 'with no matches' do
- let(:variables) { {} }
-
- it 'does not create a pipeline', :aggregate_failures do
- expect(response).to be_error
- expect(pipeline).not_to be_persisted
- end
-
- context 'when a job requires the same variable' do
- let(:config) do
- <<-EOY
- workflow:
- rules:
- - if: $SOME_VARIABLE
-
- build:
- stage: build
- script: 'echo build'
- rules:
- - if: $SOME_VARIABLE
-
- test1:
- stage: test
- script: 'echo test1'
- needs: [build]
-
- test2:
- stage: test
- script: 'echo test2'
- EOY
- end
-
- it 'does not create a pipeline', :aggregate_failures do
- expect(response).to be_error
- expect(pipeline).not_to be_persisted
- end
- end
- end
- end
- end
end
describe '#execute!' do
diff --git a/spec/services/ci/deployments/destroy_service_spec.rb b/spec/services/ci/deployments/destroy_service_spec.rb
new file mode 100644
index 00000000000..60a57c05728
--- /dev/null
+++ b/spec/services/ci/deployments/destroy_service_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Ci::Deployments::DestroyService do
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:environment) { create(:environment, project: project) }
+ let(:commits) { project.repository.commits(nil, { limit: 3 }) }
+ let!(:deploy) do
+ create(
+ :deployment,
+ :success,
+ project: project,
+ environment: environment,
+ deployable: nil,
+ sha: commits[2].sha
+ )
+ end
+
+ let!(:running_deploy) do
+ create(
+ :deployment,
+ :running,
+ project: project,
+ environment: environment,
+ deployable: nil,
+ sha: commits[1].sha
+ )
+ end
+
+ let!(:old_deploy) do
+ create(
+ :deployment,
+ :success,
+ project: project,
+ environment: environment,
+ deployable: nil,
+ sha: commits[0].sha,
+ finished_at: 1.year.ago
+ )
+ end
+
+ let(:user) { project.first_owner }
+
+ subject { described_class.new(project, user) }
+
+ context 'when deleting a deployment' do
+ it 'delete is accepted for old deployment' do
+ expect(subject.execute(old_deploy)).to be_success
+ end
+
+ it 'does not delete a running deployment' do
+ response = subject.execute(running_deploy)
+ expect(response).to be_an_error
+ expect(response.message).to eq("Cannot destroy running deployment")
+ end
+
+ it 'does not delete the last deployment' do
+ response = subject.execute(deploy)
+ expect(response).to be_an_error
+ expect(response.message).to eq("Deployment currently deployed to environment")
+ end
+ end
+end
diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb
index 045051c7152..6bd7fe7559c 100644
--- a/spec/services/ci/destroy_pipeline_service_spec.rb
+++ b/spec/services/ci/destroy_pipeline_service_spec.rb
@@ -90,15 +90,23 @@ RSpec.describe ::Ci::DestroyPipelineService do
end
end
- context 'when pipeline is in cancelable state' do
- before do
- allow(pipeline).to receive(:cancelable?).and_return(true)
- end
+ context 'when pipeline is in cancelable state', :sidekiq_inline do
+ let!(:build) { create(:ci_build, :running, pipeline: pipeline) }
+ let!(:child_pipeline) { create(:ci_pipeline, :running, child_of: pipeline) }
+ let!(:child_build) { create(:ci_build, :running, pipeline: child_pipeline) }
+
+ it 'cancels the pipelines sync' do
+ # turn off deletion for all instances of pipeline to allow for testing cancellation
+ allow(pipeline).to receive_message_chain(:reset, :destroy!)
+ allow_next_found_instance_of(Ci::Pipeline) { |p| allow(p).to receive_message_chain(:reset, :destroy!) }
- it 'cancels the pipeline' do
- expect(pipeline).to receive(:cancel_running)
+ # ensure cancellation happens sync so we accumulate minutes
+ expect(::Ci::CancelPipelineWorker).not_to receive(:perform)
subject
+
+ expect(build.reload.status).to eq('canceled')
+ expect(child_build.reload.status).to eq('canceled')
end
end
end
diff --git a/spec/services/ci/job_artifacts/create_service_spec.rb b/spec/services/ci/job_artifacts/create_service_spec.rb
index b7a810ce47e..7b3f67b192f 100644
--- a/spec/services/ci/job_artifacts/create_service_spec.rb
+++ b/spec/services/ci/job_artifacts/create_service_spec.rb
@@ -34,6 +34,14 @@ RSpec.describe Ci::JobArtifacts::CreateService do
subject { service.execute(artifacts_file, params, metadata_file: metadata_file) }
context 'when artifacts file is uploaded' do
+ it 'logs the created artifact' do
+ expect(Gitlab::Ci::Artifacts::Logger)
+ .to receive(:log_created)
+ .with(an_instance_of(Ci::JobArtifact))
+
+ subject
+ end
+
it 'returns artifact in the response' do
response = subject
new_artifact = job.job_artifacts.last
diff --git a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
index 05069054483..9ca39d4d32e 100644
--- a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
+++ b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
@@ -40,7 +40,14 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
expect { execute }.not_to change { artifact_with_file.file.exists? }
end
- it 'deletes the artifact records' do
+ it 'deletes the artifact records and logs them' do
+ expect(Gitlab::Ci::Artifacts::Logger)
+ .to receive(:log_deleted)
+ .with(
+ match_array([artifact_with_file, artifact_without_file]),
+ 'Ci::JobArtifacts::DestroyBatchService#execute'
+ )
+
expect { subject }.to change { Ci::JobArtifact.count }.by(-2)
end
diff --git a/spec/services/ci/list_config_variables_service_spec.rb b/spec/services/ci/list_config_variables_service_spec.rb
index 1735f4cfc97..4953b18bfcc 100644
--- a/spec/services/ci/list_config_variables_service_spec.rb
+++ b/spec/services/ci/list_config_variables_service_spec.rb
@@ -40,8 +40,8 @@ RSpec.describe Ci::ListConfigVariablesService, :use_clean_rails_memory_store_cac
it 'returns variable list' do
expect(subject['KEY1']).to eq({ value: 'val 1', description: 'description 1' })
expect(subject['KEY2']).to eq({ value: 'val 2', description: '' })
- expect(subject['KEY3']).to eq({ value: 'val 3', description: nil })
- expect(subject['KEY4']).to eq({ value: 'val 4', description: nil })
+ expect(subject['KEY3']).to eq({ value: 'val 3' })
+ expect(subject['KEY4']).to eq({ value: 'val 4' })
end
end
diff --git a/spec/services/ci/parse_dotenv_artifact_service_spec.rb b/spec/services/ci/parse_dotenv_artifact_service_spec.rb
index aaab849cd93..7b3af33ac72 100644
--- a/spec/services/ci/parse_dotenv_artifact_service_spec.rb
+++ b/spec/services/ci/parse_dotenv_artifact_service_spec.rb
@@ -292,7 +292,7 @@ RSpec.describe Ci::ParseDotenvArtifactService do
end
context 'when build does not have a dotenv artifact' do
- let!(:artifact) { }
+ let!(:artifact) {}
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError)
diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb
index 7868629d34d..289e004fcce 100644
--- a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb
+++ b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb
@@ -87,7 +87,7 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection
describe '#processing_processables' do
it 'returns processables marked as processing' do
- expect(collection.processing_processables.map { |processable| processable[:id]} )
+ expect(collection.processing_processables.map { |processable| processable[:id] } )
.to contain_exactly(build_a.id, build_b.id, test_a.id, test_b.id, deploy.id)
end
end
diff --git a/spec/services/ci/process_build_service_spec.rb b/spec/services/ci/process_build_service_spec.rb
index b54fc45d36a..2fcb4ce73ff 100644
--- a/spec/services/ci/process_build_service_spec.rb
+++ b/spec/services/ci/process_build_service_spec.rb
@@ -101,7 +101,7 @@ RSpec.describe Ci::ProcessBuildService, '#execute' do
context 'when build has delayed option' do
before do
- allow(Ci::BuildScheduleWorker).to receive(:perform_at) { }
+ allow(Ci::BuildScheduleWorker).to receive(:perform_at) {}
end
let(:build) { create(:ci_build, :created, :schedulable, user: user, project: project) }
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 2316575f164..cabd60a22d1 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -129,6 +129,12 @@ module Ci
let!(:build2_project2) { create(:ci_build, :pending, :queued, pipeline: pipeline2) }
let!(:build1_project3) { create(:ci_build, :pending, :queued, pipeline: pipeline3) }
+ it 'picks builds one-by-one' do
+ expect(Ci::Build).to receive(:find).with(pending_job.id).and_call_original
+
+ expect(execute(shared_runner)).to eq(build1_project1)
+ end
+
context 'when using fair scheduling' do
context 'when all builds are pending' do
it 'prefers projects without builds first' do
@@ -485,6 +491,48 @@ module Ci
end
context 'when "dependencies" keyword is specified' do
+ let!(:pre_stage_job) do
+ create(:ci_build, :success, :artifacts, pipeline: pipeline, name: 'test', stage_idx: 0)
+ end
+
+ let!(:pending_job) do
+ create(:ci_build, :pending, :queued,
+ pipeline: pipeline, stage_idx: 1,
+ options: { script: ["bash"], dependencies: dependencies })
+ end
+
+ let(:dependencies) { %w[test] }
+
+ subject { execute(specific_runner) }
+
+ it 'picks a build with a dependency' do
+ picked_build = execute(specific_runner)
+
+ expect(picked_build).to be_present
+ end
+
+ context 'when there are multiple dependencies with artifacts' do
+ let!(:pre_stage_job_second) do
+ create(:ci_build, :success, :artifacts, pipeline: pipeline, name: 'deploy', stage_idx: 0)
+ end
+
+ let(:dependencies) { %w[test deploy] }
+
+ it 'logs build artifacts size' do
+ execute(specific_runner)
+
+ artifacts_size = [pre_stage_job, pre_stage_job_second].sum do |job|
+ job.job_artifacts_archive.size
+ end
+
+ expect(artifacts_size).to eq 107464 * 2
+ expect(Gitlab::ApplicationContext.current).to include({
+ 'meta.artifacts_dependencies_size' => artifacts_size,
+ 'meta.artifacts_dependencies_count' => 2
+ })
+ end
+ end
+
shared_examples 'not pick' do
it 'does not pick the build and drops the build' do
expect(subject).to be_nil
@@ -572,16 +620,6 @@ module Ci
end
end
- let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) }
-
- let!(:pending_job) do
- create(:ci_build, :pending, :queued,
- pipeline: pipeline, stage_idx: 1,
- options: { script: ["bash"], dependencies: ['test'] })
- end
-
- subject { execute(specific_runner) }
-
it_behaves_like 'validation is active'
end
@@ -739,16 +777,6 @@ module Ci
end
end
- context 'when a long queue is created' do
- it 'picks builds one-by-one' do
- expect(Ci::Build).to receive(:find).with(pending_job.id).and_call_original
-
- expect(execute(specific_runner)).to eq(pending_job)
- end
-
- include_examples 'handles runner assignment'
- end
-
context 'when using pending builds table' do
include_examples 'handles runner assignment'
diff --git a/spec/services/ci/retry_job_service_spec.rb b/spec/services/ci/retry_job_service_spec.rb
index f042471bd1f..b14e4187c7a 100644
--- a/spec/services/ci/retry_job_service_spec.rb
+++ b/spec/services/ci/retry_job_service_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe Ci::RetryJobService do
name: 'test')
end
+ let(:job_variables_attributes) { [{ key: 'MANUAL_VAR', value: 'manual test var' }] }
let(:user) { developer }
let(:service) { described_class.new(project, user) }
@@ -206,6 +207,14 @@ RSpec.describe Ci::RetryJobService do
include_context 'retryable bridge'
it_behaves_like 'clones the job'
+
+ context 'when given variables' do
+ let(:new_job) { service.clone!(job, variables: job_variables_attributes) }
+
+ it 'does not give variables to the new bridge' do
+ expect { new_job }.not_to raise_error
+ end
+ end
end
context 'when the job to be cloned is a build' do
@@ -250,6 +259,28 @@ RSpec.describe Ci::RetryJobService do
expect { new_job }.not_to change { Environment.count }
end
end
+
+ context 'when given variables' do
+ let(:new_job) { service.clone!(job, variables: job_variables_attributes) }
+
+ context 'when the build is actionable' do
+ let_it_be_with_refind(:job) { create(:ci_build, :actionable, pipeline: pipeline) }
+
+ it 'gives variables to the new build' do
+ expect(new_job.job_variables.count).to be(1)
+ expect(new_job.job_variables.first.key).to eq('MANUAL_VAR')
+ expect(new_job.job_variables.first.value).to eq('manual test var')
+ end
+ end
+
+ context 'when the build is not actionable' do
+ let_it_be_with_refind(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'does not give variables to the new build' do
+ expect(new_job.job_variables.count).to be_zero
+ end
+ end
+ end
end
end
@@ -260,6 +291,14 @@ RSpec.describe Ci::RetryJobService do
include_context 'retryable bridge'
it_behaves_like 'retries the job'
+
+ context 'when given variables' do
+ let(:new_job) { service.clone!(job, variables: job_variables_attributes) }
+
+ it 'does not give variables to the new bridge' do
+ expect { new_job }.not_to raise_error
+ end
+ end
end
context 'when the job to be retried is a build' do
@@ -288,6 +327,28 @@ RSpec.describe Ci::RetryJobService do
expect { service.execute(job) }.not_to exceed_all_query_limit(control_count)
end
end
+
+ context 'when given variables' do
+ let(:new_job) { service.clone!(job, variables: job_variables_attributes) }
+
+ context 'when the build is actionable' do
+ let_it_be_with_refind(:job) { create(:ci_build, :actionable, pipeline: pipeline) }
+
+ it 'gives variables to the new build' do
+ expect(new_job.job_variables.count).to be(1)
+ expect(new_job.job_variables.first.key).to eq('MANUAL_VAR')
+ expect(new_job.job_variables.first.value).to eq('manual test var')
+ end
+ end
+
+ context 'when the build is not actionable' do
+ let_it_be_with_refind(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'does not give variables to the new build' do
+ expect(new_job.job_variables.count).to be_zero
+ end
+ end
+ end
end
end
end
diff --git a/spec/services/ci/runners/assign_runner_service_spec.rb b/spec/services/ci/runners/assign_runner_service_spec.rb
index 00b176bb759..08bb99830fb 100644
--- a/spec/services/ci/runners/assign_runner_service_spec.rb
+++ b/spec/services/ci/runners/assign_runner_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe ::Ci::Runners::AssignRunnerService, '#execute' do
- subject { described_class.new(runner, project, user).execute }
+ subject(:execute) { described_class.new(runner, project, user).execute }
let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
let_it_be(:project) { create(:project) }
@@ -11,30 +11,32 @@ RSpec.describe ::Ci::Runners::AssignRunnerService, '#execute' do
context 'without user' do
let(:user) { nil }
- it 'does not call assign_to on runner and returns false' do
+ it 'does not call assign_to on runner and returns error response', :aggregate_failures do
expect(runner).not_to receive(:assign_to)
- is_expected.to eq(false)
+ is_expected.to be_error
+ expect(execute.message).to eq('user not allowed to assign runner')
end
end
context 'with unauthorized user' do
let(:user) { build(:user) }
- it 'does not call assign_to on runner and returns false' do
+ it 'does not call assign_to on runner and returns error message' do
expect(runner).not_to receive(:assign_to)
- is_expected.to eq(false)
+ is_expected.to be_error
+ expect(execute.message).to eq('user not allowed to assign runner')
end
end
context 'with admin user', :enable_admin_mode do
let(:user) { create_default(:user, :admin) }
- it 'calls assign_to on runner and returns value unchanged' do
- expect(runner).to receive(:assign_to).with(project, user).once.and_return('assign_to return value')
+ it 'calls assign_to on runner and returns success response' do
+ expect(runner).to receive(:assign_to).with(project, user).once.and_call_original
- is_expected.to eq('assign_to return value')
+ is_expected.to be_success
end
end
end
diff --git a/spec/services/ci/runners/bulk_delete_runners_service_spec.rb b/spec/services/ci/runners/bulk_delete_runners_service_spec.rb
new file mode 100644
index 00000000000..8e9fc4e3012
--- /dev/null
+++ b/spec/services/ci/runners/bulk_delete_runners_service_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Ci::Runners::BulkDeleteRunnersService, '#execute' do
+ subject(:execute) { described_class.new(**service_args).execute }
+
+ let(:service_args) { { runners: runners_arg } }
+ let(:runners_arg) {}
+
+ context 'with runners specified' do
+ let!(:instance_runner) { create(:ci_runner) }
+ let!(:group_runner) { create(:ci_runner, :group) }
+ let!(:project_runner) { create(:ci_runner, :project) }
+
+ shared_examples 'a service deleting runners in bulk' do
+ it 'destroys runners', :aggregate_failures do
+ expect { subject }.to change { Ci::Runner.count }.by(-2)
+
+ is_expected.to be_success
+ expect(execute.payload).to eq({ deleted_count: 2, deleted_ids: [instance_runner.id, project_runner.id] })
+ expect(instance_runner[:errors]).to be_nil
+ expect(project_runner[:errors]).to be_nil
+ expect { project_runner.runner_projects.first.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { group_runner.reload }.not_to raise_error
+ expect { instance_runner.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { project_runner.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ context 'with some runners already deleted' do
+ before do
+ instance_runner.destroy!
+ end
+
+ let(:runners_arg) { [instance_runner.id, project_runner.id] }
+
+ it 'destroys runners and returns only deleted runners', :aggregate_failures do
+ expect { subject }.to change { Ci::Runner.count }.by(-1)
+
+ is_expected.to be_success
+ expect(execute.payload).to eq({ deleted_count: 1, deleted_ids: [project_runner.id] })
+ expect(instance_runner[:errors]).to be_nil
+ expect(project_runner[:errors]).to be_nil
+ expect { project_runner.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'with too many runners specified' do
+ before do
+ stub_const("#{described_class}::RUNNER_LIMIT", 1)
+ end
+
+ it 'deletes only first RUNNER_LIMIT runners' do
+ expect { subject }.to change { Ci::Runner.count }.by(-1)
+
+ is_expected.to be_success
+ expect(execute.payload).to eq({ deleted_count: 1, deleted_ids: [instance_runner.id] })
+ end
+ end
+ end
+
+ context 'with runners specified as relation' do
+ let(:runners_arg) { Ci::Runner.not_group_type }
+
+ include_examples 'a service deleting runners in bulk'
+ end
+
+ context 'with runners specified as array of IDs' do
+ let(:runners_arg) { Ci::Runner.not_group_type.ids }
+
+ include_examples 'a service deleting runners in bulk'
+ end
+
+ context 'with no arguments specified' do
+ let(:runners_arg) { nil }
+
+ it 'returns 0 deleted runners' do
+ is_expected.to be_success
+ expect(execute.payload).to eq({ deleted_count: 0, deleted_ids: [] })
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/runners/process_runner_version_update_service_spec.rb b/spec/services/ci/runners/process_runner_version_update_service_spec.rb
new file mode 100644
index 00000000000..b885138fc7a
--- /dev/null
+++ b/spec/services/ci/runners/process_runner_version_update_service_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::Runners::ProcessRunnerVersionUpdateService do
+ subject(:service) { described_class.new(version) }
+
+ let(:version) { '1.0.0' }
+ let(:available_runner_releases) { %w[1.0.0 1.0.1] }
+
+ describe '#execute' do
+ subject(:execute) { service.execute }
+
+ context 'with upgrade check returning error' do
+ let(:service_double) { instance_double(Gitlab::Ci::RunnerUpgradeCheck) }
+
+ before do
+ allow(service_double).to receive(:check_runner_upgrade_suggestion).with(version)
+ .and_return([version, :error])
+ allow(service).to receive(:upgrade_check_service).and_return(service_double)
+ end
+
+ it 'does not update ci_runner_versions records', :aggregate_failures do
+ expect do
+ expect(execute).to be_error
+ expect(execute.message).to eq 'upgrade version check failed'
+ end.not_to change(Ci::RunnerVersion, :count).from(0)
+ expect(service_double).to have_received(:check_runner_upgrade_suggestion).with(version).once
+ end
+ end
+
+ context 'with successful result from upgrade check' do
+ before do
+ url = ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url
+
+ WebMock.stub_request(:get, url).to_return(
+ body: available_runner_releases.map { |v| { name: v } }.to_json,
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+ end
+
+ context 'with no existing ci_runner_version record' do
+ it 'creates ci_runner_versions record', :aggregate_failures do
+ expect do
+ expect(execute).to be_success
+ expect(execute.http_status).to eq :ok
+ expect(execute.payload).to eq({ upgrade_status: 'recommended' })
+ end.to change(Ci::RunnerVersion, :all).to contain_exactly(
+ an_object_having_attributes(version: version, status: 'recommended')
+ )
+ end
+ end
+
+ context 'with existing ci_runner_version record' do
+ let!(:runner_version) { create(:ci_runner_version, version: '1.0.0', status: :not_available) }
+
+ it 'updates ci_runner_versions record', :aggregate_failures do
+ expect do
+ expect(execute).to be_success
+ expect(execute.http_status).to eq :ok
+ expect(execute.payload).to eq({ upgrade_status: 'recommended' })
+ end.to change { runner_version.reload.status }.from('not_available').to('recommended')
+ end
+ end
+
+ context 'with up-to-date ci_runner_version record' do
+ let!(:runner_version) { create(:ci_runner_version, version: '1.0.0', status: :recommended) }
+
+ it 'does not update ci_runner_versions record', :aggregate_failures do
+ expect do
+ expect(execute).to be_success
+ expect(execute.http_status).to eq :ok
+ expect(execute.payload).to eq({ upgrade_status: 'recommended' })
+ end.not_to change { runner_version.reload.status }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb b/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb
index f8313eaab90..1690190320a 100644
--- a/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb
+++ b/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute' do
+ include RunnerReleasesHelper
+
subject(:execute) { described_class.new.execute }
let_it_be(:runner_14_0_1) { create(:ci_runner, version: '14.0.1') }
@@ -11,12 +13,12 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute'
end
context 'with RunnerUpgradeCheck recommending 14.0.2' do
+ let(:upgrade_check) { instance_double(::Gitlab::Ci::RunnerUpgradeCheck) }
+
before do
stub_const('Ci::Runners::ReconcileExistingRunnerVersionsService::VERSION_BATCH_SIZE', 1)
- allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
- .to receive(:check_runner_upgrade_status)
- .and_return({ recommended: ::Gitlab::VersionInfo.new(14, 0, 2) })
+ allow(::Gitlab::Ci::RunnerUpgradeCheck).to receive(:new).and_return(upgrade_check).once
end
context 'with runner with new version' do
@@ -25,10 +27,11 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute'
let!(:runner_14_0_0) { create(:ci_runner, version: '14.0.0') }
before do
- allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
- .to receive(:check_runner_upgrade_status)
+ allow(upgrade_check).to receive(:check_runner_upgrade_suggestion)
+ .and_return([::Gitlab::VersionInfo.new(14, 0, 2), :recommended])
+ allow(upgrade_check).to receive(:check_runner_upgrade_suggestion)
.with('14.0.2')
- .and_return({ not_available: ::Gitlab::VersionInfo.new(14, 0, 2) })
+ .and_return([::Gitlab::VersionInfo.new(14, 0, 2), :not_available])
.once
end
@@ -39,14 +42,13 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute'
.once
.and_call_original
- result = nil
- expect { result = execute }
+ expect { execute }
.to change { runner_version_14_0_0.reload.status }.from('not_available').to('recommended')
.and change { runner_version_14_0_1.reload.status }.from('not_available').to('recommended')
.and change { ::Ci::RunnerVersion.find_by(version: '14.0.2')&.status }.from(nil).to('not_available')
- expect(result).to eq({
- status: :success,
+ expect(execute).to be_success
+ expect(execute.payload).to eq({
total_inserted: 1, # 14.0.2 is inserted
total_updated: 3, # 14.0.0, 14.0.1 are updated, and newly inserted 14.0.2's status is calculated
total_deleted: 0
@@ -58,19 +60,17 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute'
let!(:runner_version_14_0_2) { create(:ci_runner_version, version: '14.0.2', status: :not_available) }
before do
- allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
- .to receive(:check_runner_upgrade_status)
- .and_return({ not_available: ::Gitlab::VersionInfo.new(14, 0, 2) })
+ allow(upgrade_check).to receive(:check_runner_upgrade_suggestion)
+ .and_return([::Gitlab::VersionInfo.new(14, 0, 2), :not_available])
end
it 'deletes orphan ci_runner_versions entry', :aggregate_failures do
- result = nil
- expect { result = execute }
+ expect { execute }
.to change { ::Ci::RunnerVersion.find_by_version('14.0.2')&.status }.from('not_available').to(nil)
.and not_change { runner_version_14_0_1.reload.status }.from('not_available')
- expect(result).to eq({
- status: :success,
+ expect(execute).to be_success
+ expect(execute.payload).to eq({
total_inserted: 0,
total_updated: 0,
total_deleted: 1 # 14.0.2 is deleted
@@ -80,17 +80,15 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute'
context 'with no runner version changes' do
before do
- allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
- .to receive(:check_runner_upgrade_status)
- .and_return({ not_available: ::Gitlab::VersionInfo.new(14, 0, 1) })
+ allow(upgrade_check).to receive(:check_runner_upgrade_suggestion)
+ .and_return([::Gitlab::VersionInfo.new(14, 0, 1), :not_available])
end
it 'does not modify ci_runner_versions entries', :aggregate_failures do
- result = nil
- expect { result = execute }.not_to change { runner_version_14_0_1.reload.status }.from('not_available')
+ expect { execute }.not_to change { runner_version_14_0_1.reload.status }.from('not_available')
- expect(result).to eq({
- status: :success,
+ expect(execute).to be_success
+ expect(execute.payload).to eq({
total_inserted: 0,
total_updated: 0,
total_deleted: 0
@@ -100,17 +98,15 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute'
context 'with failing version check' do
before do
- allow(::Gitlab::Ci::RunnerUpgradeCheck.instance)
- .to receive(:check_runner_upgrade_status)
- .and_return({ error: ::Gitlab::VersionInfo.new(14, 0, 1) })
+ allow(upgrade_check).to receive(:check_runner_upgrade_suggestion)
+ .and_return([::Gitlab::VersionInfo.new(14, 0, 1), :error])
end
it 'makes no changes to ci_runner_versions', :aggregate_failures do
- result = nil
- expect { result = execute }.not_to change { runner_version_14_0_1.reload.status }.from('not_available')
+ expect { execute }.not_to change { runner_version_14_0_1.reload.status }.from('not_available')
- expect(result).to eq({
- status: :success,
+ expect(execute).to be_success
+ expect(execute.payload).to eq({
total_inserted: 0,
total_updated: 0,
total_deleted: 0
@@ -120,26 +116,15 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute'
end
context 'integration testing with Gitlab::Ci::RunnerUpgradeCheck' do
- let(:available_runner_releases) do
- %w[14.0.0 14.0.1]
- end
-
before do
- url = ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url
-
- WebMock.stub_request(:get, url).to_return(
- body: available_runner_releases.map { |v| { name: v } }.to_json,
- status: 200,
- headers: { 'Content-Type' => 'application/json' }
- )
+ stub_runner_releases(%w[14.0.0 14.0.1])
end
it 'does not modify ci_runner_versions entries', :aggregate_failures do
- result = nil
- expect { result = execute }.not_to change { runner_version_14_0_1.reload.status }.from('not_available')
+ expect { execute }.not_to change { runner_version_14_0_1.reload.status }.from('not_available')
- expect(result).to eq({
- status: :success,
+ expect(execute).to be_success
+ expect(execute.payload).to eq({
total_inserted: 0,
total_updated: 0,
total_deleted: 0
diff --git a/spec/services/ci/runners/register_runner_service_spec.rb b/spec/services/ci/runners/register_runner_service_spec.rb
index 03dcf851e53..6d7b39de21e 100644
--- a/spec/services/ci/runners/register_runner_service_spec.rb
+++ b/spec/services/ci/runners/register_runner_service_spec.rb
@@ -4,8 +4,9 @@ require 'spec_helper'
RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
let(:registration_token) { 'abcdefg123456' }
- let(:token) { }
+ let(:token) {}
let(:args) { {} }
+ let(:runner) { execute.payload[:runner] }
before do
stub_feature_flags(runner_registration_control: false)
@@ -13,21 +14,25 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
stub_application_setting(valid_runner_registrars: ApplicationSetting::VALID_RUNNER_REGISTRAR_TYPES)
end
- subject(:runner) { described_class.new.execute(token, args) }
+ subject(:execute) { described_class.new.execute(token, args) }
context 'when no token is provided' do
let(:token) { '' }
- it 'returns nil' do
- is_expected.to be_nil
+ it 'returns error response' do
+ expect(execute).to be_error
+ expect(execute.message).to eq 'invalid token supplied'
+ expect(execute.http_status).to eq :forbidden
end
end
context 'when invalid token is provided' do
let(:token) { 'invalid' }
- it 'returns nil' do
- is_expected.to be_nil
+ it 'returns error response' do
+ expect(execute).to be_error
+ expect(execute.message).to eq 'invalid token supplied'
+ expect(execute.http_status).to eq :forbidden
end
end
@@ -36,12 +41,14 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
let(:token) { registration_token }
it 'creates runner with default values' do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.persisted?).to be_truthy
- expect(subject.run_untagged).to be true
- expect(subject.active).to be true
- expect(subject.token).not_to eq(registration_token)
- expect(subject).to be_instance_type
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.persisted?).to be_truthy
+ expect(runner.run_untagged).to be true
+ expect(runner.active).to be true
+ expect(runner.token).not_to eq(registration_token)
+ expect(runner).to be_instance_type
end
context 'with non-default arguments' do
@@ -67,25 +74,27 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
end
it 'creates runner with specified values', :aggregate_failures do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.active).to eq args[:active]
- expect(subject.locked).to eq args[:locked]
- expect(subject.run_untagged).to eq args[:run_untagged]
- expect(subject.tags).to contain_exactly(
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.active).to eq args[:active]
+ expect(runner.locked).to eq args[:locked]
+ expect(runner.run_untagged).to eq args[:run_untagged]
+ expect(runner.tags).to contain_exactly(
an_object_having_attributes(name: 'tag1'),
an_object_having_attributes(name: 'tag2')
)
- expect(subject.access_level).to eq args[:access_level]
- expect(subject.maximum_timeout).to eq args[:maximum_timeout]
- expect(subject.name).to eq args[:name]
- expect(subject.version).to eq args[:version]
- expect(subject.revision).to eq args[:revision]
- expect(subject.platform).to eq args[:platform]
- expect(subject.architecture).to eq args[:architecture]
- expect(subject.ip_address).to eq args[:ip_address]
-
- expect(Ci::Runner.tagged_with('tag1')).to include(subject)
- expect(Ci::Runner.tagged_with('tag2')).to include(subject)
+ expect(runner.access_level).to eq args[:access_level]
+ expect(runner.maximum_timeout).to eq args[:maximum_timeout]
+ expect(runner.name).to eq args[:name]
+ expect(runner.version).to eq args[:version]
+ expect(runner.revision).to eq args[:revision]
+ expect(runner.platform).to eq args[:platform]
+ expect(runner.architecture).to eq args[:architecture]
+ expect(runner.ip_address).to eq args[:ip_address]
+
+ expect(Ci::Runner.tagged_with('tag1')).to include(runner)
+ expect(Ci::Runner.tagged_with('tag2')).to include(runner)
end
end
@@ -95,8 +104,10 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
end
it 'creates runner with token expiration' do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.token_expires_at).to eq(5.days.from_now)
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.token_expires_at).to eq(5.days.from_now)
end
end
end
@@ -106,12 +117,14 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
let(:token) { project.runners_token }
it 'creates project runner' do
- is_expected.to be_an_instance_of(::Ci::Runner)
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
expect(project.runners.size).to eq(1)
- is_expected.to eq(project.runners.first)
- expect(subject.token).not_to eq(registration_token)
- expect(subject.token).not_to eq(project.runners_token)
- expect(subject).to be_project_type
+ expect(runner).to eq(project.runners.first)
+ expect(runner.token).not_to eq(registration_token)
+ expect(runner.token).not_to eq(project.runners_token)
+ expect(runner).to be_project_type
end
context 'when it exceeds the application limits' do
@@ -121,9 +134,13 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
end
it 'does not create runner' do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.persisted?).to be_falsey
- expect(subject.errors.messages).to eq('runner_projects.base': ['Maximum number of ci registered project runners (1) exceeded'])
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.persisted?).to be_falsey
+ expect(runner.errors.messages).to eq(
+ 'runner_projects.base': ['Maximum number of ci registered project runners (1) exceeded']
+ )
expect(project.runners.reload.size).to eq(1)
end
end
@@ -135,8 +152,10 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
end
it 'creates runner' do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.errors).to be_empty
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.errors).to be_empty
expect(project.runners.reload.size).to eq(2)
expect(project.runners.recent.size).to eq(1)
end
@@ -153,15 +172,18 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
end
it 'returns 403 error' do
- is_expected.to be_nil
+ expect(execute).to be_error
+ expect(execute.http_status).to eq :forbidden
end
end
context 'when feature flag is disabled' do
it 'registers the runner' do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.errors).to be_empty
- expect(subject.active).to be true
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.errors).to be_empty
+ expect(runner.active).to be true
end
end
end
@@ -172,12 +194,14 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
let(:token) { group.runners_token }
it 'creates a group runner' do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.errors).to be_empty
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.errors).to be_empty
expect(group.runners.reload.size).to eq(1)
- expect(subject.token).not_to eq(registration_token)
- expect(subject.token).not_to eq(group.runners_token)
- expect(subject).to be_group_type
+ expect(runner.token).not_to eq(registration_token)
+ expect(runner.token).not_to eq(group.runners_token)
+ expect(runner).to be_group_type
end
context 'when it exceeds the application limits' do
@@ -187,9 +211,13 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
end
it 'does not create runner' do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.persisted?).to be_falsey
- expect(subject.errors.messages).to eq('runner_namespaces.base': ['Maximum number of ci registered group runners (1) exceeded'])
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.persisted?).to be_falsey
+ expect(runner.errors.messages).to eq(
+ 'runner_namespaces.base': ['Maximum number of ci registered group runners (1) exceeded']
+ )
expect(group.runners.reload.size).to eq(1)
end
end
@@ -202,8 +230,10 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
end
it 'creates runner' do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.errors).to be_empty
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.errors).to be_empty
expect(group.runners.reload.size).to eq(3)
expect(group.runners.recent.size).to eq(1)
end
@@ -219,16 +249,18 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do
stub_feature_flags(runner_registration_control: true)
end
- it 'returns nil' do
- is_expected.to be_nil
+ it 'returns error response' do
+ is_expected.to be_error
end
end
context 'when feature flag is disabled' do
it 'registers the runner' do
- is_expected.to be_an_instance_of(::Ci::Runner)
- expect(subject.errors).to be_empty
- expect(subject.active).to be true
+ expect(execute).to be_success
+
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.errors).to be_empty
+ expect(runner.active).to be true
end
end
end
diff --git a/spec/services/ci/runners/reset_registration_token_service_spec.rb b/spec/services/ci/runners/reset_registration_token_service_spec.rb
index c4bfff51cc8..79059712032 100644
--- a/spec/services/ci/runners/reset_registration_token_service_spec.rb
+++ b/spec/services/ci/runners/reset_registration_token_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe ::Ci::Runners::ResetRegistrationTokenService, '#execute' do
- subject { described_class.new(scope, current_user).execute }
+ subject(:execute) { described_class.new(scope, current_user).execute }
let_it_be(:user) { build(:user) }
let_it_be(:admin_user) { create(:user, :admin) }
@@ -12,20 +12,20 @@ RSpec.describe ::Ci::Runners::ResetRegistrationTokenService, '#execute' do
context 'without user' do
let(:current_user) { nil }
- it 'does not reset registration token and returns nil' do
+ it 'does not reset registration token and returns error response' do
expect(scope).not_to receive(token_reset_method_name)
- is_expected.to be_nil
+ expect(execute).to be_error
end
end
context 'with unauthorized user' do
let(:current_user) { user }
- it 'does not reset registration token and returns nil' do
+ it 'does not reset registration token and returns error response' do
expect(scope).not_to receive(token_reset_method_name)
- is_expected.to be_nil
+ expect(execute).to be_error
end
end
@@ -37,7 +37,8 @@ RSpec.describe ::Ci::Runners::ResetRegistrationTokenService, '#execute' do
expect(scope).to receive(token_method_name).once.and_return("#{token_method_name} return value")
end
- is_expected.to eq("#{token_method_name} return value")
+ expect(execute).to be_success
+ expect(execute.payload[:new_registration_token]).to eq("#{token_method_name} return value")
end
end
end
diff --git a/spec/services/ci/runners/unassign_runner_service_spec.rb b/spec/services/ci/runners/unassign_runner_service_spec.rb
index 3fb6925f4bd..cf710cf6893 100644
--- a/spec/services/ci/runners/unassign_runner_service_spec.rb
+++ b/spec/services/ci/runners/unassign_runner_service_spec.rb
@@ -3,21 +3,21 @@
require 'spec_helper'
RSpec.describe ::Ci::Runners::UnassignRunnerService, '#execute' do
- subject(:service) { described_class.new(runner_project, user).execute }
-
- let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
let_it_be(:project) { create(:project) }
+ let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:runner_project) { runner.runner_projects.last }
+ subject(:execute) { described_class.new(runner_project, user).execute }
+
context 'without user' do
let(:user) { nil }
it 'does not destroy runner_project', :aggregate_failures do
expect(runner_project).not_to receive(:destroy)
- expect { service }.not_to change { runner.runner_projects.count }.from(1)
+ expect { execute }.not_to change { runner.runner_projects.count }.from(1)
- is_expected.to eq(false)
+ is_expected.to be_error
end
end
@@ -27,17 +27,27 @@ RSpec.describe ::Ci::Runners::UnassignRunnerService, '#execute' do
it 'does not call destroy on runner_project' do
expect(runner).not_to receive(:destroy)
- service
+ is_expected.to be_error
end
end
context 'with admin user', :enable_admin_mode do
let(:user) { create_default(:user, :admin) }
- it 'destroys runner_project' do
- expect(runner_project).to receive(:destroy).once
+ context 'with destroy returning false' do
+ it 'returns error response' do
+ expect(runner_project).to receive(:destroy).once.and_return(false)
+
+ is_expected.to be_error
+ end
+ end
+
+ context 'with destroy returning true' do
+ it 'returns success response' do
+ expect(runner_project).to receive(:destroy).once.and_return(true)
- service
+ is_expected.to be_success
+ end
end
end
end
diff --git a/spec/services/ci/runners/unregister_runner_service_spec.rb b/spec/services/ci/runners/unregister_runner_service_spec.rb
index df1a0a90067..77fc299e4e1 100644
--- a/spec/services/ci/runners/unregister_runner_service_spec.rb
+++ b/spec/services/ci/runners/unregister_runner_service_spec.rb
@@ -3,13 +3,16 @@
require 'spec_helper'
RSpec.describe ::Ci::Runners::UnregisterRunnerService, '#execute' do
- subject { described_class.new(runner, 'some_token').execute }
+ subject(:execute) { described_class.new(runner, 'some_token').execute }
let(:runner) { create(:ci_runner) }
it 'destroys runner' do
expect(runner).to receive(:destroy).once.and_call_original
- expect { subject }.to change { Ci::Runner.count }.by(-1)
+
+ expect do
+ expect(execute).to be_success
+ end.to change { Ci::Runner.count }.by(-1)
expect(runner[:errors]).to be_nil
end
end
diff --git a/spec/services/ci/runners/update_runner_service_spec.rb b/spec/services/ci/runners/update_runner_service_spec.rb
index b02ea8f58b0..e008fde9982 100644
--- a/spec/services/ci/runners/update_runner_service_spec.rb
+++ b/spec/services/ci/runners/update_runner_service_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Ci::Runners::UpdateRunnerService do
end
context 'with cost factor params' do
- let(:params) { { public_projects_minutes_cost_factor: 1.1, private_projects_minutes_cost_factor: 2.2 }}
+ let(:params) { { public_projects_minutes_cost_factor: 1.1, private_projects_minutes_cost_factor: 2.2 } }
it 'updates the runner cost factors' do
expect(update).to be_truthy
diff --git a/spec/services/ci/stuck_builds/drop_pending_service_spec.rb b/spec/services/ci/stuck_builds/drop_pending_service_spec.rb
index ebc57af77a0..a452a65829a 100644
--- a/spec/services/ci/stuck_builds/drop_pending_service_spec.rb
+++ b/spec/services/ci/stuck_builds/drop_pending_service_spec.rb
@@ -9,8 +9,8 @@ RSpec.describe Ci::StuckBuilds::DropPendingService do
create(:ci_build, pipeline: pipeline, runner: runner)
end
- let(:created_at) { }
- let(:updated_at) { }
+ let(:created_at) {}
+ let(:updated_at) {}
subject(:service) { described_class.new }
diff --git a/spec/services/ci/stuck_builds/drop_scheduled_service_spec.rb b/spec/services/ci/stuck_builds/drop_scheduled_service_spec.rb
index 1416fab3d25..a4f9f97fffc 100644
--- a/spec/services/ci/stuck_builds/drop_scheduled_service_spec.rb
+++ b/spec/services/ci/stuck_builds/drop_scheduled_service_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe Ci::StuckBuilds::DropScheduledService do
end
context 'when there are no stale scheduled builds' do
- let(:job) { }
+ let(:job) {}
it 'does not drop the stale scheduled build yet' do
expect { service.execute }.not_to raise_error
diff --git a/spec/services/ci/track_failed_build_service_spec.rb b/spec/services/ci/track_failed_build_service_spec.rb
new file mode 100644
index 00000000000..d83e56f0669
--- /dev/null
+++ b/spec/services/ci/track_failed_build_service_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::TrackFailedBuildService do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) }
+
+ let_it_be(:exit_code) { 42 }
+ let_it_be(:failure_reason) { "script_failure" }
+
+ describe '#execute' do
+ context 'when a build has failed' do
+ let_it_be(:build) { create(:ci_build, :failed, :sast_report, pipeline: pipeline, user: user) }
+
+ subject { described_class.new(build: build, exit_code: exit_code, failure_reason: failure_reason) }
+
+ it 'tracks the build failed event', :snowplow do
+ response = subject.execute
+
+ expect(response.success?).to be true
+
+ expect_snowplow_event(
+ category: 'ci::build',
+ action: 'failed',
+ context: [{
+ schema: described_class::SCHEMA_URL,
+ data: {
+ build_id: build.id,
+ build_name: build.name,
+ build_artifact_types: ["sast"],
+ exit_code: exit_code,
+ failure_reason: failure_reason
+ }
+ }],
+ user: user,
+ project: project.id)
+ end
+ end
+
+ context 'when a build has not failed' do
+ let_it_be(:build) { create(:ci_build, :success, :sast_report, pipeline: pipeline, user: user) }
+
+ subject { described_class.new(build: build, exit_code: nil, failure_reason: nil) }
+
+ it 'does not track the build failed event', :snowplow do
+ response = subject.execute
+
+ expect(response.error?).to be true
+
+ expect_no_snowplow_event
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/update_build_state_service_spec.rb b/spec/services/ci/update_build_state_service_spec.rb
index 937b19beff5..90a86e7ae59 100644
--- a/spec/services/ci/update_build_state_service_spec.rb
+++ b/spec/services/ci/update_build_state_service_spec.rb
@@ -33,6 +33,24 @@ RSpec.describe Ci::UpdateBuildStateService do
end
end
+ context 'when build has failed' do
+ let(:params) do
+ {
+ output: { checksum: 'crc32:12345678', bytesize: 123 },
+ state: 'failed',
+ failure_reason: 'script_failure',
+ exit_code: 7
+ }
+ end
+
+ it 'sends a build failed event to Snowplow' do
+ expect(::Ci::TrackFailedBuildWorker)
+ .to receive(:perform_async).with(build.id, params[:exit_code], params[:failure_reason])
+
+ subject.execute
+ end
+ end
+
context 'when build does not have checksum' do
context 'when state has changed' do
let(:params) { { state: 'success' } }