diff options
Diffstat (limited to 'spec/lib/gitlab/ci')
46 files changed, 1090 insertions, 469 deletions
diff --git a/spec/lib/gitlab/ci/badge/release/latest_release_spec.rb b/spec/lib/gitlab/ci/badge/release/latest_release_spec.rb new file mode 100644 index 00000000000..36f9f4fb321 --- /dev/null +++ b/spec/lib/gitlab/ci/badge/release/latest_release_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Badge::Release::LatestRelease do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + project.add_guest(user) + create(:release, project: project, released_at: 1.day.ago) + end + + subject { described_class.new(project, user) } + + describe '#entity' do + it 'describes latest release' do + expect(subject.entity).to eq 'Latest Release' + end + end + + describe '#tag' do + it 'returns latest release tag for the project ordered using release_at' do + create(:release, tag: "v1.0.0", project: project, released_at: 1.hour.ago) + latest_release = create(:release, tag: "v2.0.0", project: project, released_at: Time.current) + + expect(subject.tag).to eq latest_release.tag + end + end + + describe '#metadata' do + it 'returns correct metadata' do + expect(subject.metadata.image_url).to include 'release.svg' + end + end + + describe '#template' do + it 'returns correct template' do + expect(subject.template.key_text).to eq 'Latest Release' + end + end +end diff --git a/spec/lib/gitlab/ci/badge/release/metadata_spec.rb b/spec/lib/gitlab/ci/badge/release/metadata_spec.rb new file mode 100644 index 00000000000..d68358f1458 --- /dev/null +++ b/spec/lib/gitlab/ci/badge/release/metadata_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'lib/gitlab/ci/badge/shared/metadata' + +RSpec.describe Gitlab::Ci::Badge::Release::Metadata do + let(:project) { create(:project) } + let(:ref) { 'feature' } + let!(:release) { create(:release, tag: ref, project: project) } + let(:user) { create(:user) } + let(:badge) do + Gitlab::Ci::Badge::Release::LatestRelease.new(project, user) + end + + let(:metadata) { described_class.new(badge) } + + before do + project.add_guest(user) + end + + it_behaves_like 'badge metadata' + + describe '#title' do + it 'returns latest release title' do + expect(metadata.title).to eq 'Latest Release' + end + end + + describe '#image_url' do + it 'returns valid url' do + expect(metadata.image_url).to include "/-/badges/release.svg" + end + end + + describe '#link_url' do + it 'returns valid link' do + expect(metadata.link_url).to include "/-/releases" + end + end +end diff --git a/spec/lib/gitlab/ci/badge/release/template_spec.rb b/spec/lib/gitlab/ci/badge/release/template_spec.rb new file mode 100644 index 00000000000..2b66c296a94 --- /dev/null +++ b/spec/lib/gitlab/ci/badge/release/template_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Badge::Release::Template do + let(:project) { create(:project) } + let(:ref) { 'v1.2.3' } + let(:user) { create(:user) } + let!(:release) { create(:release, tag: ref, project: project) } + let(:badge) { Gitlab::Ci::Badge::Release::LatestRelease.new(project, user) } + let(:template) { described_class.new(badge) } + + before do + project.add_guest(user) + end + + describe '#key_text' do + it 'defaults to latest release' do + expect(template.key_text).to eq 'Latest Release' + end + + it 'returns custom key text' do + key_text = 'Test Release' + badge = Gitlab::Ci::Badge::Release::LatestRelease.new(project, user, opts: { key_text: key_text }) + + expect(described_class.new(badge).key_text).to eq key_text + end + end + + describe '#value_text' do + context 'when a release exists' do + it 'returns the tag of the release' do + expect(template.value_text).to eq ref + end + end + + context 'no releases exist' do + before do + allow(badge).to receive(:tag).and_return(nil) + end + + it 'returns string that latest release is none' do + expect(template.value_text).to eq 'none' + end + end + end + + describe '#key_width' do + it 'returns the default key width' do + expect(template.key_width).to eq 90 + end + + it 'returns custom key width' do + key_width = 100 + badge = Gitlab::Ci::Badge::Release::LatestRelease.new(project, user, opts: { key_width: key_width }) + + expect(described_class.new(badge).key_width).to eq key_width + end + end + + describe '#value_width' do + it 'has a fixed value width' do + expect(template.value_width).to eq 54 + end + end + + describe '#key_color' do + it 'always has the same color' do + expect(template.key_color).to eq '#555' + end + end + + describe '#value_color' do + context 'when release exists' do + it 'is blue' do + expect(template.value_color).to eq '#3076af' + end + end + + context 'when release does not exist' do + before do + allow(badge).to receive(:tag).and_return(nil) + end + + it 'is red' do + expect(template.value_color).to eq '#e05d44' + end + end + end +end diff --git a/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb b/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb index 0e26a9fa571..889878cf3ef 100644 --- a/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Build::Artifacts::ExpireInParser do - describe '.validate_duration' do + describe '.validate_duration', :request_store do subject { described_class.validate_duration(value) } context 'with never' do @@ -20,14 +20,33 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::ExpireInParser do context 'with a duration' do let(:value) { '1 Day' } + let(:other_value) { '30 seconds' } it { is_expected.to be_truthy } + + it 'caches data' do + expect(ChronicDuration).to receive(:parse).with(value).once.and_call_original + expect(ChronicDuration).to receive(:parse).with(other_value).once.and_call_original + + 2.times do + expect(described_class.validate_duration(value)).to eq(86400) + expect(described_class.validate_duration(other_value)).to eq(30) + end + end end context 'without a duration' do let(:value) { 'something' } it { is_expected.to be_falsy } + + it 'caches data' do + expect(ChronicDuration).to receive(:parse).with(value).once.and_call_original + + 2.times do + expect(described_class.validate_duration(value)).to be_falsey + end + end end end diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb index 532c83f6768..4ac8bf61738 100644 --- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb @@ -4,14 +4,23 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do describe '#satisfied_by?' do + subject { described_class.new(globs).satisfied_by?(pipeline, context) } + it_behaves_like 'a glob matching rule' do let(:pipeline) { build(:ci_pipeline) } + let(:context) {} before do allow(pipeline).to receive(:modified_paths).and_return(files.keys) end + end - subject { described_class.new(globs).satisfied_by?(pipeline, nil) } + context 'when pipeline is nil' do + let(:pipeline) {} + let(:context) {} + let(:globs) { [] } + + it { is_expected.to be_truthy } end context 'when using variable expansion' do @@ -20,8 +29,6 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do let(:globs) { ['$HELM_DIR/**/*'] } let(:context) { double('context') } - subject { described_class.new(globs).satisfied_by?(pipeline, context) } - before do allow(pipeline).to receive(:modified_paths).and_return(modified_paths) end @@ -32,6 +39,12 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do it { is_expected.to be_falsey } end + context 'when modified paths are nil' do + let(:modified_paths) {} + + it { is_expected.to be_truthy } + end + context 'when context has the specified variables' do let(:variables_hash) do { 'HELM_DIR' => 'helm' } diff --git a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb index 0505b17ea91..e83d4974bb7 100644 --- a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fast_spec_helper' +require_dependency 'active_model' RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do let(:factory) do diff --git a/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb index c255d6e9dd6..d5988dbbb58 100644 --- a/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fast_spec_helper' +require_dependency 'active_model' RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules do let(:factory) do diff --git a/spec/lib/gitlab/ci/config/entry/include_spec.rb b/spec/lib/gitlab/ci/config/entry/include_spec.rb index 275cdcddeb0..fd7f85c9298 100644 --- a/spec/lib/gitlab/ci/config/entry/include_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/include_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fast_spec_helper' +require_dependency 'active_model' RSpec.describe ::Gitlab::Ci::Config::Entry::Include do subject(:include_entry) { described_class.new(config) } diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index 9a2a67389fc..b03175cd80f 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -70,6 +70,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do it 'reports error' do expect(entry.errors).to include 'jobs rspec config should implement a script: or a trigger: keyword' end + + context 'when the job name cannot be cast directly to a symbol' do + let(:config) { { true => nil } } + + it 'properly parses the job name without raising a NoMethodError' do + expect(entry.errors).to include 'jobs true config should implement a script: or a trigger: keyword' + end + end end context 'when no visible jobs present' do diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 46800055dd9..e5de0fb38e3 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'support/helpers/stub_feature_flags' -require_dependency 'active_model' +require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Policy do let(:entry) { described_class.new(config) } @@ -47,6 +45,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Policy do end context 'when using unsafe regexp' do + # When removed we could use `require 'fast_spec_helper'` again. include StubFeatureFlags let(:config) { ['/^(?!master).+/'] } @@ -89,7 +88,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Policy do describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include /policy config should be an array of strings or regexps/ + .to include /policy config should be an array of strings or regular expressions/ end end end @@ -107,6 +106,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Policy do end context 'when using unsafe regexp' do + # When removed we could use `require 'fast_spec_helper'` again. include StubFeatureFlags let(:config) { { refs: ['/^(?!master).+/'] } } diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 749d1386ed9..daf58aff116 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -55,13 +55,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do } end - context 'when deprecated types keyword is defined' do + context 'when deprecated types/type keywords are defined' do let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:hash) do { types: %w(test deploy), - rspec: { script: 'rspec' } } + rspec: { script: 'rspec', type: 'test' } } end before do @@ -69,11 +69,15 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do end it 'returns array of types as stages with a warning' do + expect(root.jobs_value[:rspec][:stage]).to eq 'test' expect(root.stages_value).to eq %w[test deploy] - expect(root.warnings).to match_array(["root `types` is deprecated in 9.0 and will be removed in 15.0."]) + expect(root.warnings).to match_array([ + "root `types` is deprecated in 9.0 and will be removed in 15.0.", + "jobs:rspec `type` is deprecated in 9.0 and will be removed in 15.0." + ]) end - it 'logs usage of types keyword' do + it 'logs usage of keywords' do expect(Gitlab::AppJsonLogger).to( receive(:info) .with(event: 'ci_used_deprecated_keyword', @@ -350,9 +354,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do root.compose! end - context 'when before script is not an array' do + context 'when before script is a number' do let(:hash) do - { before_script: 'ls' } + { before_script: 123 } end describe '#valid?' do @@ -364,7 +368,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do describe '#errors' do it 'reports errors from child nodes' do expect(root.errors) - .to include 'before_script config should be an array containing strings and arrays of strings' + .to include 'before_script config should be a string or a nested array of strings up to 10 levels deep' end end end diff --git a/spec/lib/gitlab/ci/config/entry/script_spec.rb b/spec/lib/gitlab/ci/config/entry/script_spec.rb deleted file mode 100644 index 1ddf7881e81..00000000000 --- a/spec/lib/gitlab/ci/config/entry/script_spec.rb +++ /dev/null @@ -1,109 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Ci::Config::Entry::Script do - let(:entry) { described_class.new(config) } - - describe 'validations' do - context 'when entry config value is array of strings' do - let(:config) { %w(ls pwd) } - - describe '#value' do - it 'returns array of strings' do - expect(entry.value).to eq config - end - end - - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end - end - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when entry config value is array of arrays of strings' do - let(:config) { [['ls'], ['pwd', 'echo 1']] } - - describe '#value' do - it 'returns array of strings' do - expect(entry.value).to eq ['ls', 'pwd', 'echo 1'] - end - end - - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end - end - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when entry config value is array containing strings and arrays of strings' do - let(:config) { ['ls', ['pwd', 'echo 1']] } - - describe '#value' do - it 'returns array of strings' do - expect(entry.value).to eq ['ls', 'pwd', 'echo 1'] - end - end - - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end - end - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when entry value is string' do - let(:config) { 'ls' } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'script config should be an array containing strings and arrays of strings' - end - end - - describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid - end - end - end - - context 'when entry value is multi-level nested array' do - let(:config) { [['ls', ['echo 1']], 'pwd'] } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'script config should be an array containing strings and arrays of strings' - end - end - - describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid - end - end - end - end -end diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index cebe8984741..f8754d7e124 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -175,27 +175,35 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do end end - context "when duplicate 'include' is defined" do + context "when duplicate 'include's are defined" do + let(:values) do + { include: [ + { 'local' => local_file }, + { 'local' => local_file } + ], + image: 'ruby:2.7' } + end + + it 'does not raise an exception' do + expect { subject }.not_to raise_error + end + end + + context 'when passing max number of files' do let(:values) do { include: [ { 'local' => local_file }, - { 'local' => local_file } + { 'remote' => remote_url } ], image: 'ruby:2.7' } end - it 'raises an exception' do - expect { subject }.to raise_error(described_class::DuplicateIncludesError) + before do + stub_const("#{described_class}::MAX_INCLUDES", 2) end - context 'when including multiple files from a project' do - let(:values) do - { include: { project: project.full_path, file: [local_file, local_file] } } - end - - it 'raises an exception' do - expect { subject }.to raise_error(described_class::DuplicateIncludesError) - end + it 'does not raise an exception' do + expect { subject }.not_to raise_error end end diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb index 091bd3b07e6..e2bb55f3854 100644 --- a/spec/lib/gitlab/ci/config/external/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb @@ -45,7 +45,7 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['Dockerfile']) } before do - project.repository.create_file(project.owner, 'Dockerfile', "commit", message: 'test', branch_name: "master") + project.repository.create_file(project.first_owner, 'Dockerfile', "commit", message: 'test', branch_name: "master") end it { is_expected.to eq(true) } diff --git a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb index a29471706cc..1cc8b462224 100644 --- a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb +++ b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true require 'fast_spec_helper' -require 'support/helpers/stubbed_feature' -require 'support/helpers/stub_feature_flags' RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do - include StubFeatureFlags - describe '.applies_to?' do subject { described_class.applies_to?(config) } diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 1b3e8a2ce4a..05ff1f3618b 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -462,7 +462,7 @@ RSpec.describe Gitlab::Ci::Config do expect(project.repository).to receive(:blob_data_at) .with('eeff1122', local_location) - described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122', user: user) + described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122', user: user, pipeline: pipeline) end end @@ -470,7 +470,7 @@ RSpec.describe Gitlab::Ci::Config do it 'is using latest SHA on the default branch' do expect(project.repository).to receive(:root_ref_sha) - described_class.new(gitlab_ci_yml, project: project, sha: nil, user: user) + described_class.new(gitlab_ci_yml, project: project, sha: nil, user: user, pipeline: pipeline) end end end diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb index 1e433d7854a..747ff13c840 100644 --- a/spec/lib/gitlab/ci/lint_spec.rb +++ b/spec/lib/gitlab/ci/lint_spec.rb @@ -7,9 +7,10 @@ RSpec.describe Gitlab::Ci::Lint do let_it_be(:user) { create(:user) } let(:lint) { described_class.new(project: project, current_user: user) } + let(:ref) { project.default_branch } describe '#validate' do - subject { lint.validate(content, dry_run: dry_run) } + subject { lint.validate(content, dry_run: dry_run, ref: ref) } shared_examples 'content is valid' do let(:content) do @@ -251,6 +252,29 @@ RSpec.describe Gitlab::Ci::Lint do end end + context 'when using a ref other than the default branch' do + let(:ref) { 'feature' } + let(:content) do + <<~YAML + build: + stage: build + script: echo 1 + rules: + - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" + test: + stage: test + script: echo 2 + rules: + - if: "$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH" + YAML + end + + it 'includes only jobs that are excluded on the default branch' do + expect(subject.jobs.size).to eq(1) + expect(subject.jobs[0][:name]).to eq('test') + end + end + it_behaves_like 'sets merged yaml' include_context 'advanced validations' do @@ -298,4 +322,102 @@ RSpec.describe Gitlab::Ci::Lint do end end end + + context 'pipeline logger' do + let(:counters) do + { + 'count' => a_kind_of(Numeric), + 'avg' => a_kind_of(Numeric), + 'max' => a_kind_of(Numeric), + 'min' => a_kind_of(Numeric) + } + end + + let(:loggable_data) do + { + 'class' => 'Gitlab::Ci::Pipeline::Logger', + 'config_build_context_duration_s' => counters, + 'config_build_variables_duration_s' => counters, + 'config_compose_duration_s' => counters, + 'config_expand_duration_s' => counters, + 'config_external_process_duration_s' => counters, + 'config_stages_inject_duration_s' => counters, + 'config_tags_resolve_duration_s' => counters, + 'config_yaml_extend_duration_s' => counters, + 'config_yaml_load_duration_s' => counters, + 'pipeline_creation_caller' => 'Gitlab::Ci::Lint', + 'pipeline_creation_service_duration_s' => a_kind_of(Numeric), + 'pipeline_persisted' => false, + 'pipeline_source' => 'unknown', + 'project_id' => project&.id, + 'yaml_process_duration_s' => counters + } + end + + let(:content) do + <<~YAML + build: + script: echo + YAML + end + + subject(:validate) { lint.validate(content, dry_run: false) } + + before do + project&.add_developer(user) + end + + context 'when the duration is under the threshold' do + it 'does not create a log entry' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + + validate + end + end + + context 'when the durations exceeds the threshold' do + let(:timer) do + proc do + @timer = @timer.to_i + 30 + end + end + + before do + allow(Gitlab::Ci::Pipeline::Logger) + .to receive(:current_monotonic_time) { timer.call } + end + + it 'creates a log entry' do + expect(Gitlab::AppJsonLogger).to receive(:info).with(loggable_data) + + validate + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(ci_pipeline_creation_logger: false) + end + + it 'does not create a log entry' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + + validate + end + end + + context 'when project is not provided' do + let(:project) { nil } + + let(:project_nil_loggable_data) do + loggable_data.except('project_id') + end + + it 'creates a log entry without project_id' do + expect(Gitlab::AppJsonLogger).to receive(:info).with(project_nil_loggable_data) + + validate + end + end + end + end end diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb index c49673f5a4a..7eec78ff186 100644 --- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb @@ -40,60 +40,142 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do allow(validator_class).to receive(:new).and_call_original end - context 'when the validate flag is set as `false`' do - let(:validate) { false } + context 'when enforce_security_report_validation is enabled' do + before do + stub_feature_flags(enforce_security_report_validation: true) + end - it 'does not run the validation logic' do - parse_report + context 'when the validate flag is set as `true`' do + let(:validate) { true } - expect(validator_class).not_to have_received(:new) - end - end + it 'instantiates the validator with correct params' do + parse_report - context 'when the validate flag is set as `true`' do - let(:validate) { true } - let(:valid?) { false } + expect(validator_class).to have_received(:new).with(report.type, {}) + end - before do - allow_next_instance_of(validator_class) do |instance| - allow(instance).to receive(:valid?).and_return(valid?) - allow(instance).to receive(:errors).and_return(['foo']) + context 'when the report data is valid according to the schema' do + let(:valid?) { true } + + before do + allow_next_instance_of(validator_class) do |instance| + allow(instance).to receive(:valid?).and_return(valid?) + allow(instance).to receive(:errors).and_return([]) + end + + allow(parser).to receive_messages(create_scanner: true, create_scan: true) + end + + it 'does not add errors to the report' do + expect { parse_report }.not_to change { report.errors }.from([]) + end + + it 'adds the schema validation status to the report' do + parse_report + + expect(report.schema_validation_status).to eq(:valid_schema) + end + + it 'keeps the execution flow as normal' do + parse_report + + expect(parser).to have_received(:create_scanner) + expect(parser).to have_received(:create_scan) + end end - allow(parser).to receive_messages(create_scanner: true, create_scan: true) - end + context 'when the report data is not valid according to the schema' do + let(:valid?) { false } - it 'instantiates the validator with correct params' do - parse_report + before do + allow_next_instance_of(validator_class) do |instance| + allow(instance).to receive(:valid?).and_return(valid?) + allow(instance).to receive(:errors).and_return(['foo']) + end - expect(validator_class).to have_received(:new).with(report.type, {}) - end + allow(parser).to receive_messages(create_scanner: true, create_scan: true) + end + + it 'adds errors to the report' do + expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }]) + end + + it 'adds the schema validation status to the report' do + parse_report - context 'when the report data is not valid according to the schema' do - it 'adds errors to the report' do - expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }]) + expect(report.schema_validation_status).to eq(:invalid_schema) + end + + it 'does not try to create report entities' do + parse_report + + expect(parser).not_to have_received(:create_scanner) + expect(parser).not_to have_received(:create_scan) + end end + end + end + + context 'when enforce_security_report_validation is disabled' do + before do + stub_feature_flags(enforce_security_report_validation: false) + end + + context 'when the validate flag is set as `false`' do + let(:validate) { false } - it 'does not try to create report entities' do + it 'does not run the validation logic' do parse_report - expect(parser).not_to have_received(:create_scanner) - expect(parser).not_to have_received(:create_scan) + expect(validator_class).not_to have_received(:new) end end - context 'when the report data is valid according to the schema' do - let(:valid?) { true } + context 'when the validate flag is set as `true`' do + let(:validate) { true } + let(:valid?) { false } - it 'does not add errors to the report' do - expect { parse_report }.not_to change { report.errors }.from([]) + before do + allow_next_instance_of(validator_class) do |instance| + allow(instance).to receive(:valid?).and_return(valid?) + allow(instance).to receive(:errors).and_return(['foo']) + end + + allow(parser).to receive_messages(create_scanner: true, create_scan: true) end - it 'keeps the execution flow as normal' do + it 'instantiates the validator with correct params' do parse_report - expect(parser).to have_received(:create_scanner) - expect(parser).to have_received(:create_scan) + expect(validator_class).to have_received(:new).with(report.type, {}) + end + + context 'when the report data is not valid according to the schema' do + it 'adds errors to the report' do + expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }]) + end + + it 'does not try to create report entities' do + parse_report + + expect(parser).not_to have_received(:create_scanner) + expect(parser).not_to have_received(:create_scan) + end + end + + context 'when the report data is valid according to the schema' do + let(:valid?) { true } + + it 'does not add errors to the report' do + expect { parse_report }.not_to change { report.errors }.from([]) + end + + it 'keeps the execution flow as normal' do + parse_report + + expect(parser).to have_received(:create_scanner) + expect(parser).to have_received(:create_scan) + end end end end diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb index 4ca8f74e57f..82fa11d5f98 100644 --- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb +++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb @@ -99,6 +99,19 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do 'Some failure' end + context 'and has failure with no message but has system-err' do + let(:testcase_content) do + <<-EOF.strip_heredoc + <failure></failure> + <system-err>Some failure</system-err> + EOF + end + + it_behaves_like '<testcase> XML parser', + ::Gitlab::Ci::Reports::TestCase::STATUS_FAILED, + 'Some failure' + end + context 'and has error' do let(:testcase_content) { '<error>Some error</error>' } @@ -107,6 +120,19 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do 'Some error' end + context 'and has error with no message but has system-err' do + let(:testcase_content) do + <<-EOF.strip_heredoc + <error></error> + <system-err>Some error</system-err> + EOF + end + + it_behaves_like '<testcase> XML parser', + ::Gitlab::Ci::Reports::TestCase::STATUS_ERROR, + 'Some error' + end + context 'and has skipped' do let(:testcase_content) { '<skipped/>' } diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb index 0a592395c3a..375841ce236 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb @@ -47,18 +47,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments do expect(job.deployment).to be_nil end end - - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it 'does not create a deployment record' do - expect { subject }.not_to change { Deployment.count } - - expect(job.deployment).to be_nil - end - end end context 'when a pipeline contains a teardown job' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb index 253928e1a19..6a7d9b58a05 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb @@ -57,18 +57,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do expect(job.persisted_environment).to be_nil end end - - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it 'does not create any environments' do - expect { subject }.not_to change { Environment.count } - - expect(job.persisted_environment).to be_nil - end - end end context 'when a pipeline contains a teardown job' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb index 87df5a3e21b..571455d6279 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb @@ -60,18 +60,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureResourceGroups do expect(job.resource_group).to be_nil end end - - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it 'does not create any resource groups' do - expect { subject }.not_to change { Ci::ResourceGroup.count } - - expect(job.resource_group).to be_nil - end - end end context 'when a pipeline does not contain a job that requires a resource group' do diff --git a/spec/lib/gitlab/ci/pipeline/logger_spec.rb b/spec/lib/gitlab/ci/pipeline/logger_spec.rb index a488bc184f8..f31361431f2 100644 --- a/spec/lib/gitlab/ci/pipeline/logger_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/logger_spec.rb @@ -47,13 +47,15 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do end def loggable_data(count:, db_count: nil) - keys = %w[ + database_name = Ci::ApplicationRecord.connection.pool.db_config.name + + keys = %W[ expensive_operation_duration_s expensive_operation_db_count expensive_operation_db_primary_count expensive_operation_db_primary_duration_s - expensive_operation_db_main_count - expensive_operation_db_main_duration_s + expensive_operation_db_#{database_name}_count + expensive_operation_db_#{database_name}_duration_s ] data = keys.each.with_object({}) do |key, accumulator| @@ -75,7 +77,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do end context 'with a single query' do - let(:operation) { -> { Project.count } } + let(:operation) { -> { Ci::Pipeline.count } } it { is_expected.to eq(operation.call) } @@ -201,6 +203,35 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do expect(commit).to be_truthy end end + + context 'when project is not passed and pipeline is not persisted' do + let(:project) {} + let(:pipeline) { build(:ci_pipeline) } + + let(:loggable_data) do + { + 'class' => described_class.name.to_s, + 'pipeline_persisted' => false, + 'pipeline_creation_service_duration_s' => a_kind_of(Numeric), + 'pipeline_creation_caller' => 'source', + 'pipeline_save_duration_s' => { + 'avg' => 60, 'count' => 1, 'max' => 60, 'min' => 60 + }, + 'pipeline_creation_duration_s' => { + 'avg' => 20, 'count' => 2, 'max' => 30, 'min' => 10 + } + } + end + + it 'logs to application.json' do + expect(Gitlab::AppJsonLogger) + .to receive(:info) + .with(a_hash_including(loggable_data)) + .and_call_original + + expect(commit).to be_truthy + end + end end context 'when the feature flag is disabled' do diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 2f9fcd7caac..49505d397c2 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -411,171 +411,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do describe '#to_resource' do subject { seed_build.to_resource } - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - context 'when job is Ci::Build' do - it { is_expected.to be_a(::Ci::Build) } - it { is_expected.to be_valid } - - shared_examples_for 'deployment job' do - it 'returns a job with deployment' do - expect { subject }.to change { Environment.count }.by(1) - - expect(subject.deployment).not_to be_nil - expect(subject.deployment.deployable).to eq(subject) - expect(subject.deployment.environment.name).to eq(expected_environment_name) - end - end - - shared_examples_for 'non-deployment job' do - it 'returns a job without deployment' do - expect(subject.deployment).to be_nil - end - end - - shared_examples_for 'ensures environment existence' do - it 'has environment' do - expect { subject }.to change { Environment.count }.by(1) - - expect(subject).to be_has_environment - expect(subject.environment).to eq(environment_name) - expect(subject.metadata.expanded_environment_name).to eq(expected_environment_name) - expect(Environment.exists?(name: expected_environment_name)).to eq(true) - end - end - - shared_examples_for 'ensures environment inexistence' do - it 'does not have environment' do - expect { subject }.not_to change { Environment.count } - - expect(subject).not_to be_has_environment - expect(subject.environment).to be_nil - expect(subject.metadata&.expanded_environment_name).to be_nil - expect(Environment.exists?(name: expected_environment_name)).to eq(false) - end - end - - context 'when job deploys to production' do - let(:environment_name) { 'production' } - let(:expected_environment_name) { 'production' } - let(:attributes) { { name: 'deploy', ref: 'master', environment: 'production' } } - - it_behaves_like 'deployment job' - it_behaves_like 'ensures environment existence' - - context 'when create_deployment_in_separate_transaction feature flag is enabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: true) - end - - it 'does not create any deployments nor environments' do - expect(subject.deployment).to be_nil - expect(Environment.count).to eq(0) - expect(Deployment.count).to eq(0) - end - end - - context 'when the environment name is invalid' do - let(:attributes) { { name: 'deploy', ref: 'master', environment: '!!!' } } - - it 'fails the job with a failure reason and does not create an environment' do - expect(subject).to be_failed - expect(subject).to be_environment_creation_failure - expect(subject.metadata.expanded_environment_name).to be_nil - expect(Environment.exists?(name: expected_environment_name)).to eq(false) - end - end - end - - context 'when job starts a review app' do - let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' } - let(:expected_environment_name) { "review/#{pipeline.ref}" } - - let(:attributes) do - { - name: 'deploy', ref: 'master', environment: environment_name, - options: { environment: { name: environment_name } } - } - end - - it_behaves_like 'deployment job' - it_behaves_like 'ensures environment existence' - end - - context 'when job stops a review app' do - let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' } - let(:expected_environment_name) { "review/#{pipeline.ref}" } - - let(:attributes) do - { - name: 'deploy', ref: 'master', environment: environment_name, - options: { environment: { name: environment_name, action: 'stop' } } - } - end - - it 'returns a job without deployment' do - expect(subject.deployment).to be_nil - end - - it_behaves_like 'non-deployment job' - it_behaves_like 'ensures environment existence' - end - - context 'when job belongs to a resource group' do - let(:resource_group) { 'iOS' } - let(:attributes) { { name: 'rspec', ref: 'master', resource_group_key: resource_group, environment: 'production' }} - - it 'returns a job with resource group' do - expect(subject.resource_group).not_to be_nil - expect(subject.resource_group.key).to eq('iOS') - expect(Ci::ResourceGroup.count).to eq(1) - end - - context 'when create_deployment_in_separate_transaction feature flag is enabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: true) - end - - it 'does not create any resource groups' do - expect(subject.resource_group).to be_nil - expect(Ci::ResourceGroup.count).to eq(0) - end - end - - context 'when resource group has $CI_ENVIRONMENT_NAME in it' do - let(:resource_group) { 'test/$CI_ENVIRONMENT_NAME' } - - it 'expands environment name' do - expect(subject.resource_group.key).to eq('test/production') - end - end - end - end - - context 'when job is a bridge' do - let(:base_attributes) do - { - name: 'rspec', ref: 'master', options: { trigger: 'my/project' }, scheduling_type: :stage - } - end - - let(:attributes) { base_attributes } - - it { is_expected.to be_a(::Ci::Bridge) } - it { is_expected.to be_valid } - - context 'when job belongs to a resource group' do - let(:attributes) { base_attributes.merge(resource_group_key: 'iOS') } - - it 'returns a job with resource group' do - expect(subject.resource_group).not_to be_nil - expect(subject.resource_group.key).to eq('iOS') - end - end - end - it 'memoizes a resource object' do expect(subject.object_id).to eq seed_build.to_resource.object_id end diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb index 3b0eaffc54e..f4b47893805 100644 --- a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb @@ -85,6 +85,9 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do let(:info) { build(:codequality_degradation, :info) } let(:major_2) { build(:codequality_degradation, :major) } let(:critical) { build(:codequality_degradation, :critical) } + let(:uppercase_major) { build(:codequality_degradation, severity: 'MAJOR') } + let(:unknown) { build(:codequality_degradation, severity: 'unknown') } + let(:codequality_report) { described_class.new } before do @@ -94,6 +97,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do codequality_report.add_degradation(major_2) codequality_report.add_degradation(info) codequality_report.add_degradation(critical) + codequality_report.add_degradation(unknown) codequality_report.sort_degradations! end @@ -105,8 +109,30 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do major, major_2, minor, - info + info, + unknown ]) end + + context 'with non-existence and uppercase severities' do + let(:other_report) { described_class.new } + let(:non_existent) { build(:codequality_degradation, severity: 'non-existent') } + + before do + other_report.add_degradation(blocker) + other_report.add_degradation(uppercase_major) + other_report.add_degradation(minor) + other_report.add_degradation(non_existent) + end + + it 'sorts unknown last' do + expect(other_report.degradations.values).to eq([ + blocker, + uppercase_major, + minor, + non_existent + ]) + end + end end end diff --git a/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb b/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb index 784c1183320..2320f011cf5 100644 --- a/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb @@ -6,36 +6,47 @@ RSpec.describe Gitlab::Ci::Reports::Security::FindingKey do using RSpec::Parameterized::TableSyntax describe '#==' do - where(:location_fp_1, :location_fp_2, :identifier_fp_1, :identifier_fp_2, :equals?) do - nil | 'different location fp' | 'identifier fp' | 'different identifier fp' | false - 'location fp' | nil | 'identifier fp' | 'different identifier fp' | false - 'location fp' | 'different location fp' | nil | 'different identifier fp' | false - 'location fp' | 'different location fp' | 'identifier fp' | nil | false - nil | nil | 'identifier fp' | 'identifier fp' | false - 'location fp' | 'location fp' | nil | nil | false - nil | nil | nil | nil | false - 'location fp' | 'different location fp' | 'identifier fp' | 'different identifier fp' | false - 'location fp' | 'different location fp' | 'identifier fp' | 'identifier fp' | false - 'location fp' | 'location fp' | 'identifier fp' | 'different identifier fp' | false - 'location fp' | 'location fp' | 'identifier fp' | 'identifier fp' | true - end - - with_them do - let(:finding_key_1) do - build(:ci_reports_security_finding_key, - location_fingerprint: location_fp_1, - identifier_fingerprint: identifier_fp_1) + context 'when the comparison is done between FindingKey instances' do + where(:location_fp_1, :location_fp_2, :identifier_fp_1, :identifier_fp_2, :equals?) do + nil | 'different location fp' | 'identifier fp' | 'different identifier fp' | false + 'location fp' | nil | 'identifier fp' | 'different identifier fp' | false + 'location fp' | 'different location fp' | nil | 'different identifier fp' | false + 'location fp' | 'different location fp' | 'identifier fp' | nil | false + nil | nil | 'identifier fp' | 'identifier fp' | false + 'location fp' | 'location fp' | nil | nil | false + nil | nil | nil | nil | false + 'location fp' | 'different location fp' | 'identifier fp' | 'different identifier fp' | false + 'location fp' | 'different location fp' | 'identifier fp' | 'identifier fp' | false + 'location fp' | 'location fp' | 'identifier fp' | 'different identifier fp' | false + 'location fp' | 'location fp' | 'identifier fp' | 'identifier fp' | true end - let(:finding_key_2) do - build(:ci_reports_security_finding_key, - location_fingerprint: location_fp_2, - identifier_fingerprint: identifier_fp_2) + with_them do + let(:finding_key_1) do + build(:ci_reports_security_finding_key, + location_fingerprint: location_fp_1, + identifier_fingerprint: identifier_fp_1) + end + + let(:finding_key_2) do + build(:ci_reports_security_finding_key, + location_fingerprint: location_fp_2, + identifier_fingerprint: identifier_fp_2) + end + + subject { finding_key_1 == finding_key_2 } + + it { is_expected.to be(equals?) } end + end + + context 'when the comparison is not done between FindingKey instances' do + let(:finding_key) { build(:ci_reports_security_finding_key) } + let(:uuid) { SecureRandom.uuid } - subject { finding_key_1 == finding_key_2 } + subject { finding_key == uuid } - it { is_expected.to be(equals?) } + it { is_expected.to be_falsey } end end end diff --git a/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb index f8df2266689..8204b104832 100644 --- a/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb @@ -8,7 +8,7 @@ RSpec.describe '5-Minute-Production-App.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_branch) { default_branch } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } diff --git a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb index ca6f6872f89..27de8324206 100644 --- a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Deploy-ECS.gitlab-ci.yml' do let(:default_branch) { project.default_branch_or_main } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb index bd701aec8fc..21052f03cb8 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Jobs/Build.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { project.owner } + let_it_be(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb index 64243f2d205..d88d9782021 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { project.owner } + let_it_be(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb index 789f694b4b4..b657f73fa77 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb @@ -29,7 +29,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project, refind: true) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) } @@ -66,6 +66,11 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do expect(build_names).not_to include('review') end + it 'when CI_DEPLOY_FREEZE is present' do + create(:ci_variable, project: project, key: 'CI_DEPLOY_FREEZE', value: 'true') + expect(build_names).to eq %w(placeholder) + end + it 'when CANARY_ENABLED' do create(:ci_variable, project: project, key: 'CANARY_ENABLED', value: 'true') diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb index b9256ece78b..0f97bc06a4e 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { project.owner } + let_it_be(:user) { project.first_owner } let(:default_branch) { 'main' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb index db9d7496251..a92a8397e96 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { project.owner } + let_it_be(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb index 4685d843ce0..5e9224cebd9 100644 --- a/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Terraform/Base.gitlab-ci.yml' do let(:default_branch) { 'master' } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb index e35f2eabe8e..0ab81f97f20 100644 --- a/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Terraform/Base.latest.gitlab-ci.yml' do let(:default_branch) { 'master' } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb index 004261bc617..d6c7cd32f79 100644 --- a/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb @@ -20,7 +20,7 @@ RSpec.describe 'Verify/Load-Performance-Testing.gitlab-ci.yml' do describe 'the created pipeline' do let(:project) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb index 64ef6ecd7f8..6a4be1fa072 100644 --- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do describe 'the created pipeline' do let(:pipeline_branch) { default_branch } let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } @@ -276,7 +276,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do with_them do let(:project) { create(:project, :custom_repo, files: files) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: default_branch ) } let(:pipeline) { service.execute(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb index 3d97b47473d..de94eec09fe 100644 --- a/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'Flutter.gitlab-ci.yml' do describe 'the created pipeline' do let(:pipeline_branch) { 'master' } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb index c7dbbea4622..ebf52e6d65a 100644 --- a/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'Kaniko.gitlab-ci.yml' do describe 'the created pipeline' do let(:pipeline_branch) { 'master' } let(:project) { create(:project, :custom_repo, files: { 'Dockerfile' => 'FROM alpine:latest' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/npm_spec.rb b/spec/lib/gitlab/ci/templates/npm_spec.rb index ea954690133..d86a3a67823 100644 --- a/spec/lib/gitlab/ci/templates/npm_spec.rb +++ b/spec/lib/gitlab/ci/templates/npm_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'npm.gitlab-ci.yml' do let(:repo_files) { { 'package.json' => '{}', 'README.md' => '' } } let(:modified_files) { %w[package.json] } let(:project) { create(:project, :custom_repo, files: repo_files) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:pipeline_branch) { project.default_branch } let(:pipeline_tag) { 'v1.2.1' } let(:pipeline_ref) { pipeline_branch } diff --git a/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb index 936cd6ac8aa..346ab9f7af7 100644 --- a/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'Terraform.gitlab-ci.yml' do let(:default_branch) { project.default_branch_or_main } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb index fd5d5d6af7f..6c06403adff 100644 --- a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'Terraform.latest.gitlab-ci.yml' do let(:default_branch) { project.default_branch_or_main } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/variables/builder/instance_spec.rb b/spec/lib/gitlab/ci/variables/builder/instance_spec.rb new file mode 100644 index 00000000000..7abda2bd615 --- /dev/null +++ b/spec/lib/gitlab/ci/variables/builder/instance_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Variables::Builder::Instance do + let_it_be(:variable) { create(:ci_instance_variable, protected: false) } + let_it_be(:protected_variable) { create(:ci_instance_variable, protected: true) } + + let(:builder) { described_class.new } + + describe '#secret_variables' do + let(:variable_item) { item(variable) } + let(:protected_variable_item) { item(protected_variable) } + + subject do + builder.secret_variables(protected_ref: protected_ref) + end + + context 'when the ref is protected' do + let(:protected_ref) { true } + + it 'contains all the variables' do + is_expected.to contain_exactly(variable_item, protected_variable_item) + end + end + + context 'when the ref is not protected' do + let(:protected_ref) { false } + + it 'contains only unprotected variables' do + is_expected.to contain_exactly(variable_item) + end + end + end + + def item(variable) + Gitlab::Ci::Variables::Collection::Item.fabricate(variable) + end +end diff --git a/spec/lib/gitlab/ci/variables/builder/project_spec.rb b/spec/lib/gitlab/ci/variables/builder/project_spec.rb new file mode 100644 index 00000000000..b64b6ea98e2 --- /dev/null +++ b/spec/lib/gitlab/ci/variables/builder/project_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Variables::Builder::Project do + let_it_be(:project) { create(:project, :repository) } + + let(:builder) { described_class.new(project) } + + describe '#secret_variables' do + let(:environment) { '*' } + let(:protected_ref) { false } + + let_it_be(:variable) do + create(:ci_variable, + value: 'secret', + project: project) + end + + let_it_be(:protected_variable) do + create(:ci_variable, :protected, + value: 'protected', + project: project) + end + + let(:variable_item) { item(variable) } + let(:protected_variable_item) { item(protected_variable) } + + subject do + builder.secret_variables( + environment: environment, + protected_ref: protected_ref) + end + + context 'when the ref is protected' do + let(:protected_ref) { true } + + it 'contains all the variables' do + is_expected.to contain_exactly(variable_item, protected_variable_item) + end + end + + context 'when the ref is not protected' do + let(:protected_ref) { false } + + it 'contains only the unprotected variables' do + is_expected.to contain_exactly(variable_item) + end + end + + context 'when environment name is specified' do + let(:environment) { 'review/name' } + + before do + Ci::Variable.update_all(environment_scope: environment_scope) + end + + context 'when environment scope is exactly matched' do + let(:environment_scope) { 'review/name' } + + it { is_expected.to contain_exactly(variable_item) } + end + + context 'when environment scope is matched by wildcard' do + let(:environment_scope) { 'review/*' } + + it { is_expected.to contain_exactly(variable_item) } + end + + context 'when environment scope does not match' do + let(:environment_scope) { 'review/*/special' } + + it { is_expected.not_to contain_exactly(variable_item) } + end + + context 'when environment scope has _' do + let(:environment_scope) { '*_*' } + + it 'does not treat it as wildcard' do + is_expected.not_to contain_exactly(variable_item) + end + end + + context 'when environment name contains underscore' do + let(:environment) { 'foo_bar/test' } + let(:environment_scope) { 'foo_bar/*' } + + it 'matches literally for _' do + is_expected.to contain_exactly(variable_item) + end + end + + # The environment name and scope cannot have % at the moment, + # but we're considering relaxing it and we should also make sure + # it doesn't break in case some data sneaked in somehow as we're + # not checking this integrity in database level. + context 'when environment scope has %' do + let(:environment_scope) { '*%*' } + + it 'does not treat it as wildcard' do + is_expected.not_to contain_exactly(variable_item) + end + end + + context 'when environment name contains a percent' do + let(:environment) { 'foo%bar/test' } + let(:environment_scope) { 'foo%bar/*' } + + it 'matches literally for _' do + is_expected.to contain_exactly(variable_item) + end + end + end + + context 'when variables with the same name have different environment scopes' do + let(:environment) { 'review/name' } + + let_it_be(:partially_matched_variable) do + create(:ci_variable, + key: variable.key, + value: 'partial', + environment_scope: 'review/*', + project: project) + end + + let_it_be(:perfectly_matched_variable) do + create(:ci_variable, + key: variable.key, + value: 'prefect', + environment_scope: 'review/name', + project: project) + end + + it 'puts variables matching environment scope more in the end' do + variables_collection = Gitlab::Ci::Variables::Collection.new([ + variable, + partially_matched_variable, + perfectly_matched_variable + ]).to_runner_variables + + expect(subject.to_runner_variables).to eq(variables_collection) + end + end + end + + def item(variable) + Gitlab::Ci::Variables::Collection::Item.fabricate(variable) + end +end diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb index 8a87cbe45c1..6e144d62ac0 100644 --- a/spec/lib/gitlab/ci/variables/builder_spec.rb +++ b/spec/lib/gitlab/ci/variables/builder_spec.rb @@ -3,10 +3,11 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Variables::Builder do - let_it_be(:project) { create(:project, :repository) } - let_it_be(:pipeline) { create(:ci_pipeline, project: project) } - let_it_be(:user) { project.owner } - let_it_be(:job) do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :repository, namespace: group) } + let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:job) do create(:ci_build, pipeline: pipeline, user: user, @@ -153,7 +154,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder do before do allow(builder).to receive(:predefined_variables) { [var('A', 1), var('B', 1)] } - allow(project).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] } + allow(pipeline.project).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] } allow(pipeline).to receive(:predefined_variables) { [var('C', 3), var('D', 3)] } allow(job).to receive(:runner) { double(predefined_variables: [var('D', 4), var('E', 4)]) } allow(builder).to receive(:kubernetes_variables) { [var('E', 5), var('F', 5)] } @@ -201,4 +202,240 @@ RSpec.describe Gitlab::Ci::Variables::Builder do end end end + + describe '#user_variables' do + context 'with user' do + subject { builder.user_variables(user).to_hash } + + let(:expected_variables) do + { + 'GITLAB_USER_EMAIL' => user.email, + 'GITLAB_USER_ID' => user.id.to_s, + 'GITLAB_USER_LOGIN' => user.username, + 'GITLAB_USER_NAME' => user.name + } + end + + it { is_expected.to eq(expected_variables) } + end + + context 'without user' do + subject { builder.user_variables(nil).to_hash } + + it { is_expected.to be_empty } + end + end + + describe '#kubernetes_variables' do + let(:service) { double(execute: template) } + let(:template) { double(to_yaml: 'example-kubeconfig', valid?: template_valid) } + let(:template_valid) { true } + + subject { builder.kubernetes_variables(job) } + + before do + allow(Ci::GenerateKubeconfigService).to receive(:new).with(job).and_return(service) + end + + it { is_expected.to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) } + + context 'generated config is invalid' do + let(:template_valid) { false } + + it { is_expected.not_to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) } + end + end + + describe '#deployment_variables' do + let(:environment) { 'production' } + let(:kubernetes_namespace) { 'namespace' } + let(:project_variables) { double } + + subject { builder.deployment_variables(environment: environment, job: job) } + + before do + allow(job).to receive(:expanded_kubernetes_namespace) + .and_return(kubernetes_namespace) + + allow(project).to receive(:deployment_variables) + .with(environment: environment, kubernetes_namespace: kubernetes_namespace) + .and_return(project_variables) + end + + context 'environment is nil' do + let(:environment) { nil } + + it { is_expected.to be_empty } + end + end + + shared_examples "secret CI variables" do + context 'when ref is branch' do + context 'when ref is protected' do + before do + create(:protected_branch, :developers_can_merge, name: job.ref, project: project) + end + + it { is_expected.to contain_exactly(protected_variable_item, unprotected_variable_item) } + end + + context 'when ref is not protected' do + it { is_expected.to contain_exactly(unprotected_variable_item) } + end + end + + context 'when ref is tag' do + before do + job.update!(ref: 'v1.1.0', tag: true) + pipeline.update!(ref: 'v1.1.0', tag: true) + end + + context 'when ref is protected' do + before do + create(:protected_tag, project: project, name: 'v*') + end + + it { is_expected.to contain_exactly(protected_variable_item, unprotected_variable_item) } + end + + context 'when ref is not protected' do + it { is_expected.to contain_exactly(unprotected_variable_item) } + end + end + + context 'when ref is merge request' do + let_it_be(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project) } + let_it_be(:pipeline) { merge_request.pipelines_for_merge_request.first } + let_it_be(:job) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline) } + + context 'when ref is protected' do + before do + create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project) + end + + it 'does not return protected variables as it is not supported for merge request pipelines' do + is_expected.to contain_exactly(unprotected_variable_item) + end + end + + context 'when ref is not protected' do + it { is_expected.to contain_exactly(unprotected_variable_item) } + end + end + end + + describe '#secret_instance_variables' do + subject { builder.secret_instance_variables } + + let_it_be(:protected_variable) { create(:ci_instance_variable, protected: true) } + let_it_be(:unprotected_variable) { create(:ci_instance_variable, protected: false) } + + let(:protected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(protected_variable) } + let(:unprotected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(unprotected_variable) } + + include_examples "secret CI variables" + end + + describe '#secret_group_variables' do + subject { builder.secret_group_variables(ref: job.git_ref, environment: job.expanded_environment_name) } + + let_it_be(:protected_variable) { create(:ci_group_variable, protected: true, group: group) } + let_it_be(:unprotected_variable) { create(:ci_group_variable, protected: false, group: group) } + + let(:protected_variable_item) { protected_variable } + let(:unprotected_variable_item) { unprotected_variable } + + include_examples "secret CI variables" + end + + describe '#secret_project_variables' do + let_it_be(:protected_variable) { create(:ci_variable, protected: true, project: project) } + let_it_be(:unprotected_variable) { create(:ci_variable, protected: false, project: project) } + + let(:ref) { job.git_ref } + let(:environment) { job.expanded_environment_name } + + subject { builder.secret_project_variables(ref: ref, environment: environment) } + + context 'with ci_variables_builder_memoize_secret_variables disabled' do + before do + stub_feature_flags(ci_variables_builder_memoize_secret_variables: false) + end + + let(:protected_variable_item) { protected_variable } + let(:unprotected_variable_item) { unprotected_variable } + + include_examples "secret CI variables" + end + + context 'with ci_variables_builder_memoize_secret_variables enabled' do + before do + stub_feature_flags(ci_variables_builder_memoize_secret_variables: true) + end + + let(:protected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(protected_variable) } + let(:unprotected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(unprotected_variable) } + + include_examples "secret CI variables" + + context 'variables memoization' do + let_it_be(:scoped_variable) { create(:ci_variable, project: project, environment_scope: 'scoped') } + + let(:scoped_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(scoped_variable) } + + context 'with protected environments' do + it 'memoizes the result by environment' do + expect(pipeline.project) + .to receive(:protected_for?) + .with(pipeline.jobs_git_ref) + .once.and_return(true) + + expect_next_instance_of(described_class::Project) do |project_variables_builder| + expect(project_variables_builder) + .to receive(:secret_variables) + .with(environment: 'production', protected_ref: true) + .once + .and_call_original + end + + 2.times do + expect(builder.secret_project_variables(ref: ref, environment: 'production')) + .to contain_exactly(unprotected_variable_item, protected_variable_item) + end + end + end + + context 'with unprotected environments' do + it 'memoizes the result by environment' do + expect(pipeline.project) + .to receive(:protected_for?) + .with(pipeline.jobs_git_ref) + .once.and_return(false) + + expect_next_instance_of(described_class::Project) do |project_variables_builder| + expect(project_variables_builder) + .to receive(:secret_variables) + .with(environment: nil, protected_ref: false) + .once + .and_call_original + + expect(project_variables_builder) + .to receive(:secret_variables) + .with(environment: 'scoped', protected_ref: false) + .once + .and_call_original + end + + 2.times do + expect(builder.secret_project_variables(ref: 'other', environment: nil)) + .to contain_exactly(unprotected_variable_item) + + expect(builder.secret_project_variables(ref: 'other', environment: 'scoped')) + .to contain_exactly(unprotected_variable_item, scoped_variable_item) + end + end + end + end + end + end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 20af84ce648..5f46607b042 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -9,6 +9,10 @@ module Gitlab subject { described_class.new(config, user: nil).execute } + before do + stub_feature_flags(allow_unsafe_ruby_regexp: false) + end + shared_examples 'returns errors' do |error_message| it 'adds a message when an error is encountered' do expect(subject.errors).to include(error_message) @@ -609,13 +613,13 @@ module Gitlab context 'when it is an array of integers' do let(:only) { [1, 1] } - it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regexps' + it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regular expressions using re2 syntax' end context 'when it is invalid regex' do let(:only) { ["/*invalid/"] } - it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regexps' + it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regular expressions using re2 syntax' end end @@ -633,13 +637,13 @@ module Gitlab context 'when it is an array of integers' do let(:except) { [1, 1] } - it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regexps' + it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regular expressions using re2 syntax' end context 'when it is invalid regex' do let(:except) { ["/*invalid/"] } - it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regexps' + it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regular expressions using re2 syntax' end end end @@ -710,16 +714,16 @@ module Gitlab end end - context 'when script is array of arrays of strings' do + context 'when script is nested arrays of strings' do let(:config) do { - before_script: [["global script", "echo 1"], ["ls"], "pwd"], + before_script: [[["global script"], "echo 1"], "echo 2", ["ls"], "pwd"], test: { script: ["script"] } } end it "return commands with scripts concatenated" do - expect(subject[:options][:before_script]).to eq(["global script", "echo 1", "ls", "pwd"]) + expect(subject[:options][:before_script]).to eq(["global script", "echo 1", "echo 2", "ls", "pwd"]) end end end @@ -737,15 +741,15 @@ module Gitlab end end - context 'when script is array of arrays of strings' do + context 'when script is nested arrays of strings' do let(:config) do { - test: { script: [["script"], ["echo 1"], "ls"] } + test: { script: [[["script"], "echo 1", "echo 2"], "ls"] } } end it "return commands with scripts concatenated" do - expect(subject[:options][:script]).to eq(["script", "echo 1", "ls"]) + expect(subject[:options][:script]).to eq(["script", "echo 1", "echo 2", "ls"]) end end end @@ -790,16 +794,16 @@ module Gitlab end end - context 'when script is array of arrays of strings' do + context 'when script is nested arrays of strings' do let(:config) do { - after_script: [["global script", "echo 1"], ["ls"], "pwd"], + after_script: [[["global script"], "echo 1"], "echo 2", ["ls"], "pwd"], test: { script: ["script"] } } end it "return after_script in options" do - expect(subject[:options][:after_script]).to eq(["global script", "echo 1", "ls", "pwd"]) + expect(subject[:options][:after_script]).to eq(["global script", "echo 1", "echo 2", "ls", "pwd"]) end end end @@ -2469,40 +2473,16 @@ module Gitlab it_behaves_like 'returns errors', 'jobs:rspec:tags config should be an array of strings' end - context 'returns errors if before_script parameter is invalid' do - let(:config) { YAML.dump({ before_script: "bundle update", rspec: { script: "test" } }) } - - it_behaves_like 'returns errors', 'before_script config should be an array containing strings and arrays of strings' - end - context 'returns errors if job before_script parameter is not an array of strings' do let(:config) { YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } }) } - it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be an array containing strings and arrays of strings' - end - - context 'returns errors if job before_script parameter is multi-level nested array of strings' do - let(:config) { YAML.dump({ rspec: { script: "test", before_script: [["ls", ["pwd"]], "test"] } }) } - - it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be an array containing strings and arrays of strings' - end - - context 'returns errors if after_script parameter is invalid' do - let(:config) { YAML.dump({ after_script: "bundle update", rspec: { script: "test" } }) } - - it_behaves_like 'returns errors', 'after_script config should be an array containing strings and arrays of strings' + it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be a string or a nested array of strings up to 10 levels deep' end context 'returns errors if job after_script parameter is not an array of strings' do let(:config) { YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } }) } - it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be an array containing strings and arrays of strings' - end - - context 'returns errors if job after_script parameter is multi-level nested array of strings' do - let(:config) { YAML.dump({ rspec: { script: "test", after_script: [["ls", ["pwd"]], "test"] } }) } - - it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be an array containing strings and arrays of strings' + it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be a string or a nested array of strings up to 10 levels deep' end context 'returns errors if image parameter is invalid' do |