diff options
Diffstat (limited to 'spec')
31 files changed, 890 insertions, 175 deletions
diff --git a/spec/factories/ci/ref.rb b/spec/factories/ci/ref.rb new file mode 100644 index 00000000000..891d8848a72 --- /dev/null +++ b/spec/factories/ci/ref.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :ci_ref, class: 'Ci::Ref' do + ref { 'master' } + status { :success } + tag { false } + project + + before(:create) do |ref, evaluator| + next if ref.pipelines.exists? + + ref.update!(last_updated_by_pipeline: create(:ci_pipeline, project: evaluator.project, ref: evaluator.ref, tag: evaluator.tag, status: evaluator.status)) + end + end +end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 1c72c54f0a1..561c0552007 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -356,7 +356,7 @@ describe 'Pipeline', :js do end end - context 'test tabs' do + describe 'test tabs' do let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project) } before do @@ -364,21 +364,31 @@ describe 'Pipeline', :js do wait_for_requests end - it 'shows badge counter in Tests tab' do - expect(pipeline.test_reports.total_count).to eq(4) - expect(page.find('.js-test-report-badge-counter').text).to eq(pipeline.test_reports.total_count.to_s) - end + context 'with test reports' do + it 'shows badge counter in Tests tab' do + expect(pipeline.test_reports.total_count).to eq(4) + expect(page.find('.js-test-report-badge-counter').text).to eq(pipeline.test_reports.total_count.to_s) + end + + it 'does not call test_report.json endpoint by default', :js do + expect(page).to have_selector('.js-no-tests-to-show', visible: :all) + end - it 'does not call test_report.json endpoint by default', :js do - expect(page).to have_selector('.js-no-tests-to-show', visible: :all) + it 'does call test_report.json endpoint when tab is selected', :js do + find('.js-tests-tab-link').click + wait_for_requests + + expect(page).to have_content('Test suites') + expect(page).to have_selector('.js-tests-detail', visible: :all) + end end - it 'does call test_report.json endpoint when tab is selected', :js do - find('.js-tests-tab-link').click - wait_for_requests + context 'without test reports' do + let(:pipeline) { create(:ci_pipeline, project: project) } - expect(page).to have_content('Test suites') - expect(page).to have_selector('.js-tests-detail', visible: :all) + it 'shows nothing' do + expect(page.find('.js-test-report-badge-counter', visible: :all).text).to eq("") + end end end diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/frontend/diffs/components/compare_versions_dropdown_spec.js index e0686901483..5033bdd9044 100644 --- a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js +++ b/spec/frontend/diffs/components/compare_versions_dropdown_spec.js @@ -2,6 +2,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import CompareVersionsDropdown from '~/diffs/components/compare_versions_dropdown.vue'; import diffsMockData from '../mock_data/merge_request_diffs'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import { TEST_HOST } from 'helpers/test_constants'; const localVue = createLocalVue(); const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 }; @@ -109,6 +110,24 @@ describe('CompareVersionsDropdown', () => { expect(findLastLink().attributes('href')).toEqual(baseVersionPath); expect(findLastLink().text()).toContain('(base)'); + expect(findLastLink().text()).not.toContain('(HEAD)'); + }); + + it('should render a correct head version link', () => { + Object.defineProperty(window, 'location', { + writable: true, + value: { href: `${TEST_HOST}?diff_head=true` }, + }); + + createComponent({ + baseVersionPath, + otherVersions: diffsMockData.slice(1), + targetBranch, + }); + + expect(findLastLink().attributes('href')).toEqual(baseVersionPath); + expect(findLastLink().text()).not.toContain('(base)'); + expect(findLastLink().text()).toContain('(HEAD)'); }); it('should not render commits count if no showCommitsCount is passed', () => { diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb index 2384c87b377..d8dcce203fe 100644 --- a/spec/helpers/notifications_helper_spec.rb +++ b/spec/helpers/notifications_helper_spec.rb @@ -21,6 +21,7 @@ describe NotificationsHelper do describe '#notification_event_name' do it { expect(notification_event_name(:success_pipeline)).to match('Successful pipeline') } it { expect(notification_event_name(:failed_pipeline)).to match('Failed pipeline') } + it { expect(notification_event_name(:fixed_pipeline)).to match('Fixed pipeline') } end describe '#notification_icon_level' do diff --git a/spec/lib/gitlab/checks/snippet_check_spec.rb b/spec/lib/gitlab/checks/snippet_check_spec.rb index 7cb29debd1e..7cfcde7183f 100644 --- a/spec/lib/gitlab/checks/snippet_check_spec.rb +++ b/spec/lib/gitlab/checks/snippet_check_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Checks::SnippetCheck do let(:newrev) { '0000000000000000000000000000000000000000' } it 'raises an error' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can not create or delete branches.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.') end end @@ -27,7 +27,7 @@ describe Gitlab::Checks::SnippetCheck do let(:oldrev) { '0000000000000000000000000000000000000000' } it 'raises an error' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can not create or delete branches.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.') end end end diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb index de19db38176..a68ac8ee8fe 100644 --- a/spec/lib/gitlab/git_access_snippet_spec.rb +++ b/spec/lib/gitlab/git_access_snippet_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::GitAccessSnippet do let(:actor) { build(:deploy_key) } it 'does not allow push and pull access' do - expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:authentication_mechanism]) + expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:authentication_mechanism]) end end @@ -76,8 +76,8 @@ describe Gitlab::GitAccessSnippet do it 'blocks access when the user did not accept terms' do message = /must accept the Terms of Service in order to perform this action/ - expect { push_access_check }.to raise_unauthorized(message) - expect { pull_access_check }.to raise_unauthorized(message) + expect { push_access_check }.to raise_forbidden(message) + expect { pull_access_check }.to raise_forbidden(message) end it 'allows access when the user accepted the terms' do @@ -101,13 +101,13 @@ describe Gitlab::GitAccessSnippet do if Ability.allowed?(user, :update_snippet, snippet) expect { push_access_check }.not_to raise_error else - expect { push_access_check }.to raise_error(described_class::UnauthorizedError) + expect { push_access_check }.to raise_error(described_class::ForbiddenError) end if Ability.allowed?(user, :read_snippet, snippet) expect { pull_access_check }.not_to raise_error else - expect { pull_access_check }.to raise_error(described_class::UnauthorizedError) + expect { pull_access_check }.to raise_error(described_class::ForbiddenError) end end end @@ -154,7 +154,7 @@ describe Gitlab::GitAccessSnippet do with_them do it "respects accessibility" do - error_class = described_class::UnauthorizedError + error_class = described_class::ForbiddenError if Ability.allowed?(user, :update_snippet, snippet) expect { push_access_check }.not_to raise_error @@ -180,7 +180,7 @@ describe Gitlab::GitAccessSnippet do allow(::Gitlab::Database).to receive(:read_only?).and_return(true) allow(::Gitlab::Geo).to receive(:secondary_with_primary?).and_return(true) - expect { push_access_check }.to raise_unauthorized(/You can't push code to a read-only GitLab instance/) + expect { push_access_check }.to raise_forbidden(/You can't push code to a read-only GitLab instance/) end end @@ -198,10 +198,10 @@ describe Gitlab::GitAccessSnippet do it 'raises error if SnippetCheck raises error' do expect_next_instance_of(Gitlab::Checks::SnippetCheck) do |check| - allow(check).to receive(:exec).and_raise(Gitlab::GitAccess::UnauthorizedError, 'foo') + allow(check).to receive(:exec).and_raise(Gitlab::GitAccess::ForbiddenError, 'foo') end - expect { push_access_check }.to raise_unauthorized('foo') + expect { push_access_check }.to raise_forbidden('foo') end end @@ -215,7 +215,7 @@ describe Gitlab::GitAccessSnippet do raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found]) end - def raise_unauthorized(message) - raise_error(Gitlab::GitAccess::UnauthorizedError, message) + def raise_forbidden(message) + raise_error(Gitlab::GitAccess::ForbiddenError, message) end end diff --git a/spec/lib/gitlab/graphql/docs/renderer_spec.rb b/spec/lib/gitlab/graphql/docs/renderer_spec.rb new file mode 100644 index 00000000000..5ba70bb8f0a --- /dev/null +++ b/spec/lib/gitlab/graphql/docs/renderer_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Docs::Renderer do + describe '#contents' do + # Returns a Schema that uses the given `type` + def mock_schema(type) + query_type = Class.new(GraphQL::Schema::Object) do + graphql_name 'QueryType' + + field :foo, type, null: true + end + + GraphQL::Schema.define(query: query_type) + end + + let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/', 'default.md.haml') } + + subject(:contents) do + described_class.new( + mock_schema(type).graphql_definition, + output_dir: nil, + template: template + ).contents + end + + context 'A type with a field with a [Array] return type' do + let(:type) do + Class.new(GraphQL::Schema::Object) do + graphql_name 'ArrayTest' + + field :foo, [GraphQL::STRING_TYPE], null: false, description: 'A description' + end + end + + specify do + expectation = <<~DOC + ## ArrayTest + + | Name | Type | Description | + | --- | ---- | ---------- | + | `foo` | String! => Array | A description | + DOC + + is_expected.to include(expectation) + end + end + + context 'A type with fields defined in reverse alphabetical order' do + let(:type) do + Class.new(GraphQL::Schema::Object) do + graphql_name 'OrderingTest' + + field :foo, GraphQL::STRING_TYPE, null: false, description: 'A description of foo field' + field :bar, GraphQL::STRING_TYPE, null: false, description: 'A description of bar field' + end + end + + specify do + expectation = <<~DOC + ## OrderingTest + + | Name | Type | Description | + | --- | ---- | ---------- | + | `bar` | String! | A description of bar field | + | `foo` | String! | A description of foo field | + DOC + + is_expected.to include(expectation) + end + end + + context 'A type with a deprecated field' do + let(:type) do + Class.new(GraphQL::Schema::Object) do + graphql_name 'DeprecatedTest' + + field :foo, GraphQL::STRING_TYPE, null: false, deprecation_reason: 'This is deprecated', description: 'A description' + end + end + + specify do + expectation = <<~DOC + ## DeprecatedTest + + | Name | Type | Description | + | --- | ---- | ---------- | + | `foo` **{warning-solid}** | String! | **Deprecated:** This is deprecated | + DOC + + is_expected.to include(expectation) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index db45b9c42fd..d97d76cf35e 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -192,6 +192,7 @@ ci_pipelines: - environments - chat_data - source_pipeline +- ref_status - source_bridge - source_job - sourced_pipelines @@ -359,6 +360,7 @@ project: - ci_pipelines - all_pipelines - stages +- ci_refs - builds - runner_projects - runners diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index f5a6ea4d5b0..2dd45d18ee9 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -89,6 +89,17 @@ describe Gitlab::IncomingEmail do it 'does not match emails with extra bits' do expect(described_class.key_from_address('somereplies+somekey@example.com.someotherdomain.com')).to be nil end + + context 'when a custom wildcard address is used' do + let(:wildcard_address) { 'custom.address+%{key}@example.com' } + + it 'finds key if email matches address pattern' do + key = described_class.key_from_address( + 'custom.address+foo@example.com', wildcard_address: wildcard_address + ) + expect(key).to eq('foo') + end + end end context 'self.key_from_fallback_message_id' do diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb new file mode 100644 index 00000000000..b6e47afc7e8 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::Client, :clean_gitlab_redis_queues do + let(:worker_class) do + Class.new do + def self.name + 'TestDeduplicationWorker' + end + + include ApplicationWorker + + def perform(*args) + end + end + end + + before do + stub_const('TestDeduplicationWorker', worker_class) + end + + describe '#call' do + it 'adds a correct duplicate tag to the jobs', :aggregate_failures do + TestDeduplicationWorker.bulk_perform_async([['args1'], ['args2'], ['args1']]) + + job1, job2, job3 = TestDeduplicationWorker.jobs + + expect(job1['duplicate-of']).to be_nil + expect(job2['duplicate-of']).to be_nil + expect(job3['duplicate-of']).to eq(job1['jid']) + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb new file mode 100644 index 00000000000..2334439461e --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do + subject(:duplicate_job) do + described_class.new(job, queue) + end + + let(:job) { { 'class' => 'AuthorizedProjectsWorker', 'args' => [1], 'jid' => '123' } } + let(:queue) { 'authorized_projects' } + + let(:idempotency_key) do + hash = Digest::SHA256.hexdigest("#{job['class']}:#{job['args'].join('-')}") + "#{Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE}:duplicate:#{queue}:#{hash}" + end + + describe '#schedule' do + it 'calls schedule on the strategy' do + expect do |block| + expect_next_instance_of(Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting) do |strategy| + expect(strategy).to receive(:schedule).with(job, &block) + end + + duplicate_job.schedule(&block) + end.to yield_control + end + end + + describe '#perform' do + it 'calls perform on the strategy' do + expect do |block| + expect_next_instance_of(Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting) do |strategy| + expect(strategy).to receive(:perform).with(job, &block) + end + + duplicate_job.perform(&block) + end.to yield_control + end + end + + describe '#check!' do + context 'when there was no job in the queue yet' do + it { expect(duplicate_job.check!).to eq('123') } + + it "adds a key with ttl set to #{described_class::DUPLICATE_KEY_TTL}" do + expect { duplicate_job.check! } + .to change { read_idempotency_key_with_ttl(idempotency_key) } + .from([nil, -2]) + .to(['123', be_within(1).of(described_class::DUPLICATE_KEY_TTL)]) + end + end + + context 'when there was already a job with same arguments in the same queue' do + before do + set_idempotency_key(idempotency_key, 'existing-key') + end + + it { expect(duplicate_job.check!).to eq('existing-key') } + + it "does not change the existing key's TTL" do + expect { duplicate_job.check! } + .not_to change { read_idempotency_key_with_ttl(idempotency_key) } + .from(['existing-key', -1]) + end + + it 'sets the existing jid' do + duplicate_job.check! + + expect(duplicate_job.existing_jid).to eq('existing-key') + end + end + end + + describe '#delete!' do + context "when we didn't track the definition" do + it { expect { duplicate_job.delete! }.not_to raise_error } + end + + context 'when the key exists in redis' do + before do + set_idempotency_key(idempotency_key, 'existing-key') + end + + it 'removes the key from redis' do + expect { duplicate_job.delete! } + .to change { read_idempotency_key_with_ttl(idempotency_key) } + .from(['existing-key', -1]) + .to([nil, -2]) + end + end + end + + describe '#duplicate?' do + it "raises an error if the check wasn't performed" do + expect { duplicate_job.duplicate? }.to raise_error /Call `#check!` first/ + end + + it 'returns false if the existing jid equals the job jid' do + duplicate_job.check! + + expect(duplicate_job.duplicate?).to be(false) + end + + it 'returns false if the existing jid is different from the job jid' do + set_idempotency_key(idempotency_key, 'a different jid') + duplicate_job.check! + + expect(duplicate_job.duplicate?).to be(true) + end + end + + def set_idempotency_key(key, value = '1') + Sidekiq.redis { |r| r.set(key, value) } + end + + def read_idempotency_key_with_ttl(key) + Sidekiq.redis do |redis| + redis.pipelined do |p| + p.get(key) + p.ttl(key) + end + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb new file mode 100644 index 00000000000..0ea248fbcf1 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::Server, :clean_gitlab_redis_queues do + let(:worker_class) do + Class.new do + def self.name + 'TestDeduplicationWorker' + end + + include ApplicationWorker + + def perform(*args) + end + end + end + + before do + stub_const('TestDeduplicationWorker', worker_class) + end + + around do |example| + Sidekiq::Testing.inline! { example.run } + end + + before(:context) do + Sidekiq::Testing.server_middleware do |chain| + chain.add described_class + end + end + + after(:context) do + Sidekiq::Testing.server_middleware do |chain| + chain.remove described_class + end + end + + describe '#call' do + it 'removes the stored job from redis' do + bare_job = { 'class' => 'TestDeduplicationWorker', 'args' => ['hello'] } + job_definition = Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob.new(bare_job.dup, 'test_deduplication') + + expect(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob) + .to receive(:new).with(a_hash_including(bare_job), 'test_deduplication') + .and_return(job_definition).twice # once in client middleware + expect(job_definition).to receive(:delete!).and_call_original + + TestDeduplicationWorker.perform_async('hello') + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb new file mode 100644 index 00000000000..f40e829f9a5 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do + let(:fake_duplicate_job) do + instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob) + end + + subject(:strategy) { described_class.new(fake_duplicate_job) } + + describe '#schedule' do + it 'checks for duplicates before yielding' do + expect(fake_duplicate_job).to receive(:check!).ordered.and_return('a jid') + expect(fake_duplicate_job).to receive(:duplicate?).ordered.and_return(false) + expect { |b| strategy.schedule({}, &b) }.to yield_control + end + + it 'adds the jid of the existing job to the job hash' do + allow(fake_duplicate_job).to receive(:check!).and_return('the jid') + job_hash = {} + + expect(fake_duplicate_job).to receive(:duplicate?).and_return(true) + expect(fake_duplicate_job).to receive(:existing_jid).and_return('the jid') + + strategy.schedule(job_hash) {} + + expect(job_hash).to include('duplicate-of' => 'the jid') + end + end + + describe '#perform' do + it 'deletes the lock before executing' do + expect(fake_duplicate_job).to receive(:delete!).ordered + expect { |b| strategy.perform({}, &b) }.to yield_control + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb new file mode 100644 index 00000000000..6ecc2a3a5f8 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies do + describe '.for' do + it 'returns the right class for `until_executing`' do + expect(described_class.for(:until_executing)).to eq(described_class::UntilExecuting) + end + + it 'raises an UnknownStrategyError when passing an unknown key' do + expect { described_class.for(:unknown) }.to raise_error(described_class::UnknownStrategyError) + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb index 19242d25e27..2f325fd5052 100644 --- a/spec/lib/gitlab/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb @@ -46,7 +46,8 @@ describe Gitlab::SidekiqMiddleware do Gitlab::SidekiqMiddleware::MemoryKiller, Gitlab::SidekiqMiddleware::RequestStoreMiddleware, Gitlab::SidekiqMiddleware::WorkerContext::Server, - Gitlab::SidekiqMiddleware::AdminMode::Server + Gitlab::SidekiqMiddleware::AdminMode::Server, + Gitlab::SidekiqMiddleware::DuplicateJobs::Server ] end let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } @@ -117,7 +118,8 @@ describe Gitlab::SidekiqMiddleware do Gitlab::SidekiqMiddleware::ClientMetrics, Gitlab::SidekiqMiddleware::WorkerContext::Client, Labkit::Middleware::Sidekiq::Client, - Gitlab::SidekiqMiddleware::AdminMode::Client + Gitlab::SidekiqMiddleware::AdminMode::Client, + Gitlab::SidekiqMiddleware::DuplicateJobs::Client ] end diff --git a/spec/mailers/emails/pipelines_spec.rb b/spec/mailers/emails/pipelines_spec.rb index 9996bd9a6c4..cc901da98dc 100644 --- a/spec/mailers/emails/pipelines_spec.rb +++ b/spec/mailers/emails/pipelines_spec.rb @@ -106,4 +106,17 @@ describe Emails::Pipelines do let(:status_text) { 'Your pipeline has failed.' } end end + + describe '#pipeline_fixed_email' do + subject { Notify.pipeline_fixed_email(pipeline, pipeline.user.try(:email)) } + + let(:pipeline) { create(:ci_pipeline, project: project, ref: ref, sha: sha) } + let(:ref) { 'master' } + let(:sha) { project.commit(ref).sha } + + it_behaves_like 'correct pipeline information' do + let(:status) { 'been fixed' } + let(:status_text) { 'Your pipeline has been fixed!' } + end + end end diff --git a/spec/models/ci/ref_spec.rb b/spec/models/ci/ref_spec.rb new file mode 100644 index 00000000000..aa3b8cdbc3e --- /dev/null +++ b/spec/models/ci/ref_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::Ref do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:last_updated_by_pipeline) } + + it { is_expected.to validate_inclusion_of(:status).in_array(%w[success failed fixed]) } + it { is_expected.to validate_presence_of(:last_updated_by_pipeline) } +end diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb index f6a36dbb3fc..05aeafaa4d4 100644 --- a/spec/models/notification_recipient_spec.rb +++ b/spec/models/notification_recipient_spec.rb @@ -176,8 +176,20 @@ describe NotificationRecipient do ) end - before do - notification_setting.update!(failed_pipeline: true) + it 'returns true' do + expect(recipient.suitable_notification_level?).to eq true + end + end + + context "when action is fixed_pipeline" do + let(:recipient) do + described_class.new( + user, + :watch, + custom_action: :fixed_pipeline, + target: target, + project: project + ) end it 'returns true' do @@ -185,7 +197,7 @@ describe NotificationRecipient do end end - context "when action is not failed_pipeline" do + context "when action is not fixed_pipeline or failed_pipeline" do let(:recipient) do described_class.new( user, @@ -196,10 +208,6 @@ describe NotificationRecipient do ) end - before do - notification_setting.update!(success_pipeline: true) - end - it 'returns false' do expect(recipient.suitable_notification_level?).to eq false end @@ -309,6 +317,26 @@ describe NotificationRecipient do expect(recipient.suitable_notification_level?).to eq false end end + + context 'when custom_action is fixed_pipeline and success_pipeline event is enabled' do + let(:recipient) do + described_class.new( + user, + :watch, + custom_action: :fixed_pipeline, + target: target, + project: project + ) + end + + before do + notification_setting.update!(success_pipeline: true) + end + + it 'returns true' do + expect(recipient.suitable_notification_level?).to eq true + end + end end end diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 094c60e3e09..9ab9ae494ec 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -110,7 +110,8 @@ RSpec.describe NotificationSetting do :reassign_merge_request, :merge_merge_request, :failed_pipeline, - :success_pipeline + :success_pipeline, + :fixed_pipeline ) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9b1c724f0c2..e7deae38b46 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -72,6 +72,7 @@ describe Project do it { is_expected.to have_one(:project_setting) } it { is_expected.to have_many(:commit_statuses) } it { is_expected.to have_many(:ci_pipelines) } + it { is_expected.to have_many(:ci_refs) } it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:build_trace_section_names)} it { is_expected.to have_many(:runner_projects) } diff --git a/spec/services/ci/update_ci_ref_status_service_spec.rb b/spec/services/ci/update_ci_ref_status_service_spec.rb new file mode 100644 index 00000000000..2b069452a55 --- /dev/null +++ b/spec/services/ci/update_ci_ref_status_service_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::UpdateCiRefStatusService do + describe '#call' do + subject { described_class.new(pipeline) } + + shared_examples 'creates ci_ref' do + it 'creates a ci_ref with the pipeline attributes' do + expect do + expect(subject.call).to eq(true) + end.to change { Ci::Ref.count }.by(1) + + created_ref = pipeline.reload.ref_status + %w[ref tag project status].each do |attr| + expect(created_ref[attr]).to eq(pipeline[attr]) + end + end + + it 'calls PipelineNotificationWorker pasing the ref_status' do + expect(PipelineNotificationWorker).to receive(:perform_async).with(pipeline.id, ref_status: pipeline.status) + + subject.call + end + end + + shared_examples 'updates ci_ref' do + where(:ref_status, :pipeline_status, :next_status) do + [ + %w[failed success fixed], + %w[failed failed failed], + %w[success success success], + %w[success failed failed] + ] + end + + with_them do + let(:ci_ref) { create(:ci_ref, status: ref_status) } + let(:pipeline) { create(:ci_pipeline, status: pipeline_status, project: ci_ref.project, ref: ci_ref.ref) } + + it 'sets ci_ref.status to next_status' do + expect do + expect(subject.call).to eq(true) + expect(ci_ref.reload.status).to eq(next_status) + end.not_to change { Ci::Ref.count } + end + + it 'calls PipelineNotificationWorker pasing the ref_status' do + expect(PipelineNotificationWorker).to receive(:perform_async).with(pipeline.id, ref_status: next_status) + + subject.call + end + end + end + + shared_examples 'does a noop' do + it "doesn't change ci_ref" do + expect do + expect do + expect(subject.call).to eq(false) + end.not_to change { ci_ref.reload.status } + end.not_to change { Ci::Ref.count } + end + + it "doesn't call PipelineNotificationWorker" do + expect(PipelineNotificationWorker).not_to receive(:perform_async) + + subject.call + end + end + + context "ci_ref doesn't exists" do + let(:pipeline) { create(:ci_pipeline, :success, ref: 'new-ref') } + + it_behaves_like 'creates ci_ref' + + context 'when an ActiveRecord::RecordNotUnique validation is raised' do + let(:ci_ref) { create(:ci_ref, status: 'failed') } + let(:pipeline) { create(:ci_pipeline, status: :success, project: ci_ref.project, ref: ci_ref.ref) } + + it 'reloads the ci_ref and retries once' do + subject.instance_variable_set("@ref", subject.send(:build_ref)) + + expect do + expect(subject.call).to eq(true) + end.not_to change { Ci::Ref.count } + expect(ci_ref.reload.status).to eq('fixed') + end + + it 'raises error on multiple retries' do + allow_any_instance_of(Ci::Ref).to receive(:update) + .and_raise(ActiveRecord::RecordNotUnique) + + expect { subject.call }.to raise_error(ActiveRecord::RecordNotUnique) + end + end + end + + context 'ci_ref exists' do + let!(:ci_ref) { create(:ci_ref, status: 'failed') } + let(:pipeline) { ci_ref.pipelines.first } + + it_behaves_like 'updates ci_ref' + + context 'pipeline status is invalid' do + let!(:pipeline) { create(:ci_pipeline, :running, project: ci_ref.project, ref: ci_ref.ref, tag: ci_ref.tag) } + + it_behaves_like 'does a noop' + end + + context 'newer pipeline finished' do + let(:newer_pipeline) { create(:ci_pipeline, :success, project: ci_ref.project, ref: ci_ref.ref, tag: ci_ref.tag) } + + before do + ci_ref.update!(last_updated_by_pipeline: newer_pipeline) + end + + it_behaves_like 'does a noop' + end + + context 'ref is stale' do + let(:pipeline1) { create(:ci_pipeline, :success, project: ci_ref.project, ref: ci_ref.ref, tag: ci_ref.tag) } + let(:pipeline2) { create(:ci_pipeline, :success, project: ci_ref.project, ref: ci_ref.ref, tag: ci_ref.tag) } + + it 'reloads the ref and retry' do + service1 = described_class.new(pipeline1) + service2 = described_class.new(pipeline2) + + service2.send(:ref) + service1.call + expect(ci_ref.reload.status).to eq('fixed') + expect do + expect(service2.call).to eq(true) + # We expect 'success' in this case rather than 'fixed' because + # the ref is correctly reloaded on stale error. + expect(ci_ref.reload.status).to eq('success') + end.not_to change { Ci::Ref.count } + end + + it 'aborts when a newer pipeline finished' do + service1 = described_class.new(pipeline1) + service2 = described_class.new(pipeline2) + + service2.call + expect do + expect(service1.call).to eq(false) + expect(ci_ref.reload.status).to eq('fixed') + end.not_to change { Ci::Ref.count } + end + end + + context 'ref exists as both tag/branch and tag' do + let(:pipeline) { create(:ci_pipeline, :failed, project: ci_ref.project, ref: ci_ref.ref, tag: true) } + let!(:branch_pipeline) { create(:ci_pipeline, :success, project: ci_ref.project, ref: ci_ref.ref, tag: false) } + + it_behaves_like 'creates ci_ref' + end + end + end +end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 07a1be6c12b..120bfc6d0ca 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2315,6 +2315,7 @@ describe NotificationService, :mailer do user = create_user_with_notification(:custom, 'custom_enabled') update_custom_notification(:success_pipeline, user, resource: project) update_custom_notification(:failed_pipeline, user, resource: project) + update_custom_notification(:fixed_pipeline, user, resource: project) user end @@ -2322,6 +2323,7 @@ describe NotificationService, :mailer do user = create_user_with_notification(:custom, 'custom_disabled') update_custom_notification(:success_pipeline, user, resource: project, value: false) update_custom_notification(:failed_pipeline, user, resource: project, value: false) + update_custom_notification(:fixed_pipeline, user, resource: project, value: false) user end @@ -2514,6 +2516,85 @@ describe NotificationService, :mailer do end end end + + context 'with a fixed pipeline' do + let(:ref_status) { 'fixed' } + + context 'when the creator has no custom notification set' do + let(:pipeline) { create_pipeline(u_member, :success) } + + it 'emails only the creator' do + notification.pipeline_finished(pipeline, ref_status: ref_status) + + should_only_email(u_member, kind: :bcc) + end + + it_behaves_like 'project emails are disabled' do + let(:notification_target) { pipeline } + let(:notification_trigger) { notification.pipeline_finished(pipeline, ref_status: ref_status) } + end + + context 'when the creator has group notification email set' do + let(:group_notification_email) { 'user+group@example.com' } + + before do + group = create(:group) + + project.update(group: group) + create(:notification_setting, user: u_member, source: group, notification_email: group_notification_email) + end + + it 'sends to group notification email' do + notification.pipeline_finished(pipeline, ref_status: ref_status) + + expect(email_recipients(kind: :bcc).first).to eq(group_notification_email) + end + end + end + + context 'when the creator has watch set' do + before do + pipeline = create_pipeline(u_watcher, :success) + notification.pipeline_finished(pipeline, ref_status: ref_status) + end + + it 'emails only the creator' do + should_only_email(u_watcher, kind: :bcc) + end + end + + context 'when the creator has custom notifications, but without any set' do + before do + pipeline = create_pipeline(u_custom_notification_unset, :success) + notification.pipeline_finished(pipeline, ref_status: ref_status) + end + + it 'emails only the creator' do + should_only_email(u_custom_notification_unset, kind: :bcc) + end + end + + context 'when the creator has custom notifications disabled' do + before do + pipeline = create_pipeline(u_custom_notification_disabled, :success) + notification.pipeline_finished(pipeline, ref_status: ref_status) + end + + it 'notifies nobody' do + should_not_email_anyone + end + end + + context 'when the creator has custom notifications set' do + it 'emails only the creator' do + pipeline = create_pipeline(u_custom_notification_enabled, :success) + + notification.pipeline_finished(pipeline, ref_status: ref_status) + + should_only_email(u_custom_notification_enabled, kind: :bcc) + end + end + end end end diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb index 60c0ec45c71..8f5bfdacc3a 100644 --- a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb @@ -65,9 +65,16 @@ shared_examples 'resource notes mentions migration' do |migration_class, resourc end shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes| + before do + stub_const("#{described_class.name}::BATCH_SIZE", 1) + end + it 'schedules background migrations' do Sidekiq::Testing.fake! do Timecop.freeze do + resource_count = is_for_notes ? Note.count : resource_class.count + expect(resource_count).to eq 5 + migrate! migration = described_class::MIGRATION diff --git a/spec/support/shared_examples/views/pipeline_status_changes_email.rb b/spec/support/shared_examples/views/pipeline_status_changes_email.rb new file mode 100644 index 00000000000..15b4ce9c44e --- /dev/null +++ b/spec/support/shared_examples/views/pipeline_status_changes_email.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +shared_examples 'pipeline status changes email' do + include Devise::Test::ControllerHelpers + + let(:user) { create(:user, developer_projects: [project]) } + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, :simple, source_project: project) } + + let(:pipeline) do + create(:ci_pipeline, + project: project, + user: user, + ref: project.default_branch, + sha: project.commit.sha, + status: status) + end + + before do + assign(:project, project) + assign(:pipeline, pipeline) + assign(:merge_request, merge_request) + end + + shared_examples_for 'renders the pipeline status changes email correctly' do + context 'pipeline with user' do + it 'renders the email correctly' do + render + + expect(rendered).to have_content title + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ') + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content pipeline.user.name + + if status == :failed + expect(rendered).to have_content build.name + end + end + + it_behaves_like 'correct pipeline information for pipelines for merge requests' + end + + context 'pipeline without user' do + before do + pipeline.update_attribute(:user, nil) + end + + it 'renders the email correctly' do + render + + expect(rendered).to have_content title + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ') + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content "by API" + + if status == :failed + expect(rendered).to have_content build.name + end + end + end + end + + context 'when the pipeline contains a failed job' do + let!(:build) { create(:ci_build, status: status, pipeline: pipeline, project: pipeline.project) } + + it_behaves_like 'renders the pipeline status changes email correctly' + end + + context 'when the latest failed job is a bridge job' do + let!(:build) { create(:ci_bridge, status: status, pipeline: pipeline, project: pipeline.project) } + + it_behaves_like 'renders the pipeline status changes email correctly' + end +end diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb index a540a53c91d..80dc14b523d 100644 --- a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb +++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb @@ -3,72 +3,8 @@ require 'spec_helper' describe 'notify/pipeline_failed_email.html.haml' do - include Devise::Test::ControllerHelpers - - let(:user) { create(:user, developer_projects: [project]) } - let(:project) { create(:project, :repository) } - let(:merge_request) { create(:merge_request, :simple, source_project: project) } - - let(:pipeline) do - create(:ci_pipeline, - project: project, - user: user, - ref: project.default_branch, - sha: project.commit.sha, - status: :failed) - end - - before do - assign(:project, project) - assign(:pipeline, pipeline) - assign(:merge_request, merge_request) - end - - shared_examples_for 'renders the pipeline failed email correctly' do - context 'pipeline with user' do - it 'renders the email correctly' do - render - - expect(rendered).to have_content "Your pipeline has failed" - expect(rendered).to have_content pipeline.project.name - expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ') - expect(rendered).to have_content pipeline.commit.author_name - expect(rendered).to have_content "##{pipeline.id}" - expect(rendered).to have_content pipeline.user.name - expect(rendered).to have_content build.name - end - - it_behaves_like 'correct pipeline information for pipelines for merge requests' - end - - context 'pipeline without user' do - before do - pipeline.update_attribute(:user, nil) - end - - it 'renders the email correctly' do - render - - expect(rendered).to have_content "Your pipeline has failed" - expect(rendered).to have_content pipeline.project.name - expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ') - expect(rendered).to have_content pipeline.commit.author_name - expect(rendered).to have_content "##{pipeline.id}" - expect(rendered).to have_content "by API" - expect(rendered).to have_content build.name - end - end - end - - context 'when the pipeline contains a failed job' do - let!(:build) { create(:ci_build, :failed, pipeline: pipeline, project: pipeline.project) } - - it_behaves_like 'renders the pipeline failed email correctly' - end - - context 'when the latest failed job is a bridge job' do - let!(:build) { create(:ci_bridge, status: :failed, pipeline: pipeline, project: pipeline.project) } - - it_behaves_like 'renders the pipeline failed email correctly' + it_behaves_like 'pipeline status changes email' do + let(:title) { 'Your pipeline has failed' } + let(:status) { :failed } end end diff --git a/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb b/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb new file mode 100644 index 00000000000..382fc5ecdd3 --- /dev/null +++ b/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'notify/pipeline_fixed_email.html.haml' do + it_behaves_like 'pipeline status changes email' do + let(:title) { 'Your pipeline has been fixed!' } + let(:status) { :success } + end +end diff --git a/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb b/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb new file mode 100644 index 00000000000..ec540dc3f77 --- /dev/null +++ b/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'notify/pipeline_fixed_email.text.erb' do + it_behaves_like 'pipeline status changes email' do + let(:title) { 'Your pipeline has been fixed!' } + let(:status) { :success } + end +end diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb index fbf33b7ec35..417909fd67b 100644 --- a/spec/views/notify/pipeline_success_email.html.haml_spec.rb +++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb @@ -3,56 +3,8 @@ require 'spec_helper' describe 'notify/pipeline_success_email.html.haml' do - include Devise::Test::ControllerHelpers - - let(:user) { create(:user, developer_projects: [project]) } - let(:project) { create(:project, :repository) } - let(:merge_request) { create(:merge_request, :simple, source_project: project) } - - let(:pipeline) do - create(:ci_pipeline, - project: project, - user: user, - ref: project.default_branch, - sha: project.commit.sha, - status: :success) - end - - before do - assign(:project, project) - assign(:pipeline, pipeline) - assign(:merge_request, merge_request) - end - - context 'pipeline with user' do - it 'renders the email correctly' do - render - - expect(rendered).to have_content "Your pipeline has passed" - expect(rendered).to have_content pipeline.project.name - expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ') - expect(rendered).to have_content pipeline.commit.author_name - expect(rendered).to have_content "##{pipeline.id}" - expect(rendered).to have_content pipeline.user.name - end - - it_behaves_like 'correct pipeline information for pipelines for merge requests' - end - - context 'pipeline without user' do - before do - pipeline.update_attribute(:user, nil) - end - - it 'renders the email correctly' do - render - - expect(rendered).to have_content "Your pipeline has passed" - expect(rendered).to have_content pipeline.project.name - expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ') - expect(rendered).to have_content pipeline.commit.author_name - expect(rendered).to have_content "##{pipeline.id}" - expect(rendered).to have_content "by API" - end + it_behaves_like 'pipeline status changes email' do + let(:title) { 'Your pipeline has passed' } + let(:status) { :success } end end diff --git a/spec/views/notify/pipeline_success_email.text.erb_spec.rb b/spec/views/notify/pipeline_success_email.text.erb_spec.rb index ba4633bc346..4a914cab85e 100644 --- a/spec/views/notify/pipeline_success_email.text.erb_spec.rb +++ b/spec/views/notify/pipeline_success_email.text.erb_spec.rb @@ -3,24 +3,8 @@ require 'spec_helper' describe 'notify/pipeline_success_email.text.erb' do - let(:user) { create(:user, developer_projects: [project]) } - let(:project) { create(:project, :repository) } - let(:merge_request) { create(:merge_request, :simple, source_project: project) } - - let(:pipeline) do - create(:ci_pipeline, - :success, - project: project, - user: user, - ref: project.default_branch, - sha: project.commit.sha) - end - - before do - assign(:project, project) - assign(:pipeline, pipeline) - assign(:merge_request, merge_request) + it_behaves_like 'pipeline status changes email' do + let(:title) { 'Your pipeline has passed' } + let(:status) { :success } end - - it_behaves_like 'correct pipeline information for pipelines for merge requests' end diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb index 98b0f139fe2..5defd3d5bd7 100644 --- a/spec/workers/pipeline_notification_worker_spec.rb +++ b/spec/workers/pipeline_notification_worker_spec.rb @@ -3,13 +3,16 @@ require 'spec_helper' describe PipelineNotificationWorker, :mailer do - let(:pipeline) { create(:ci_pipeline) } + let_it_be(:pipeline) { create(:ci_pipeline) } describe '#execute' do it 'calls NotificationService#pipeline_finished when the pipeline exists' do - expect(NotificationService).to receive_message_chain(:new, :pipeline_finished) + notification_service_double = double + expect(notification_service_double).to receive(:pipeline_finished) + .with(pipeline, ref_status: 'success', recipients: ['test@gitlab.com']) + expect(NotificationService).to receive(:new).and_return(notification_service_double) - subject.perform(pipeline.id) + subject.perform(pipeline.id, ref_status: 'success', recipients: ['test@gitlab.com']) end it 'does nothing when the pipeline does not exist' do diff --git a/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb b/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb new file mode 100644 index 00000000000..7228de4f895 --- /dev/null +++ b/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PipelineUpdateCiRefStatusWorker do + let(:worker) { described_class.new } + let(:pipeline) { create(:ci_pipeline) } + + describe '#perform' do + it 'updates the ci_ref status' do + expect(Ci::UpdateCiRefStatusService).to receive(:new) + .with(pipeline) + .and_return(double(call: true)) + + worker.perform(pipeline.id) + end + end +end |