Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-20 13:43:29 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-20 13:43:29 +0300
commit3b1af5cc7ed2666ff18b718ce5d30fa5a2756674 (patch)
tree3bc4a40e0ee51ec27eabf917c537033c0c5b14d4 /spec/models/ci
parent9bba14be3f2c211bf79e15769cd9b77bc73a13bc (diff)
Add latest changes from gitlab-org/gitlab@16-1-stable-eev16.1.0-rc42
Diffstat (limited to 'spec/models/ci')
-rw-r--r--spec/models/ci/build_spec.rb384
-rw-r--r--spec/models/ci/catalog/listing_spec.rb31
-rw-r--r--spec/models/ci/catalog/resource_spec.rb46
-rw-r--r--spec/models/ci/group_variable_spec.rb30
-rw-r--r--spec/models/ci/job_annotation_spec.rb81
-rw-r--r--spec/models/ci/pipeline_spec.rb359
-rw-r--r--spec/models/ci/processable_spec.rb4
-rw-r--r--spec/models/ci/runner_spec.rb3
-rw-r--r--spec/models/ci/secure_file_spec.rb27
9 files changed, 348 insertions, 617 deletions
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index e3e78acb7e5..51cd6efb85f 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1882,20 +1882,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
it { is_expected.to eq('review/x') }
end
- context 'when FF `ci_remove_legacy_predefined_variables` is disabled' do
- before do
- stub_feature_flags(ci_remove_legacy_predefined_variables: false)
- end
-
- context 'when using persisted variables' do
- let(:build) do
- create(:ci_build, environment: 'review/x$CI_BUILD_ID', pipeline: pipeline)
- end
-
- it { is_expected.to eq('review/x') }
- end
- end
-
context 'when environment name uses a nested variable' do
let(:yaml_variables) do
[
@@ -2664,16 +2650,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
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', pipeline: pipeline) }
-
- before do
- create(:deployment, :blocked, deployable: subject)
- end
-
- it { is_expected.not_to be_playable }
- end
end
describe 'project settings' do
@@ -2954,97 +2930,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
]
end
- # Remove this definition when FF `ci_remove_legacy_predefined_variables` is removed
- let(:predefined_with_legacy_variables) do
- [
- { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true, masked: false },
- { key: 'CI_PIPELINE_URL', value: project.web_url + "/-/pipelines/#{pipeline.id}", public: true, masked: false },
- { key: 'CI_JOB_ID', value: build.id.to_s, public: true, masked: false },
- { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true, masked: false },
- { key: 'CI_JOB_TOKEN', value: 'my-token', public: false, masked: true },
- { key: 'CI_JOB_STARTED_AT', value: build.started_at&.iso8601, public: true, masked: false },
- { key: 'CI_BUILD_ID', value: build.id.to_s, public: true, masked: false },
- { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false, masked: true },
- { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false },
- { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: true },
- { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false },
- { key: 'CI_DEPENDENCY_PROXY_USER', value: 'gitlab-ci-token', public: true, masked: false },
- { key: 'CI_DEPENDENCY_PROXY_PASSWORD', value: 'my-token', public: false, masked: true },
- { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true },
- { key: 'CI_JOB_JWT_V1', value: 'ci.job.jwt', public: false, masked: true },
- { key: 'CI_JOB_JWT_V2', value: 'ci.job.jwtv2', public: false, masked: true },
- { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false },
- { key: 'CI_JOB_NAME_SLUG', value: 'test', public: true, masked: false },
- { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false },
- { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false },
- { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false },
- { key: 'CI_BUILD_STAGE', value: 'test', public: true, masked: false },
- { key: 'CI', value: 'true', public: true, masked: false },
- { key: 'GITLAB_CI', value: 'true', public: true, masked: false },
- { key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url, public: true, masked: false },
- { key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host, public: true, masked: false },
- { key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s, public: true, masked: false },
- { key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol, public: true, masked: false },
- { key: 'CI_SERVER_SHELL_SSH_HOST', value: Gitlab.config.gitlab_shell.ssh_host.to_s, public: true, masked: false },
- { key: 'CI_SERVER_SHELL_SSH_PORT', value: Gitlab.config.gitlab_shell.ssh_port.to_s, public: true, masked: false },
- { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false },
- { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false },
- { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false },
- { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true, masked: false },
- { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true, masked: false },
- { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true, masked: false },
- { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false },
- { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true, masked: false },
- { key: 'CI_PROJECT_NAME', value: project.path, public: true, masked: false },
- { key: 'CI_PROJECT_TITLE', value: project.title, public: true, masked: false },
- { key: 'CI_PROJECT_DESCRIPTION', value: project.description, public: true, masked: false },
- { key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false },
- { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false },
- { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false },
- { key: 'CI_PROJECT_NAMESPACE_ID', value: project.namespace.id.to_s, public: true, masked: false },
- { key: 'CI_PROJECT_ROOT_NAMESPACE', value: project.namespace.root_ancestor.path, public: true, masked: false },
- { key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false },
- { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false },
- { key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false },
- { key: 'CI_PROJECT_CLASSIFICATION_LABEL', value: project.external_authorization_classification_label, public: true, masked: false },
- { key: 'CI_DEFAULT_BRANCH', value: project.default_branch, public: true, masked: false },
- { key: 'CI_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false },
- { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false },
- { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
- { key: 'CI_DEPENDENCY_PROXY_SERVER', value: Gitlab.host_with_port, public: true, masked: false },
- { key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX',
- value: "#{Gitlab.host_with_port}/#{project.namespace.root_ancestor.path.downcase}#{DependencyProxy::URL_SUFFIX}",
- public: true,
- masked: false },
- { key: 'CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX',
- value: "#{Gitlab.host_with_port}/#{project.namespace.full_path.downcase}#{DependencyProxy::URL_SUFFIX}",
- public: true,
- masked: false },
- { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
- { key: 'CI_API_GRAPHQL_URL', value: 'http://localhost/api/graphql', public: true, masked: false },
- { key: 'CI_TEMPLATE_REGISTRY_HOST', value: template_registry_host, public: true, masked: false },
- { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false },
- { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false },
- { key: 'CI_PIPELINE_CREATED_AT', value: pipeline.created_at.iso8601, public: true, masked: false },
- { key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false },
- { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false },
- { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
- { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true, masked: false },
- { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true, masked: false },
- { key: 'CI_COMMIT_BRANCH', value: build.ref, public: true, masked: false },
- { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false },
- { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false },
- { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false },
- { key: 'CI_COMMIT_REF_PROTECTED', value: (!!pipeline.protected_ref?).to_s, public: true, masked: false },
- { key: 'CI_COMMIT_TIMESTAMP', value: pipeline.git_commit_timestamp, public: true, masked: false },
- { key: 'CI_COMMIT_AUTHOR', value: pipeline.git_author_full_text, public: true, masked: false },
- { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false },
- { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
- { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false },
- { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true, masked: false }
- ]
- end
-
before do
allow(Gitlab::Ci::Jwt).to receive(:for_build).and_return('ci.job.jwt')
allow(Gitlab::Ci::JwtV2).to receive(:for_build).and_return('ci.job.jwtv2')
@@ -3055,14 +2940,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
it { is_expected.to be_instance_of(Gitlab::Ci::Variables::Collection) }
it { expect(subject.to_runner_variables).to eq(predefined_variables) }
- context 'when FF `ci_remove_legacy_predefined_variables` is disabled' do
- before do
- stub_feature_flags(ci_remove_legacy_predefined_variables: false)
- end
-
- it { expect(subject.to_runner_variables).to eq(predefined_with_legacy_variables) }
- end
-
it 'excludes variables that require an environment or user' do
environment_based_variables_collection = subject.filter do |variable|
%w[
@@ -3204,80 +3081,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
end
end
-
- context 'when FF `ci_remove_legacy_predefined_variables` is disabled' do
- before do
- stub_feature_flags(ci_remove_legacy_predefined_variables: false)
- end
-
- context 'when build has environment and user-provided variables' do
- let(:expected_variables) do
- predefined_with_legacy_variables.map { |variable| variable.fetch(:key) } +
- %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG
- CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_TIER CI_ENVIRONMENT_URL]
- end
-
- before do
- create(:environment, project: build.project, name: 'staging')
-
- build.yaml_variables = [{ key: 'YAML_VARIABLE', value: 'var', public: true }]
- build.environment = 'staging'
-
- # CI_ENVIRONMENT_NAME is set in predefined_variables when job environment is provided
- predefined_with_legacy_variables.insert(20, { key: 'CI_ENVIRONMENT_NAME', value: 'staging', public: true, masked: false })
- end
-
- it 'matches explicit variables ordering' do
- received_variables = subject.map { |variable| variable[:key] }
-
- expect(received_variables).to eq expected_variables
- end
-
- describe 'CI_ENVIRONMENT_ACTION' do
- let(:enviroment_action_variable) { subject.find { |variable| variable[:key] == 'CI_ENVIRONMENT_ACTION' } }
-
- shared_examples 'defaults value' do
- it 'value matches start' do
- expect(enviroment_action_variable[:value]).to eq('start')
- end
- end
-
- it_behaves_like 'defaults value'
-
- context 'when options is set' do
- before do
- build.update!(options: options)
- end
-
- context 'when options is empty' do
- let(:options) { {} }
-
- it_behaves_like 'defaults value'
- end
-
- context 'when options is nil' do
- let(:options) { nil }
-
- it_behaves_like 'defaults value'
- end
-
- context 'when options environment is specified' do
- let(:options) { { environment: {} } }
-
- it_behaves_like 'defaults value'
- end
-
- context 'when options environment action specified' do
- let(:options) { { environment: { action: 'stop' } } }
-
- it 'matches the specified action' do
- expect(enviroment_action_variable[:value]).to eq('stop')
- end
- end
- end
- end
- end
- end
end
context 'when the build has ID tokens' do
@@ -3880,7 +3683,9 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
context 'for the apple_app_store integration' do
- let_it_be(:apple_app_store_integration) { create(:apple_app_store_integration) }
+ before do
+ allow(build.pipeline).to receive(:protected_ref?).and_return(pipeline_protected_ref)
+ end
let(:apple_app_store_variables) do
[
@@ -3891,39 +3696,70 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
]
end
- context 'when the apple_app_store exists' do
- context 'when a build is protected' do
- before do
- allow(build.pipeline).to receive(:protected_ref?).and_return(true)
- build.project.update!(apple_app_store_integration: apple_app_store_integration)
+ shared_examples 'does not include the apple_app_store variables' do
+ specify do
+ expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64' }).to be_nil
+ end
+ end
+
+ shared_examples 'includes apple_app_store variables' do
+ specify do
+ expect(subject).to include(*apple_app_store_variables)
+ end
+ end
+
+ context 'when an Apple App Store integration exists' do
+ let_it_be(:apple_app_store_integration) do
+ create(:apple_app_store_integration, project: project)
+ end
+
+ context 'when app_store_protected_refs is true' do
+ context 'when a build is protected' do
+ let(:pipeline_protected_ref) { true }
+
+ include_examples 'includes apple_app_store variables'
end
- it 'includes apple_app_store variables' do
- is_expected.to include(*apple_app_store_variables)
+ context 'when a build is not protected' do
+ let(:pipeline_protected_ref) { false }
+
+ include_examples 'does not include the apple_app_store variables'
end
end
- context 'when a build is not protected' do
+ context 'when app_store_protected_refs is false' do
before do
- allow(build.pipeline).to receive(:protected_ref?).and_return(false)
- build.project.update!(apple_app_store_integration: apple_app_store_integration)
+ apple_app_store_integration.update!(app_store_protected_refs: false)
end
- it 'does not include the apple_app_store variables' do
- expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil
- expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil
- expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil
- expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64' }).to be_nil
+ context 'when a build is protected' do
+ let(:pipeline_protected_ref) { true }
+
+ include_examples 'includes apple_app_store variables'
+ end
+
+ context 'when a build is not protected' do
+ let(:pipeline_protected_ref) { false }
+
+ include_examples 'includes apple_app_store variables'
end
end
end
- context 'when the apple_app_store integration does not exist' do
- it 'does not include apple_app_store variables' do
- expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil
- expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil
- expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil
- expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64' }).to be_nil
+ context 'when an Apple App Store integration does not exist' do
+ context 'when a build is protected' do
+ let(:pipeline_protected_ref) { true }
+
+ include_examples 'does not include the apple_app_store variables'
+ end
+
+ context 'when a build is not protected' do
+ let(:pipeline_protected_ref) { false }
+
+ include_examples 'does not include the apple_app_store variables'
end
end
end
@@ -4020,6 +3856,80 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
end
end
+
+ context 'when ID tokens are defined with variables' do
+ let(:ci_server_url) { Gitlab.config.gitlab.url }
+
+ let(:ci_server_host) { Gitlab.config.gitlab.host }
+
+ before do
+ rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s
+ stub_application_setting(ci_jwt_signing_key: rsa_key)
+ build.metadata.update!(id_tokens: {
+ 'ID_TOKEN_1' => { aud: '$CI_SERVER_URL' },
+ 'ID_TOKEN_2' => { aud: 'https://$CI_SERVER_HOST' },
+ 'ID_TOKEN_3' => { aud: ['developers', '$CI_SERVER_URL', 'https://$CI_SERVER_HOST'] }
+ })
+ build.runner = build_stubbed(:ci_runner)
+ end
+
+ subject(:runner_vars) { build.variables.to_runner_variables }
+
+ it 'includes the ID token variables with expanded aud values' do
+ expect(runner_vars).to include(
+ a_hash_including(key: 'ID_TOKEN_1', public: false, masked: true),
+ a_hash_including(key: 'ID_TOKEN_2', public: false, masked: true),
+ a_hash_including(key: 'ID_TOKEN_3', public: false, masked: true)
+ )
+
+ id_token_var_1 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_1' }
+ id_token_var_2 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_2' }
+ id_token_var_3 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_3' }
+ id_token_1 = JWT.decode(id_token_var_1[:value], nil, false).first
+ id_token_2 = JWT.decode(id_token_var_2[:value], nil, false).first
+ id_token_3 = JWT.decode(id_token_var_3[:value], nil, false).first
+ expect(id_token_1['aud']).to eq(ci_server_url)
+ expect(id_token_2['aud']).to eq("https://#{ci_server_host}")
+ expect(id_token_3['aud']).to match_array(['developers', ci_server_url, "https://#{ci_server_host}"])
+ end
+ end
+
+ context 'when ID tokens are defined with variables of an environment' do
+ let!(:envprod) do
+ create(:environment, project: build.project, name: 'production')
+ end
+
+ let!(:varprod) do
+ create(:ci_variable, project: build.project, key: 'ENVIRONMENT_SCOPED_VAR', value: 'https://prod', environment_scope: 'prod*')
+ end
+
+ before do
+ build.update!(environment: 'production')
+ rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s
+ stub_application_setting(ci_jwt_signing_key: rsa_key)
+ build.metadata.update!(id_tokens: {
+ 'ID_TOKEN_1' => { aud: '$ENVIRONMENT_SCOPED_VAR' },
+ 'ID_TOKEN_2' => { aud: ['$CI_ENVIRONMENT_NAME', '$ENVIRONMENT_SCOPED_VAR'] }
+ })
+ build.runner = build_stubbed(:ci_runner)
+ end
+
+ subject(:runner_vars) { build.variables.to_runner_variables }
+
+ it 'includes the ID token variables with expanded aud values' do
+ expect(runner_vars).to include(
+ a_hash_including(key: 'ID_TOKEN_1', public: false, masked: true),
+ a_hash_including(key: 'ID_TOKEN_2', public: false, masked: true)
+ )
+
+ id_token_var_1 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_1' }
+ id_token_var_2 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_2' }
+ id_token_1 = JWT.decode(id_token_var_1[:value], nil, false).first
+ id_token_2 = JWT.decode(id_token_var_2[:value], nil, false).first
+ expect(id_token_1['aud']).to eq('https://prod')
+ expect(id_token_2['aud']).to match_array(['production', 'https://prod'])
+ end
+ end
end
describe '#scoped_variables' do
@@ -4091,30 +4001,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
expect(names).not_to include(*keys)
end
end
-
- context 'when FF `ci_remove_legacy_predefined_variables` is disabled' do
- before do
- stub_feature_flags(ci_remove_legacy_predefined_variables: false)
- end
-
- it 'does not return prohibited variables' do
- keys = %w[CI_JOB_ID
- CI_JOB_URL
- CI_JOB_TOKEN
- CI_BUILD_ID
- CI_BUILD_TOKEN
- CI_REGISTRY_USER
- CI_REGISTRY_PASSWORD
- CI_REPOSITORY_URL
- CI_ENVIRONMENT_URL
- CI_DEPLOY_USER
- CI_DEPLOY_PASSWORD]
-
- build.scoped_variables.map { |env| env[:key] }.tap do |names|
- expect(names).not_to include(*keys)
- end
- end
- end
end
context 'with dependency variables' do
@@ -4253,18 +4139,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
end
- describe 'when the build is waiting for deployment approval' do
- let(:build) { create(:ci_build, :manual, environment: 'production', pipeline: pipeline) }
-
- before do
- create(:deployment, :blocked, deployable: build)
- end
-
- it 'does not allow the build to be enqueued' do
- expect { build.enqueue! }.to raise_error(StateMachines::InvalidTransition)
- end
- end
-
describe 'state transition: any => [:pending]' do
let(:build) { create(:ci_build, :created, pipeline: pipeline) }
diff --git a/spec/models/ci/catalog/listing_spec.rb b/spec/models/ci/catalog/listing_spec.rb
index 93d70a3f63e..159b70d7f8f 100644
--- a/spec/models/ci/catalog/listing_spec.rb
+++ b/spec/models/ci/catalog/listing_spec.rb
@@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe Ci::Catalog::Listing, feature_category: :pipeline_composition do
let_it_be(:namespace) { create(:group) }
- let_it_be(:project_1) { create(:project, namespace: namespace) }
- let_it_be(:project_2) { create(:project, namespace: namespace) }
+ let_it_be(:project_1) { create(:project, namespace: namespace, name: 'X Project') }
+ let_it_be(:project_2) { create(:project, namespace: namespace, name: 'B Project') }
let_it_be(:project_3) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -34,11 +34,32 @@ RSpec.describe Ci::Catalog::Listing, feature_category: :pipeline_composition do
end
context 'when the namespace has catalog resources' do
- let!(:resource) { create(:catalog_resource, project: project_1) }
- let!(:other_namespace_resource) { create(:catalog_resource, project: project_3) }
+ let_it_be(:resource) { create(:catalog_resource, project: project_1) }
+ let_it_be(:resource_2) { create(:catalog_resource, project: project_2) }
+ let_it_be(:other_namespace_resource) { create(:catalog_resource, project: project_3) }
it 'contains only catalog resources for projects in that namespace' do
- is_expected.to contain_exactly(resource)
+ is_expected.to contain_exactly(resource, resource_2)
+ end
+
+ context 'with a sort parameter' do
+ subject(:resources) { list.resources(sort: sort) }
+
+ context 'when the sort is name ascending' do
+ let_it_be(:sort) { :name_asc }
+
+ it 'contains catalog resources for projects sorted by name' do
+ is_expected.to eq([resource_2, resource])
+ end
+ end
+
+ context 'when the sort is name descending' do
+ let_it_be(:sort) { :name_desc }
+
+ it 'contains catalog resources for projects sorted by name' do
+ is_expected.to eq([resource, resource_2])
+ end
+ end
end
end
end
diff --git a/spec/models/ci/catalog/resource_spec.rb b/spec/models/ci/catalog/resource_spec.rb
index a239bbad857..4c1ade5c308 100644
--- a/spec/models/ci/catalog/resource_spec.rb
+++ b/spec/models/ci/catalog/resource_spec.rb
@@ -3,22 +3,24 @@
require 'spec_helper'
RSpec.describe Ci::Catalog::Resource, feature_category: :pipeline_composition do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, name: 'A') }
+ let_it_be(:project_2) { build(:project, name: 'Z') }
+ let_it_be(:project_3) { build(:project, name: 'L') }
let_it_be(:resource) { create(:catalog_resource, project: project) }
+ let_it_be(:resource_2) { create(:catalog_resource, project: project_2) }
+ let_it_be(:resource_3) { create(:catalog_resource, project: project_3) }
- let_it_be(:releases) do
- [
- create(:release, project: project, released_at: Time.zone.now - 2.days),
- create(:release, project: project, released_at: Time.zone.now - 1.day),
- create(:release, project: project, released_at: Time.zone.now)
- ]
- end
+ let_it_be(:release1) { create(:release, project: project, released_at: Time.zone.now - 2.days) }
+ let_it_be(:release2) { create(:release, project: project, released_at: Time.zone.now - 1.day) }
+ let_it_be(:release3) { create(:release, project: project, released_at: Time.zone.now) }
it { is_expected.to belong_to(:project) }
it { is_expected.to delegate_method(:avatar_path).to(:project) }
it { is_expected.to delegate_method(:description).to(:project) }
it { is_expected.to delegate_method(:name).to(:project) }
+ it { is_expected.to delegate_method(:star_count).to(:project) }
+ it { is_expected.to delegate_method(:forks_count).to(:project) }
describe '.for_projects' do
it 'returns catalog resources for the given project IDs' do
@@ -28,15 +30,39 @@ RSpec.describe Ci::Catalog::Resource, feature_category: :pipeline_composition do
end
end
+ describe '.order_by_created_at_desc' do
+ it 'returns catalog resources sorted by descending created at' do
+ ordered_resources = described_class.order_by_created_at_desc
+
+ expect(ordered_resources.to_a).to eq([resource_3, resource_2, resource])
+ end
+ end
+
+ describe '.order_by_name_desc' do
+ it 'returns catalog resources sorted by descending name' do
+ ordered_resources = described_class.order_by_name_desc
+
+ expect(ordered_resources.pluck(:name)).to eq(%w[Z L A])
+ end
+ end
+
+ describe '.order_by_name_asc' do
+ it 'returns catalog resources sorted by ascending name' do
+ ordered_resources = described_class.order_by_name_asc
+
+ expect(ordered_resources.pluck(:name)).to eq(%w[A L Z])
+ end
+ end
+
describe '#versions' do
it 'returns releases ordered by released date descending' do
- expect(resource.versions).to eq(releases.reverse)
+ expect(resource.versions).to eq([release3, release2, release1])
end
end
describe '#latest_version' do
it 'returns the latest release' do
- expect(resource.latest_version).to eq(releases.last)
+ expect(resource.latest_version).to eq(release3)
end
end
end
diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb
index a2751b9fb20..5a8a2b391e1 100644
--- a/spec/models/ci/group_variable_spec.rb
+++ b/spec/models/ci/group_variable_spec.rb
@@ -54,6 +54,36 @@ RSpec.describe Ci::GroupVariable, feature_category: :secrets_management do
it { expect(described_class.for_groups([group.id])).to eq([group_variable]) }
end
+ describe '.for_environment_scope_like' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:variable1_on_staging1) { create(:ci_group_variable, group: group, environment_scope: 'staging1') }
+ let_it_be(:variable2_on_staging2) { create(:ci_group_variable, group: group, environment_scope: 'staging2') }
+ let_it_be(:variable3_on_production) { create(:ci_group_variable, group: group, environment_scope: 'production') }
+
+ it {
+ expect(described_class.for_environment_scope_like('staging'))
+ .to match_array([variable1_on_staging1, variable2_on_staging2])
+ }
+
+ it {
+ expect(described_class.for_environment_scope_like('production'))
+ .to match_array([variable3_on_production])
+ }
+ end
+
+ describe '.environment_scope_names' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:variable1_on_staging1) { create(:ci_group_variable, group: group, environment_scope: 'staging1') }
+ let_it_be(:variable2_on_staging2) { create(:ci_group_variable, group: group, environment_scope: 'staging2') }
+ let_it_be(:variable3_on_staging2) { create(:ci_group_variable, group: group, environment_scope: 'staging2') }
+ let_it_be(:variable4_on_production) { create(:ci_group_variable, group: group, environment_scope: 'production') }
+
+ it 'groups and orders' do
+ expect(described_class.environment_scope_names)
+ .to match_array(%w[production staging1 staging2])
+ end
+ end
+
it_behaves_like 'cleanup by a loose foreign key' do
let!(:model) { create(:ci_group_variable) }
diff --git a/spec/models/ci/job_annotation_spec.rb b/spec/models/ci/job_annotation_spec.rb
new file mode 100644
index 00000000000..f94494bc91d
--- /dev/null
+++ b/spec/models/ci/job_annotation_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::JobAnnotation, feature_category: :build_artifacts do
+ let_it_be_with_reload(:job) { create(:ci_build, :success) }
+
+ describe 'validations' do
+ subject { create(:ci_job_annotation, job: job) }
+
+ it { is_expected.to belong_to(:job).class_name('Ci::Build').inverse_of(:job_annotations) }
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_length_of(:name).is_at_most(255) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to([:job_id, :partition_id]) }
+ end
+
+ describe '.create' do
+ context 'when JSON data is valid' do
+ subject do
+ job.job_annotations.create!(
+ name: 'external',
+ data: [{ external_link: { label: 'Example', url: 'https://example.com/' } }]
+ )
+ end
+
+ it 'creates the object' do
+ expect(subject).to be_a(described_class)
+ expect(subject.data).to contain_exactly(a_hash_including('external_link' =>
+ a_hash_including('label' => 'Example', 'url' => 'https://example.com/')))
+ end
+ end
+
+ context 'when JSON data is invalid' do
+ subject { job.job_annotations.create!(name: 'external', data: [{ invalid: 'invalid' }]) }
+
+ it 'throws an error' do
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+
+ context 'when there are more than 1000 JSON entries' do
+ subject { job.job_annotations.create!(data: [{ external_link: { label: 'Example', url: 'https://example.com/' } }] * 1001) }
+
+ it 'throws an error' do
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+
+ describe 'partitioning' do
+ context 'with job' do
+ before do
+ job.partition_id = 123
+ end
+
+ let(:annotation) { build(:ci_job_annotation, job: job) }
+
+ it 'copies the partition_id from job' do
+ expect { annotation.valid? }.to change { annotation.partition_id }.to(123)
+ end
+
+ context 'when it is already set' do
+ let(:annotation) { build(:ci_job_annotation, job: job, partition_id: 125) }
+
+ it 'does not change the partition_id value' do
+ expect { annotation.valid? }.not_to change { annotation.partition_id }
+ end
+ end
+ end
+
+ context 'without job' do
+ let(:annotation) { build(:ci_job_annotation, job: nil) }
+
+ it { is_expected.to validate_presence_of(:partition_id) }
+
+ it 'does not change the partition_id value' do
+ expect { annotation.valid? }.not_to change { annotation.partition_id }
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 5b67cbbc86b..b9e331affb1 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -498,6 +498,28 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
end
end
+ describe '.ci_and_security_orchestration_sources' do
+ subject { described_class.ci_and_security_orchestration_sources }
+
+ let_it_be(:push_pipeline) { create(:ci_pipeline, source: :push) }
+ let_it_be(:web_pipeline) { create(:ci_pipeline, source: :web) }
+ let_it_be(:api_pipeline) { create(:ci_pipeline, source: :api) }
+ let_it_be(:webide_pipeline) { create(:ci_pipeline, source: :webide) }
+ let_it_be(:child_pipeline) { create(:ci_pipeline, source: :parent_pipeline) }
+ let_it_be(:merge_request_pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline) }
+ let_it_be(:sec_orchestration_pipeline) { create(:ci_pipeline, :security_orchestration_policy) }
+
+ it 'contains pipelines having CI and security_orchestration_policy sources' do
+ expect(subject).to contain_exactly(push_pipeline, web_pipeline, api_pipeline, merge_request_pipeline, sec_orchestration_pipeline)
+ end
+
+ it 'filters on expected sources' do
+ expect(::Enums::Ci::Pipeline.ci_and_security_orchestration_sources.keys).to contain_exactly(
+ *%i[unknown push web trigger schedule api external pipeline chat merge_request_event
+ external_pull_request_event security_orchestration_policy])
+ end
+ end
+
describe '.outside_pipeline_family' do
subject(:outside_pipeline_family) { described_class.outside_pipeline_family(upstream_pipeline) }
@@ -1821,14 +1843,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated'
end
-
- context 'when pipeline_trigger_merge_status feature flag is disabled' do
- before do
- stub_feature_flags(pipeline_trigger_merge_status: false)
- end
-
- it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated'
- end
end
context 'when pipeline has merge requests' do
@@ -3015,335 +3029,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
end
end
- describe '#cancel_running' do
- let(:latest_status) { pipeline.statuses.pluck(:status) }
-
- let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) }
-
- it 'logs the event' do
- allow(Gitlab::AppJsonLogger).to receive(:info)
-
- pipeline.cancel_running
-
- expect(Gitlab::AppJsonLogger)
- .to have_received(:info)
- .with(
- a_hash_including(
- event: 'pipeline_cancel_running',
- pipeline_id: pipeline.id,
- auto_canceled_by_pipeline_id: nil,
- cascade_to_children: true,
- execute_async: true
- )
- )
- end
-
- context 'when there is a running external job and a regular job' do
- before do
- create(:ci_build, :running, pipeline: pipeline)
- create(:generic_commit_status, :running, pipeline: pipeline)
-
- pipeline.cancel_running
- end
-
- it 'cancels both jobs' do
- expect(latest_status).to contain_exactly('canceled', 'canceled')
- end
- end
-
- context 'when jobs are in different stages' do
- before do
- create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
- create(:ci_build, :running, stage_idx: 1, pipeline: pipeline)
-
- pipeline.cancel_running
- end
-
- it 'cancels both jobs' do
- expect(latest_status).to contain_exactly('canceled', 'canceled')
- end
- end
-
- context 'when there are created builds present in the pipeline' do
- before do
- create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
- create(:ci_build, :created, stage_idx: 1, pipeline: pipeline)
-
- pipeline.cancel_running
- end
-
- it 'cancels created builds' do
- expect(latest_status).to eq %w(canceled canceled)
- end
- end
-
- context 'with bridge jobs' do
- before do
- create(:ci_bridge, :created, pipeline: pipeline)
-
- pipeline.cancel_running
- end
-
- it 'bridges are canceled' do
- expect(pipeline.bridges.first.status).to eq 'canceled'
- end
- end
-
- context 'when pipeline is not cancelable' do
- before do
- create(:ci_build, :canceled, stage_idx: 0, pipeline: pipeline)
-
- pipeline.cancel_running
- end
-
- it 'does not send cancel signal to cancel self' do
- expect(pipeline).not_to receive(:cancel_self_only)
-
- pipeline.cancel_running
- end
- end
-
- context 'preloading relations' do
- let(:pipeline1) { create(:ci_empty_pipeline, :created) }
- let(:pipeline2) { create(:ci_empty_pipeline, :created) }
-
- before do
- create(:ci_build, :pending, pipeline: pipeline1)
- create(:generic_commit_status, :pending, pipeline: pipeline1)
-
- create(:ci_build, :pending, pipeline: pipeline2)
- create(:ci_build, :pending, pipeline: pipeline2)
- create(:generic_commit_status, :pending, pipeline: pipeline2)
- create(:generic_commit_status, :pending, pipeline: pipeline2)
- create(:generic_commit_status, :pending, pipeline: pipeline2)
- end
-
- it 'preloads relations for each build to avoid N+1 queries' do
- control1 = ActiveRecord::QueryRecorder.new do
- pipeline1.cancel_running
- end
-
- control2 = ActiveRecord::QueryRecorder.new do
- pipeline2.cancel_running
- end
-
- extra_update_queries = 4 # transition ... => :canceled, queue pop
- extra_generic_commit_status_validation_queries = 2 # name_uniqueness_across_types
-
- expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries)
- end
- end
-
- shared_examples 'retries' do
- context 'when the first try cannot get an exclusive lock' do
- let(:retries) { 1 }
-
- subject { pipeline.cancel_running(retries: retries) }
-
- before do
- create(:ci_build, :running, pipeline: pipeline)
-
- stub_first_cancel_call_fails
- end
-
- it 'retries again and cancels the build' do
- subject
-
- expect(latest_status).to contain_exactly('canceled')
- end
-
- context 'when the retries parameter is 0' do
- let(:retries) { 0 }
-
- it 'raises error' do
- expect { subject }.to raise_error(ActiveRecord::StaleObjectError)
- end
- end
- end
-
- def stub_first_cancel_call_fails
- call_count = 0
-
- allow_next_found_instance_of(Ci::Build) do |build|
- allow(build).to receive(:cancel).and_wrap_original do |original, *args| # rubocop:disable RSpec/AnyInstanceOf
- call_count >= retries ? raise(ActiveRecord::StaleObjectError) : original.call(*args)
-
- call_count += 1
- end
- end
- end
- end
-
- it_behaves_like 'retries'
-
- context 'when auto canceled' do
- let!(:canceled_by) { create(:ci_empty_pipeline) }
-
- before do
- create(:ci_build, :running, pipeline: pipeline)
-
- pipeline.cancel_running(auto_canceled_by_pipeline_id: canceled_by.id)
- end
-
- it 'sets auto cancel' do
- jobs_canceled_by = pipeline.statuses.map { |s| s.auto_canceled_by.id }
-
- expect(jobs_canceled_by).to contain_exactly(canceled_by.id)
- expect(pipeline.auto_canceled_by.id).to eq(canceled_by.id)
- end
- end
-
- context 'when there are child pipelines', :sidekiq_inline do
- let_it_be(:child_pipeline) { create(:ci_empty_pipeline, :created, child_of: pipeline) }
-
- before do
- project.clear_memoization(:cascade_cancel_pipelines_enabled)
-
- pipeline.reload
- end
-
- context 'when cascade_to_children is true' do
- let(:cascade_to_children) { true }
- let(:canceled_by) { nil }
- let(:execute_async) { true }
-
- let(:params) do
- {
- cascade_to_children: cascade_to_children,
- execute_async: execute_async
- }.tap do |p|
- p.merge!(auto_canceled_by_pipeline_id: canceled_by.id) if canceled_by
- end
- end
-
- subject(:cancel_running) { pipeline.cancel_running(**params) }
-
- context 'when cancelable child pipeline builds' do
- before do
- create(:ci_build, :created, pipeline: child_pipeline)
- create(:ci_build, :running, pipeline: child_pipeline)
- end
-
- it 'cancels child builds' do
- cancel_running
-
- latest_status_for_child = child_pipeline.statuses.pluck(:status)
- expect(latest_status_for_child).to eq %w(canceled canceled)
- expect(latest_status).to eq %w(canceled)
- end
-
- it 'cancels bridges' do
- create(:ci_bridge, :created, pipeline: pipeline)
- create(:ci_bridge, :created, pipeline: child_pipeline)
-
- cancel_running
-
- expect(pipeline.bridges.reload.first.status).to eq 'canceled'
- expect(child_pipeline.bridges.reload.first.status).to eq 'canceled'
- end
-
- context 'with nested child pipelines' do
- let!(:nested_child_pipeline) { create(:ci_empty_pipeline, :created, child_of: child_pipeline) }
- let!(:nested_child_pipeline_build) { create(:ci_build, :created, pipeline: nested_child_pipeline) }
-
- it 'cancels them' do
- cancel_running
-
- expect(nested_child_pipeline.reload.status).to eq 'canceled'
- expect(nested_child_pipeline_build.reload.status).to eq 'canceled'
- end
- end
-
- context 'when auto canceled' do
- let(:canceled_by) { create(:ci_empty_pipeline) }
-
- it 'sets auto cancel' do
- cancel_running
-
- pipeline.reload
-
- jobs_canceled_by_ids = pipeline.statuses.map(&:auto_canceled_by_id)
- child_pipelines_canceled_by_ids = pipeline.child_pipelines.map(&:auto_canceled_by_id)
- child_pipelines_jobs_canceled_by_ids = pipeline.child_pipelines.map(&:statuses).flatten.map(&:auto_canceled_by_id)
-
- expect(jobs_canceled_by_ids).to contain_exactly(canceled_by.id)
- expect(pipeline.auto_canceled_by_id).to eq(canceled_by.id)
- expect(child_pipelines_canceled_by_ids).to contain_exactly(canceled_by.id)
- expect(child_pipelines_jobs_canceled_by_ids).to contain_exactly(canceled_by.id, canceled_by.id)
- end
- end
-
- context 'when execute_async is false' do
- let(:execute_async) { false }
-
- it 'runs sync' do
- expect(::Ci::CancelPipelineWorker).not_to receive(:perform_async)
-
- cancel_running
- end
-
- it 'cancels children' do
- cancel_running
-
- latest_status_for_child = child_pipeline.statuses.pluck(:status)
- expect(latest_status_for_child).to eq %w(canceled canceled)
- expect(latest_status).to eq %w(canceled)
- end
-
- context 'with nested child pipelines' do
- let!(:nested_child_pipeline) { create(:ci_empty_pipeline, :created, child_of: child_pipeline) }
- let!(:nested_child_pipeline_build) { create(:ci_build, :created, pipeline: nested_child_pipeline) }
-
- it 'cancels them' do
- cancel_running
-
- expect(nested_child_pipeline.reload.status).to eq 'canceled'
- expect(nested_child_pipeline_build.reload.status).to eq 'canceled'
- end
- end
- end
- end
-
- it 'does not cancel uncancelable child pipeline builds' do
- create(:ci_build, :failed, pipeline: child_pipeline)
-
- cancel_running
-
- latest_status_for_child = child_pipeline.statuses.pluck(:status)
- expect(latest_status_for_child).to eq %w(failed)
- expect(latest_status).to eq %w(canceled)
- end
- end
-
- context 'when cascade_to_children is false' do
- let(:cascade_to_children) { false }
-
- subject(:cancel_running) { pipeline.cancel_running(cascade_to_children: cascade_to_children) }
-
- it 'does not cancel cancelable child pipeline builds' do
- create(:ci_build, :created, pipeline: child_pipeline)
- create(:ci_build, :running, pipeline: child_pipeline)
-
- cancel_running
-
- latest_status_for_child = child_pipeline.statuses.order_id_desc.pluck(:status)
- expect(latest_status_for_child).to eq %w(running created)
- expect(latest_status).to eq %w(canceled)
- end
-
- it 'does not cancel uncancelable child pipeline builds' do
- create(:ci_build, :failed, pipeline: child_pipeline)
-
- cancel_running
-
- latest_status_for_child = child_pipeline.statuses.pluck(:status)
- expect(latest_status_for_child).to eq %w(failed)
- expect(latest_status).to eq %w(canceled)
- end
- end
- end
- end
-
describe '.cancelable' do
subject { described_class.cancelable }
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index 34a56162dd9..86894ebcf2d 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -76,7 +76,8 @@ RSpec.describe Ci::Processable, feature_category: :continuous_integration do
job_artifacts_cobertura needs job_artifacts_accessibility
job_artifacts_requirements job_artifacts_coverage_fuzzing
job_artifacts_requirements_v2
- job_artifacts_api_fuzzing terraform_state_versions job_artifacts_cyclonedx].freeze
+ job_artifacts_api_fuzzing terraform_state_versions job_artifacts_cyclonedx
+ job_annotations].freeze
end
let(:ignore_accessors) do
@@ -102,6 +103,7 @@ RSpec.describe Ci::Processable, feature_category: :continuous_integration do
create(:ci_job_variable, :dotenv_source, job: processable)
create(:terraform_state_version, build: processable)
+ create(:ci_job_annotation, :external_link, job: processable)
end
before do
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index d202fef0ed0..b0ff070e4a6 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -317,8 +317,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
before do
stub_feature_flags(
use_traversal_ids: false,
- use_traversal_ids_for_ancestors: false,
- use_traversal_ids_for_ancestor_scopes: false
+ use_traversal_ids_for_ancestors: false
)
end
diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb
index 1043da33022..367f68c4773 100644
--- a/spec/models/ci/secure_file_spec.rb
+++ b/spec/models/ci/secure_file_spec.rb
@@ -2,13 +2,14 @@
require 'spec_helper'
-RSpec.describe Ci::SecureFile do
+RSpec.describe Ci::SecureFile, factory_default: :keep, feature_category: :mobile_devops do
+ let_it_be(:project) { create_default(:project).freeze }
+ let(:sample_file) { fixture_file('ci_secure_files/upload-keystore.jks') }
+
before do
stub_ci_secure_file_object_storage
end
- let(:sample_file) { fixture_file('ci_secure_files/upload-keystore.jks') }
-
subject { create(:ci_secure_file, file: CarrierWaveStringFile.new(sample_file)) }
it { is_expected.to be_a FileStoreMounter }
@@ -60,10 +61,9 @@ RSpec.describe Ci::SecureFile do
describe 'ordered scope' do
it 'returns the newest item first' do
- project = create(:project)
- file1 = create(:ci_secure_file, created_at: 1.week.ago, project: project)
- file2 = create(:ci_secure_file, created_at: 2.days.ago, project: project)
- file3 = create(:ci_secure_file, created_at: 1.day.ago, project: project)
+ file1 = create(:ci_secure_file, created_at: 1.week.ago)
+ file2 = create(:ci_secure_file, created_at: 2.days.ago)
+ file3 = create(:ci_secure_file, created_at: 1.day.ago)
files = project.secure_files.order_by_created_at
@@ -199,4 +199,17 @@ RSpec.describe Ci::SecureFile do
corrupt_file.update_metadata!
end
end
+
+ describe '#local?' do
+ it 'returns true when using local storage' do
+ secure_file = create(:ci_secure_file)
+ secure_file.update!(file_store: ObjectStorage::Store::LOCAL)
+ expect(secure_file.local?).to be true
+ end
+
+ it 'returns false when using object storage' do
+ secure_file = create(:ci_secure_file, file_store: ObjectStorage::Store::REMOTE)
+ expect(secure_file.local?).to be false
+ end
+ end
end