summaryrefslogtreecommitdiff
path: root/spec/models/ci
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/ci')
-rw-r--r--spec/models/ci/bridge_spec.rb40
-rw-r--r--spec/models/ci/build_metadata_spec.rb4
-rw-r--r--spec/models/ci/build_pending_state_spec.rb9
-rw-r--r--spec/models/ci/build_spec.rb504
-rw-r--r--spec/models/ci/group_variable_spec.rb7
-rw-r--r--spec/models/ci/job_artifact_spec.rb31
-rw-r--r--spec/models/ci/job_token/allowlist_spec.rb39
-rw-r--r--spec/models/ci/job_token/project_scope_link_spec.rb29
-rw-r--r--spec/models/ci/job_token/scope_spec.rb165
-rw-r--r--spec/models/ci/pipeline_spec.rb322
-rw-r--r--spec/models/ci/processable_spec.rb10
-rw-r--r--spec/models/ci/runner_machine_spec.rb158
-rw-r--r--spec/models/ci/runner_spec.rb67
-rw-r--r--spec/models/ci/runner_version_spec.rb18
-rw-r--r--spec/models/ci/running_build_spec.rb2
-rw-r--r--spec/models/ci/secure_file_spec.rb5
-rw-r--r--spec/models/ci/trigger_spec.rb38
-rw-r--r--spec/models/ci/variable_spec.rb7
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) }