diff options
Diffstat (limited to 'spec/models/ci/pipeline_spec.rb')
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 479 |
1 files changed, 375 insertions, 104 deletions
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 61422978df7..5b67cbbc86b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -19,32 +19,67 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:user) } - it { is_expected.to belong_to(:auto_canceled_by) } + it { is_expected.to belong_to(:auto_canceled_by).class_name('Ci::Pipeline').inverse_of(:auto_canceled_pipelines) } it { is_expected.to belong_to(:pipeline_schedule) } it { is_expected.to belong_to(:merge_request) } it { is_expected.to belong_to(:external_pull_request) } it { is_expected.to have_many(:statuses) } - it { is_expected.to have_many(:trigger_requests) } + it { is_expected.to have_many(:trigger_requests).with_foreign_key(:commit_id).inverse_of(:pipeline) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:builds) } - it { is_expected.to have_many(:statuses_order_id_desc) } + + it do + is_expected.to have_many(:statuses_order_id_desc) + .class_name('CommitStatus').with_foreign_key(:commit_id).inverse_of(:pipeline) + end + it { is_expected.to have_many(:bridges) } it { is_expected.to have_many(:job_artifacts).through(:builds) } it { is_expected.to have_many(:build_trace_chunks).through(:builds) } - it { is_expected.to have_many(:auto_canceled_pipelines) } - it { is_expected.to have_many(:auto_canceled_jobs) } - it { is_expected.to have_many(:sourced_pipelines) } + it { is_expected.to have_many(:triggered_pipelines) } it { is_expected.to have_many(:pipeline_artifacts) } - it { is_expected.to have_one(:chat_data) } + it do + is_expected.to have_many(:failed_builds).class_name('Ci::Build') + .with_foreign_key(:commit_id).inverse_of(:pipeline) + end + + it do + is_expected.to have_many(:cancelable_statuses).class_name('CommitStatus') + .with_foreign_key(:commit_id).inverse_of(:pipeline) + end + + it do + is_expected.to have_many(:auto_canceled_pipelines).class_name('Ci::Pipeline') + .with_foreign_key(:auto_canceled_by_id).inverse_of(:auto_canceled_by) + end + + it do + is_expected.to have_many(:auto_canceled_jobs).class_name('CommitStatus') + .with_foreign_key(:auto_canceled_by_id).inverse_of(:auto_canceled_by) + end + + it do + is_expected.to have_many(:sourced_pipelines).class_name('Ci::Sources::Pipeline') + .with_foreign_key(:source_pipeline_id).inverse_of(:source_pipeline) + end + it { is_expected.to have_one(:source_pipeline) } + it { is_expected.to have_one(:chat_data) } it { is_expected.to have_one(:triggered_by_pipeline) } it { is_expected.to have_one(:source_job) } it { is_expected.to have_one(:pipeline_config) } it { is_expected.to have_one(:pipeline_metadata) } + it do + is_expected.to have_many(:daily_build_group_report_results).class_name('Ci::DailyBuildGroupReportResult') + .with_foreign_key(:last_pipeline_id).inverse_of(:last_pipeline) + end + + it { is_expected.to have_many(:latest_builds_report_results).through(:latest_builds).source(:report_results) } + it { is_expected.to respond_to :git_author_name } it { is_expected.to respond_to :git_author_email } it { is_expected.to respond_to :git_author_full_text } @@ -409,6 +444,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end end + describe '.preload_pipeline_metadata' do + let_it_be(:pipeline) { create(:ci_empty_pipeline, project: project, user: user, name: 'Chatops pipeline') } + + it 'loads associations' do + result = described_class.preload_pipeline_metadata.first + + expect(result.association(:pipeline_metadata).loaded?).to be(true) + end + end + describe '.ci_sources' do subject { described_class.ci_sources } @@ -462,11 +507,13 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: let!(:other_pipeline) { create(:ci_pipeline, project: project) } before do - create(:ci_sources_pipeline, - source_job: create(:ci_build, pipeline: upstream_pipeline), - source_project: project, - pipeline: child_pipeline, - project: project) + create( + :ci_sources_pipeline, + source_job: create(:ci_build, pipeline: upstream_pipeline), + source_project: project, + pipeline: child_pipeline, + project: project + ) end it 'only returns pipelines outside pipeline family' do @@ -485,11 +532,13 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: let!(:other_pipeline) { create(:ci_pipeline, project: project) } before do - create(:ci_sources_pipeline, - source_job: create(:ci_build, pipeline: upstream_pipeline), - source_project: project, - pipeline: child_pipeline, - project: project) + create( + :ci_sources_pipeline, + source_job: create(:ci_build, pipeline: upstream_pipeline), + source_project: project, + pipeline: child_pipeline, + project: project + ) end it 'only returns older pipelines outside pipeline family' do @@ -497,6 +546,17 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end end + describe '.order_id_desc' do + subject(:pipelines_ordered_by_id) { described_class.order_id_desc } + + let(:older_pipeline) { create(:ci_pipeline, id: 99, project: project) } + let(:newest_pipeline) { create(:ci_pipeline, id: 100, project: project) } + + it 'only returns the pipelines ordered by id' do + expect(pipelines_ordered_by_id).to eq([newest_pipeline, older_pipeline]) + end + end + describe '.jobs_count_in_alive_pipelines' do before do ::Ci::HasStatus::ALIVE_STATUSES.each do |status| @@ -872,6 +932,26 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: expect(pipeline).to be_valid end end + + context 'when source is unknown' do + subject(:pipeline) { create(:ci_empty_pipeline, :created) } + + let(:attr) { :source } + let(:attr_value) { :unknown } + + it_behaves_like 'having enum with nil value' + end + end + + describe '#config_source' do + context 'when source is unknown' do + subject(:pipeline) { create(:ci_empty_pipeline, :created) } + + let(:attr) { :config_source } + let(:attr_value) { :unknown_source } + + it_behaves_like 'having enum with nil value' + end end describe '#block' do @@ -1131,29 +1211,41 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: describe 'legacy stages' do before do - create(:commit_status, pipeline: pipeline, - stage: 'build', - name: 'linux', - stage_idx: 0, - status: 'success') - - create(:commit_status, pipeline: pipeline, - stage: 'build', - name: 'mac', - stage_idx: 0, - status: 'failed') - - create(:commit_status, pipeline: pipeline, - stage: 'deploy', - name: 'staging', - stage_idx: 2, - status: 'running') - - create(:commit_status, pipeline: pipeline, - stage: 'test', - name: 'rspec', - stage_idx: 1, - status: 'success') + create( + :commit_status, + pipeline: pipeline, + stage: 'build', + name: 'linux', + stage_idx: 0, + status: 'success' + ) + + create( + :commit_status, + pipeline: pipeline, + stage: 'build', + name: 'mac', + stage_idx: 0, + status: 'failed' + ) + + create( + :commit_status, + pipeline: pipeline, + stage: 'deploy', + name: 'staging', + stage_idx: 2, + status: 'running' + ) + + create( + :commit_status, + pipeline: pipeline, + stage: 'test', + name: 'rspec', + stage_idx: 1, + status: 'success' + ) end describe '#stages_count' do @@ -1604,8 +1696,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: before do upstream_bridge = create(:ci_bridge, :strategy_depend, pipeline: upstream_of_upstream_pipeline) - create(:ci_sources_pipeline, pipeline: upstream_pipeline, - source_job: upstream_bridge) + create(:ci_sources_pipeline, pipeline: upstream_pipeline, source_job: upstream_bridge) end context 'when the downstream pipeline first fails then retries and succeeds' do @@ -1661,13 +1752,163 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end end + describe 'merge status subscription trigger' do + shared_examples 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated' do + context 'when state transitions to running' do + it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.run } + end + end + + context 'when state transitions to success' do + it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.succeed } + end + end + + context 'when state transitions to failed' do + it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.drop } + end + end + + context 'when state transitions to canceled' do + it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.cancel } + end + end + + context 'when state transitions to skipped' do + it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.skip } + end + end + end + + shared_examples 'state transition triggering GraphQL subscription mergeRequestMergeStatusUpdated' do + context 'when state transitions to running' do + it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.run } + end + end + + context 'when state transitions to success' do + it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.succeed } + end + end + + context 'when state transitions to failed' do + it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.drop } + end + end + + context 'when state transitions to canceled' do + it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.cancel } + end + end + + context 'when state transitions to skipped' do + it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do + let(:action) { pipeline.skip } + end + end + + context 'when only_allow_merge_if_pipeline_succeeds? returns false' do + let(:only_allow_merge_if_pipeline_succeeds?) { false } + + it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated' + end + + context 'when pipeline_trigger_merge_status feature flag is disabled' do + before do + stub_feature_flags(pipeline_trigger_merge_status: false) + end + + it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated' + end + end + + context 'when pipeline has merge requests' do + let(:merge_request) do + create( + :merge_request, + :simple, + source_project: project, + target_project: project + ) + end + + let(:only_allow_merge_if_pipeline_succeeds?) { true } + + before do + allow(project) + .to receive(:only_allow_merge_if_pipeline_succeeds?) + .and_return(only_allow_merge_if_pipeline_succeeds?) + end + + context 'when for a specific merge request' do + let(:pipeline) do + create( + :ci_pipeline, + project: project, + merge_request: merge_request + ) + end + + it_behaves_like 'state transition triggering GraphQL subscription mergeRequestMergeStatusUpdated' + + context 'when pipeline is a child' do + let(:parent_pipeline) do + create( + :ci_pipeline, + project: project, + merge_request: merge_request + ) + end + + let(:pipeline) do + create( + :ci_pipeline, + child_of: parent_pipeline, + merge_request: merge_request + ) + end + + it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated' + end + end + + context 'when for merge requests matching the source branch and SHA' do + let(:pipeline) do + create( + :ci_pipeline, + project: project, + ref: merge_request.source_branch, + sha: merge_request.diff_head_sha + ) + end + + it_behaves_like 'state transition triggering GraphQL subscription mergeRequestMergeStatusUpdated' + end + end + + context 'when pipeline has no merge requests' do + it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated' + end + end + def create_build(name, *traits, queued_at: current, started_from: 0, **opts) - create(:ci_build, *traits, - name: name, - pipeline: pipeline, - queued_at: queued_at, - started_at: queued_at + started_from, - **opts) + create( + :ci_build, *traits, + name: name, + pipeline: pipeline, + queued_at: queued_at, + started_at: queued_at + started_from, + **opts + ) end end @@ -1715,9 +1956,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: let(:pipeline) { build(:ci_pipeline, merge_request: merge_request) } let(:merge_request) do - create(:merge_request, :simple, - source_project: project, - target_project: project) + create(:merge_request, :simple, source_project: project, target_project: project) end it 'returns false' do @@ -1758,17 +1997,17 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: context 'when ref is merge request' do let(:pipeline) do - create(:ci_pipeline, - source: :merge_request_event, - merge_request: merge_request) + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) end let(:merge_request) do - create(:merge_request, - source_project: project, - source_branch: 'feature', - target_project: project, - target_branch: 'master') + create( + :merge_request, + source_project: project, + source_branch: 'feature', + target_project: project, + target_branch: 'master' + ) end it 'returns branch ref' do @@ -1812,35 +2051,63 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: context 'with non-empty project' do let(:pipeline) do - create(:ci_pipeline, - ref: project.default_branch, - sha: project.commit.sha) + create( + :ci_pipeline, + project: project, + ref: project.default_branch, + sha: project.commit.sha + ) end describe '#lazy_ref_commit' do let(:another) do - create(:ci_pipeline, - ref: 'feature', - sha: project.commit('feature').sha) + create( + :ci_pipeline, + project: project, + ref: 'feature', + sha: project.commit('feature').sha + ) end let(:unicode) do - create(:ci_pipeline, - ref: 'ü/unicode/multi-byte') + create( + :ci_pipeline, + project: project, + ref: 'ü/unicode/multi-byte' + ) + end + + let(:in_another_project) do + other_project = create(:project, :repository) + create( + :ci_pipeline, + project: other_project, + ref: other_project.default_branch, + sha: other_project.commit.sha + ) end - it 'returns the latest commit for a ref lazily' do + it 'returns the latest commit for a ref lazily', :aggregate_failures do expect(project.repository) .to receive(:list_commits_by_ref_name).once .and_call_original + requests_before = Gitlab::GitalyClient.get_request_count pipeline.lazy_ref_commit another.lazy_ref_commit unicode.lazy_ref_commit + in_another_project.lazy_ref_commit + requests_after = Gitlab::GitalyClient.get_request_count + + expect(requests_after - requests_before).to eq(0) expect(pipeline.lazy_ref_commit.id).to eq pipeline.sha expect(another.lazy_ref_commit.id).to eq another.sha - expect(unicode.lazy_ref_commit).to be_nil + expect(unicode.lazy_ref_commit.itself).to be_nil + expect(in_another_project.lazy_ref_commit.id).to eq in_another_project.sha + + expect(pipeline.lazy_ref_commit.repository.container).to eq project + expect(in_another_project.lazy_ref_commit.repository.container).to eq in_another_project.project end end @@ -1969,9 +2236,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end let(:merge_request) do - create(:merge_request, :simple, - source_project: project, - target_project: project) + create(:merge_request, :simple, source_project: project, target_project: project) end it 'returns merge request modified paths' do @@ -1996,8 +2261,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: describe '#modified_paths_since' do let(:project) do - create(:project, :custom_repo, - files: { 'file1.txt' => 'file 1' }) + create(:project, :custom_repo, files: { 'file1.txt' => 'file 1' }) end let(:user) { project.owner } @@ -3270,19 +3534,23 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: let(:target_branch) { 'master' } let!(:pipeline) do - create(:ci_pipeline, - source: :merge_request_event, - project: pipeline_project, - ref: source_branch, - merge_request: merge_request) + create( + :ci_pipeline, + source: :merge_request_event, + project: pipeline_project, + ref: source_branch, + merge_request: merge_request + ) end let(:merge_request) do - create(:merge_request, - source_project: pipeline_project, - source_branch: source_branch, - target_project: project, - target_branch: target_branch) + create( + :merge_request, + source_project: pipeline_project, + source_branch: source_branch, + target_project: project, + target_branch: target_branch + ) end it 'returns an associated merge request' do @@ -3293,19 +3561,23 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: let(:target_branch_2) { 'merge-test' } let!(:pipeline_2) do - create(:ci_pipeline, - source: :merge_request_event, - project: pipeline_project, - ref: source_branch, - merge_request: merge_request_2) + create( + :ci_pipeline, + source: :merge_request_event, + project: pipeline_project, + ref: source_branch, + merge_request: merge_request_2 + ) end let(:merge_request_2) do - create(:merge_request, - source_project: pipeline_project, - source_branch: source_branch, - target_project: project, - target_branch: target_branch_2) + create( + :merge_request, + source_project: pipeline_project, + source_branch: source_branch, + target_project: project, + target_branch: target_branch_2 + ) end it 'does not return an associated merge request' do @@ -3701,10 +3973,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: let(:project) { create(:project, :repository, namespace: namespace) } let(:pipeline) do - create(:ci_pipeline, - project: project, - sha: project.commit('master').sha, - user: project.first_owner) + create( + :ci_pipeline, + project: project, + sha: project.commit('master').sha, + user: project.first_owner + ) end before do @@ -4488,10 +4762,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: let(:stage_name) { 'test' } let(:stage) do - create(:ci_stage, - pipeline: pipeline, - project: pipeline.project, - name: 'test') + create(:ci_stage, pipeline: pipeline, project: pipeline.project, name: 'test') end before do @@ -5208,11 +5479,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: describe '#cluster_agent_authorizations' do let(:pipeline) { create(:ci_empty_pipeline, :created) } - let(:authorization) { instance_double(Clusters::Agents::GroupAuthorization) } + let(:authorization) { instance_double(Clusters::Agents::Authorizations::CiAccess::GroupAuthorization) } let(:finder) { double(execute: [authorization]) } it 'retrieves authorization records from the finder and caches the result' do - expect(Clusters::AgentAuthorizationsFinder).to receive(:new).once + expect(Clusters::Agents::Authorizations::CiAccess::Finder).to receive(:new).once .with(pipeline.project) .and_return(finder) |