diff options
Diffstat (limited to 'spec/models/ci')
-rw-r--r-- | spec/models/ci/bridge_spec.rb | 40 | ||||
-rw-r--r-- | spec/models/ci/build_metadata_spec.rb | 4 | ||||
-rw-r--r-- | spec/models/ci/build_pending_state_spec.rb | 9 | ||||
-rw-r--r-- | spec/models/ci/build_spec.rb | 504 | ||||
-rw-r--r-- | spec/models/ci/group_variable_spec.rb | 7 | ||||
-rw-r--r-- | spec/models/ci/job_artifact_spec.rb | 31 | ||||
-rw-r--r-- | spec/models/ci/job_token/allowlist_spec.rb | 39 | ||||
-rw-r--r-- | spec/models/ci/job_token/project_scope_link_spec.rb | 29 | ||||
-rw-r--r-- | spec/models/ci/job_token/scope_spec.rb | 165 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 322 | ||||
-rw-r--r-- | spec/models/ci/processable_spec.rb | 10 | ||||
-rw-r--r-- | spec/models/ci/runner_machine_spec.rb | 158 | ||||
-rw-r--r-- | spec/models/ci/runner_spec.rb | 67 | ||||
-rw-r--r-- | spec/models/ci/runner_version_spec.rb | 18 | ||||
-rw-r--r-- | spec/models/ci/running_build_spec.rb | 2 | ||||
-rw-r--r-- | spec/models/ci/secure_file_spec.rb | 5 | ||||
-rw-r--r-- | spec/models/ci/trigger_spec.rb | 38 | ||||
-rw-r--r-- | spec/models/ci/variable_spec.rb | 7 |
18 files changed, 906 insertions, 549 deletions
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 70e977e37ba..7b307de87c7 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -37,8 +37,18 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do describe '#retryable?' do let(:bridge) { create(:ci_bridge, :success) } - it 'returns false' do - expect(bridge.retryable?).to eq(false) + it 'returns true' do + expect(bridge.retryable?).to eq(true) + end + + context 'without ci_recreate_downstream_pipeline ff' do + before do + stub_feature_flags(ci_recreate_downstream_pipeline: false) + end + + it 'returns false' do + expect(bridge.retryable?).to eq(false) + end end end @@ -570,4 +580,30 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do end end end + + describe 'metadata partitioning', :ci_partitioning do + let(:pipeline) { create(:ci_pipeline, project: project, partition_id: ci_testing_partition_id) } + + let(:bridge) do + build(:ci_bridge, pipeline: pipeline) + end + + it 'creates the metadata record and assigns its partition' do + # the factory doesn't use any metadatable setters by default + # so the record will be initialized by the before_validation callback + expect(bridge.metadata).to be_nil + + expect(bridge.save!).to be_truthy + + expect(bridge.metadata).to be_present + expect(bridge.metadata).to be_valid + expect(bridge.metadata.partition_id).to eq(ci_testing_partition_id) + end + end + + describe '#deployment_job?' do + subject { bridge.deployment_job? } + + it { is_expected.to eq(false) } + end end diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index 8bf3af44be6..fb50ba89cd3 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -20,6 +20,10 @@ RSpec.describe Ci::BuildMetadata do it_behaves_like 'having unique enum values' + it { is_expected.to belong_to(:build) } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:runner_machine) } + describe '#update_timeout_state' do subject { metadata } diff --git a/spec/models/ci/build_pending_state_spec.rb b/spec/models/ci/build_pending_state_spec.rb index 756180621ec..bff0b35f878 100644 --- a/spec/models/ci/build_pending_state_spec.rb +++ b/spec/models/ci/build_pending_state_spec.rb @@ -2,7 +2,14 @@ require 'spec_helper' -RSpec.describe Ci::BuildPendingState do +RSpec.describe Ci::BuildPendingState, feature_category: :continuous_integration do + describe 'validations' do + subject(:pending_state) { build(:ci_build_pending_state) } + + it { is_expected.to belong_to(:build) } + it { is_expected.to validate_presence_of(:build) } + end + describe '#crc32' do context 'when checksum does not exist' do let(:pending_state) do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index dd1fbd7d0d5..2b3dc97e06d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2,16 +2,16 @@ require 'spec_helper' -RSpec.describe Ci::Build, feature_category: :continuous_integration do +RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_default: :keep do include Ci::TemplateHelpers include AfterNextHelpers let_it_be(:user) { create(:user) } - let_it_be(:group, reload: true) { create(:group) } - let_it_be(:project, reload: true) { create(:project, :repository, group: group) } + let_it_be(:group, reload: true) { create_default(:group) } + let_it_be(:project, reload: true) { create_default(:project, :repository, group: group) } let_it_be(:pipeline, reload: true) do - create(:ci_pipeline, project: project, + create_default(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch, status: 'success') @@ -23,17 +23,20 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do it { is_expected.to belong_to(:trigger_request) } it { is_expected.to belong_to(:erased_by) } - it { is_expected.to have_many(:needs) } - it { is_expected.to have_many(:sourced_pipelines) } - it { is_expected.to have_one(:sourced_pipeline) } - it { is_expected.to have_many(:job_variables) } - it { is_expected.to have_many(:report_results) } - it { is_expected.to have_many(:pages_deployments) } + it { is_expected.to have_many(:needs).with_foreign_key(:build_id) } + it { is_expected.to have_many(:sourced_pipelines).with_foreign_key(:source_job_id) } + it { is_expected.to have_one(:sourced_pipeline).with_foreign_key(:source_job_id) } + it { is_expected.to have_many(:job_variables).with_foreign_key(:job_id) } + it { is_expected.to have_many(:report_results).with_foreign_key(:build_id) } + it { is_expected.to have_many(:pages_deployments).with_foreign_key(:ci_build_id) } it { is_expected.to have_one(:deployment) } - it { is_expected.to have_one(:runner_session) } - it { is_expected.to have_one(:trace_metadata) } - it { is_expected.to have_many(:terraform_state_versions).inverse_of(:build) } + it { is_expected.to have_one(:runner_machine).through(:metadata) } + it { is_expected.to have_one(:runner_session).with_foreign_key(:build_id) } + it { is_expected.to have_one(:trace_metadata).with_foreign_key(:build_id) } + it { is_expected.to have_one(:runtime_metadata).with_foreign_key(:build_id) } + it { is_expected.to have_one(:pending_state).with_foreign_key(:build_id) } + it { is_expected.to have_many(:terraform_state_versions).inverse_of(:build).with_foreign_key(:ci_build_id) } it { is_expected.to validate_presence_of(:ref) } @@ -66,7 +69,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do it 'executes hooks' do expect_next(described_class).to receive(:execute_hooks) - create(:ci_build) + create(:ci_build, pipeline: pipeline) end end end @@ -105,19 +108,19 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { described_class.ref_protected } context 'when protected is true' do - let!(:job) { create(:ci_build, :protected) } + let!(:job) { create(:ci_build, :protected, pipeline: pipeline) } it { is_expected.to include(job) } end context 'when protected is false' do - let!(:job) { create(:ci_build) } + let!(:job) { create(:ci_build, pipeline: pipeline) } it { is_expected.not_to include(job) } end context 'when protected is nil' do - let!(:job) { create(:ci_build) } + let!(:job) { create(:ci_build, pipeline: pipeline) } before do job.update_attribute(:protected, nil) @@ -131,7 +134,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { described_class.with_downloadable_artifacts } context 'when job does not have a downloadable artifact' do - let!(:job) { create(:ci_build) } + let!(:job) { create(:ci_build, pipeline: pipeline) } it 'does not return the job' do is_expected.not_to include(job) @@ -141,7 +144,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do ::Ci::JobArtifact::DOWNLOADABLE_TYPES.each do |type| context "when job has a #{type} artifact" do it 'returns the job' do - job = create(:ci_build) + job = create(:ci_build, pipeline: pipeline) create( :ci_job_artifact, file_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[type.to_sym], @@ -155,7 +158,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when job has a non-downloadable artifact' do - let!(:job) { create(:ci_build, :trace_artifact) } + let!(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) } it 'does not return the job' do is_expected.not_to include(job) @@ -167,7 +170,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { described_class.with_erasable_artifacts } context 'when job does not have any artifacts' do - let!(:job) { create(:ci_build) } + let!(:job) { create(:ci_build, pipeline: pipeline) } it 'does not return the job' do is_expected.not_to include(job) @@ -177,7 +180,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do ::Ci::JobArtifact.erasable_file_types.each do |type| context "when job has a #{type} artifact" do it 'returns the job' do - job = create(:ci_build) + job = create(:ci_build, pipeline: pipeline) create( :ci_job_artifact, file_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[type.to_sym], @@ -191,7 +194,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when job has a non-erasable artifact' do - let!(:job) { create(:ci_build, :trace_artifact) } + let!(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) } it 'does not return the job' do is_expected.not_to include(job) @@ -199,11 +202,39 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end end + describe '.with_any_artifacts' do + subject { described_class.with_any_artifacts } + + context 'when job does not have any artifacts' do + it 'does not return the job' do + job = create(:ci_build, project: project) + + is_expected.not_to include(job) + end + end + + ::Ci::JobArtifact.file_types.each_key do |type| + context "when job has a #{type} artifact" do + it 'returns the job' do + job = create(:ci_build, project: project) + create( + :ci_job_artifact, + file_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[type.to_sym], + file_type: type, + job: job + ) + + is_expected.to include(job) + end + end + end + end + describe '.with_live_trace' do subject { described_class.with_live_trace } context 'when build has live trace' do - let!(:build) { create(:ci_build, :success, :trace_live) } + let!(:build) { create(:ci_build, :success, :trace_live, pipeline: pipeline) } it 'selects the build' do is_expected.to eq([build]) @@ -211,7 +242,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build does not have live trace' do - let!(:build) { create(:ci_build, :success, :trace_artifact) } + let!(:build) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) } it 'does not select the build' do is_expected.to be_empty @@ -223,7 +254,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { described_class.with_stale_live_trace } context 'when build has a stale live trace' do - let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.day.ago) } + let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.day.ago, pipeline: pipeline) } it 'selects the build' do is_expected.to eq([build]) @@ -231,7 +262,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build does not have a stale live trace' do - let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.hour.ago) } + let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.hour.ago, pipeline: pipeline) } it 'does not select the build' do is_expected.to be_empty @@ -242,9 +273,9 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '.license_management_jobs' do subject { described_class.license_management_jobs } - let!(:management_build) { create(:ci_build, :success, name: :license_management) } - let!(:scanning_build) { create(:ci_build, :success, name: :license_scanning) } - let!(:another_build) { create(:ci_build, :success, name: :another_type) } + let!(:management_build) { create(:ci_build, :success, name: :license_management, pipeline: pipeline) } + let!(:scanning_build) { create(:ci_build, :success, name: :license_scanning, pipeline: pipeline) } + let!(:another_build) { create(:ci_build, :success, name: :another_type, pipeline: pipeline) } it 'returns license_scanning jobs' do is_expected.to include(scanning_build) @@ -265,7 +296,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do let(:date) { 1.hour.ago } context 'when build has finished one day ago' do - let!(:build) { create(:ci_build, :success, finished_at: 1.day.ago) } + let!(:build) { create(:ci_build, :success, finished_at: 1.day.ago, pipeline: pipeline) } it 'selects the build' do is_expected.to eq([build]) @@ -273,7 +304,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build has finished 30 minutes ago' do - let!(:build) { create(:ci_build, :success, finished_at: 30.minutes.ago) } + let!(:build) { create(:ci_build, :success, finished_at: 30.minutes.ago, pipeline: pipeline) } it 'returns an empty array' do is_expected.to be_empty @@ -281,7 +312,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build is still running' do - let!(:build) { create(:ci_build, :running) } + let!(:build) { create(:ci_build, :running, pipeline: pipeline) } it 'returns an empty array' do is_expected.to be_empty @@ -292,9 +323,9 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '.with_exposed_artifacts' do subject { described_class.with_exposed_artifacts } - let!(:job1) { create(:ci_build) } - let!(:job2) { create(:ci_build, options: options) } - let!(:job3) { create(:ci_build) } + let!(:job1) { create(:ci_build, pipeline: pipeline) } + let!(:job2) { create(:ci_build, options: options, pipeline: pipeline) } + let!(:job3) { create(:ci_build, pipeline: pipeline) } context 'when some jobs have exposed artifacs and some not' do let(:options) { { artifacts: { expose_as: 'test', paths: ['test'] } } } @@ -334,7 +365,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do context 'when there are multiple builds containing artifacts' do before do - create_list(:ci_build, 5, :success, :test_reports) + create_list(:ci_build, 5, :success, :test_reports, pipeline: pipeline) end it 'does not execute a query for selecting job artifact one by one' do @@ -350,8 +381,8 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '.with_needs' do - let!(:build) { create(:ci_build) } - let!(:build_b) { create(:ci_build) } + let!(:build) { create(:ci_build, pipeline: pipeline) } + let!(:build_b) { create(:ci_build, pipeline: pipeline) } let!(:build_need_a) { create(:ci_build_need, build: build) } let!(:build_need_b) { create(:ci_build_need, build: build_b) } @@ -390,7 +421,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#stick_build_if_status_changed' do it 'sticks the build if the status changed' do - job = create(:ci_build, :pending) + job = create(:ci_build, :pending, pipeline: pipeline) expect(described_class.sticking).to receive(:stick) .with(:build, job.id) @@ -400,7 +431,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#enqueue' do - let(:build) { create(:ci_build, :created) } + let(:build) { create(:ci_build, :created, pipeline: pipeline) } before do allow(build).to receive(:any_unmet_prerequisites?).and_return(has_prerequisites) @@ -477,7 +508,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#enqueue_preparing' do - let(:build) { create(:ci_build, :preparing) } + let(:build) { create(:ci_build, :preparing, pipeline: pipeline) } before do allow(build).to receive(:any_unmet_prerequisites?).and_return(has_unmet_prerequisites) @@ -532,7 +563,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#run' do context 'when build has been just created' do - let(:build) { create(:ci_build, :created) } + let(:build) { create(:ci_build, :created, pipeline: pipeline) } it 'creates queuing entry and then removes it' do build.enqueue! @@ -544,7 +575,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build status transition fails' do - let(:build) { create(:ci_build, :pending) } + let(:build) { create(:ci_build, :pending, pipeline: pipeline) } before do create(:ci_pending_build, build: build, project: build.project) @@ -560,7 +591,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build has been picked by a shared runner' do - let(:build) { create(:ci_build, :pending) } + let(:build) { create(:ci_build, :pending, pipeline: pipeline) } it 'creates runtime metadata entry' do build.runner = create(:ci_runner, :instance_type) @@ -574,7 +605,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#drop' do context 'when has a runtime tracking entry' do - let(:build) { create(:ci_build, :pending) } + let(:build) { create(:ci_build, :pending, pipeline: pipeline) } it 'removes runtime tracking entry' do build.runner = create(:ci_runner, :instance_type) @@ -611,10 +642,10 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#outdated_deployment?' do subject { build.outdated_deployment? } - let(:build) { create(:ci_build, :created, :with_deployment, project: project, environment: 'production') } + let(:build) { create(:ci_build, :created, :with_deployment, pipeline: pipeline, environment: 'production') } context 'when build has no environment' do - let(:build) { create(:ci_build, :created, project: project, environment: nil) } + let(:build) { create(:ci_build, :created, pipeline: pipeline, environment: nil) } it { expect(subject).to be_falsey } end @@ -644,7 +675,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build is older than the latest deployment but succeeded once' do - let(:build) { create(:ci_build, :success, :with_deployment, project: project, environment: 'production') } + let(:build) { create(:ci_build, :success, :with_deployment, pipeline: pipeline, environment: 'production') } before do allow(build.deployment).to receive(:older_than_last_successful_deployment?).and_return(true) @@ -660,13 +691,13 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { build.schedulable? } context 'when build is schedulable' do - let(:build) { create(:ci_build, :created, :schedulable, project: project) } + let(:build) { create(:ci_build, :created, :schedulable, pipeline: pipeline) } it { expect(subject).to be_truthy } end context 'when build is not schedulable' do - let(:build) { create(:ci_build, :created, project: project) } + let(:build) { create(:ci_build, :created, pipeline: pipeline) } it { expect(subject).to be_falsy } end @@ -679,7 +710,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do project.add_developer(user) end - let(:build) { create(:ci_build, :created, :schedulable, user: user, project: project) } + let(:build) { create(:ci_build, :created, :schedulable, user: user, pipeline: pipeline) } it 'transits to scheduled' do allow(Ci::BuildScheduleWorker).to receive(:perform_at) @@ -740,7 +771,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#options_scheduled_at' do subject { build.options_scheduled_at } - let(:build) { build_stubbed(:ci_build, options: option) } + let(:build) { build_stubbed(:ci_build, options: option, pipeline: pipeline) } context 'when start_in is 1 day' do let(:option) { { start_in: '1 day' } } @@ -878,18 +909,18 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do context 'when new artifacts are used' do context 'artifacts archive does not exist' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } it { is_expected.to be_falsy } end context 'artifacts archive exists' do - let(:build) { create(:ci_build, :artifacts) } + let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } it { is_expected.to be_truthy } context 'is expired' do - let(:build) { create(:ci_build, :artifacts, :expired) } + let(:build) { create(:ci_build, :artifacts, :expired, pipeline: pipeline) } it { is_expected.to be_falsy } end @@ -901,36 +932,32 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject(:locked_artifacts) { build.locked_artifacts? } context 'when pipeline is artifacts_locked' do - before do - build.pipeline.artifacts_locked! - end + let(:pipeline) { create(:ci_pipeline, locked: :artifacts_locked) } context 'artifacts archive does not exist' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } it { is_expected.to be_falsy } end context 'artifacts archive exists' do - let(:build) { create(:ci_build, :artifacts) } + let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } it { is_expected.to be_truthy } end end context 'when pipeline is unlocked' do - before do - build.pipeline.unlocked! - end + let(:pipeline) { create(:ci_pipeline, locked: :unlocked) } context 'artifacts archive does not exist' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } it { is_expected.to be_falsy } end context 'artifacts archive exists' do - let(:build) { create(:ci_build, :artifacts) } + let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } it { is_expected.to be_falsy } end @@ -938,7 +965,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#available_artifacts?' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } subject { build.available_artifacts? } @@ -997,7 +1024,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { build.browsable_artifacts? } context 'artifacts metadata does exists' do - let(:build) { create(:ci_build, :artifacts) } + let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } it { is_expected.to be_truthy } end @@ -1007,13 +1034,13 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { build.artifacts_public? } context 'artifacts with defaults' do - let(:build) { create(:ci_build, :artifacts) } + let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } it { is_expected.to be_truthy } end context 'non public artifacts' do - let(:build) { create(:ci_build, :artifacts, :non_public_artifacts) } + let(:build) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) } it { is_expected.to be_falsey } end @@ -1047,7 +1074,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'artifacts archive is a zip file and metadata exists' do - let(:build) { create(:ci_build, :artifacts) } + let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } it { is_expected.to be_truthy } end @@ -1274,12 +1301,12 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#has_live_trace?' do subject { build.has_live_trace? } - let(:build) { create(:ci_build, :trace_live) } + let(:build) { create(:ci_build, :trace_live, pipeline: pipeline) } it { is_expected.to be_truthy } context 'when build does not have live trace' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } it { is_expected.to be_falsy } end @@ -1288,12 +1315,12 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#has_archived_trace?' do subject { build.has_archived_trace? } - let(:build) { create(:ci_build, :trace_artifact) } + let(:build) { create(:ci_build, :trace_artifact, pipeline: pipeline) } it { is_expected.to be_truthy } context 'when build does not have archived trace' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } it { is_expected.to be_falsy } end @@ -1303,7 +1330,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { build.has_job_artifacts? } context 'when build has a job artifact' do - let(:build) { create(:ci_build, :artifacts) } + let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } it { is_expected.to be_truthy } end @@ -1313,13 +1340,13 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { build.has_test_reports? } context 'when build has a test report' do - let(:build) { create(:ci_build, :test_reports) } + let(:build) { create(:ci_build, :test_reports, pipeline: pipeline) } it { is_expected.to be_truthy } end context 'when build does not have a test report' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } it { is_expected.to be_falsey } end @@ -1392,7 +1419,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end with_them do - let(:build) { create(:ci_build, trait, project: project, pipeline: pipeline) } + let(:build) { create(:ci_build, trait, pipeline: pipeline) } let(:event) { state } context "when transitioning to #{params[:state]}" do @@ -1416,7 +1443,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe 'state transition as a deployable' do subject { build.send(event) } - let!(:build) { create(:ci_build, :with_deployment, :start_review_app, project: project, pipeline: pipeline) } + let!(:build) { create(:ci_build, :with_deployment, :start_review_app, pipeline: pipeline) } let(:deployment) { build.deployment } let(:environment) { deployment.environment } @@ -1565,7 +1592,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do it 'transitions to running and calls webhook' do freeze_time do expect(Deployments::HooksWorker) - .to receive(:perform_async).with(deployment_id: deployment.id, status: 'running', status_changed_at: Time.current) + .to receive(:perform_async).with(hash_including({ 'deployment_id' => deployment.id, 'status' => 'running', 'status_changed_at' => Time.current.to_s })) subject end @@ -1580,7 +1607,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { build.on_stop } context 'when a job has a specification that it can be stopped from the other job' do - let(:build) { create(:ci_build, :start_review_app) } + let(:build) { create(:ci_build, :start_review_app, pipeline: pipeline) } it 'returns the other job name' do is_expected.to eq('stop_review_app') @@ -1588,7 +1615,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when a job does not have environment information' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } it 'returns nil' do is_expected.to be_nil @@ -1663,7 +1690,8 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do let(:build) do create(:ci_build, ref: 'master', - environment: 'review/$CI_COMMIT_REF_NAME') + environment: 'review/$CI_COMMIT_REF_NAME', + pipeline: pipeline) end it { is_expected.to eq('review/master') } @@ -1673,7 +1701,8 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do let(:build) do create(:ci_build, yaml_variables: [{ key: :APP_HOST, value: 'host' }], - environment: 'review/$APP_HOST') + environment: 'review/$APP_HOST', + pipeline: pipeline) end it 'returns an expanded environment name with a list of variables' do @@ -1695,7 +1724,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do context 'when using persisted variables' do let(:build) do - create(:ci_build, environment: 'review/x$CI_BUILD_ID') + create(:ci_build, environment: 'review/x$CI_BUILD_ID', pipeline: pipeline) end it { is_expected.to eq('review/x') } @@ -1712,7 +1741,8 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do create(:ci_build, ref: 'master', yaml_variables: yaml_variables, - environment: 'review/$ENVIRONMENT_NAME') + environment: 'review/$ENVIRONMENT_NAME', + pipeline: pipeline) end it { is_expected.to eq('review/master') } @@ -1720,7 +1750,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#expanded_kubernetes_namespace' do - let(:build) { create(:ci_build, environment: environment, options: options) } + let(:build) { create(:ci_build, environment: environment, options: options, pipeline: pipeline) } subject { build.expanded_kubernetes_namespace } @@ -1856,7 +1886,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'build is not erasable' do - let!(:build) { create(:ci_build) } + let!(:build) { create(:ci_build, pipeline: pipeline) } describe '#erasable?' do subject { build.erasable? } @@ -1867,7 +1897,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do context 'build is erasable' do context 'new artifacts' do - let!(:build) { create(:ci_build, :test_reports, :trace_artifact, :success, :artifacts) } + let!(:build) { create(:ci_build, :test_reports, :trace_artifact, :success, :artifacts, pipeline: pipeline) } describe '#erasable?' do subject { build.erasable? } @@ -1876,7 +1906,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#erased?' do - let!(:build) { create(:ci_build, :trace_artifact, :success, :artifacts) } + let!(:build) { create(:ci_build, :trace_artifact, :success, :artifacts, pipeline: pipeline) } subject { build.erased? } @@ -1970,13 +2000,13 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build is created' do - let(:build) { create(:ci_build, :created) } + let(:build) { create(:ci_build, :created, pipeline: pipeline) } it { is_expected.to be_cancelable } end context 'when build is waiting for resource' do - let(:build) { create(:ci_build, :waiting_for_resource) } + let(:build) { create(:ci_build, :waiting_for_resource, pipeline: pipeline) } it { is_expected.to be_cancelable } end @@ -2028,8 +2058,18 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end end + describe '#runner_machine' do + let_it_be(:runner) { create(:ci_runner) } + let_it_be(:runner_machine) { create(:ci_runner_machine, runner: runner) } + let_it_be(:build) { create(:ci_build, runner_machine: runner_machine) } + + subject(:build_runner_machine) { described_class.find(build.id).runner_machine } + + it { is_expected.to eq(runner_machine) } + end + describe '#tag_list' do - let_it_be(:build) { create(:ci_build, tag_list: ['tag']) } + let_it_be(:build) { create(:ci_build, tag_list: ['tag'], pipeline: pipeline) } context 'when tags are preloaded' do it 'does not trigger queries' do @@ -2046,7 +2086,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#save_tags' do - let(:build) { create(:ci_build, tag_list: ['tag']) } + let(:build) { create(:ci_build, tag_list: ['tag'], pipeline: pipeline) } it 'saves tags' do build.save! @@ -2075,13 +2115,13 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#has_tags?' do context 'when build has tags' do - subject { create(:ci_build, tag_list: ['tag']) } + subject { create(:ci_build, tag_list: ['tag'], pipeline: pipeline) } it { is_expected.to have_tags } end context 'when build does not have tags' do - subject { create(:ci_build, tag_list: []) } + subject { create(:ci_build, tag_list: [], pipeline: pipeline) } it { is_expected.not_to have_tags } end @@ -2136,9 +2176,9 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '.keep_artifacts!' do - let!(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) } + let!(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days, pipeline: pipeline) } let!(:builds_for_update) do - Ci::Build.where(id: create_list(:ci_build, 3, artifacts_expire_at: Time.current + 7.days).map(&:id)) + Ci::Build.where(id: create_list(:ci_build, 3, artifacts_expire_at: Time.current + 7.days, pipeline: pipeline).map(&:id)) end it 'resets expire_at' do @@ -2180,7 +2220,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#keep_artifacts!' do - let(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) } + let(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days, pipeline: pipeline) } subject { build.keep_artifacts! } @@ -2202,7 +2242,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#auto_retry_expected?' do - subject { create(:ci_build, :failed) } + subject { create(:ci_build, :failed, pipeline: pipeline) } context 'when build is failed and auto retry is configured' do before do @@ -2223,20 +2263,20 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end end - describe '#artifacts_file_for_type' do - let(:build) { create(:ci_build, :artifacts) } + describe '#artifact_for_type' do + let(:build) { create(:ci_build) } + let!(:archive) { create(:ci_job_artifact, :archive, job: build) } + let!(:codequality) { create(:ci_job_artifact, :codequality, job: build) } let(:file_type) { :archive } - subject { build.artifacts_file_for_type(file_type) } - - it 'queries artifacts for type' do - expect(build).to receive_message_chain(:job_artifacts, :find_by).with(file_type: [Ci::JobArtifact.file_types[file_type]]) + subject { build.artifact_for_type(file_type) } - subject - end + it { is_expected.to eq(archive) } end describe '#merge_request' do + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + subject { pipeline.builds.take.merge_request } context 'on a branch pipeline' do @@ -2281,19 +2321,23 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'on a detached merged request pipeline' do - let(:pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline, :with_job) } + let(:pipeline) do + create(:ci_pipeline, :detached_merge_request_pipeline, :with_job, merge_request: merge_request) + end it { is_expected.to eq(pipeline.merge_request) } end context 'on a legacy detached merged request pipeline' do - let(:pipeline) { create(:ci_pipeline, :legacy_detached_merge_request_pipeline, :with_job) } + let(:pipeline) do + create(:ci_pipeline, :legacy_detached_merge_request_pipeline, :with_job, merge_request: merge_request) + end it { is_expected.to eq(pipeline.merge_request) } end context 'on a pipeline for merged results' do - let(:pipeline) { create(:ci_pipeline, :merged_result_pipeline, :with_job) } + let(:pipeline) { create(:ci_pipeline, :merged_result_pipeline, :with_job, merge_request: merge_request) } it { is_expected.to eq(pipeline.merge_request) } end @@ -2329,7 +2373,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when options include artifacts:expose_as' do - let(:build) { create(:ci_build, options: { artifacts: { expose_as: 'test' } }) } + let(:build) { create(:ci_build, options: { artifacts: { expose_as: 'test' } }, pipeline: pipeline) } it 'saves the presence of expose_as into build metadata' do expect(build.metadata).to have_exposed_artifacts @@ -2455,56 +2499,56 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#playable?' do context 'when build is a manual action' do context 'when build has been skipped' do - subject { build_stubbed(:ci_build, :manual, status: :skipped) } + subject { build_stubbed(:ci_build, :manual, status: :skipped, pipeline: pipeline) } it { is_expected.not_to be_playable } end context 'when build has been canceled' do - subject { build_stubbed(:ci_build, :manual, status: :canceled) } + subject { build_stubbed(:ci_build, :manual, status: :canceled, pipeline: pipeline) } it { is_expected.to be_playable } end context 'when build is successful' do - subject { build_stubbed(:ci_build, :manual, status: :success) } + subject { build_stubbed(:ci_build, :manual, status: :success, pipeline: pipeline) } it { is_expected.to be_playable } end context 'when build has failed' do - subject { build_stubbed(:ci_build, :manual, status: :failed) } + subject { build_stubbed(:ci_build, :manual, status: :failed, pipeline: pipeline) } it { is_expected.to be_playable } end context 'when build is a manual untriggered action' do - subject { build_stubbed(:ci_build, :manual, status: :manual) } + subject { build_stubbed(:ci_build, :manual, status: :manual, pipeline: pipeline) } it { is_expected.to be_playable } end context 'when build is a manual and degenerated' do - subject { build_stubbed(:ci_build, :manual, :degenerated, status: :manual) } + subject { build_stubbed(:ci_build, :manual, :degenerated, status: :manual, pipeline: pipeline) } it { is_expected.not_to be_playable } end end context 'when build is scheduled' do - subject { build_stubbed(:ci_build, :scheduled) } + subject { build_stubbed(:ci_build, :scheduled, pipeline: pipeline) } it { is_expected.to be_playable } end context 'when build is not a manual action' do - subject { build_stubbed(:ci_build, :success) } + subject { build_stubbed(:ci_build, :success, pipeline: pipeline) } it { is_expected.not_to be_playable } end context 'when build is waiting for deployment approval' do - subject { build_stubbed(:ci_build, :manual, environment: 'production') } + subject { build_stubbed(:ci_build, :manual, environment: 'production', pipeline: pipeline) } before do create(:deployment, :blocked, deployable: subject) @@ -2601,7 +2645,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do it { is_expected.to be_truthy } - context "and there are specific runner" do + context "and there is a project runner" do let!(:runner) { create(:ci_runner, :project, projects: [build.project], contacted_at: 1.second.ago) } it { is_expected.to be_falsey } @@ -2855,7 +2899,13 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do before do allow_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder| + pipeline_variables_builder = double( + ::Gitlab::Ci::Variables::Builder::Pipeline, + predefined_variables: [pipeline_pre_var] + ) + allow(builder).to receive(:predefined_variables) { [build_pre_var] } + allow(builder).to receive(:pipeline_variables_builder) { pipeline_variables_builder } end allow(build).to receive(:yaml_variables) { [build_yaml_var] } @@ -2868,9 +2918,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do .to receive(:predefined_variables) { [project_pre_var] } project.variables.create!(key: 'secret', value: 'value') - - allow(build.pipeline) - .to receive(:predefined_variables).and_return([pipeline_pre_var]) end it 'returns variables in order depending on resource hierarchy' do @@ -3754,7 +3801,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#any_unmet_prerequisites?' do - let(:build) { create(:ci_build, :created) } + let(:build) { create(:ci_build, :created, pipeline: pipeline) } subject { build.any_unmet_prerequisites? } @@ -3841,7 +3888,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe 'state transition: any => [:preparing]' do - let(:build) { create(:ci_build, :created) } + let(:build) { create(:ci_build, :created, pipeline: pipeline) } before do allow(build).to receive(:prerequisites).and_return([double]) @@ -3855,7 +3902,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe 'when the build is waiting for deployment approval' do - let(:build) { create(:ci_build, :manual, environment: 'production') } + let(:build) { create(:ci_build, :manual, environment: 'production', pipeline: pipeline) } before do create(:deployment, :blocked, deployable: build) @@ -3867,7 +3914,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe 'state transition: any => [:pending]' do - let(:build) { create(:ci_build, :created) } + let(:build) { create(:ci_build, :created, pipeline: pipeline) } it 'queues BuildQueueWorker' do expect(BuildQueueWorker).to receive(:perform_async).with(build.id) @@ -3887,8 +3934,10 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe 'state transition: pending: :running' do - let(:runner) { create(:ci_runner) } - let(:job) { create(:ci_build, :pending, runner: runner) } + let_it_be_with_reload(:runner) { create(:ci_runner) } + let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project) } + + let(:job) { create(:ci_build, :pending, runner: runner, pipeline: pipeline) } before do job.project.update_attribute(:build_timeout, 1800) @@ -3992,7 +4041,9 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when artifacts of depended job has been erased' do - let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) } + let!(:pre_stage_job) do + create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) + end it { expect(job).not_to have_valid_build_dependencies } end @@ -4049,7 +4100,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build is configured to be retried' do - subject { create(:ci_build, :running, options: { script: ["ls -al"], retry: 3 }, project: project, user: user) } + subject { create(:ci_build, :running, options: { script: ["ls -al"], retry: 3 }, pipeline: pipeline, user: user) } it 'retries build and assigns the same user to it' do expect_next_instance_of(::Ci::RetryJobService) do |service| @@ -4098,7 +4149,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build is not configured to be retried' do - subject { create(:ci_build, :running, project: project, user: user, pipeline: pipeline) } + subject { create(:ci_build, :running, pipeline: pipeline, user: user) } let(:pipeline) do create(:ci_pipeline, @@ -4162,7 +4213,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '.matches_tag_ids' do - let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) } + let_it_be(:build, reload: true) { create(:ci_build, pipeline: pipeline, user: user) } let(:tag_ids) { ::ActsAsTaggableOn::Tag.named_any(tag_list).ids } @@ -4210,7 +4261,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '.matches_tags' do - let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) } + let_it_be(:build, reload: true) { create(:ci_build, pipeline: pipeline, user: user) } subject { described_class.where(id: build).with_any_tags } @@ -4236,7 +4287,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe 'pages deployments' do - let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) } + let_it_be(:build, reload: true) { create(:ci_build, pipeline: pipeline, user: user) } context 'when job is "pages"' do before do @@ -4562,7 +4613,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#artifacts_metadata_entry' do - let_it_be(:build) { create(:ci_build, project: project) } + let_it_be(:build) { create(:ci_build, pipeline: pipeline) } let(:path) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' } @@ -4622,7 +4673,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#publishes_artifacts_reports?' do - let(:build) { create(:ci_build, options: options) } + let(:build) { create(:ci_build, options: options, pipeline: pipeline) } subject { build.publishes_artifacts_reports? } @@ -4650,7 +4701,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#runner_required_feature_names' do - let(:build) { create(:ci_build, options: options) } + let(:build) { create(:ci_build, options: options, pipeline: pipeline) } subject { build.runner_required_feature_names } @@ -4672,7 +4723,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#supported_runner?' do - let_it_be_with_refind(:build) { create(:ci_build) } + let_it_be_with_refind(:build) { create(:ci_build, pipeline: pipeline) } subject { build.supported_runner?(runner_features) } @@ -4780,7 +4831,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build is a last deployment' do - let(:build) { create(:ci_build, :success, environment: 'production', pipeline: pipeline, project: project) } + let(:build) { create(:ci_build, :success, environment: 'production', pipeline: pipeline) } let(:environment) { create(:environment, name: 'production', project: build.project) } let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } @@ -4788,7 +4839,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when there is a newer build with deployment' do - let(:build) { create(:ci_build, :success, environment: 'production', pipeline: pipeline, project: project) } + let(:build) { create(:ci_build, :success, environment: 'production', pipeline: pipeline) } let(:environment) { create(:environment, name: 'production', project: build.project) } let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } let!(:last_deployment) { create(:deployment, :success, environment: environment, project: environment.project) } @@ -4797,7 +4848,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build with deployment has failed' do - let(:build) { create(:ci_build, :failed, environment: 'production', pipeline: pipeline, project: project) } + let(:build) { create(:ci_build, :failed, environment: 'production', pipeline: pipeline) } let(:environment) { create(:environment, name: 'production', project: build.project) } let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } @@ -4805,7 +4856,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build with deployment is running' do - let(:build) { create(:ci_build, environment: 'production', pipeline: pipeline, project: project) } + let(:build) { create(:ci_build, environment: 'production', pipeline: pipeline) } let(:environment) { create(:environment, name: 'production', project: build.project) } let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } @@ -4815,13 +4866,13 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#degenerated?' do context 'when build is degenerated' do - subject { create(:ci_build, :degenerated) } + subject { create(:ci_build, :degenerated, pipeline: pipeline) } it { is_expected.to be_degenerated } end context 'when build is valid' do - subject { create(:ci_build) } + subject { create(:ci_build, pipeline: pipeline) } it { is_expected.not_to be_degenerated } @@ -4836,7 +4887,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe 'degenerate!' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } subject { build.degenerate! } @@ -4856,13 +4907,13 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#archived?' do context 'when build is degenerated' do - subject { create(:ci_build, :degenerated) } + subject { create(:ci_build, :degenerated, pipeline: pipeline) } it { is_expected.to be_archived } end context 'for old build' do - subject { create(:ci_build, created_at: 1.day.ago) } + subject { create(:ci_build, created_at: 1.day.ago, pipeline: pipeline) } context 'when archive_builds_in is set' do before do @@ -4883,7 +4934,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#read_metadata_attribute' do - let(:build) { create(:ci_build, :degenerated) } + let(:build) { create(:ci_build, :degenerated, pipeline: pipeline) } let(:build_options) { { key: "build" } } let(:metadata_options) { { key: "metadata" } } let(:default_options) { { key: "default" } } @@ -4920,7 +4971,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#write_metadata_attribute' do - let(:build) { create(:ci_build, :degenerated) } + let(:build) { create(:ci_build, :degenerated, pipeline: pipeline) } let(:options) { { key: "new options" } } let(:existing_options) { { key: "existing options" } } @@ -5046,13 +5097,15 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do subject { build.environment_auto_stop_in } context 'when build option has environment auto_stop_in' do - let(:build) { create(:ci_build, options: { environment: { name: 'test', auto_stop_in: '1 day' } }) } + let(:build) do + create(:ci_build, options: { environment: { name: 'test', auto_stop_in: '1 day' } }, pipeline: pipeline) + end it { is_expected.to eq('1 day') } end context 'when build option does not have environment auto_stop_in' do - let(:build) { create(:ci_build) } + let(:build) { create(:ci_build, pipeline: pipeline) } it { is_expected.to be_nil } end @@ -5372,7 +5425,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '.build_matchers' do - let_it_be(:pipeline) { create(:ci_pipeline, :protected) } + let_it_be(:pipeline) { create(:ci_pipeline, :protected, project: project) } subject(:matchers) { pipeline.builds.build_matchers(pipeline.project) } @@ -5421,7 +5474,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#build_matcher' do let_it_be(:build) do - build_stubbed(:ci_build, tag_list: %w[tag1 tag2]) + build_stubbed(:ci_build, tag_list: %w[tag1 tag2], pipeline: pipeline) end subject(:matcher) { build.build_matcher } @@ -5557,7 +5610,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do it 'does not generate cross DB queries when a record is created via FactoryBot' do with_cross_database_modification_prevented do - create(:ci_build) + create(:ci_build, pipeline: pipeline) end end @@ -5585,7 +5638,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end it_behaves_like 'cleanup by a loose foreign key' do - let!(:model) { create(:ci_build, user: create(:user)) } + let!(:model) { create(:ci_build, user: create(:user), pipeline: pipeline) } let!(:parent) { model.user } end @@ -5595,7 +5648,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do context 'when given new job variables' do context 'when the cloned build has an action' do it 'applies the new job variables' do - build = create(:ci_build, :actionable) + build = create(:ci_build, :actionable, pipeline: pipeline) create(:ci_job_variable, job: build, key: 'TEST_KEY', value: 'old value') create(:ci_job_variable, job: build, key: 'OLD_KEY', value: 'i will not live for long') @@ -5614,7 +5667,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do context 'when the cloned build does not have an action' do it 'applies the old job variables' do - build = create(:ci_build) + build = create(:ci_build, pipeline: pipeline) create(:ci_job_variable, job: build, key: 'TEST_KEY', value: 'old value') new_build = build.clone( @@ -5632,7 +5685,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do context 'when not given new job variables' do it 'applies the old job variables' do - build = create(:ci_build) + build = create(:ci_build, pipeline: pipeline) create(:ci_job_variable, job: build, key: 'TEST_KEY', value: 'old value') new_build = build.clone(current_user: user) @@ -5646,14 +5699,14 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end describe '#test_suite_name' do - let(:build) { create(:ci_build, name: 'test') } + let(:build) { create(:ci_build, name: 'test', pipeline: pipeline) } it 'uses the group name for test suite name' do expect(build.test_suite_name).to eq('test') end context 'when build is part of parallel build' do - let(:build) { create(:ci_build, name: 'build 1/2') } + let(:build) { create(:ci_build, name: 'build 1/2', pipeline: pipeline) } it 'uses the group name for test suite name' do expect(build.test_suite_name).to eq('build') @@ -5661,7 +5714,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end context 'when build is part of matrix build' do - let!(:matrix_build) { create(:ci_build, :matrix) } + let!(:matrix_build) { create(:ci_build, :matrix, pipeline: pipeline) } it 'uses the job name for the test suite' do expect(matrix_build.test_suite_name).to eq(matrix_build.name) @@ -5672,7 +5725,8 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe '#runtime_hooks' do let(:build1) do FactoryBot.build(:ci_build, - options: { hooks: { pre_get_sources_script: ["echo 'hello pre_get_sources_script'"] } }) + options: { hooks: { pre_get_sources_script: ["echo 'hello pre_get_sources_script'"] } }, + pipeline: pipeline) end subject(:runtime_hooks) { build1.runtime_hooks } @@ -5687,7 +5741,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe 'partitioning', :ci_partitionable do include Ci::PartitioningHelpers - let(:new_pipeline) { create(:ci_pipeline) } + let(:new_pipeline) { create(:ci_pipeline, project: project) } let(:ci_build) { FactoryBot.build(:ci_build, pipeline: new_pipeline) } before do @@ -5711,7 +5765,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do describe 'assigning token', :ci_partitionable do include Ci::PartitioningHelpers - let(:new_pipeline) { create(:ci_pipeline) } + let(:new_pipeline) { create(:ci_pipeline, project: project) } let(:ci_build) { create(:ci_build, pipeline: new_pipeline) } before do @@ -5741,4 +5795,118 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do expect { build.remove_token! }.not_to change(build, :token) end end + + describe 'metadata partitioning', :ci_partitioning do + let(:pipeline) { create(:ci_pipeline, project: project, partition_id: ci_testing_partition_id) } + + let(:build) do + FactoryBot.build(:ci_build, pipeline: pipeline) + end + + it 'creates the metadata record and assigns its partition' do + # The record is initialized by the factory calling metadatable setters + build.metadata = nil + + expect(build.metadata).to be_nil + + expect(build.save!).to be_truthy + + expect(build.metadata).to be_present + expect(build.metadata).to be_valid + expect(build.metadata.partition_id).to eq(ci_testing_partition_id) + end + end + + describe 'secrets management id_tokens usage data' do + context 'when ID tokens are defined' do + context 'on create' do + let(:ci_build) { FactoryBot.build(:ci_build, user: user, id_tokens: { 'ID_TOKEN_1' => { aud: 'developers' } }) } + + it 'tracks RedisHLL event with user_id' do + expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event) + .with('i_ci_secrets_management_id_tokens_build_created', values: user.id) + + ci_build.save! + end + + it 'tracks Snowplow event with RedisHLL context' do + params = { + category: described_class.to_s, + action: 'create_id_tokens', + namespace: ci_build.namespace, + user: user, + label: 'redis_hll_counters.ci_secrets_management.i_ci_secrets_management_id_tokens_build_created_monthly', + ultimate_namespace_id: ci_build.namespace.root_ancestor.id, + context: [Gitlab::Tracking::ServicePingContext.new( + data_source: :redis_hll, + event: 'i_ci_secrets_management_id_tokens_build_created' + ).to_context.to_json] + } + + ci_build.save! + expect_snowplow_event(**params) + end + end + + context 'on update' do + let_it_be(:ci_build) { create(:ci_build, user: user, id_tokens: { 'ID_TOKEN_1' => { aud: 'developers' } }) } + + it 'does not track RedisHLL event' do + expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event) + + ci_build.success + end + + it 'does not track Snowplow event' do + ci_build.success + + expect_no_snowplow_event + end + end + end + + context 'when ID tokens are not defined' do + let(:ci_build) { FactoryBot.build(:ci_build, user: user) } + + context 'on create' do + it 'does not track RedisHLL event' do + expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event) + + ci_build.save! + end + + it 'does not track Snowplow event' do + ci_build.save! + expect_no_snowplow_event + end + end + end + end + + describe 'job artifact associations' do + Ci::JobArtifact.file_types.each do |type, _| + method = "job_artifacts_#{type}" + + describe "##{method}" do + subject { build.send(method) } + + context "when job has an artifact of type #{type}" do + let!(:artifact) do + create( + :ci_job_artifact, + job: build, + file_type: type, + file_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[type.to_sym] + ) + end + + it { is_expected.to eq(artifact) } + end + + context "when job has no artifact of type #{type}" do + it { is_expected.to be_nil } + end + end + end + end end diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb index fc5a9c879f6..e73319cfcd7 100644 --- a/spec/models/ci/group_variable_spec.rb +++ b/spec/models/ci/group_variable_spec.rb @@ -2,10 +2,13 @@ require 'spec_helper' -RSpec.describe Ci::GroupVariable do - subject { build(:ci_group_variable) } +RSpec.describe Ci::GroupVariable, feature_category: :pipeline_authoring do + let_it_be_with_refind(:group) { create(:group) } + + subject { build(:ci_group_variable, group: group) } it_behaves_like "CI variable" + it_behaves_like 'includes Limitable concern' it { is_expected.to include_module(Presentable) } it { is_expected.to include_module(Ci::Maskable) } diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index a1fd51f60ea..e94445f17cd 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::JobArtifact do +RSpec.describe Ci::JobArtifact, feature_category: :build_artifacts do let(:artifact) { create(:ci_job_artifact, :archive) } describe "Associations" do @@ -27,6 +27,29 @@ RSpec.describe Ci::JobArtifact do subject { build(:ci_job_artifact, :archive, job: job, size: 107464) } end + describe 'after_create_commit callback' do + it 'logs the job artifact create' do + artifact = build(:ci_job_artifact, file_type: 3, size: 8888, file_format: 2, locked: 1) + + expect(Gitlab::Ci::Artifacts::Logger).to receive(:log_created) do |record| + expect(record.size).to eq(artifact.size) + expect(record.file_type).to eq(artifact.file_type) + expect(record.file_format).to eq(artifact.file_format) + expect(record.locked).to eq(artifact.locked) + end + + artifact.save! + end + end + + describe 'after_destroy_commit callback' do + it 'logs the job artifact destroy' do + expect(Gitlab::Ci::Artifacts::Logger).to receive(:log_deleted).with(artifact, :log_destroy) + + artifact.destroy! + end + end + describe '.not_expired' do it 'returns artifacts that have not expired' do _expired_artifact = create(:ci_job_artifact, :expired) @@ -770,4 +793,10 @@ RSpec.describe Ci::JobArtifact do end end end + + describe '#filename' do + subject { artifact.filename } + + it { is_expected.to eq(artifact.file.filename) } + end end diff --git a/spec/models/ci/job_token/allowlist_spec.rb b/spec/models/ci/job_token/allowlist_spec.rb index 45083d64393..3a2673c7c26 100644 --- a/spec/models/ci/job_token/allowlist_spec.rb +++ b/spec/models/ci/job_token/allowlist_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integration do + include Ci::JobTokenScopeHelpers using RSpec::Parameterized::TableSyntax let_it_be(:source_project) { create(:project) } @@ -24,11 +25,11 @@ RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integratio end context 'when projects are added to the scope' do - include_context 'with scoped projects' + include_context 'with a project in each allowlist' where(:direction, :additional_project) do - :outbound | ref(:outbound_scoped_project) - :inbound | ref(:inbound_scoped_project) + :outbound | ref(:outbound_allowlist_project) + :inbound | ref(:inbound_allowlist_project) end with_them do @@ -39,6 +40,26 @@ RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integratio end end + describe 'add!' do + let_it_be(:added_project) { create(:project) } + let_it_be(:user) { create(:user) } + + subject { allowlist.add!(added_project, user: user) } + + [:inbound, :outbound].each do |d| + let(:direction) { d } + + it 'adds the project' do + subject + + expect(allowlist.projects).to contain_exactly(source_project, added_project) + expect(subject.added_by_id).to eq(user.id) + expect(subject.source_project_id).to eq(source_project.id) + expect(subject.target_project_id).to eq(added_project.id) + end + end + end + describe '#includes?' do subject { allowlist.includes?(includes_project) } @@ -57,16 +78,16 @@ RSpec.describe Ci::JobToken::Allowlist, feature_category: :continuous_integratio end end - context 'with scoped projects' do - include_context 'with scoped projects' + context 'with a project in each allowlist' do + include_context 'with a project in each allowlist' where(:includes_project, :direction, :result) do ref(:source_project) | :outbound | false ref(:source_project) | :inbound | false - ref(:inbound_scoped_project) | :outbound | false - ref(:inbound_scoped_project) | :inbound | true - ref(:outbound_scoped_project) | :outbound | true - ref(:outbound_scoped_project) | :inbound | false + ref(:inbound_allowlist_project) | :outbound | false + ref(:inbound_allowlist_project) | :inbound | true + ref(:outbound_allowlist_project) | :outbound | true + ref(:outbound_allowlist_project) | :inbound | false ref(:unscoped_project1) | :outbound | false ref(:unscoped_project1) | :inbound | false ref(:unscoped_project2) | :outbound | false diff --git a/spec/models/ci/job_token/project_scope_link_spec.rb b/spec/models/ci/job_token/project_scope_link_spec.rb index 91491733c44..310f9b550f4 100644 --- a/spec/models/ci/job_token/project_scope_link_spec.rb +++ b/spec/models/ci/job_token/project_scope_link_spec.rb @@ -18,15 +18,40 @@ RSpec.describe Ci::JobToken::ProjectScopeLink, feature_category: :continuous_int describe 'unique index' do let!(:link) { create(:ci_job_token_project_scope_link) } - it 'raises an error' do + it 'raises an error, when not unique' do expect do create(:ci_job_token_project_scope_link, source_project: link.source_project, - target_project: link.target_project) + target_project: link.target_project, + direction: link.direction) end.to raise_error(ActiveRecord::RecordNotUnique) end end + describe '.create' do + let_it_be(:target) { create(:project) } + let(:new_link) { described_class.create(source_project: project, target_project: target) } # rubocop:disable Rails/SaveBang + + context 'when there are more than PROJECT_LINK_DIRECTIONAL_LIMIT existing links' do + before do + create_list(:ci_job_token_project_scope_link, 5, source_project: project) + stub_const("#{described_class}::PROJECT_LINK_DIRECTIONAL_LIMIT", 3) + end + + it 'invalidates new links and prevents them from being created' do + expect { new_link }.not_to change { described_class.count } + expect(new_link).not_to be_persisted + expect(new_link.errors.full_messages) + .to include('Source project exceeds the allowable number of project links in this direction') + end + + it 'does not invalidate existing links' do + expect(described_class.count).to be > described_class::PROJECT_LINK_DIRECTIONAL_LIMIT + expect(described_class.all).to all(be_valid) + end + end + end + describe 'validations' do it 'must have a source project', :aggregate_failures do link = build(:ci_job_token_project_scope_link, source_project: nil) diff --git a/spec/models/ci/job_token/scope_spec.rb b/spec/models/ci/job_token/scope_spec.rb index 37c56973506..9ae061a3702 100644 --- a/spec/models/ci/job_token/scope_spec.rb +++ b/spec/models/ci/job_token/scope_spec.rb @@ -2,78 +2,171 @@ require 'spec_helper' -RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration do - let_it_be(:source_project) { create(:project, ci_outbound_job_token_scope_enabled: true) } +RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration, factory_default: :keep do + include Ci::JobTokenScopeHelpers + using RSpec::Parameterized::TableSyntax + + let_it_be(:project) { create_default(:project) } + let_it_be(:user) { create_default(:user) } + let_it_be(:namespace) { create_default(:namespace) } + + let_it_be(:source_project) do + create(:project, + ci_outbound_job_token_scope_enabled: true, + ci_inbound_job_token_scope_enabled: true + ) + end + + let(:current_project) { source_project } - let(:scope) { described_class.new(source_project) } + let(:scope) { described_class.new(current_project) } - describe '#all_projects' do - subject(:all_projects) { scope.all_projects } + describe '#outbound_projects' do + subject { scope.outbound_projects } context 'when no projects are added to the scope' do it 'returns the project defining the scope' do - expect(all_projects).to contain_exactly(source_project) + expect(subject).to contain_exactly(current_project) end end context 'when projects are added to the scope' do - include_context 'with scoped projects' + include_context 'with accessible and inaccessible projects' it 'returns all projects that can be accessed from a given scope' do - expect(subject).to contain_exactly(source_project, outbound_scoped_project) + expect(subject).to contain_exactly(current_project, outbound_allowlist_project, fully_accessible_project) end end end - describe '#allows?' do - subject { scope.allows?(includes_project) } + describe '#inbound_projects' do + subject { scope.inbound_projects } - context 'without scoped projects' do - context 'when self referential' do - let(:includes_project) { source_project } + context 'when no projects are added to the scope' do + it 'returns the project defining the scope' do + expect(subject).to contain_exactly(current_project) + end + end + + context 'when projects are added to the scope' do + include_context 'with accessible and inaccessible projects' - it { is_expected.to be_truthy } + it 'returns all projects that can be accessed from a given scope' do + expect(subject).to contain_exactly(current_project, inbound_allowlist_project) end end + end + + describe 'add!' do + let_it_be(:new_project) { create(:project) } - context 'with scoped projects' do - include_context 'with scoped projects' + subject { scope.add!(new_project, direction: direction, user: user) } - context 'when project is in outbound scope' do - let(:includes_project) { outbound_scoped_project } + [:inbound, :outbound].each do |d| + let(:direction) { d } - it { is_expected.to be_truthy } + it 'adds the project' do + subject + + expect(scope.send("#{direction}_projects")).to contain_exactly(current_project, new_project) end + end - context 'when project is in inbound scope' do - let(:includes_project) { inbound_scoped_project } + # Context and before block can go away leaving just the example in 16.0 + context 'with inbound only enabled' do + before do + project.ci_cd_settings.update!(job_token_scope_enabled: false) + end - it { is_expected.to be_falsey } + it 'provides access' do + expect do + scope.add!(new_project, direction: :inbound, user: user) + end.to change { described_class.new(new_project).accessible?(current_project) }.from(false).to(true) end + end + end + + RSpec.shared_examples 'enforces outbound scope only' do + include_context 'with accessible and inaccessible projects' + + where(:accessed_project, :result) do + ref(:current_project) | true + ref(:inbound_allowlist_project) | false + ref(:unscoped_project1) | false + ref(:unscoped_project2) | false + ref(:outbound_allowlist_project) | true + ref(:inbound_accessible_project) | false + ref(:fully_accessible_project) | true + end - context 'when project is linked to a different project' do - let(:includes_project) { unscoped_project1 } + with_them do + it { is_expected.to eq(result) } + end + end + + describe 'accessible?' do + subject { scope.accessible?(accessed_project) } + + context 'with inbound and outbound scopes enabled' do + context 'when inbound and outbound access setup' do + include_context 'with accessible and inaccessible projects' + + where(:accessed_project, :result) do + ref(:current_project) | true + ref(:inbound_allowlist_project) | false + ref(:unscoped_project1) | false + ref(:unscoped_project2) | false + ref(:outbound_allowlist_project) | false + ref(:inbound_accessible_project) | false + ref(:fully_accessible_project) | true + end + + with_them do + it 'allows self and projects allowed from both directions' do + is_expected.to eq(result) + end + end + end + end - it { is_expected.to be_falsey } + context 'with inbound scope enabled and outbound scope disabled' do + before do + accessed_project.update!(ci_inbound_job_token_scope_enabled: true) + current_project.update!(ci_outbound_job_token_scope_enabled: false) end - context 'when project is unlinked to a project' do - let(:includes_project) { unscoped_project2 } + include_context 'with accessible and inaccessible projects' - it { is_expected.to be_falsey } + where(:accessed_project, :result) do + ref(:current_project) | true + ref(:inbound_allowlist_project) | false + ref(:unscoped_project1) | false + ref(:unscoped_project2) | false + ref(:outbound_allowlist_project) | false + ref(:inbound_accessible_project) | true + ref(:fully_accessible_project) | true end - context 'when project scope setting is disabled' do - let(:includes_project) { unscoped_project1 } + with_them do + it { is_expected.to eq(result) } + end + end - before do - source_project.ci_outbound_job_token_scope_enabled = false - end + context 'with inbound scope disabled and outbound scope enabled' do + before do + accessed_project.update!(ci_inbound_job_token_scope_enabled: false) + current_project.update!(ci_outbound_job_token_scope_enabled: true) + end - it 'considers any project to be part of the scope' do - expect(subject).to be_truthy - end + include_examples 'enforces outbound scope only' + end + + context 'when inbound scope flag disabled' do + before do + stub_feature_flags(ci_inbound_job_token_scope: false) end + + include_examples 'enforces outbound scope only' end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 5888f9d109c..61422978df7 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -226,9 +226,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: let_it_be(:pipeline2) { create(:ci_pipeline, name: 'Chatops pipeline') } context 'when name exists' do - let(:name) { 'build Pipeline' } + let(:name) { 'Build pipeline' } - it 'performs case insensitive compare' do + it 'performs exact compare' do is_expected.to contain_exactly(pipeline1) end end @@ -1070,296 +1070,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end end - describe '#predefined_variables' do - subject { pipeline.predefined_variables } - - let(:pipeline) { build(:ci_empty_pipeline, :created) } - - it 'includes all predefined variables in a valid order' do - keys = subject.map { |variable| variable[:key] } - - expect(keys).to eq %w[ - CI_PIPELINE_IID - CI_PIPELINE_SOURCE - CI_PIPELINE_CREATED_AT - CI_COMMIT_SHA - CI_COMMIT_SHORT_SHA - CI_COMMIT_BEFORE_SHA - CI_COMMIT_REF_NAME - CI_COMMIT_REF_SLUG - CI_COMMIT_BRANCH - CI_COMMIT_MESSAGE - CI_COMMIT_TITLE - CI_COMMIT_DESCRIPTION - CI_COMMIT_REF_PROTECTED - CI_COMMIT_TIMESTAMP - CI_COMMIT_AUTHOR - CI_BUILD_REF - CI_BUILD_BEFORE_SHA - CI_BUILD_REF_NAME - CI_BUILD_REF_SLUG - ] - end - - context 'when merge request is present' do - let_it_be(:assignees) { create_list(:user, 2) } - let_it_be(:milestone) { create(:milestone, project: project) } - let_it_be(:labels) { create_list(:label, 2) } - - let(:merge_request) do - create(:merge_request, :simple, - source_project: project, - target_project: project, - assignees: assignees, - milestone: milestone, - labels: labels) - end - - context 'when pipeline for merge request is created' do - let(:pipeline) do - create(:ci_pipeline, :detached_merge_request_pipeline, - ci_ref_presence: false, - user: user, - merge_request: merge_request) - end - - before do - project.add_developer(user) - end - - it 'exposes merge request pipeline variables' do - expect(subject.to_hash) - .to include( - 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s, - 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s, - 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s, - 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s, - 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path, - 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url, - 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s, - 'CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED' => ProtectedBranch.protected?(merge_request.target_project, merge_request.target_branch).to_s, - 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => '', - 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s, - 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path, - 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url, - 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s, - 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => '', - 'CI_MERGE_REQUEST_TITLE' => merge_request.title, - 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list, - 'CI_MERGE_REQUEST_MILESTONE' => milestone.title, - 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','), - 'CI_MERGE_REQUEST_EVENT_TYPE' => 'detached', - 'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true)) - end - - it 'exposes diff variables' do - expect(subject.to_hash) - .to include( - 'CI_MERGE_REQUEST_DIFF_ID' => merge_request.merge_request_diff.id.to_s, - 'CI_MERGE_REQUEST_DIFF_BASE_SHA' => merge_request.merge_request_diff.base_commit_sha) - end - - context 'without assignee' do - let(:assignees) { [] } - - it 'does not expose assignee variable' do - expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES') - end - end - - context 'without milestone' do - let(:milestone) { nil } - - it 'does not expose milestone variable' do - expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_MILESTONE') - end - end - - context 'without labels' do - let(:labels) { [] } - - it 'does not expose labels variable' do - expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS') - end - end - end - - context 'when pipeline on branch is created' do - let(:pipeline) do - create(:ci_pipeline, project: project, user: user, ref: 'feature') - end - - context 'when a merge request is created' do - before do - merge_request - end - - context 'when user has access to project' do - before do - project.add_developer(user) - end - - it 'merge request references are returned matching the pipeline' do - expect(subject.to_hash).to include( - 'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true)) - end - end - - context 'when user does not have access to project' do - it 'CI_OPEN_MERGE_REQUESTS is not returned' do - expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS') - end - end - end - - context 'when no a merge request is created' do - it 'CI_OPEN_MERGE_REQUESTS is not returned' do - expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS') - end - end - end - - context 'with merged results' do - let(:pipeline) do - create(:ci_pipeline, :merged_result_pipeline, merge_request: merge_request) - end - - it 'exposes merge request pipeline variables' do - expect(subject.to_hash) - .to include( - 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s, - 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s, - 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s, - 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s, - 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path, - 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url, - 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s, - 'CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED' => ProtectedBranch.protected?(merge_request.target_project, merge_request.target_branch).to_s, - 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => merge_request.target_branch_sha, - 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s, - 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path, - 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url, - 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s, - 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => merge_request.source_branch_sha, - 'CI_MERGE_REQUEST_TITLE' => merge_request.title, - 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list, - 'CI_MERGE_REQUEST_MILESTONE' => milestone.title, - 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','), - 'CI_MERGE_REQUEST_EVENT_TYPE' => 'merged_result') - end - - it 'exposes diff variables' do - expect(subject.to_hash) - .to include( - 'CI_MERGE_REQUEST_DIFF_ID' => merge_request.merge_request_diff.id.to_s, - 'CI_MERGE_REQUEST_DIFF_BASE_SHA' => merge_request.merge_request_diff.base_commit_sha) - end - end - end - - context 'when source is external pull request' do - let(:pipeline) do - create(:ci_pipeline, source: :external_pull_request_event, external_pull_request: pull_request) - end - - let(:pull_request) { create(:external_pull_request, project: project) } - - it 'exposes external pull request pipeline variables' do - expect(subject.to_hash) - .to include( - 'CI_EXTERNAL_PULL_REQUEST_IID' => pull_request.pull_request_iid.to_s, - 'CI_EXTERNAL_PULL_REQUEST_SOURCE_REPOSITORY' => pull_request.source_repository, - 'CI_EXTERNAL_PULL_REQUEST_TARGET_REPOSITORY' => pull_request.target_repository, - 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_SHA' => pull_request.source_sha, - 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA' => pull_request.target_sha, - 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME' => pull_request.source_branch, - 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME' => pull_request.target_branch - ) - end - end - - describe 'variable CI_KUBERNETES_ACTIVE' do - context 'when pipeline.has_kubernetes_active? is true' do - before do - allow(pipeline).to receive(:has_kubernetes_active?).and_return(true) - end - - it "is included with value 'true'" do - expect(subject.to_hash).to include('CI_KUBERNETES_ACTIVE' => 'true') - end - end - - context 'when pipeline.has_kubernetes_active? is false' do - before do - allow(pipeline).to receive(:has_kubernetes_active?).and_return(false) - end - - it 'is not included' do - expect(subject.to_hash).not_to have_key('CI_KUBERNETES_ACTIVE') - end - end - end - - describe 'variable CI_GITLAB_FIPS_MODE' do - context 'when FIPS flag is enabled' do - before do - allow(Gitlab::FIPS).to receive(:enabled?).and_return(true) - end - - it "is included with value 'true'" do - expect(subject.to_hash).to include('CI_GITLAB_FIPS_MODE' => 'true') - end - end - - context 'when FIPS flag is disabled' do - before do - allow(Gitlab::FIPS).to receive(:enabled?).and_return(false) - end - - it 'is not included' do - expect(subject.to_hash).not_to have_key('CI_GITLAB_FIPS_MODE') - end - end - end - - context 'when tag is not found' do - let(:pipeline) do - create(:ci_pipeline, project: project, ref: 'not_found_tag', tag: true) - end - - it 'does not expose tag variables' do - expect(subject.to_hash.keys) - .not_to include( - 'CI_COMMIT_TAG', - 'CI_COMMIT_TAG_MESSAGE', - 'CI_BUILD_TAG' - ) - end - end - - context 'without a commit' do - let(:pipeline) { build(:ci_empty_pipeline, :created, sha: nil) } - - it 'does not expose commit variables' do - expect(subject.to_hash.keys) - .not_to include( - 'CI_COMMIT_SHA', - 'CI_COMMIT_SHORT_SHA', - 'CI_COMMIT_BEFORE_SHA', - 'CI_COMMIT_REF_NAME', - 'CI_COMMIT_REF_SLUG', - 'CI_COMMIT_BRANCH', - 'CI_COMMIT_TAG', - 'CI_COMMIT_MESSAGE', - 'CI_COMMIT_TITLE', - 'CI_COMMIT_DESCRIPTION', - 'CI_COMMIT_REF_PROTECTED', - 'CI_COMMIT_TIMESTAMP', - 'CI_COMMIT_AUTHOR') - end - end - end - describe '#protected_ref?' do let(:pipeline) { build(:ci_empty_pipeline, :created) } @@ -5664,6 +5374,34 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end end + describe '#merge_request_diff' do + context 'when the pipeline has no merge request' do + it 'is nil' do + pipeline = build(:ci_empty_pipeline) + + expect(pipeline.merge_request_diff).to be_nil + end + end + + context 'when the pipeline has a merge request' do + context 'when the pipeline is a merged result pipeline' do + it 'returns the diff for the source sha' do + pipeline = create(:ci_pipeline, :merged_result_pipeline) + + expect(pipeline.merge_request_diff.head_commit_sha).to eq(pipeline.source_sha) + end + end + + context 'when the pipeline is not a merged result pipeline' do + it 'returns the diff for the pipeline sha' do + pipeline = create(:ci_pipeline, merge_request: create(:merge_request)) + + expect(pipeline.merge_request_diff.head_commit_sha).to eq(pipeline.sha) + end + end + end + end + describe 'partitioning' do let(:pipeline) { build(:ci_pipeline, partition_id: nil) } diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb index 07fac4ee2f7..db22d8f3a6c 100644 --- a/spec/models/ci/processable_spec.rb +++ b/spec/models/ci/processable_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::Processable do +RSpec.describe Ci::Processable, feature_category: :continuous_integration do let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } @@ -83,7 +83,7 @@ RSpec.describe Ci::Processable do runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason sourced_pipelines sourced_pipeline artifacts_file_store artifacts_metadata_store - metadata runner_session trace_chunks upstream_pipeline_id + metadata runner_machine_id runner_machine runner_session trace_chunks upstream_pipeline_id artifacts_file artifacts_metadata artifacts_size commands resource resource_group_id processed security_scans author pipeline_id report_results pending_state pages_deployments @@ -287,6 +287,12 @@ RSpec.describe Ci::Processable do end end + context 'when the processable is a bridge' do + subject(:processable) { create(:ci_bridge, pipeline: pipeline) } + + it_behaves_like 'retryable processable' + end + context 'when the processable is a build' do subject(:processable) { create(:ci_build, pipeline: pipeline) } diff --git a/spec/models/ci/runner_machine_spec.rb b/spec/models/ci/runner_machine_spec.rb index e39f987110f..d0979d8a485 100644 --- a/spec/models/ci/runner_machine_spec.rb +++ b/spec/models/ci/runner_machine_spec.rb @@ -6,11 +6,14 @@ RSpec.describe Ci::RunnerMachine, feature_category: :runner_fleet, type: :model it_behaves_like 'having unique enum values' it { is_expected.to belong_to(:runner) } + it { is_expected.to belong_to(:runner_version).with_foreign_key(:version) } + it { is_expected.to have_many(:build_metadata) } + it { is_expected.to have_many(:builds).through(:build_metadata) } describe 'validation' do it { is_expected.to validate_presence_of(:runner) } - it { is_expected.to validate_presence_of(:machine_xid) } - it { is_expected.to validate_length_of(:machine_xid).is_at_most(64) } + it { is_expected.to validate_presence_of(:system_xid) } + it { is_expected.to validate_length_of(:system_xid).is_at_most(64) } it { is_expected.to validate_length_of(:version).is_at_most(2048) } it { is_expected.to validate_length_of(:revision).is_at_most(255) } it { is_expected.to validate_length_of(:platform).is_at_most(255) } @@ -37,10 +40,11 @@ RSpec.describe Ci::RunnerMachine, feature_category: :runner_fleet, type: :model describe '.stale', :freeze_time do subject { described_class.stale.ids } - let!(:runner_machine1) { create(:ci_runner_machine, created_at: 8.days.ago, contacted_at: 7.days.ago) } - let!(:runner_machine2) { create(:ci_runner_machine, created_at: 7.days.ago, contacted_at: nil) } - let!(:runner_machine3) { create(:ci_runner_machine, created_at: 5.days.ago, contacted_at: nil) } - let!(:runner_machine4) do + let!(:runner_machine1) { create(:ci_runner_machine, :stale) } + let!(:runner_machine2) { create(:ci_runner_machine, :stale, contacted_at: nil) } + let!(:runner_machine3) { create(:ci_runner_machine, created_at: 6.months.ago, contacted_at: Time.current) } + let!(:runner_machine4) { create(:ci_runner_machine, created_at: 5.days.ago) } + let!(:runner_machine5) do create(:ci_runner_machine, created_at: (7.days - 1.second).ago, contacted_at: (7.days - 1.second).ago) end @@ -48,4 +52,146 @@ RSpec.describe Ci::RunnerMachine, feature_category: :runner_fleet, type: :model is_expected.to match_array([runner_machine1.id, runner_machine2.id]) end end + + describe '#heartbeat', :freeze_time do + let(:runner_machine) { create(:ci_runner_machine) } + let(:executor) { 'shell' } + let(:version) { '15.0.1' } + let(:values) do + { + ip_address: '8.8.8.8', + architecture: '18-bit', + config: { gpus: "all" }, + executor: executor, + version: version + } + end + + subject(:heartbeat) do + runner_machine.heartbeat(values) + end + + context 'when database was updated recently' do + before do + runner_machine.contacted_at = Time.current + end + + it 'schedules version update' do + expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).to receive(:perform_async).with(version).once + + heartbeat + + expect(runner_machine.runner_version).to be_nil + end + + it 'updates cache' do + expect_redis_update + + heartbeat + end + + context 'with only ip_address specified' do + let(:values) do + { ip_address: '1.1.1.1' } + end + + it 'updates only ip_address' do + attrs = Gitlab::Json.dump(ip_address: '1.1.1.1', contacted_at: Time.current) + + Gitlab::Redis::Cache.with do |redis| + redis_key = runner_machine.send(:cache_attribute_key) + expect(redis).to receive(:set).with(redis_key, attrs, any_args) + end + + heartbeat + end + end + end + + context 'when database was not updated recently' do + before do + runner_machine.contacted_at = 2.hours.ago + + allow(Ci::Runners::ProcessRunnerVersionUpdateWorker).to receive(:perform_async).with(version).once + end + + context 'with invalid runner_machine' do + before do + runner_machine.runner = nil + end + + it 'still updates redis cache and database' do + expect(runner_machine).to be_invalid + + expect_redis_update + does_db_update + + expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).to have_received(:perform_async) + .with(version).once + end + end + + context 'with unchanged runner_machine version' do + let(:runner_machine) { create(:ci_runner_machine, version: version) } + + it 'does not schedule ci_runner_versions update' do + heartbeat + + expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).not_to have_received(:perform_async) + end + end + + it 'updates redis cache and database' do + expect_redis_update + does_db_update + + expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).to have_received(:perform_async) + .with(version).once + end + + Ci::Runner::EXECUTOR_NAME_TO_TYPES.each_key do |executor| + context "with #{executor} executor" do + let(:executor) { executor } + + it 'updates with expected executor type' do + expect_redis_update + + heartbeat + + expect(runner_machine.reload.read_attribute(:executor_type)).to eq(expected_executor_type) + end + + def expected_executor_type + executor.gsub(/[+-]/, '_') + end + end + end + + context "with an unknown executor type" do + let(:executor) { 'some-unknown-type' } + + it 'updates with unknown executor type' do + expect_redis_update + + heartbeat + + expect(runner_machine.reload.read_attribute(:executor_type)).to eq('unknown') + end + end + end + + def expect_redis_update + Gitlab::Redis::Cache.with do |redis| + redis_key = runner_machine.send(:cache_attribute_key) + expect(redis).to receive(:set).with(redis_key, anything, any_args).and_call_original + end + end + + def does_db_update + expect { heartbeat }.to change { runner_machine.reload.read_attribute(:contacted_at) } + .and change { runner_machine.reload.read_attribute(:architecture) } + .and change { runner_machine.reload.read_attribute(:config) } + .and change { runner_machine.reload.read_attribute(:executor_type) } + end + end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index b7c7b67b98f..01d5fe7f90b 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::Runner, feature_category: :runner do +RSpec.describe Ci::Runner, type: :model, feature_category: :runner do include StubGitlabCalls it_behaves_like 'having unique enum values' @@ -85,6 +85,7 @@ RSpec.describe Ci::Runner, feature_category: :runner do describe 'validation' do it { is_expected.to validate_presence_of(:access_level) } it { is_expected.to validate_presence_of(:runner_type) } + it { is_expected.to validate_presence_of(:registration_type) } context 'when runner is not allowed to pick untagged jobs' do context 'when runner does not have tags' do @@ -259,16 +260,16 @@ RSpec.describe Ci::Runner, feature_category: :runner do end describe '.belonging_to_project' do - it 'returns the specific project runner' do + it 'returns the project runner' do # own - specific_project = create(:project) - specific_runner = create(:ci_runner, :project, projects: [specific_project]) + own_project = create(:project) + own_runner = create(:ci_runner, :project, projects: [own_project]) # other other_project = create(:project) create(:ci_runner, :project, projects: [other_project]) - expect(described_class.belonging_to_project(specific_project.id)).to eq [specific_runner] + expect(described_class.belonging_to_project(own_project.id)).to eq [own_runner] end end @@ -285,7 +286,7 @@ RSpec.describe Ci::Runner, feature_category: :runner do subject(:result) { described_class.belonging_to_parent_group_of_project(project_id) } - it 'returns the specific group runner' do + it 'returns the group runner' do expect(result).to contain_exactly(runner1) end @@ -339,7 +340,7 @@ RSpec.describe Ci::Runner, feature_category: :runner do describe '.owned_or_instance_wide' do subject { described_class.owned_or_instance_wide(project.id) } - it 'returns a globally shared, a project specific and a group specific runner' do + it 'returns a shared, project and group runner' do is_expected.to contain_exactly(group_runner, project_runner, shared_runner) end end @@ -352,7 +353,7 @@ RSpec.describe Ci::Runner, feature_category: :runner do project_runner end - it 'returns a globally shared and a group specific runner' do + it 'returns a globally shared and a group runner' do is_expected.to contain_exactly(group_runner, shared_runner) end end @@ -382,7 +383,7 @@ RSpec.describe Ci::Runner, feature_category: :runner do context 'with group runners disabled' do let(:group_runners_enabled) { false } - it 'returns only the project specific runner' do + it 'returns only the project runner' do is_expected.to contain_exactly(project_runner) end end @@ -390,7 +391,7 @@ RSpec.describe Ci::Runner, feature_category: :runner do context 'with group runners enabled' do let(:group_runners_enabled) { true } - it 'returns a project specific and a group specific runner' do + it 'returns a project runner and a group runner' do is_expected.to contain_exactly(group_runner, project_runner) end end @@ -404,7 +405,7 @@ RSpec.describe Ci::Runner, feature_category: :runner do project_runner end - it 'returns a group specific runner' do + it 'returns a group runner' do is_expected.to contain_exactly(group_runner) end end @@ -1737,6 +1738,40 @@ RSpec.describe Ci::Runner, feature_category: :runner do end end + describe '#short_sha' do + subject(:short_sha) { runner.short_sha } + + context 'when registered via command-line' do + let(:runner) { create(:ci_runner) } + + specify { expect(runner.token).not_to start_with(described_class::CREATED_RUNNER_TOKEN_PREFIX) } + it { is_expected.not_to start_with(described_class::CREATED_RUNNER_TOKEN_PREFIX) } + end + + context 'when creating new runner via UI' do + let(:runner) { create(:ci_runner, registration_type: :authenticated_user) } + + specify { expect(runner.token).to start_with(described_class::CREATED_RUNNER_TOKEN_PREFIX) } + it { is_expected.not_to start_with(described_class::CREATED_RUNNER_TOKEN_PREFIX) } + end + end + + describe '#token' do + subject(:token) { runner.token } + + context 'when runner is registered' do + let(:runner) { create(:ci_runner) } + + it { is_expected.not_to start_with('glrt-') } + end + + context 'when runner is created via UI' do + let(:runner) { create(:ci_runner, registration_type: :authenticated_user) } + + it { is_expected.to start_with('glrt-') } + end + end + describe '#token_expires_at', :freeze_time do shared_examples 'expiring token' do |interval:| it 'expires' do @@ -1915,7 +1950,7 @@ RSpec.describe Ci::Runner, feature_category: :runner do end end - describe '#with_upgrade_status' do + describe '.with_upgrade_status' do subject { described_class.with_upgrade_status(upgrade_status) } let_it_be(:runner_14_0_0) { create(:ci_runner, version: '14.0.0') } @@ -1923,12 +1958,12 @@ RSpec.describe Ci::Runner, feature_category: :runner do let_it_be(:runner_14_1_1) { create(:ci_runner, version: '14.1.1') } let_it_be(:runner_version_14_0_0) { create(:ci_runner_version, version: '14.0.0', status: :available) } let_it_be(:runner_version_14_1_0) { create(:ci_runner_version, version: '14.1.0', status: :recommended) } - let_it_be(:runner_version_14_1_1) { create(:ci_runner_version, version: '14.1.1', status: :not_available) } + let_it_be(:runner_version_14_1_1) { create(:ci_runner_version, version: '14.1.1', status: :unavailable) } - context ':not_available' do - let(:upgrade_status) { :not_available } + context ':unavailable' do + let(:upgrade_status) { :unavailable } - it 'returns runners whose version is assigned :not_available' do + it 'returns runners whose version is assigned :unavailable' do is_expected.to contain_exactly(runner_14_1_1) end end diff --git a/spec/models/ci/runner_version_spec.rb b/spec/models/ci/runner_version_spec.rb index dfaa2201859..51a2f14c57c 100644 --- a/spec/models/ci/runner_version_spec.rb +++ b/spec/models/ci/runner_version_spec.rb @@ -3,33 +3,35 @@ require 'spec_helper' RSpec.describe Ci::RunnerVersion, feature_category: :runner_fleet do - let_it_be(:runner_version_recommended) do + let_it_be(:runner_version_upgrade_recommended) do create(:ci_runner_version, version: 'abc234', status: :recommended) end - let_it_be(:runner_version_not_available) do - create(:ci_runner_version, version: 'abc123', status: :not_available) + let_it_be(:runner_version_upgrade_unavailable) do + create(:ci_runner_version, version: 'abc123', status: :unavailable) end + it { is_expected.to have_many(:runner_machines).with_foreign_key(:version) } + it_behaves_like 'having unique enum values' - describe '.not_available' do - subject { described_class.not_available } + describe '.unavailable' do + subject { described_class.unavailable } - it { is_expected.to match_array([runner_version_not_available]) } + it { is_expected.to match_array([runner_version_upgrade_unavailable]) } end describe '.potentially_outdated' do subject { described_class.potentially_outdated } let_it_be(:runner_version_nil) { create(:ci_runner_version, version: 'abc345', status: nil) } - let_it_be(:runner_version_available) do + let_it_be(:runner_version_upgrade_available) do create(:ci_runner_version, version: 'abc456', status: :available) end it 'contains any valid or unprocessed runner version that is not already recommended' do is_expected.to match_array( - [runner_version_nil, runner_version_not_available, runner_version_available] + [runner_version_nil, runner_version_upgrade_unavailable, runner_version_upgrade_available] ) end end diff --git a/spec/models/ci/running_build_spec.rb b/spec/models/ci/running_build_spec.rb index 1a5ea044ba3..7f254bd235c 100644 --- a/spec/models/ci/running_build_spec.rb +++ b/spec/models/ci/running_build_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Ci::RunningBuild, feature_category: :continuous_integration do end end - context 'when build has been picked by a specific runner' do + context 'when build has been picked by a project runner' do let(:runner) { create(:ci_runner, :project) } it 'raises an error' do diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb index 87077fe2db1..38ae908fb00 100644 --- a/spec/models/ci/secure_file_spec.rb +++ b/spec/models/ci/secure_file_spec.rb @@ -101,6 +101,11 @@ RSpec.describe Ci::SecureFile do file = build(:ci_secure_file, name: 'file1.tar.gz') expect(file.file_extension).to eq('gz') end + + it 'returns nil if there is no file extension' do + file = build(:ci_secure_file, name: 'file1') + expect(file.file_extension).to be nil + end end describe '#metadata_parsable?' do diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 8517e583ec7..5eef719ae0c 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::Trigger do +RSpec.describe Ci::Trigger, feature_category: :continuous_integration do let(:project) { create :project } describe 'associations' do @@ -86,4 +86,40 @@ RSpec.describe Ci::Trigger do let!(:model) { create(:ci_trigger, project: parent) } end end + + describe 'encrypted_token' do + context 'when token is not provided' do + it 'encrypts the generated token' do + trigger = create(:ci_trigger_without_token, project: project) + + expect(trigger.token).not_to be_nil + expect(trigger.encrypted_token).not_to be_nil + expect(trigger.encrypted_token_iv).not_to be_nil + + expect(trigger.reload.encrypted_token_tmp).to eq(trigger.token) + end + end + + context 'when token is provided' do + it 'encrypts the given token' do + trigger = create(:ci_trigger, project: project) + + expect(trigger.token).not_to be_nil + expect(trigger.encrypted_token).not_to be_nil + expect(trigger.encrypted_token_iv).not_to be_nil + + expect(trigger.reload.encrypted_token_tmp).to eq(trigger.token) + end + end + + context 'when token is being updated' do + it 'encrypts the given token' do + trigger = create(:ci_trigger, project: project, token: "token") + expect { trigger.update!(token: "new token") } + .to change { trigger.encrypted_token } + .and change { trigger.encrypted_token_iv } + .and change { trigger.encrypted_token_tmp }.from("token").to("new token") + end + end + end end diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 5f2b5971508..ce64b3ea158 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -2,10 +2,13 @@ require 'spec_helper' -RSpec.describe Ci::Variable do - subject { build(:ci_variable) } +RSpec.describe Ci::Variable, feature_category: :pipeline_authoring do + let_it_be_with_reload(:project) { create(:project) } + + subject { build(:ci_variable, project: project) } it_behaves_like "CI variable" + it_behaves_like 'includes Limitable concern' describe 'validations' do it { is_expected.to include_module(Presentable) } |