diff options
Diffstat (limited to 'spec/services/ci')
-rw-r--r-- | spec/services/ci/after_requeue_job_service_spec.rb | 23 | ||||
-rw-r--r-- | spec/services/ci/create_pipeline_service/rate_limit_spec.rb | 91 | ||||
-rw-r--r-- | spec/services/ci/create_pipeline_service_spec.rb | 14 | ||||
-rw-r--r-- | spec/services/ci/create_web_ide_terminal_service_spec.rb | 2 | ||||
-rw-r--r-- | spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb | 46 | ||||
-rw-r--r-- | spec/services/ci/job_artifacts/destroy_batch_service_spec.rb | 12 | ||||
-rw-r--r-- | spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb | 145 | ||||
-rw-r--r-- | spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb | 6 | ||||
-rw-r--r-- | spec/services/ci/register_job_service_spec.rb | 14 | ||||
-rw-r--r-- | spec/services/ci/retry_job_service_spec.rb (renamed from spec/services/ci/retry_build_service_spec.rb) | 32 | ||||
-rw-r--r-- | spec/services/ci/retry_pipeline_service_spec.rb | 2 |
11 files changed, 329 insertions, 58 deletions
diff --git a/spec/services/ci/after_requeue_job_service_spec.rb b/spec/services/ci/after_requeue_job_service_spec.rb index 2f2baa15945..c9bd44f78e2 100644 --- a/spec/services/ci/after_requeue_job_service_spec.rb +++ b/spec/services/ci/after_requeue_job_service_spec.rb @@ -85,7 +85,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do c2: 'skipped' ) - new_a1 = Ci::RetryBuildService.new(project, user).clone!(a1) + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) new_a1.enqueue! check_jobs_statuses( a1: 'pending', @@ -172,7 +172,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do c2: 'skipped' ) - new_a1 = Ci::RetryBuildService.new(project, user).clone!(a1) + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) new_a1.enqueue! check_jobs_statuses( a1: 'pending', @@ -196,25 +196,6 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do c2: 'created' ) end - - context 'when the FF ci_fix_order_of_subsequent_jobs is disabled' do - before do - stub_feature_flags(ci_fix_order_of_subsequent_jobs: false) - end - - it 'does not mark b1 as processable' do - execute_after_requeue_service(a1) - - check_jobs_statuses( - a1: 'pending', - a2: 'created', - b1: 'skipped', - b2: 'created', - c1: 'created', - c2: 'created' - ) - end - end end private diff --git a/spec/services/ci/create_pipeline_service/rate_limit_spec.rb b/spec/services/ci/create_pipeline_service/rate_limit_spec.rb new file mode 100644 index 00000000000..caea165cc6c --- /dev/null +++ b/spec/services/ci/create_pipeline_service/rate_limit_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Ci::CreatePipelineService, :freeze_time, :clean_gitlab_redis_rate_limiting do + describe 'rate limiting' do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.first_owner } + + let(:ref) { 'refs/heads/master' } + + before do + stub_ci_pipeline_yaml_file(gitlab_ci_yaml) + stub_feature_flags(ci_throttle_pipelines_creation_dry_run: false) + + allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits) + .and_return(pipelines_create: { threshold: 1, interval: 1.minute }) + end + + context 'when user is under the limit' do + let(:pipeline) { create_pipelines(count: 1) } + + it 'allows pipeline creation' do + expect(pipeline).to be_created_successfully + expect(pipeline.statuses).not_to be_empty + end + end + + context 'when user is over the limit' do + let(:pipeline) { create_pipelines } + + it 'blocks pipeline creation' do + throttle_message = 'Too many pipelines created in the last minute. Try again later.' + + expect(pipeline).not_to be_persisted + expect(pipeline.statuses).to be_empty + expect(pipeline.errors.added?(:base, throttle_message)).to be_truthy + end + end + + context 'with different users' do + let(:other_user) { create(:user) } + + before do + project.add_maintainer(other_user) + end + + it 'allows other members to create pipelines' do + blocked_pipeline = create_pipelines(user: user) + allowed_pipeline = create_pipelines(count: 1, user: other_user) + + expect(blocked_pipeline).not_to be_persisted + expect(allowed_pipeline).to be_created_successfully + end + end + + context 'with different commits' do + it 'allows user to create pipeline' do + blocked_pipeline = create_pipelines(ref: ref) + allowed_pipeline = create_pipelines(count: 1, ref: 'refs/heads/feature') + + expect(blocked_pipeline).not_to be_persisted + expect(allowed_pipeline).to be_created_successfully + end + end + + context 'with different projects' do + let_it_be(:other_project) { create(:project, :repository) } + + before do + other_project.add_maintainer(user) + end + + it 'allows user to create pipeline' do + blocked_pipeline = create_pipelines(project: project) + allowed_pipeline = create_pipelines(count: 1, project: other_project) + + expect(blocked_pipeline).not_to be_persisted + expect(allowed_pipeline).to be_created_successfully + end + end + end + + def create_pipelines(attrs = {}) + attrs.reverse_merge!(user: user, ref: ref, project: project, count: 2) + + service = described_class.new(attrs[:project], attrs[:user], { ref: attrs[:ref] }) + + attrs[:count].pred.times { service.execute(:push) } + service.execute(:push).payload + end +end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index a7026f5062e..943d70ba142 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -12,6 +12,10 @@ RSpec.describe Ci::CreatePipelineService do before do stub_ci_pipeline_to_return_yaml_file + + # Disable rate limiting for pipeline creation + allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits) + .and_return(pipelines_create: { threshold: 0, interval: 1.minute }) end describe '#execute' do @@ -526,7 +530,7 @@ RSpec.describe Ci::CreatePipelineService do let(:ci_yaml) do <<-EOS image: - name: ruby:2.7 + name: image:1.0 ports: - 80 EOS @@ -538,12 +542,12 @@ RSpec.describe Ci::CreatePipelineService do context 'in the job image' do let(:ci_yaml) do <<-EOS - image: ruby:2.7 + image: image:1.0 test: script: rspec image: - name: ruby:2.7 + name: image:1.0 ports: - 80 EOS @@ -555,11 +559,11 @@ RSpec.describe Ci::CreatePipelineService do context 'in the service' do let(:ci_yaml) do <<-EOS - image: ruby:2.7 + image: image:1.0 test: script: rspec - image: ruby:2.7 + image: image:1.0 services: - name: test ports: diff --git a/spec/services/ci/create_web_ide_terminal_service_spec.rb b/spec/services/ci/create_web_ide_terminal_service_spec.rb index 0804773442d..3462b48cfe7 100644 --- a/spec/services/ci/create_web_ide_terminal_service_spec.rb +++ b/spec/services/ci/create_web_ide_terminal_service_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Ci::CreateWebIdeTerminalService do <<-EOS terminal: image: - name: ruby:2.7 + name: image:1.0 ports: - 80 script: rspec diff --git a/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb b/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb index e95a449d615..1c6963e4a31 100644 --- a/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb +++ b/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb @@ -19,8 +19,23 @@ RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_s let!(:artifact) { create(:ci_job_artifact, :expired, job: job, locked: job.pipeline.locked) } context 'with preloaded relationships' do + let(:second_artifact) { create(:ci_job_artifact, :expired, :junit, job: job) } + + let(:more_artifacts) do + [ + create(:ci_job_artifact, :expired, :sast, job: job), + create(:ci_job_artifact, :expired, :metadata, job: job), + create(:ci_job_artifact, :expired, :codequality, job: job), + create(:ci_job_artifact, :expired, :accessibility, job: job) + ] + end + before do - stub_const("#{described_class}::LARGE_LOOP_LIMIT", 1) + stub_const("#{described_class}::LOOP_LIMIT", 1) + + # This artifact-with-file is created before the control execution to ensure + # that the DeletedObject operations are accounted for in the query count. + second_artifact end context 'with ci_destroy_unlocked_job_artifacts feature flag disabled' do @@ -28,19 +43,12 @@ RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_s stub_feature_flags(ci_destroy_unlocked_job_artifacts: false) end - it 'performs the smallest number of queries for job_artifacts' do - log = ActiveRecord::QueryRecorder.new { subject } + it 'performs a consistent number of queries' do + control = ActiveRecord::QueryRecorder.new { service.execute } - # SELECT expired ci_job_artifacts - 3 queries from each_batch - # PRELOAD projects, routes, project_statistics - # BEGIN - # INSERT into ci_deleted_objects - # DELETE loaded ci_job_artifacts - # DELETE security_findings -- for EE - # COMMIT - # SELECT next expired ci_job_artifacts + more_artifacts - expect(log.count).to be_within(1).of(10) + expect { subject }.not_to exceed_query_limit(control.count) end end @@ -49,9 +57,12 @@ RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_s stub_feature_flags(ci_destroy_unlocked_job_artifacts: true) end - it 'performs the smallest number of queries for job_artifacts' do - log = ActiveRecord::QueryRecorder.new { subject } - expect(log.count).to be_within(1).of(8) + it 'performs a consistent number of queries' do + control = ActiveRecord::QueryRecorder.new { service.execute } + + more_artifacts + + expect { subject }.not_to exceed_query_limit(control.count) end end end @@ -119,7 +130,7 @@ RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_s let!(:artifact) { create(:ci_job_artifact, :expired, job: job, locked: job.pipeline.locked) } before do - stub_const("#{described_class}::LARGE_LOOP_LIMIT", 10) + stub_const("#{described_class}::LOOP_LIMIT", 10) end context 'when the import fails' do @@ -189,8 +200,7 @@ RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_s context 'when loop reached loop limit' do before do - stub_feature_flags(ci_artifact_fast_removal_large_loop_limit: false) - stub_const("#{described_class}::SMALL_LOOP_LIMIT", 1) + stub_const("#{described_class}::LOOP_LIMIT", 1) end it 'destroys one artifact' do 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 67d664a617b..5e77041a632 100644 --- a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb +++ b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::JobArtifacts::DestroyBatchService do - let(:artifacts) { Ci::JobArtifact.where(id: [artifact_with_file.id, artifact_without_file.id]) } + let(:artifacts) { Ci::JobArtifact.where(id: [artifact_with_file.id, artifact_without_file.id, trace_artifact.id]) } let(:service) { described_class.new(artifacts, pick_up_at: Time.current) } let_it_be(:artifact_with_file, refind: true) do @@ -18,6 +18,10 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do create(:ci_job_artifact) end + let_it_be(:trace_artifact, refind: true) do + create(:ci_job_artifact, :trace, :expired) + end + describe '.execute' do subject(:execute) { service.execute } @@ -42,6 +46,12 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do execute end + it 'preserves trace artifacts and removes any timestamp' do + expect { subject } + .to change { trace_artifact.reload.expire_at }.from(trace_artifact.expire_at).to(nil) + .and not_change { Ci::JobArtifact.exists?(trace_artifact.id) } + end + context 'ProjectStatistics' do it 'resets project statistics' do expect(ProjectStatistics).to receive(:increment_statistic).once diff --git a/spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb b/spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb new file mode 100644 index 00000000000..67412e41fb8 --- /dev/null +++ b/spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::JobArtifacts::UpdateUnknownLockedStatusService, :clean_gitlab_redis_shared_state do + include ExclusiveLeaseHelpers + + let(:service) { described_class.new } + + describe '.execute' do + subject { service.execute } + + let_it_be(:locked_pipeline) { create(:ci_pipeline, :artifacts_locked) } + let_it_be(:pipeline) { create(:ci_pipeline, :unlocked) } + let_it_be(:locked_job) { create(:ci_build, :success, pipeline: locked_pipeline) } + let_it_be(:job) { create(:ci_build, :success, pipeline: pipeline) } + + let!(:unknown_unlocked_artifact) do + create(:ci_job_artifact, :junit, expire_at: 1.hour.ago, job: job, locked: Ci::JobArtifact.lockeds[:unknown]) + end + + let!(:unknown_locked_artifact) do + create(:ci_job_artifact, :lsif, + expire_at: 1.day.ago, + job: locked_job, + locked: Ci::JobArtifact.lockeds[:unknown] + ) + end + + let!(:unlocked_artifact) do + create(:ci_job_artifact, :archive, expire_at: 1.hour.ago, job: job, locked: Ci::JobArtifact.lockeds[:unlocked]) + end + + let!(:locked_artifact) do + create(:ci_job_artifact, :sast, :raw, + expire_at: 1.day.ago, + job: locked_job, + locked: Ci::JobArtifact.lockeds[:artifacts_locked] + ) + end + + context 'when artifacts are expired' do + it 'sets artifact_locked when the pipeline is locked' do + expect { service.execute } + .to change { unknown_locked_artifact.reload.locked }.from('unknown').to('artifacts_locked') + .and not_change { Ci::JobArtifact.exists?(locked_artifact.id) } + end + + it 'destroys the artifact when the pipeline is unlocked' do + expect { subject }.to change { Ci::JobArtifact.exists?(unknown_unlocked_artifact.id) }.from(true).to(false) + end + + it 'does not update ci_job_artifact rows with known locked values' do + expect { service.execute } + .to not_change(locked_artifact, :attributes) + .and not_change { Ci::JobArtifact.exists?(locked_artifact.id) } + .and not_change(unlocked_artifact, :attributes) + .and not_change { Ci::JobArtifact.exists?(unlocked_artifact.id) } + end + + it 'logs the counts of affected artifacts' do + expect(subject).to eq({ removed: 1, locked: 1 }) + end + end + + context 'in a single iteration' do + before do + stub_const("#{described_class}::BATCH_SIZE", 1) + end + + context 'due to the LOOP_TIMEOUT' do + before do + stub_const("#{described_class}::LOOP_TIMEOUT", 0.seconds) + end + + it 'affects the earliest expired artifact first' do + subject + + expect(unknown_locked_artifact.reload.locked).to eq('artifacts_locked') + expect(unknown_unlocked_artifact.reload.locked).to eq('unknown') + end + + it 'reports the number of destroyed artifacts' do + is_expected.to eq({ removed: 0, locked: 1 }) + end + end + + context 'due to @loop_limit' do + before do + stub_const("#{described_class}::LARGE_LOOP_LIMIT", 1) + end + + it 'affects the most recently expired artifact first' do + subject + + expect(unknown_locked_artifact.reload.locked).to eq('artifacts_locked') + expect(unknown_unlocked_artifact.reload.locked).to eq('unknown') + end + + it 'reports the number of destroyed artifacts' do + is_expected.to eq({ removed: 0, locked: 1 }) + end + end + end + + context 'when artifact is not expired' do + let!(:unknown_unlocked_artifact) do + create(:ci_job_artifact, :junit, + expire_at: 1.year.from_now, + job: job, + locked: Ci::JobArtifact.lockeds[:unknown] + ) + end + + it 'does not change the locked status' do + expect { service.execute }.not_to change { unknown_unlocked_artifact.locked } + expect(Ci::JobArtifact.exists?(unknown_unlocked_artifact.id)).to eq(true) + end + end + + context 'when exclusive lease has already been taken by the other instance' do + before do + stub_exclusive_lease_taken(described_class::EXCLUSIVE_LOCK_KEY, timeout: described_class::LOCK_TIMEOUT) + end + + it 'raises an error and' do + expect { subject }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError) + end + end + + context 'when there are no unknown status artifacts' do + before do + Ci::JobArtifact.update_all(locked: :unlocked) + end + + it 'does not raise error' do + expect { subject }.not_to raise_error + end + + it 'reports the number of destroyed artifacts' do + is_expected.to eq({ removed: 0, locked: 0 }) + end + end + end +end diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb index 7365ad162d2..5bc508447c1 100644 --- a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb +++ b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb @@ -725,7 +725,7 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService do expect(builds_names).to eq ['build:1', 'build:2', 'test:1', 'test:2'] - Ci::Build.retry(pipeline.builds.find_by(name: 'test:2'), user).reset.success! + Ci::RetryJobService.new(pipeline.project, user).execute(pipeline.builds.find_by(name: 'test:2'))[:job].reset.success! expect(builds_names).to eq ['build:1', 'build:2', 'test:1', 'test:2', 'test:2', 'deploy:1', 'deploy:2'] @@ -1111,11 +1111,11 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService do end def enqueue_scheduled(name) - builds.scheduled.find_by(name: name).enqueue_scheduled + builds.scheduled.find_by(name: name).enqueue! end def retry_build(name) - Ci::Build.retry(builds.find_by(name: name), user) + Ci::RetryJobService.new(project, user).execute(builds.find_by(name: name)) end def manual_actions diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index 245118e71fa..74adbc4efc8 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -103,6 +103,20 @@ module Ci pending_job.create_queuing_entry! end + context 'when build owner has been blocked' do + let(:user) { create(:user, :blocked) } + + before do + pending_job.update!(user: user) + end + + it 'does not pick the build and drops the build' do + expect(execute(shared_runner)).to be_falsey + + expect(pending_job.reload).to be_user_blocked + end + end + context 'for multiple builds' do let!(:project2) { create :project, shared_runners_enabled: true } let!(:pipeline2) { create :ci_pipeline, project: project2 } diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_job_service_spec.rb index 2421fd56c47..25aab73ab01 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_job_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::RetryBuildService do +RSpec.describe Ci::RetryJobService do let_it_be(:reporter) { create(:user) } let_it_be(:developer) { create(:user) } let_it_be(:project) { create(:project, :repository) } @@ -17,7 +17,7 @@ RSpec.describe Ci::RetryBuildService do name: 'test') end - let_it_be_with_refind(:build) { create(:ci_build, pipeline: pipeline, stage_id: stage.id) } + let_it_be_with_refind(:build) { create(:ci_build, :success, pipeline: pipeline, stage_id: stage.id) } let(:user) { developer } @@ -30,7 +30,7 @@ RSpec.describe Ci::RetryBuildService do project.add_reporter(reporter) end - clone_accessors = described_class.clone_accessors.without(described_class.extra_accessors) + clone_accessors = ::Ci::Build.clone_accessors.without(::Ci::Build.extra_accessors) reject_accessors = %i[id status user token token_encrypted coverage trace runner @@ -94,6 +94,10 @@ RSpec.describe Ci::RetryBuildService do create(:terraform_state_version, build: build) end + before do + build.update!(retried: false, status: :success) + end + describe 'clone accessors' do let(:forbidden_associations) do Ci::Build.reflect_on_all_associations.each_with_object(Set.new) do |assoc, memo| @@ -156,8 +160,8 @@ RSpec.describe Ci::RetryBuildService do Ci::Build.attribute_aliases.keys.map(&:to_sym) + Ci::Build.reflect_on_all_associations.map(&:name) + [:tag_list, :needs_attributes, :job_variables_attributes] - - # ee-specific accessors should be tested in ee/spec/services/ci/retry_build_service_spec.rb instead - described_class.extra_accessors - + # ee-specific accessors should be tested in ee/spec/services/ci/retry_job_service_spec.rb instead + Ci::Build.extra_accessors - [:dast_site_profiles_build, :dast_scanner_profiles_build] # join tables current_accessors.uniq! @@ -170,7 +174,7 @@ RSpec.describe Ci::RetryBuildService do describe '#execute' do let(:new_build) do travel_to(1.second.from_now) do - service.execute(build) + service.execute(build)[:job] end end @@ -248,7 +252,7 @@ RSpec.describe Ci::RetryBuildService do context 'when build has scheduling_type' do it 'does not call populate_scheduling_type!' do - expect_any_instance_of(Ci::Pipeline).not_to receive(:ensure_scheduling_type!) + expect_any_instance_of(Ci::Pipeline).not_to receive(:ensure_scheduling_type!) # rubocop: disable RSpec/AnyInstanceOf expect(new_build.scheduling_type).to eq('stage') end @@ -286,6 +290,18 @@ RSpec.describe Ci::RetryBuildService do expect { service.execute(build) } .to raise_error Gitlab::Access::AccessDeniedError end + + context 'when the job is not retryable' do + let(:build) { create(:ci_build, :created, pipeline: pipeline) } + + it 'returns a ServiceResponse error' do + response = service.execute(build) + + expect(response).to be_a(ServiceResponse) + expect(response).to be_error + expect(response.message).to eq("Job cannot be retried") + end + end end end @@ -342,7 +358,7 @@ RSpec.describe Ci::RetryBuildService do end shared_examples_for 'when build with dynamic environment is retried' do - let_it_be(:other_developer) { create(:user).tap { |u| project.add_developer(other_developer) } } + let_it_be(:other_developer) { create(:user).tap { |u| project.add_developer(u) } } let(:environment_name) { 'review/$CI_COMMIT_REF_SLUG-$GITLAB_USER_ID' } diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index df1e159b5c0..24272801480 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -340,7 +340,7 @@ RSpec.describe Ci::RetryPipelineService, '#execute' do context 'when user is not allowed to retry build' do before do build = create(:ci_build, pipeline: pipeline, status: :failed) - allow_next_instance_of(Ci::RetryBuildService) do |service| + allow_next_instance_of(Ci::RetryJobService) do |service| allow(service).to receive(:can?).with(user, :update_build, build).and_return(false) end end |