diff options
Diffstat (limited to 'spec/lib/gitlab/ci')
75 files changed, 1766 insertions, 715 deletions
diff --git a/spec/lib/gitlab/ci/ansi2json/parser_spec.rb b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb index cf93ebe0721..b11002e8e93 100644 --- a/spec/lib/gitlab/ci/ansi2json/parser_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' # The rest of the specs for this class are covered in style_spec.rb RSpec.describe Gitlab::Ci::Ansi2json::Parser do diff --git a/spec/lib/gitlab/ci/ansi2json/result_spec.rb b/spec/lib/gitlab/ci/ansi2json/result_spec.rb index b7b4d6de8b9..14e2a9625fe 100644 --- a/spec/lib/gitlab/ci/ansi2json/result_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json/result_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Ansi2json::Result do let(:stream) { StringIO.new('hello') } diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/zip_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/zip_stream_spec.rb deleted file mode 100644 index 2c236ba3726..00000000000 --- a/spec/lib/gitlab/ci/build/artifacts/adapters/zip_stream_spec.rb +++ /dev/null @@ -1,86 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Ci::Build::Artifacts::Adapters::ZipStream do - let(:file_name) { 'single_file.zip' } - let(:fixture_path) { "lib/gitlab/ci/build/artifacts/adapters/zip_stream/#{file_name}" } - let(:stream) { File.open(expand_fixture_path(fixture_path), 'rb') } - - describe '#initialize' do - it 'initializes when stream is passed' do - expect { described_class.new(stream) }.not_to raise_error - end - - context 'when stream is not passed' do - let(:stream) { nil } - - it 'raises an error' do - expect { described_class.new(stream) }.to raise_error(described_class::InvalidStreamError) - end - end - end - - describe '#each_blob' do - let(:adapter) { described_class.new(stream) } - - context 'when stream is a zip file' do - it 'iterates file content when zip file contains one file' do - expect { |b| adapter.each_blob(&b) } - .to yield_with_args("file 1 content\n") - end - - context 'when zip file contains multiple files' do - let(:file_name) { 'multiple_files.zip' } - - it 'iterates content of all files' do - expect { |b| adapter.each_blob(&b) } - .to yield_successive_args("file 1 content\n", "file 2 content\n") - end - end - - context 'when zip file includes files in a directory' do - let(:file_name) { 'with_directory.zip' } - - it 'iterates contents from files only' do - expect { |b| adapter.each_blob(&b) } - .to yield_successive_args("file 1 content\n", "file 2 content\n") - end - end - - context 'when zip contains a file which decompresses beyond the size limit' do - let(:file_name) { '200_mb_decompressed.zip' } - - it 'does not read the file' do - expect { |b| adapter.each_blob(&b) }.not_to yield_control - end - end - - context 'when the zip contains too many files' do - let(:file_name) { '100_files.zip' } - - it 'stops processing when the limit is reached' do - expect { |b| adapter.each_blob(&b) } - .to yield_control.exactly(described_class::MAX_FILES_PROCESSED).times - end - end - - context 'when stream is a zipbomb' do - let(:file_name) { 'zipbomb.zip' } - - it 'does not read the file' do - expect { |b| adapter.each_blob(&b) }.not_to yield_control - end - end - end - - context 'when stream is not a zip file' do - let(:stream) { File.open(expand_fixture_path('junit/junit.xml.gz'), 'rb') } - - it 'does not yield any data' do - expect { |b| adapter.each_blob(&b) }.not_to yield_control - expect { adapter.each_blob { |b| b } }.not_to raise_error - end - end - end -end diff --git a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb index 27b7dac2ae4..773eaf4b3fc 100644 --- a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Build::Artifacts::Path do describe '#valid?' do diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb index 436ad59bdf7..e560f1c2b5a 100644 --- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb @@ -108,7 +108,8 @@ RSpec.describe Gitlab::Ci::Build::Policy::Variables do project: project, ref: 'master', stage: 'review', - environment: 'test/$CI_JOB_STAGE/1') + environment: 'test/$CI_JOB_STAGE/1', + ci_stage: build(:ci_stage, name: 'review', project: project, pipeline: pipeline)) end before do diff --git a/spec/lib/gitlab/ci/build/policy_spec.rb b/spec/lib/gitlab/ci/build/policy_spec.rb index b85b093fd03..4baf4a1b4c4 100644 --- a/spec/lib/gitlab/ci/build/policy_spec.rb +++ b/spec/lib/gitlab/ci/build/policy_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Build::Policy do let(:policy) { spy('policy specification') } diff --git a/spec/lib/gitlab/ci/build/port_spec.rb b/spec/lib/gitlab/ci/build/port_spec.rb index 480418e0851..51820c1ab2c 100644 --- a/spec/lib/gitlab/ci/build/port_spec.rb +++ b/spec/lib/gitlab/ci/build/port_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Build::Port do subject { described_class.new(port) } diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb index f192862c1c4..f9ebab149a5 100644 --- a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb @@ -3,44 +3,54 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do - shared_examples 'an exists rule with a context' do - subject { described_class.new(globs).satisfied_by?(pipeline, context) } + describe '#satisfied_by?' do + shared_examples 'an exists rule with a context' do + it_behaves_like 'a glob matching rule' do + let(:project) { create(:project, :custom_repo, files: files) } + end - it_behaves_like 'a glob matching rule' do - let(:project) { create(:project, :custom_repo, files: files) } - end + context 'after pattern comparision limit is reached' do + let(:globs) { ['*definitely_not_a_matching_glob*'] } + let(:project) { create(:project, :repository) } - context 'after pattern comparision limit is reached' do - let(:globs) { ['*definitely_not_a_matching_glob*'] } - let(:project) { create(:project, :repository) } + before do + stub_const('Gitlab::Ci::Build::Rules::Rule::Clause::Exists::MAX_PATTERN_COMPARISONS', 2) + expect(File).to receive(:fnmatch?).twice.and_call_original + end - before do - stub_const('Gitlab::Ci::Build::Rules::Rule::Clause::Exists::MAX_PATTERN_COMPARISONS', 2) - expect(File).to receive(:fnmatch?).twice.and_call_original + it { is_expected.to be_truthy } end - - it { is_expected.to be_truthy } end - end - describe '#satisfied_by?' do - let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) } + subject(:satisfied_by?) { described_class.new(globs).satisfied_by?(nil, context) } context 'when context is Build::Context::Build' do it_behaves_like 'an exists rule with a context' do - let(:context) { Gitlab::Ci::Build::Context::Build.new(pipeline, sha: 'abc1234') } + let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.commit.sha) } + let(:context) { Gitlab::Ci::Build::Context::Build.new(pipeline, sha: project.repository.commit.sha) } end end context 'when context is Build::Context::Global' do it_behaves_like 'an exists rule with a context' do + let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.commit.sha) } let(:context) { Gitlab::Ci::Build::Context::Global.new(pipeline, yaml_variables: {}) } end end context 'when context is Config::External::Context' do + let(:context) { Gitlab::Ci::Config::External::Context.new(project: project, sha: sha) } + it_behaves_like 'an exists rule with a context' do - let(:context) { Gitlab::Ci::Config::External::Context.new(project: project, sha: project.repository.tree.sha) } + let(:sha) { project.repository.commit.sha } + end + + context 'when context has no project' do + let(:globs) { ['Dockerfile'] } + let(:project) {} + let(:sha) { 'abc1234' } + + it { is_expected.to eq(false) } end end end diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb index 36c26c8ee4f..3562706ff33 100644 --- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb @@ -230,12 +230,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Environment do end end - context 'when auto_stop_in is invalid format' do - let(:auto_stop_in) { 'invalid' } + context 'when variables are used for auto_stop_in' do + let(:auto_stop_in) { '$TTL' } - it 'becomes invalid' do - expect(entry).not_to be_valid - expect(entry.errors).to include 'environment auto stop in should be a duration' + it 'becomes valid' do + expect(entry).to be_valid + expect(entry.auto_stop_in).to eq(auto_stop_in) end end end diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb index 6121c28070f..b37498ba10a 100644 --- a/spec/lib/gitlab/ci/config/entry/image_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb @@ -4,8 +4,6 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Image do before do - stub_feature_flags(ci_docker_image_pull_policy: true) - entry.compose! end @@ -129,18 +127,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do it 'is valid' do expect(entry).to be_valid end - - context 'when the feature flag ci_docker_image_pull_policy is disabled' do - before do - stub_feature_flags(ci_docker_image_pull_policy: false) - entry.compose! - end - - it 'is not valid' do - expect(entry).not_to be_valid - expect(entry.errors).to include('image config contains unknown keys: pull_policy') - end - end end describe '#value' do @@ -150,19 +136,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do pull_policy: ['if-not-present'] ) end - - context 'when the feature flag ci_docker_image_pull_policy is disabled' do - before do - stub_feature_flags(ci_docker_image_pull_policy: false) - entry.compose! - end - - it 'is not valid' do - expect(entry.value).to eq( - name: 'image:1.0' - ) - end - end end end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index ca336c3ecaa..75ac2ca87ab 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -605,8 +605,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do let(:deps) do double('deps', 'default_entry' => default, - 'workflow_entry' => workflow, - 'variables_value' => nil) + 'workflow_entry' => workflow) end context 'when job config overrides default config' do diff --git a/spec/lib/gitlab/ci/config/entry/legacy_variables_spec.rb b/spec/lib/gitlab/ci/config/entry/legacy_variables_spec.rb new file mode 100644 index 00000000000..e9edec9a0a4 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/legacy_variables_spec.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Config::Entry::LegacyVariables do + let(:config) { {} } + let(:metadata) { {} } + + subject(:entry) { described_class.new(config, **metadata) } + + before do + entry.compose! + end + + shared_examples 'valid config' do + describe '#value' do + it 'returns hash with key value strings' do + expect(entry.value).to eq result + 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 + + shared_examples 'invalid config' do |error_message| + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include(error_message) + end + end + end + + context 'when entry config value has key-value pairs' do + let(:config) do + { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } + end + + let(:result) do + { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } + end + + it_behaves_like 'valid config' + + describe '#value_with_data' do + it 'returns variable with data' do + expect(entry.value_with_data).to eq( + 'VARIABLE_1' => { value: 'value 1' }, + 'VARIABLE_2' => { value: 'value 2' } + ) + end + end + end + + context 'with numeric keys and values in the config' do + let(:config) { { 10 => 20 } } + let(:result) do + { '10' => '20' } + end + + it_behaves_like 'valid config' + end + + context 'when key is an array' do + let(:config) { { ['VAR1'] => 'val1' } } + let(:result) do + { 'VAR1' => 'val1' } + end + + it_behaves_like 'invalid config', /should be a hash of key value pairs/ + end + + context 'when value is a symbol' do + let(:config) { { 'VAR1' => :val1 } } + let(:result) do + { 'VAR1' => 'val1' } + end + + it_behaves_like 'valid config' + end + + context 'when value is a boolean' do + let(:config) { { 'VAR1' => true } } + let(:result) do + { 'VAR1' => 'val1' } + end + + it_behaves_like 'invalid config', /should be a hash of key value pairs/ + end + + context 'when entry config value has key-value pair and hash' do + let(:config) do + { 'VARIABLE_1' => { value: 'value 1', description: 'variable 1' }, + 'VARIABLE_2' => 'value 2' } + end + + it_behaves_like 'invalid config', /should be a hash of key value pairs/ + + context 'when metadata has use_value_data: true' do + let(:metadata) { { use_value_data: true } } + + let(:result) do + { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } + end + + it_behaves_like 'valid config' + + describe '#value_with_data' do + it 'returns variable with data' do + expect(entry.value_with_data).to eq( + 'VARIABLE_1' => { value: 'value 1', description: 'variable 1' }, + 'VARIABLE_2' => { value: 'value 2' } + ) + end + end + end + end + + context 'when entry value is an array' do + let(:config) { [:VAR, 'test'] } + + it_behaves_like 'invalid config', /should be a hash of key value pairs/ + end + + context 'when metadata has use_value_data: true' do + let(:metadata) { { use_value_data: true } } + + context 'when entry value has hash with other key-pairs' do + let(:config) do + { 'VARIABLE_1' => { value: 'value 1', hello: 'variable 1' }, + 'VARIABLE_2' => 'value 2' } + end + + it_behaves_like 'invalid config', /should be a hash of key value pairs, value can be a hash/ + end + + context 'when entry config value has hash with nil description' do + let(:config) do + { 'VARIABLE_1' => { value: 'value 1', description: nil } } + end + + it_behaves_like 'invalid config', /should be a hash of key value pairs, value can be a hash/ + end + + context 'when entry config value has hash without description' do + let(:config) do + { 'VARIABLE_1' => { value: 'value 1' } } + end + + let(:result) do + { 'VARIABLE_1' => 'value 1' } + end + + it_behaves_like 'valid config' + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/port_spec.rb b/spec/lib/gitlab/ci/config/entry/port_spec.rb index e2840c07f6b..77f846f95f0 100644 --- a/spec/lib/gitlab/ci/config/entry/port_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/port_spec.rb @@ -48,7 +48,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Port do let(:config) do { number: 80, protocol: 'http', - name: 'foobar' } + name: 'foobar' } end describe '#valid?' do diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb index 714b0a3b6aa..5f42a8c49a7 100644 --- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb @@ -197,6 +197,34 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do end end end + + context 'when a variable has an invalid data attribute' do + let(:config) do + { + script: 'echo', + variables: { 'VAR1' => 'val 1', 'VAR2' => { value: 'val 2', description: 'hello var 2' } } + } + end + + it 'reports error about variable' do + expect(entry.errors) + .to include 'variables:var2 config must be a string' + end + + context 'when the FF ci_variables_refactoring_to_variable is disabled' do + let(:entry_without_ff) { node_class.new(config, name: :rspec) } + + before do + stub_feature_flags(ci_variables_refactoring_to_variable: false) + entry_without_ff.compose! + end + + it 'reports error about variable' do + expect(entry_without_ff.errors) + .to include /config should be a hash of key value pairs/ + end + end + end end end @@ -212,13 +240,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do let(:unspecified) { double('unspecified', 'specified?' => false) } let(:default) { double('default', '[]' => unspecified) } let(:workflow) { double('workflow', 'has_rules?' => false) } - let(:variables) {} let(:deps) do double('deps', default_entry: default, - workflow_entry: workflow, - variables_value: variables) + workflow_entry: workflow) end context 'with workflow rules' do diff --git a/spec/lib/gitlab/ci/config/entry/product/matrix_spec.rb b/spec/lib/gitlab/ci/config/entry/product/matrix_spec.rb index ff44a235ea5..394d91466bf 100644 --- a/spec/lib/gitlab/ci/config/entry/product/matrix_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/product/matrix_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' require_dependency 'active_model' RSpec.describe ::Gitlab::Ci::Config::Entry::Product::Matrix do diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 55ad119ea21..3d19987a0be 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -117,49 +117,49 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do expect(root.jobs_value.keys).to eq([:rspec, :spinach, :release]) expect(root.jobs_value[:rspec]).to eq( { name: :rspec, - script: %w[rspec ls], - before_script: %w(ls pwd), - image: { name: 'image:1.0' }, - services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], - stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], - job_variables: {}, - root_variables_inheritance: true, - ignore: false, - after_script: ['make clean'], - only: { refs: %w[branches tags] }, - scheduling_type: :stage } + script: %w[rspec ls], + before_script: %w(ls pwd), + image: { name: 'image:1.0' }, + services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], + stage: 'test', + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], + job_variables: {}, + root_variables_inheritance: true, + ignore: false, + after_script: ['make clean'], + only: { refs: %w[branches tags] }, + scheduling_type: :stage } ) expect(root.jobs_value[:spinach]).to eq( { name: :spinach, - before_script: [], - script: %w[spinach], - image: { name: 'image:1.0' }, - services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], - stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], - job_variables: {}, - root_variables_inheritance: true, - ignore: false, - after_script: ['make clean'], - only: { refs: %w[branches tags] }, - scheduling_type: :stage } + before_script: [], + script: %w[spinach], + image: { name: 'image:1.0' }, + services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], + stage: 'test', + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], + job_variables: {}, + root_variables_inheritance: true, + ignore: false, + after_script: ['make clean'], + only: { refs: %w[branches tags] }, + scheduling_type: :stage } ) expect(root.jobs_value[:release]).to eq( { name: :release, - stage: 'release', - before_script: [], - script: ["make changelog | tee release_changelog.txt"], - release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" }, - image: { name: "image:1.0" }, - services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }], - cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }], - only: { refs: %w(branches tags) }, - job_variables: { 'VAR' => 'job' }, - root_variables_inheritance: true, - after_script: [], - ignore: false, - scheduling_type: :stage } + stage: 'release', + before_script: [], + script: ["make changelog | tee release_changelog.txt"], + release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" }, + image: { name: "image:1.0" }, + services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }], + cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }], + only: { refs: %w(branches tags) }, + job_variables: { 'VAR' => { value: 'job' } }, + root_variables_inheritance: true, + after_script: [], + ignore: false, + scheduling_type: :stage } ) end end @@ -196,31 +196,31 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do it 'returns jobs configuration' do expect(root.jobs_value).to eq( rspec: { name: :rspec, - script: %w[rspec ls], - before_script: %w(ls pwd), - image: { name: 'image:1.0' }, - services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], - stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], - job_variables: {}, - root_variables_inheritance: true, - ignore: false, - after_script: ['make clean'], - only: { refs: %w[branches tags] }, - scheduling_type: :stage }, + script: %w[rspec ls], + before_script: %w(ls pwd), + image: { name: 'image:1.0' }, + services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], + stage: 'test', + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], + job_variables: {}, + root_variables_inheritance: true, + ignore: false, + after_script: ['make clean'], + only: { refs: %w[branches tags] }, + scheduling_type: :stage }, spinach: { name: :spinach, - before_script: [], - script: %w[spinach], - image: { name: 'image:1.0' }, - services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], - stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], - job_variables: { 'VAR' => 'job' }, - root_variables_inheritance: true, - ignore: false, - after_script: ['make clean'], - only: { refs: %w[branches tags] }, - scheduling_type: :stage } + before_script: [], + script: %w[spinach], + image: { name: 'image:1.0' }, + services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], + stage: 'test', + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], + job_variables: { 'VAR' => { value: 'job' } }, + root_variables_inheritance: true, + ignore: false, + after_script: ['make clean'], + only: { refs: %w[branches tags] }, + scheduling_type: :stage } ) end end @@ -350,6 +350,33 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do end end end + + context 'when a variable has an invalid data key' do + let(:hash) do + { variables: { VAR1: { invalid: 'hello' } }, rspec: { script: 'hello' } } + end + + describe '#errors' do + it 'reports errors about the invalid variable' do + expect(root.errors) + .to include /var1 config uses invalid data keys: invalid/ + end + + context 'when the FF ci_variables_refactoring_to_variable is disabled' do + let(:root_without_ff) { described_class.new(hash, user: user, project: project) } + + before do + stub_feature_flags(ci_variables_refactoring_to_variable: false) + root_without_ff.compose! + end + + it 'reports errors about the invalid variable' do + expect(root_without_ff.errors) + .to include /variables config should be a hash of key value pairs, value can be a hash/ + end + end + end + end end context 'when value is not a hash' do diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb index c85fe366da6..303d825c591 100644 --- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' require 'gitlab_chronic_duration' -require_dependency 'active_model' RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do let(:factory) do @@ -363,7 +362,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do it { is_expected.not_to be_valid } it 'returns an error about invalid variables:' do - expect(subject.errors).to include(/variables config should be a hash of key value pairs/) + expect(subject.errors).to include(/variables config should be a hash/) + end + + context 'when the FF ci_variables_refactoring_to_variable is disabled' do + let(:entry_without_ff) { factory.create! } + + before do + stub_feature_flags(ci_variables_refactoring_to_variable: false) + entry_without_ff.compose! + end + + it 'returns an error about invalid variables:' do + expect(subject.errors).to include(/variables config should be a hash/) + end end end end diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb index 821ab442d61..e36484bb0ae 100644 --- a/spec/lib/gitlab/ci/config/entry/service_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Service do before do - stub_feature_flags(ci_docker_image_pull_policy: true) entry.compose! end @@ -149,18 +148,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do it 'is valid' do expect(entry).to be_valid end - - context 'when the feature flag ci_docker_image_pull_policy is disabled' do - before do - stub_feature_flags(ci_docker_image_pull_policy: false) - entry.compose! - end - - it 'is not valid' do - expect(entry).not_to be_valid - expect(entry.errors).to include('service config contains unknown keys: pull_policy') - end - end end describe '#value' do @@ -170,18 +157,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do pull_policy: ['if-not-present'] ) end - - context 'when the feature flag ci_docker_image_pull_policy is disabled' do - before do - stub_feature_flags(ci_docker_image_pull_policy: false) - end - - it 'is not valid' do - expect(entry.value).to eq( - name: 'postgresql:9.5' - ) - end - end end end end diff --git a/spec/lib/gitlab/ci/config/entry/variable_spec.rb b/spec/lib/gitlab/ci/config/entry/variable_spec.rb new file mode 100644 index 00000000000..744a89d4509 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/variable_spec.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Config::Entry::Variable do + let(:config) { {} } + let(:metadata) { {} } + + subject(:entry) do + described_class.new(config, **metadata).tap do |entry| + entry.key = 'VAR1' # composable_hash requires key to be set + end + end + + before do + entry.compose! + end + + describe 'SimpleVariable' do + context 'when config is a string' do + let(:config) { 'value' } + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#value' do + subject(:value) { entry.value } + + it { is_expected.to eq('value') } + end + end + + context 'when config is an integer' do + let(:config) { 1 } + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#value' do + subject(:value) { entry.value } + + it { is_expected.to eq('1') } + end + end + + context 'when config is an array' do + let(:config) { [] } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + subject(:errors) { entry.errors } + + it { is_expected.to include 'variable definition must be either a string or a hash' } + end + end + end + + describe 'ComplexVariable' do + context 'when config is a hash with description' do + let(:config) { { value: 'value', description: 'description' } } + + context 'when metadata allowed_value_data is not provided' do + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + subject(:errors) { entry.errors } + + it { is_expected.to include 'var1 config must be a string' } + end + end + + context 'when metadata allowed_value_data is (value, description)' do + let(:metadata) { { allowed_value_data: %i[value description] } } + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#value' do + subject(:value) { entry.value } + + it { is_expected.to eq('value') } + end + + describe '#value_with_data' do + subject(:value_with_data) { entry.value_with_data } + + it { is_expected.to eq(value: 'value', description: 'description') } + end + + context 'when config value is a symbol' do + let(:config) { { value: :value, description: 'description' } } + + describe '#value' do + subject(:value) { entry.value } + + it { is_expected.to eq('value') } + end + + describe '#value_with_data' do + subject(:value_with_data) { entry.value_with_data } + + it { is_expected.to eq(value: 'value', description: 'description') } + end + end + + context 'when config value is an integer' do + let(:config) { { value: 123, description: 'description' } } + + describe '#value' do + subject(:value) { entry.value } + + it { is_expected.to eq('123') } + end + + describe '#value_with_data' do + subject(:value_with_data) { entry.value_with_data } + + it { is_expected.to eq(value: '123', description: 'description') } + end + end + + context 'when config value is an array' do + let(:config) { { value: ['value'], description: 'description' } } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + subject(:errors) { entry.errors } + + it { is_expected.to include 'var1 config value must be an alphanumeric string' } + end + end + + context 'when config description is a symbol' do + let(:config) { { value: 'value', description: :description } } + + describe '#value' do + subject(:value) { entry.value } + + it { is_expected.to eq('value') } + end + + describe '#value_with_data' do + subject(:value_with_data) { entry.value_with_data } + + it { is_expected.to eq(value: 'value', description: :description) } + end + end + end + + context 'when metadata allowed_value_data is (value, xyz)' do + let(:metadata) { { allowed_value_data: %i[value xyz] } } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + subject(:errors) { entry.errors } + + it { is_expected.to include 'var1 config uses invalid data keys: description' } + end + end + end + + context 'when config is a hash without description' do + let(:config) { { value: 'value' } } + + context 'when metadata allowed_value_data is not provided' do + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + subject(:errors) { entry.errors } + + it { is_expected.to include 'var1 config must be a string' } + end + end + + context 'when metadata allowed_value_data is (value, description)' do + let(:metadata) { { allowed_value_data: %i[value description] } } + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#value' do + subject(:value) { entry.value } + + it { is_expected.to eq('value') } + end + + describe '#value_with_data' do + subject(:value_with_data) { entry.value_with_data } + + it { is_expected.to eq(value: 'value') } + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb index 78d37e228df..ad7290d0589 100644 --- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb @@ -3,41 +3,46 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Variables do + let(:config) { {} } let(:metadata) { {} } - subject { described_class.new(config, **metadata) } + subject(:entry) { described_class.new(config, **metadata) } + + before do + entry.compose! + end shared_examples 'valid config' do describe '#value' do it 'returns hash with key value strings' do - expect(subject.value).to eq result + expect(entry.value).to eq result end end describe '#errors' do it 'does not append errors' do - expect(subject.errors).to be_empty + expect(entry.errors).to be_empty end end describe '#valid?' do it 'is valid' do - expect(subject).to be_valid + expect(entry).to be_valid end end end - shared_examples 'invalid config' do + shared_examples 'invalid config' do |error_message| describe '#valid?' do it 'is not valid' do - expect(subject).not_to be_valid + expect(entry).not_to be_valid end end describe '#errors' do it 'saves errors' do - expect(subject.errors) - .to include /should be a hash of key value pairs/ + expect(entry.errors) + .to include(error_message) end end end @@ -52,6 +57,15 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do end it_behaves_like 'valid config' + + describe '#value_with_data' do + it 'returns variable with data' do + expect(entry.value_with_data).to eq( + 'VARIABLE_1' => { value: 'value 1' }, + 'VARIABLE_2' => { value: 'value 2' } + ) + end + end end context 'with numeric keys and values in the config' do @@ -63,33 +77,63 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do it_behaves_like 'valid config' end + context 'when key is an array' do + let(:config) { { ['VAR1'] => 'val1' } } + + it_behaves_like 'invalid config', /must be an alphanumeric string/ + end + + context 'when value is a symbol' do + let(:config) { { 'VAR1' => :val1 } } + let(:result) do + { 'VAR1' => 'val1' } + end + + it_behaves_like 'valid config' + end + + context 'when value is a boolean' do + let(:config) { { 'VAR1' => true } } + + it_behaves_like 'invalid config', /must be either a string or a hash/ + end + context 'when entry config value has key-value pair and hash' do let(:config) do { 'VARIABLE_1' => { value: 'value 1', description: 'variable 1' }, 'VARIABLE_2' => 'value 2' } end - let(:result) do - { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } - end + it_behaves_like 'invalid config', /variable_1 config must be a string/ - it_behaves_like 'invalid config' + context 'when metadata has allowed_value_data' do + let(:metadata) { { allowed_value_data: %i[value description] } } - context 'when metadata has use_value_data' do - let(:metadata) { { use_value_data: true } } + let(:result) do + { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } + end it_behaves_like 'valid config' + + describe '#value_with_data' do + it 'returns variable with data' do + expect(entry.value_with_data).to eq( + 'VARIABLE_1' => { value: 'value 1', description: 'variable 1' }, + 'VARIABLE_2' => { value: 'value 2' } + ) + end + end end end context 'when entry value is an array' do let(:config) { [:VAR, 'test'] } - it_behaves_like 'invalid config' + it_behaves_like 'invalid config', /variables config should be a hash/ end - context 'when metadata has use_value_data' do - let(:metadata) { { use_value_data: true } } + context 'when metadata has allowed_value_data' do + let(:metadata) { { allowed_value_data: %i[value description] } } context 'when entry value has hash with other key-pairs' do let(:config) do @@ -97,7 +141,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do 'VARIABLE_2' => 'value 2' } end - it_behaves_like 'invalid config' + it_behaves_like 'invalid config', /variable_1 config uses invalid data keys: hello/ end context 'when entry config value has hash with nil description' do @@ -105,7 +149,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do { 'VARIABLE_1' => { value: 'value 1', description: nil } } end - it_behaves_like 'invalid config' + it_behaves_like 'invalid config', /variable_1 config description must be an alphanumeric string/ end context 'when entry config value has hash without description' do diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb index 45dfea636f3..c22afb32756 100644 --- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -219,4 +219,43 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do ) } end + + describe '#to_hash' do + subject(:to_hash) { remote_file.to_hash } + + before do + stub_full_request(location).to_return(body: remote_file_content) + end + + context 'with a valid remote file' do + it 'returns the content as a hash' do + expect(to_hash).to eql( + before_script: ["apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs", + "ruby -v", + "which ruby", + "bundle install --jobs $(nproc) \"${FLAGS[@]}\""] + ) + end + end + + context 'when it has `include` with rules:exists' do + let(:remote_file_content) do + <<~HEREDOC + include: + - local: another-file.yml + rules: + - exists: [Dockerfile] + HEREDOC + end + + it 'returns the content as a hash' do + expect(to_hash).to eql( + include: [ + { local: 'another-file.yml', + rules: [{ exists: ['Dockerfile'] }] } + ] + ) + 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 e74fdc2071b..9eaba12f388 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -210,7 +210,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do { 'local' => local_file }, { 'local' => local_file } ], - image: 'image:1.0' } + image: 'image:1.0' } end it 'does not raise an exception' do @@ -427,7 +427,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do { 'local' => 'hello/secret-file1.yml' }, { 'local' => 'hello/secret-file2.yml' } ], - image: 'ruby:2.7' } + image: 'ruby:2.7' } end it 'has expanset with two' do diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index 841a46e197d..b1dff6f9723 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -94,6 +94,36 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do end end + context 'when the remote file has `include` with rules:exists' do + let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' } + let(:values) { { include: remote_file, image: 'image:1.0' } } + let(:external_file_content) do + <<-HEREDOC + include: + - local: another-file.yml + rules: + - exists: [Dockerfile] + + rspec: + script: + - bundle exec rspec + HEREDOC + end + + before do + stub_full_request(remote_file).to_return(body: external_file_content) + end + + it 'evaluates the rule as false' do + output = processor.perform + expect(output.keys).to match_array([:image, :rspec]) + end + + it "removes the 'include' keyword" do + expect(processor.perform[:include]).to be_nil + end + end + context 'with a valid local external file is defined' do let(:values) { { include: '/lib/gitlab/ci/templates/template.yml', image: 'image:1.0' } } let(:local_file_content) do diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb index 7e0b2b5aa8e..3d46d266c13 100644 --- a/spec/lib/gitlab/ci/lint_spec.rb +++ b/spec/lib/gitlab/ci/lint_spec.rb @@ -341,9 +341,9 @@ RSpec.describe Gitlab::Ci::Lint do let(:counters) do { 'count' => a_kind_of(Numeric), - 'avg' => a_kind_of(Numeric), - 'max' => a_kind_of(Numeric), - 'min' => a_kind_of(Numeric) + 'avg' => a_kind_of(Numeric), + 'max' => a_kind_of(Numeric), + 'min' => a_kind_of(Numeric) } end diff --git a/spec/lib/gitlab/ci/mask_secret_spec.rb b/spec/lib/gitlab/ci/mask_secret_spec.rb index 7d950c86700..ffe36e69a8f 100644 --- a/spec/lib/gitlab/ci/mask_secret_spec.rb +++ b/spec/lib/gitlab/ci/mask_secret_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::MaskSecret do subject { described_class } diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb index c99cfa94aa6..38b229e0dd8 100644 --- a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb +++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Parsers::Sbom::CyclonedxProperties do subject(:parse_source) { described_class.parse_source(properties) } diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb index 431fe9f3591..f3636106b98 100644 --- a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb +++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb @@ -102,11 +102,11 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::Cyclonedx do it 'adds each component, ignoring unused attributes' do expect(report).to receive(:add_component) - .with({ "name" => "activesupport", "version" => "5.1.4", "type" => "library" }) + .with(an_object_having_attributes(name: "activesupport", version: "5.1.4", component_type: "library")) expect(report).to receive(:add_component) - .with({ "name" => "byebug", "version" => "10.0.0", "type" => "library" }) + .with(an_object_having_attributes(name: "byebug", version: "10.0.0", component_type: "library")) expect(report).to receive(:add_component) - .with({ "name" => "minimal-component", "type" => "library" }) + .with(an_object_having_attributes(name: "minimal-component", version: nil, component_type: "library")) parse! end diff --git a/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb index 30114b17cac..7222ebc3cb8 100644 --- a/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb +++ b/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning do subject { described_class.source(property_data) } @@ -17,11 +17,11 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning do end it 'returns expected source data' do - is_expected.to eq({ - 'type' => :dependency_scanning, - 'data' => property_data, - 'fingerprint' => '4dbcb747e6f0fb3ed4f48d96b777f1d64acdf43e459fdfefad404e55c004a188' - }) + is_expected.to have_attributes( + source_type: :dependency_scanning, + data: property_data, + fingerprint: '4dbcb747e6f0fb3ed4f48d96b777f1d64acdf43e459fdfefad404e55c004a188' + ) 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 6495d1f654b..297ef1f5bb9 100644 --- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do } end - where(vulnerability_finding_signatures_enabled: [true, false]) + where(signatures_enabled: [true, false]) with_them do let_it_be(:pipeline) { create(:ci_pipeline) } @@ -44,7 +44,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do let(:validator_class) { Gitlab::Ci::Parsers::Security::Validators::SchemaValidator } let(:data) { {}.merge(scanner_data) } let(:json_data) { data.to_json } - let(:parser) { described_class.new(json_data, report, vulnerability_finding_signatures_enabled, validate: validate) } + let(:parser) { described_class.new(json_data, report, signatures_enabled: signatures_enabled, validate: validate) } subject(:parse_report) { parser.parse! } @@ -191,7 +191,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do context 'report parsing' do before do - artifact.each_blob { |blob| described_class.parse!(blob, report, vulnerability_finding_signatures_enabled) } + artifact.each_blob { |blob| described_class.parse!(blob, report, signatures_enabled: signatures_enabled) } end describe 'parsing finding.name' do @@ -262,7 +262,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do describe 'top-level scanner' do it 'is the primary scanner' do expect(report.primary_scanner.external_id).to eq('gemnasium') - expect(report.primary_scanner.name).to eq('Gemnasium') + expect(report.primary_scanner.name).to eq('Gemnasium top-level') expect(report.primary_scanner.vendor).to eq('GitLab') expect(report.primary_scanner.version).to eq('2.18.0') end @@ -278,9 +278,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do describe 'parsing scanners' do subject(:scanner) { report.findings.first.scanner } - context 'when vendor is not missing in scanner' do - it 'returns scanner with parsed vendor value' do - expect(scanner.vendor).to eq('GitLab') + context 'when the report contains top-level scanner' do + it 'sets the scanner of finding as top-level scanner' do + expect(scanner.name).to eq('Gemnasium top-level') + end + end + + context 'when the report does not contain top-level scanner' do + let(:artifact) { build(:ci_job_artifact, :common_security_report_without_top_level_scanner) } + + it 'sets the scanner of finding as `vulnerabilities[].scanner`' do + expect(scanner.name).to eq('Gemnasium') end end end @@ -465,7 +473,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do finding = report.findings.first highest_signature = finding.signatures.max_by(&:priority) - identifiers = if vulnerability_finding_signatures_enabled + identifiers = if signatures_enabled "#{finding.report_type}-#{finding.primary_identifier.fingerprint}-#{highest_signature.signature_hex}-#{report.project_id}" else "#{finding.report_type}-#{finding.primary_identifier.fingerprint}-#{finding.location.fingerprint}-#{report.project_id}" diff --git a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb index 7828aa99f6a..e730afc72b5 100644 --- a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb @@ -19,8 +19,72 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end + let(:report_data) do + { + 'scan' => { + 'analyzer' => { + 'id' => 'my-dast-analyzer', + 'name' => 'My DAST analyzer', + 'version' => '0.1.0', + 'vendor' => { 'name' => 'A DAST analyzer' } + }, + 'end_time' => '2020-01-28T03:26:02', + 'scanned_resources' => [], + 'scanner' => { + 'id' => 'my-dast-scanner', + 'name' => 'My DAST scanner', + 'version' => '0.2.0', + 'vendor' => { 'name' => 'A DAST scanner' } + }, + 'start_time' => '2020-01-28T03:26:01', + 'status' => 'success', + 'type' => 'dast' + }, + 'version' => report_version, + 'vulnerabilities' => [] + } + end + let(:validator) { described_class.new(report_type, report_data, report_version, project: project, scanner: scanner) } + shared_examples 'report is valid' do + context 'and the report is valid' do + it { is_expected.to be_truthy } + end + end + + shared_examples 'logs related information' do + it 'logs related information' do + expect(Gitlab::AppLogger).to receive(:info).with( + message: "security report schema validation problem", + security_report_type: report_type, + security_report_version: report_version, + project_id: project.id, + security_report_failure: security_report_failure, + security_report_scanner_id: 'gemnasium', + security_report_scanner_version: '2.1.0' + ) + + subject + end + end + + shared_examples 'report is invalid' do + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } + end + + let(:security_report_failure) { 'schema_validation_fails' } + + it { is_expected.to be_falsey } + + it_behaves_like 'logs related information' + end + end + describe 'SUPPORTED_VERSIONS' do schema_path = Rails.root.join("lib", "gitlab", "ci", "parsers", "security", "validators", "schemas") @@ -75,80 +139,16 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do (latest_vendored_version[0...2] << "34").join(".") end - context 'and the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_truthy } - end - - context 'and the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - it { is_expected.to be_falsey } - - it 'logs related information' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "security report schema validation problem", - security_report_type: report_type, - security_report_version: report_version, - project_id: project.id, - security_report_failure: 'schema_validation_fails', - security_report_scanner_id: 'gemnasium', - security_report_scanner_version: '2.1.0' - ) - - subject - end - end + it_behaves_like 'report is valid' + it_behaves_like 'report is invalid' end context 'when given a supported schema version' do let(:report_type) { :dast } let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } - context 'and the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_truthy } - end - - context 'and the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - it { is_expected.to be_falsey } - - it 'logs related information' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "security report schema validation problem", - security_report_type: report_type, - security_report_version: report_version, - project_id: project.id, - security_report_failure: 'schema_validation_fails', - security_report_scanner_id: 'gemnasium', - security_report_scanner_version: '2.1.0' - ) - - subject - end - end + it_behaves_like 'report is valid' + it_behaves_like 'report is invalid' end context 'when given a deprecated schema version' do @@ -173,21 +173,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end + let(:security_report_failure) { 'using_deprecated_schema_version' } + it { is_expected.to be_truthy } - it 'logs related information' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "security report schema validation problem", - security_report_type: report_type, - security_report_version: report_version, - project_id: project.id, - security_report_failure: 'using_deprecated_schema_version', - security_report_scanner_id: 'gemnasium', - security_report_scanner_version: '2.1.0' - ) - - subject - end + it_behaves_like 'logs related information' end context 'and the report does not pass schema validation' do @@ -213,21 +203,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end + let(:security_report_failure) { 'using_unsupported_schema_version' } + it { is_expected.to be_falsey } - it 'logs related information' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "security report schema validation problem", - security_report_type: report_type, - security_report_version: report_version, - project_id: project.id, - security_report_failure: 'using_unsupported_schema_version', - security_report_scanner_id: 'gemnasium', - security_report_scanner_version: '2.1.0' - ) - - subject - end + it_behaves_like 'logs related information' end context 'and the report is invalid' do @@ -282,6 +262,16 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do end end + shared_examples 'report is valid with no error' do + context 'and the report is valid' do + it { is_expected.to be_empty } + end + end + + shared_examples 'report with expected errors' do + it { is_expected.to match_array(expected_errors) } + end + describe '#errors' do subject { validator.errors } @@ -289,16 +279,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:report_type) { :dast } let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } - context 'and the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_empty } - end + it_behaves_like 'report is valid with no error' context 'and the report is invalid' do let(:report_data) do @@ -309,11 +290,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:expected_errors) do [ - 'root is missing required keys: vulnerabilities' + 'root is missing required keys: scan, vulnerabilities' ] end - it { is_expected.to match_array(expected_errors) } + it_behaves_like 'report with expected errors' end end @@ -331,16 +312,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash) end - context 'and the report passes schema validation' do - let(:report_data) do - { - 'version' => '10.0.0', - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_empty } - end + it_behaves_like 'report is valid with no error' context 'and the report does not pass schema validation' do let(:report_data) do @@ -356,7 +328,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do ] end - it { is_expected.to match_array(expected_errors) } + it_behaves_like 'report with expected errors' end end @@ -383,7 +355,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do ] end - it { is_expected.to match_array(expected_errors) } + it_behaves_like 'report with expected errors' end context 'and the report is invalid' do @@ -400,7 +372,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do ] end - it { is_expected.to match_array(expected_errors) } + it_behaves_like 'report with expected errors' end end @@ -426,10 +398,27 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do ] end - it { is_expected.to match_array(expected_errors) } + it_behaves_like 'report with expected errors' end end + shared_examples 'report is valid with no warning' do + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } + end + + it { is_expected.to be_empty } + end + end + + shared_examples 'report with expected warnings' do + it { is_expected.to match_array(expected_deprecation_warnings) } + end + describe '#deprecation_warnings' do subject { validator.deprecation_warnings } @@ -491,7 +480,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end - it { is_expected.to match_array(expected_deprecation_warnings) } + it_behaves_like 'report with expected warnings' end context 'and the report does not pass schema validation' do @@ -501,7 +490,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end - it { is_expected.to match_array(expected_deprecation_warnings) } + it_behaves_like 'report with expected warnings' end end @@ -516,7 +505,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end - it { is_expected.to match_array(expected_deprecation_warnings) } + it_behaves_like 'report with expected warnings' end end @@ -561,21 +550,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end + let(:security_report_failure) { 'schema_validation_fails' } + it { is_expected.to match_array([message]) } - it 'logs related information' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "security report schema validation problem", - security_report_type: report_type, - security_report_version: report_version, - project_id: project.id, - security_report_failure: 'schema_validation_fails', - security_report_scanner_id: 'gemnasium', - security_report_scanner_version: '2.1.0' - ) - - subject - end + it_behaves_like 'logs related information' end end @@ -583,16 +562,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:report_type) { :dast } let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } - context 'and the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_empty } - end + it_behaves_like 'report is valid with no warning' context 'and the report is invalid' do let(:report_data) do @@ -644,16 +614,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:report_type) { :dast } let(:report_version) { "12.37.0" } - context 'and the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_empty } - end + it_behaves_like 'report is valid with no warning' context 'and the report is invalid' do let(:report_data) do diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb index 82fa11d5f98..821a5057d2e 100644 --- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb +++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb @@ -4,11 +4,13 @@ require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Parsers::Test::Junit do describe '#parse!' do - subject { described_class.new.parse!(junit, test_suite, job: job) } + subject { described_class.new.parse!(junit, test_report, job: job) } - let(:test_suite) { Gitlab::Ci::Reports::TestSuite.new('rspec') } + let(:job) { double(test_suite_name: 'rspec', max_test_cases_per_report: max_test_cases) } + + let(:test_report) { Gitlab::Ci::Reports::TestReport.new } + let(:test_suite) { test_report.get_suite(job.test_suite_name) } let(:test_cases) { flattened_test_cases(test_suite) } - let(:job) { double(max_test_cases_per_report: max_test_cases) } let(:max_test_cases) { 0 } context 'when data is JUnit style XML' do diff --git a/spec/lib/gitlab/ci/parsers_spec.rb b/spec/lib/gitlab/ci/parsers_spec.rb index c9891c06507..a9adff4fce3 100644 --- a/spec/lib/gitlab/ci/parsers_spec.rb +++ b/spec/lib/gitlab/ci/parsers_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Parsers do describe '.fabricate!' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb new file mode 100644 index 00000000000..15df5b2f68c --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Pipeline::Chain::AssignPartition do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user) + end + + let(:pipeline) { build(:ci_pipeline, project: project) } + let(:step) { described_class.new(pipeline, command) } + let(:current_partition_id) { 123 } + + describe '#perform!' do + before do + allow(Ci::Pipeline).to receive(:current_partition_value) { current_partition_id } + end + + subject { step.perform! } + + it 'assigns partition_id to pipeline' do + expect { subject }.to change(pipeline, :partition_id).to(current_partition_id) + end + + context 'with parent-child pipelines' do + let(:bridge) do + instance_double(Ci::Bridge, + triggers_child_pipeline?: true, + parent_pipeline: instance_double(Ci::Pipeline, partition_id: 125)) + end + + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + project: project, + current_user: user, + bridge: bridge) + end + + it 'assigns partition_id to pipeline' do + expect { subject }.to change(pipeline, :partition_id).to(125) + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb index de43e759193..6e8b6e40928 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb @@ -302,13 +302,13 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do context 'when bridge is present' do context 'when bridge triggers a child pipeline' do - let(:bridge) { double(:bridge, triggers_child_pipeline?: true) } + let(:bridge) { instance_double(Ci::Bridge, triggers_child_pipeline?: true) } it { is_expected.to be_truthy } end context 'when bridge triggers a multi-project pipeline' do - let(:bridge) { double(:bridge, triggers_child_pipeline?: false) } + let(:bridge) { instance_double(Ci::Bridge, triggers_child_pipeline?: false) } it { is_expected.to be_falsey } end @@ -321,6 +321,38 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do end end + describe '#parent_pipeline_partition_id' do + let(:command) { described_class.new(bridge: bridge) } + + subject { command.parent_pipeline_partition_id } + + context 'when bridge is present' do + context 'when bridge triggers a child pipeline' do + let(:pipeline) { instance_double(Ci::Pipeline, partition_id: 123) } + + let(:bridge) do + instance_double(Ci::Bridge, + triggers_child_pipeline?: true, + parent_pipeline: pipeline) + end + + it { is_expected.to eq(123) } + end + + context 'when bridge triggers a multi-project pipeline' do + let(:bridge) { instance_double(Ci::Bridge, triggers_child_pipeline?: false) } + + it { is_expected.to be_nil } + end + end + + context 'when bridge is not present' do + let(:bridge) { nil } + + it { is_expected.to be_nil } + end + end + describe '#increment_pipeline_failure_reason_counter' do let(:command) { described_class.new } let(:reason) { :size_limit_exceeded } @@ -345,7 +377,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do describe '#observe_step_duration' do context 'when ci_pipeline_creation_step_duration_tracking is enabled' do it 'adds the duration to the step duration histogram' do - histogram = double(:histogram) + histogram = instance_double(Prometheus::Client::Histogram) duration = 1.hour expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_creation_step_duration_histogram) diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb index e0d656f456e..f451bd6bfef 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb @@ -11,7 +11,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content do subject { described_class.new(pipeline, command) } - describe '#perform!' do + # TODO: change this to `describe` and remove rubocop-disable + # when removing the FF ci_project_pipeline_config_refactoring + shared_context '#perform!' do # rubocop:disable RSpec/ContextWording context 'when bridge job is passed in as parameter' do let(:ci_config_path) { nil } let(:bridge) { create(:ci_bridge) } @@ -201,4 +203,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content do end end end + + it_behaves_like '#perform!' + + context 'when the FF ci_project_pipeline_config_refactoring is disabled' do + before do + stub_feature_flags(ci_project_pipeline_config_refactoring: false) + end + + it_behaves_like '#perform!' + end end 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 e07a3ca9033..7fb5b0b4200 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb @@ -2,11 +2,13 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do +RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments, :aggregate_failures do let(:project) { create(:project) } let(:user) { create(:user) } let(:stage) { build(:ci_stage, project: project, statuses: [job]) } let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:environment) { project.environments.find_by_name('review/master') } let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user) @@ -24,12 +26,26 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do context 'when a pipeline contains a deployment job' do let!(:job) { build(:ci_build, :start_review_app, project: project) } - it 'ensures environment existence for the job' do - expect { subject }.to change { Environment.count }.by(1) + context 'and the environment does not exist' do + it 'creates the environment specified by the job' do + expect { subject }.to change { Environment.count }.by(1) - expect(project.environments.find_by_name('review/master')).to be_present - expect(job.persisted_environment.name).to eq('review/master') - expect(job.metadata.expanded_environment_name).to eq('review/master') + expect(environment).to be_present + expect(job.persisted_environment.name).to eq('review/master') + expect(job.metadata.expanded_environment_name).to eq('review/master') + end + + context 'and the pipeline is for a merge request' do + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user, merge_request: merge_request) + end + + it 'associates the environment with the merge request' do + expect { subject }.to change { Environment.count }.by(1) + + expect(environment.merge_request).to eq(merge_request) + end + end end context 'when an environment has already been existed' do @@ -40,10 +56,22 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do it 'ensures environment existence for the job' do expect { subject }.not_to change { Environment.count } - expect(project.environments.find_by_name('review/master')).to be_present + expect(environment).to be_present expect(job.persisted_environment.name).to eq('review/master') expect(job.metadata.expanded_environment_name).to eq('review/master') end + + context 'and the pipeline is for a merge request' do + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user, merge_request: merge_request) + end + + it 'does not associate the environment with the merge request' do + expect { subject }.not_to change { Environment.count } + + expect(environment.merge_request).to be_nil + end + end end context 'when an environment name contains an invalid character' do @@ -65,7 +93,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do it 'ensures environment existence for the job' do expect { subject }.to change { Environment.count }.by(1) - expect(project.environments.find_by_name('review/master')).to be_present + expect(environment).to be_present expect(job.persisted_environment.name).to eq('review/master') expect(job.metadata.expanded_environment_name).to eq('review/master') end diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb index f7774e199fb..8c4f7af0ef4 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb @@ -277,7 +277,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do non_handled_sql_queries = 2 # 1. Ci::InstanceVariable Load => `Ci::InstanceVariable#cached_data` => already cached with `fetch_memory_cache` - # 2. Ci::Variable Load => `Project#ci_variables_for` => already cached with `Gitlab::SafeRequestStore` extra_jobs * non_handled_sql_queries end diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb index e8eb3333b88..ee32661f267 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb @@ -83,19 +83,36 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Sequence do .with({ source: 'push' }, 0) end - it 'records active jobs by pipeline plan in a histogram' do - allow(command.metrics) - .to receive(:active_jobs_histogram) - .and_return(histogram) + describe 'active jobs by pipeline plan histogram' do + before do + allow(command.metrics) + .to receive(:active_jobs_histogram) + .and_return(histogram) + + pipeline = create(:ci_pipeline, :running, project: project) + create_list(:ci_build, 3, pipeline: pipeline) + create(:ci_bridge, pipeline: pipeline) + end - pipeline = create(:ci_pipeline, project: project, status: :running) - create(:ci_build, :finished, project: project, pipeline: pipeline) - create(:ci_build, :failed, project: project, pipeline: pipeline) - create(:ci_build, :running, project: project, pipeline: pipeline) - subject.build! + it 'counts all the active jobs' do + subject.build! - expect(histogram).to have_received(:observe) - .with(hash_including(plan: project.actual_plan_name), 3) + expect(histogram).to have_received(:observe) + .with(hash_including(plan: project.actual_plan_name), 4) + end + + context 'when feature flag ci_limit_active_jobs_early is disabled' do + before do + stub_feature_flags(ci_limit_active_jobs_early: false) + end + + it 'counts all the active builds' do + subject.build! + + expect(histogram).to have_received(:observe) + .with(hash_including(plan: project.actual_plan_name), 3) + end + end end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb index fb1a360a4b7..52a00e0d501 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb @@ -179,6 +179,70 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do perform! end end + + describe 'credit_card' do + context 'with no registered credit_card' do + it 'returns the expected credit card counts' do + expect(::Gitlab::HTTP).to receive(:post) do |_url, params| + payload = Gitlab::Json.parse(params[:body]) + + expect(payload['credit_card']['similar_cards_count']).to eq(0) + expect(payload['credit_card']['similar_holder_names_count']).to eq(0) + end + + perform! + end + end + + context 'with a registered credit card' do + let!(:credit_card) { create(:credit_card_validation, last_digits: 10, holder_name: 'Alice', user: user) } + + it 'returns the expected credit card counts' do + expect(::Gitlab::HTTP).to receive(:post) do |_url, params| + payload = Gitlab::Json.parse(params[:body]) + + expect(payload['credit_card']['similar_cards_count']).to eq(1) + expect(payload['credit_card']['similar_holder_names_count']).to eq(1) + end + + perform! + end + + context 'with similar credit cards registered by other users' do + before do + create(:credit_card_validation, last_digits: 10, holder_name: 'Bob') + end + + it 'returns the expected credit card counts' do + expect(::Gitlab::HTTP).to receive(:post) do |_url, params| + payload = Gitlab::Json.parse(params[:body]) + + expect(payload['credit_card']['similar_cards_count']).to eq(2) + expect(payload['credit_card']['similar_holder_names_count']).to eq(1) + end + + perform! + end + end + + context 'with similar holder names registered by other users' do + before do + create(:credit_card_validation, last_digits: 11, holder_name: 'Alice') + end + + it 'returns the expected credit card counts' do + expect(::Gitlab::HTTP).to receive(:post) do |_url, params| + payload = Gitlab::Json.parse(params[:body]) + + expect(payload['credit_card']['similar_cards_count']).to eq(1) + expect(payload['credit_card']['similar_holder_names_count']).to eq(2) + end + + perform! + end + end + end + end end context 'when EXTERNAL_VALIDATION_SERVICE_TOKEN is set' do diff --git a/spec/lib/gitlab/ci/pipeline/duration_spec.rb b/spec/lib/gitlab/ci/pipeline/duration_spec.rb index e0b4928d7f7..46c7072ad8e 100644 --- a/spec/lib/gitlab/ci/pipeline/duration_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/duration_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Duration do let(:calculated_duration) { calculate(data) } diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb index 49686d1a9bd..3ca6fd9143f 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do describe '.build' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb index c6d0d2534a5..b224fca6011 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do describe '.build' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb index 3e10ca686ba..b030bd22aa1 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do describe '.build' do diff --git a/spec/lib/gitlab/ci/pipeline/metrics_spec.rb b/spec/lib/gitlab/ci/pipeline/metrics_spec.rb index 83b969ff3c4..8df3c67beaa 100644 --- a/spec/lib/gitlab/ci/pipeline/metrics_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/metrics_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe ::Gitlab::Ci::Pipeline::Metrics do describe '.pipeline_creation_step_duration_histogram' do diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 890ba51157a..75f6a773c2d 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -97,15 +97,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do let(:attributes) do { name: 'rspec', ref: 'master', - job_variables: [{ key: 'VAR1', value: 'var 1', public: true }, - { key: 'VAR2', value: 'var 2', public: true }], + job_variables: [{ key: 'VAR1', value: 'var 1' }, + { key: 'VAR2', value: 'var 2' }], rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] } end it do - is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1', public: true }, - { key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }]) + is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' }, + { key: 'VAR3', value: 'var 3' }, + { key: 'VAR2', value: 'var 2' }]) end end @@ -114,13 +114,13 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do { name: 'rspec', ref: 'master', - job_variables: [{ key: 'VARIABLE', value: 'value', public: true }], + job_variables: [{ key: 'VARIABLE', value: 'value' }], tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE'] } end it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) } - it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value', public: true }]) } + it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) } end context 'with cache:key' do @@ -257,19 +257,19 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do let(:attributes) do { name: 'rspec', ref: 'master', - yaml_variables: [{ key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }], - job_variables: [{ key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }], + yaml_variables: [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }], + job_variables: [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }], root_variables_inheritance: root_variables_inheritance } end context 'when the pipeline has variables' do let(:root_variables) do - [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true }, - { key: 'VAR2', value: 'var pipeline 2', public: true }, - { key: 'VAR3', value: 'var pipeline 3', public: true }, - { key: 'VAR4', value: 'new var pipeline 4', public: true }] + [{ key: 'VAR1', value: 'var overridden pipeline 1' }, + { key: 'VAR2', value: 'var pipeline 2' }, + { key: 'VAR3', value: 'var pipeline 3' }, + { key: 'VAR4', value: 'new var pipeline 4' }] end context 'when root_variables_inheritance is true' do @@ -277,10 +277,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'returns calculated yaml variables' do expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true }, - { key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }, - { key: 'VAR4', value: 'new var pipeline 4', public: true }] + [{ key: 'VAR1', value: 'var overridden pipeline 1' }, + { key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }, + { key: 'VAR4', value: 'new var pipeline 4' }] ) end end @@ -290,8 +290,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'returns job variables' do expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }] + [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }] ) end end @@ -301,9 +301,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'returns calculated yaml variables' do expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true }, - { key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }] + [{ key: 'VAR1', value: 'var overridden pipeline 1' }, + { key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }] ) end end @@ -314,8 +314,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'returns seed yaml variables' do expect(subject[:yaml_variables]).to match_array( - [{ key: 'VAR2', value: 'var 2', public: true }, - { key: 'VAR3', value: 'var 3', public: true }]) + [{ key: 'VAR2', value: 'var 2' }, + { key: 'VAR3', value: 'var 3' }]) end end end @@ -324,8 +324,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do let(:attributes) do { name: 'rspec', ref: 'master', - yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true }], - job_variables: [{ key: 'VAR1', value: 'var 1', public: true }], + yaml_variables: [{ key: 'VAR1', value: 'var 1' }], + job_variables: [{ key: 'VAR1', value: 'var 1' }], root_variables_inheritance: root_variables_inheritance, rules: rules } end @@ -338,14 +338,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do end it 'recalculates the variables' do - expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true }, - { key: 'VAR2', value: 'new var 2', public: true }) + expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' }, + { key: 'VAR2', value: 'new var 2' }) end end context 'when the rules use root variables' do let(:root_variables) do - [{ key: 'VAR2', value: 'var pipeline 2', public: true }] + [{ key: 'VAR2', value: 'var pipeline 2' }] end let(:rules) do @@ -353,15 +353,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do end it 'recalculates the variables' do - expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true }, - { key: 'VAR2', value: 'overridden var 2', public: true }) + expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' }, + { key: 'VAR2', value: 'overridden var 2' }) end context 'when the root_variables_inheritance is false' do let(:root_variables_inheritance) { false } it 'does not recalculate the variables' do - expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1', public: true }) + expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' }) end end end diff --git a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb index 51185be3e74..6569ce937ac 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb @@ -6,8 +6,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Deployment do let_it_be(:project, refind: true) { create(:project, :repository) } let(:pipeline) do - create(:ci_pipeline, project: project, - sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0') + create(:ci_pipeline, project: project, sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0') end let(:job) { build(:ci_build, project: project, pipeline: pipeline) } diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb index ad89f1f5cda..2b9d8127886 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb @@ -5,7 +5,9 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do let_it_be(:project) { create(:project) } - let(:job) { build(:ci_build, project: project) } + let!(:pipeline) { create(:ci_pipeline, project: project) } + + let(:job) { build(:ci_build, project: project, pipeline: pipeline) } let(:seed) { described_class.new(job) } let(:attributes) { {} } @@ -87,6 +89,28 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do it_behaves_like 'returning a correct environment' end + + context 'and job environment has an auto_stop_in variable attribute' do + let(:environment_auto_stop_in) { '10 minutes' } + let(:expected_auto_stop_in) { '10 minutes' } + + let(:attributes) do + { + environment: environment_name, + options: { + environment: { + name: environment_name, + auto_stop_in: '$TTL' + } + }, + yaml_variables: [ + { key: "TTL", value: environment_auto_stop_in, public: true } + ] + } + end + + it_behaves_like 'returning a correct environment' + end end context 'when job has deployment tier attribute' do @@ -167,5 +191,34 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do it_behaves_like 'returning a correct environment' end + + context 'when merge_request is provided' do + let(:environment_name) { 'development' } + let(:attributes) { { environment: environment_name, options: { environment: { name: environment_name } } } } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:seed) { described_class.new(job, merge_request: merge_request) } + + context 'and environment does not exist' do + let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' } + + it 'creates an environment associated with the merge request' do + expect { subject }.to change { Environment.count }.by(1) + + expect(subject.merge_request).to eq(merge_request) + end + end + + context 'and environment already exists' do + before do + create(:environment, project: project, name: environment_name) + end + + it 'does not change the merge request associated with the environment' do + expect { subject }.not_to change { Environment.count } + + expect(subject.merge_request).to be_nil + end + end + end end end diff --git a/spec/lib/gitlab/ci/processable_object_hierarchy_spec.rb b/spec/lib/gitlab/ci/processable_object_hierarchy_spec.rb new file mode 100644 index 00000000000..a844ce6486b --- /dev/null +++ b/spec/lib/gitlab/ci/processable_object_hierarchy_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::ProcessableObjectHierarchy do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.owner } + + let_it_be(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master') } + + let_it_be(:job1) { create(:ci_build, :created, pipeline: pipeline, name: 'job1') } + let_it_be(:job2) { create(:ci_build, :created, :dependent, pipeline: pipeline, name: 'job2', needed: job1) } + let_it_be(:job3) { create(:ci_build, :created, :dependent, pipeline: pipeline, name: 'job3', needed: job1) } + let_it_be(:job4) { create(:ci_build, :created, :dependent, pipeline: pipeline, name: 'job4', needed: job2) } + let_it_be(:job5) { create(:ci_build, :created, :dependent, pipeline: pipeline, name: 'job5', needed: job3) } + let_it_be(:job6) { create(:ci_build, :created, :dependent, pipeline: pipeline, name: 'job6', needed: job4) } + + describe '#base_and_ancestors' do + it 'includes the base and its ancestors' do + relation = described_class.new(::Ci::Processable.where(id: job2.id)).base_and_ancestors + + expect(relation).to eq([job2, job1]) + end + + it 'can find ancestors upto a certain level' do + relation = described_class.new(::Ci::Processable.where(id: job4.id)).base_and_ancestors(upto: job1.name) + + expect(relation).to eq([job4, job2]) + end + + describe 'hierarchy_order option' do + let(:relation) do + described_class.new(::Ci::Processable.where(id: job4.id)).base_and_ancestors(hierarchy_order: hierarchy_order) + end + + context 'for :asc' do + let(:hierarchy_order) { :asc } + + it 'orders by child to ancestor' do + expect(relation).to eq([job4, job2, job1]) + end + end + + context 'for :desc' do + let(:hierarchy_order) { :desc } + + it 'orders by ancestor to child' do + expect(relation).to eq([job1, job2, job4]) + end + end + end + end + + describe '#base_and_descendants' do + it 'includes the base and its descendants' do + relation = described_class.new(::Ci::Processable.where(id: job2.id)).base_and_descendants + + expect(relation).to contain_exactly(job2, job4, job6) + end + + context 'when with_depth is true' do + let(:relation) do + described_class.new(::Ci::Processable.where(id: job1.id)).base_and_descendants(with_depth: true) + end + + it 'includes depth in the results' do + object_depths = { + job1.id => 1, + job2.id => 2, + job3.id => 2, + job4.id => 3, + job5.id => 3, + job6.id => 4 + } + + relation.each do |object| + expect(object.depth).to eq(object_depths[object.id]) + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/project_config/repository_spec.rb b/spec/lib/gitlab/ci/project_config/repository_spec.rb new file mode 100644 index 00000000000..2105b691d9e --- /dev/null +++ b/spec/lib/gitlab/ci/project_config/repository_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::ProjectConfig::Repository do + let(:project) { create(:project, :custom_repo, files: files) } + let(:sha) { project.repository.head_commit.sha } + let(:files) { { 'README.md' => 'hello' } } + + subject(:config) { described_class.new(project, sha, nil, nil, nil) } + + describe '#content' do + subject(:content) { config.content } + + context 'when file is in repository' do + let(:config_content_result) do + <<~CICONFIG + --- + include: + - local: ".gitlab-ci.yml" + CICONFIG + end + + let(:files) { { '.gitlab-ci.yml' => 'content' } } + + it { is_expected.to eq(config_content_result) } + end + + context 'when file is not in repository' do + it { is_expected.to be_nil } + end + + context 'when Gitaly raises error' do + before do + allow(project.repository).to receive(:gitlab_ci_yml_for).and_raise(GRPC::Internal) + end + + it { is_expected.to be_nil } + end + end + + describe '#source' do + subject { config.source } + + it { is_expected.to eq(:repository_source) } + end +end diff --git a/spec/lib/gitlab/ci/project_config/source_spec.rb b/spec/lib/gitlab/ci/project_config/source_spec.rb new file mode 100644 index 00000000000..dda5c7cdce8 --- /dev/null +++ b/spec/lib/gitlab/ci/project_config/source_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::ProjectConfig::Source do + let_it_be(:custom_config_class) { Class.new(described_class) } + let_it_be(:project) { build_stubbed(:project) } + let_it_be(:sha) { '123456' } + + subject(:custom_config) { custom_config_class.new(project, sha, nil, nil, nil) } + + describe '#content' do + subject(:content) { custom_config.content } + + it { expect { content }.to raise_error(NotImplementedError) } + end + + describe '#source' do + subject(:source) { custom_config.source } + + it { expect { source }.to raise_error(NotImplementedError) } + end +end diff --git a/spec/lib/gitlab/ci/project_config_spec.rb b/spec/lib/gitlab/ci/project_config_spec.rb new file mode 100644 index 00000000000..c4b179c9ef5 --- /dev/null +++ b/spec/lib/gitlab/ci/project_config_spec.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::ProjectConfig do + let(:project) { create(:project, :empty_repo, ci_config_path: ci_config_path) } + let(:sha) { '123456' } + let(:content) { nil } + let(:source) { :push } + let(:bridge) { nil } + + subject(:config) do + described_class.new(project: project, sha: sha, + custom_content: content, pipeline_source: source, pipeline_source_bridge: bridge) + end + + context 'when bridge job is passed in as parameter' do + let(:ci_config_path) { nil } + let(:bridge) { create(:ci_bridge) } + + before do + allow(bridge).to receive(:yaml_for_downstream).and_return('the-yaml') + end + + it 'returns the content already available in command' do + expect(config.source).to eq(:bridge_source) + expect(config.content).to eq('the-yaml') + end + end + + context 'when config is defined in a custom path in the repository' do + let(:ci_config_path) { 'path/to/config.yml' } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - local: #{ci_config_path} + CICONFIG + end + + before do + allow(project.repository) + .to receive(:gitlab_ci_yml_for) + .with(sha, ci_config_path) + .and_return('the-content') + end + + it 'returns root config including the local custom file' do + expect(config.source).to eq(:repository_source) + expect(config.content).to eq(config_content_result) + end + end + + context 'when config is defined remotely' do + let(:ci_config_path) { 'http://example.com/path/to/ci/config.yml' } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - remote: #{ci_config_path} + CICONFIG + end + + it 'returns root config including the remote config' do + expect(config.source).to eq(:remote_source) + expect(config.content).to eq(config_content_result) + end + end + + context 'when config is defined in a separate repository' do + let(:ci_config_path) { 'path/to/.gitlab-ci.yml@another-group/another-repo' } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - project: another-group/another-repo + file: path/to/.gitlab-ci.yml + CICONFIG + end + + it 'returns root config including the path to another repository' do + expect(config.source).to eq(:external_project_source) + expect(config.content).to eq(config_content_result) + end + + context 'when path specifies a refname' do + let(:ci_config_path) { 'path/to/.gitlab-ci.yml@another-group/another-repo:refname' } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - project: another-group/another-repo + file: path/to/.gitlab-ci.yml + ref: refname + CICONFIG + end + + it 'returns root config including the path and refname to another repository' do + expect(config.source).to eq(:external_project_source) + expect(config.content).to eq(config_content_result) + end + end + end + + context 'when config is defined in the default .gitlab-ci.yml' do + let(:ci_config_path) { nil } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - local: ".gitlab-ci.yml" + CICONFIG + end + + before do + allow(project.repository) + .to receive(:gitlab_ci_yml_for) + .with(sha, '.gitlab-ci.yml') + .and_return('the-content') + end + + it 'returns root config including the canonical CI config file' do + expect(config.source).to eq(:repository_source) + expect(config.content).to eq(config_content_result) + end + end + + context 'when config is the Auto-Devops template' do + let(:ci_config_path) { nil } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - template: Auto-DevOps.gitlab-ci.yml + CICONFIG + end + + before do + allow(project).to receive(:auto_devops_enabled?).and_return(true) + end + + it 'returns root config including the auto-devops template' do + expect(config.source).to eq(:auto_devops_source) + expect(config.content).to eq(config_content_result) + end + end + + context 'when config is passed as a parameter' do + let(:source) { :ondemand_dast_scan } + let(:ci_config_path) { nil } + let(:content) do + <<~CICONFIG + --- + stages: + - dast + CICONFIG + end + + it 'returns the parameter content' do + expect(config.source).to eq(:parameter_source) + expect(config.content).to eq(content) + end + end + + context 'when config is not defined anywhere' do + let(:ci_config_path) { nil } + + before do + allow(project).to receive(:auto_devops_enabled?).and_return(false) + end + + it 'returns nil' do + expect(config.source).to be_nil + expect(config.content).to be_nil + end + end +end diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb index ade0e36cf1e..ad8f1dc11f8 100644 --- a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb +++ b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do let(:comparer) { described_class.new(base_report, head_report) } diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb index 8c35b2a34cf..af6844491ca 100644 --- a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::AccessibilityReports do let(:accessibility_report) { described_class.new } diff --git a/spec/lib/gitlab/ci/reports/coverage_report_spec.rb b/spec/lib/gitlab/ci/reports/coverage_report_spec.rb index 53646f7dfc0..23361a0c768 100644 --- a/spec/lib/gitlab/ci/reports/coverage_report_spec.rb +++ b/spec/lib/gitlab/ci/reports/coverage_report_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::CoverageReport do let(:coverage_report) { described_class.new } diff --git a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb index 672117c311f..06ea3433ef0 100644 --- a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb +++ b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::Sbom::Component do let(:attributes) do { - 'type' => 'library', - 'name' => 'component-name', - 'version' => 'v0.0.1' + type: 'library', + name: 'component-name', + version: 'v0.0.1' } end - subject { described_class.new(attributes) } + subject { described_class.new(**attributes) } it 'has correct attributes' do expect(subject).to have_attributes( - component_type: 'library', - name: 'component-name', - version: 'v0.0.1' + component_type: attributes[:type], + name: attributes[:name], + version: attributes[:version] ) end end diff --git a/spec/lib/gitlab/ci/reports/sbom/report_spec.rb b/spec/lib/gitlab/ci/reports/sbom/report_spec.rb index d7a285ab13c..6ffa93e5fc8 100644 --- a/spec/lib/gitlab/ci/reports/sbom/report_spec.rb +++ b/spec/lib/gitlab/ci/reports/sbom/report_spec.rb @@ -15,40 +15,22 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Report do end describe '#set_source' do - let_it_be(:source) do - { - 'type' => :dependency_scanning, - 'data' => { - 'input_file' => { 'path' => 'package-lock.json' }, - 'source_file' => { 'path' => 'package.json' }, - 'package_manager' => { 'name' => 'npm' }, - 'language' => { 'name' => 'JavaScript' } - }, - 'fingerprint' => 'c01df1dc736c1148717e053edbde56cb3a55d3e31f87cea955945b6f67c17d42' - } - end + let_it_be(:source) { create(:ci_reports_sbom_source) } it 'stores the source' do report.set_source(source) - expect(report.source).to be_a(Gitlab::Ci::Reports::Sbom::Source) + expect(report.source).to eq(source) end end describe '#add_component' do - let_it_be(:components) do - [ - { 'type' => 'library', 'name' => 'component1', 'version' => 'v0.0.1' }, - { 'type' => 'library', 'name' => 'component2', 'version' => 'v0.0.2' }, - { 'type' => 'library', 'name' => 'component2' } - ] - end + let_it_be(:components) { create_list(:ci_reports_sbom_component, 3) } it 'appends components to a list' do components.each { |component| report.add_component(component) } - expect(report.components.size).to eq(3) - expect(report.components).to all(be_a(Gitlab::Ci::Reports::Sbom::Component)) + expect(report.components).to match_array(components) end end end diff --git a/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb b/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb index 97d8d7abb33..75ea91251eb 100644 --- a/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::Sbom::Reports do subject(:reports_list) { described_class.new } diff --git a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb index 2d6434534a0..cb30bd721dd 100644 --- a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb +++ b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb @@ -1,29 +1,29 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::Sbom::Source do let(:attributes) do { - 'type' => :dependency_scanning, - 'data' => { + type: :dependency_scanning, + data: { 'category' => 'development', 'input_file' => { 'path' => 'package-lock.json' }, 'source_file' => { 'path' => 'package.json' }, 'package_manager' => { 'name' => 'npm' }, 'language' => { 'name' => 'JavaScript' } }, - 'fingerprint' => '4dbcb747e6f0fb3ed4f48d96b777f1d64acdf43e459fdfefad404e55c004a188' + fingerprint: '4dbcb747e6f0fb3ed4f48d96b777f1d64acdf43e459fdfefad404e55c004a188' } end - subject { described_class.new(attributes) } + subject { described_class.new(**attributes) } it 'has correct attributes' do expect(subject).to have_attributes( - source_type: attributes['type'], - data: attributes['data'], - fingerprint: attributes['fingerprint'] + source_type: attributes[:type], + data: attributes[:data], + fingerprint: attributes[:fingerprint] ) end end diff --git a/spec/lib/gitlab/ci/reports/security/flag_spec.rb b/spec/lib/gitlab/ci/reports/security/flag_spec.rb index d677425a8da..6ee074f7aeb 100644 --- a/spec/lib/gitlab/ci/reports/security/flag_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/flag_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::Security::Flag do subject(:security_flag) { described_class.new(type: 'flagged-as-likely-false-positive', origin: 'post analyzer X', description: 'static string to sink') } diff --git a/spec/lib/gitlab/ci/reports/security/link_spec.rb b/spec/lib/gitlab/ci/reports/security/link_spec.rb index 7b55af27f4d..0e1cdc93f6c 100644 --- a/spec/lib/gitlab/ci/reports/security/link_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/link_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::Security::Link do subject(:security_link) { described_class.new(name: 'CVE-2020-0202', url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0202') } diff --git a/spec/lib/gitlab/ci/reports/security/scan_spec.rb b/spec/lib/gitlab/ci/reports/security/scan_spec.rb index b4968ff3a6e..23427e8608c 100644 --- a/spec/lib/gitlab/ci/reports/security/scan_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/scan_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::Security::Scan do describe '#initialize' do diff --git a/spec/lib/gitlab/ci/reports/security/scanned_resource_spec.rb b/spec/lib/gitlab/ci/reports/security/scanned_resource_spec.rb index e9daa05e8b9..74a5344f79e 100644 --- a/spec/lib/gitlab/ci/reports/security/scanned_resource_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/scanned_resource_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::Security::ScannedResource do let(:url) { 'http://example.com:3001/1?foo=bar' } diff --git a/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb b/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb index 5e94fe2bb3d..f754786d071 100644 --- a/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Reports::TerraformReports do it 'initializes plans with and empty hash' do diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb index 3e1004754ba..e81c7b0f6be 100644 --- a/spec/lib/gitlab/ci/status/extended_spec.rb +++ b/spec/lib/gitlab/ci/status/extended_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Status::Extended do it 'requires subclass to implement matcher' do diff --git a/spec/lib/gitlab/ci/status/group/factory_spec.rb b/spec/lib/gitlab/ci/status/group/factory_spec.rb index c67c7ff8271..38aa1ba4ebb 100644 --- a/spec/lib/gitlab/ci/status/group/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/group/factory_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Status::Group::Factory do it 'inherits from the core factory' do diff --git a/spec/lib/gitlab/ci/templates/katalon_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/katalon_gitlab_ci_yaml_spec.rb new file mode 100644 index 00000000000..5a62324da74 --- /dev/null +++ b/spec/lib/gitlab/ci/templates/katalon_gitlab_ci_yaml_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Katalon.gitlab-ci.yml' do + subject(:template) do + <<~YAML + include: + - template: 'Katalon.gitlab-ci.yml' + + katalon_tests_placeholder: + extends: .katalon_tests + stage: test + script: + - echo "katalon tests" + + katalon_tests_with_artifacts_placeholder: + extends: .katalon_tests_with_artifacts + stage: test + script: + - echo "katalon tests with artifacts" + YAML + end + + describe 'the created pipeline' do + let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } + let(:user) { project.first_owner } + + let(:service) { Ci::CreatePipelineService.new(project, user, ref: 'master' ) } + let(:pipeline) { service.execute!(:push).payload } + let(:build_names) { pipeline.builds.pluck(:name) } + + before do + stub_ci_pipeline_yaml_file(template) + end + + it 'create katalon tests jobs' do + expect(build_names).to match_array(%w[katalon_tests_placeholder katalon_tests_with_artifacts_placeholder]) + + expect(pipeline.builds.find_by(name: 'katalon_tests_placeholder').options).to include( + image: { name: 'katalonstudio/katalon' }, + services: [{ name: 'docker:dind' }] + ) + + expect(pipeline.builds.find_by(name: 'katalon_tests_with_artifacts_placeholder').options).to include( + image: { name: 'katalonstudio/katalon' }, + services: [{ name: 'docker:dind' }], + artifacts: { when: 'always', paths: ['Reports/'], reports: { junit: ['Reports/*/*/*/*.xml'] } } + ) + end + end +end diff --git a/spec/lib/gitlab/ci/trace/archive_spec.rb b/spec/lib/gitlab/ci/trace/archive_spec.rb index 3ae0e5d1f0e..f91cb03883a 100644 --- a/spec/lib/gitlab/ci/trace/archive_spec.rb +++ b/spec/lib/gitlab/ci/trace/archive_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Trace::Archive do context 'with transactional fixtures' do - let_it_be(:job) { create(:ci_build, :success, :trace_live) } + let_it_be_with_reload(:job) { create(:ci_build, :success, :trace_live) } let_it_be_with_reload(:trace_metadata) { create(:ci_build_trace_metadata, build: job) } let_it_be(:src_checksum) do job.trace.read { |stream| Digest::MD5.hexdigest(stream.raw) } diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 888ceb7ff9a..3043c8c5467 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -10,7 +10,6 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_defa describe "associations" do it { expect(trace).to respond_to(:job) } - it { expect(trace).to delegate_method(:old_trace).to(:job) } end context 'when trace is migrated to object storage' do diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb index 6ab2089cce8..4833ccf9093 100644 --- a/spec/lib/gitlab/ci/variables/builder_spec.rb +++ b/spec/lib/gitlab/ci/variables/builder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Variables::Builder do +RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache do include Ci::TemplateHelpers let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :repository, namespace: group) } @@ -26,13 +26,13 @@ RSpec.describe Gitlab::Ci::Variables::Builder do { key: 'CI_JOB_NAME', value: job.name }, { key: 'CI_JOB_STAGE', - value: job.stage }, + value: job.stage_name }, { key: 'CI_NODE_TOTAL', value: '1' }, { key: 'CI_BUILD_NAME', value: job.name }, { key: 'CI_BUILD_STAGE', - value: job.stage }, + value: job.stage_name }, { key: 'CI', value: 'true' }, { key: 'GITLAB_CI', @@ -138,11 +138,11 @@ RSpec.describe Gitlab::Ci::Variables::Builder do { key: 'GITLAB_USER_ID', value: user.id.to_s }, { key: 'GITLAB_USER_EMAIL', - value: user.email }, + value: user.email }, { key: 'GITLAB_USER_LOGIN', - value: user.username }, + value: user.username }, { key: 'GITLAB_USER_NAME', - value: user.name } + value: user.name } ].map { |var| var.merge(public: true, masked: false) } end diff --git a/spec/lib/gitlab/ci/variables/collection/sort_spec.rb b/spec/lib/gitlab/ci/variables/collection/sort_spec.rb index 7e4e9602a92..57171e5be69 100644 --- a/spec/lib/gitlab/ci/variables/collection/sort_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/sort_spec.rb @@ -2,6 +2,7 @@ require 'fast_spec_helper' require 'rspec-parameterized' +require 'tsort' RSpec.describe Gitlab::Ci::Variables::Collection::Sort do describe '#initialize with non-Collection value' do diff --git a/spec/lib/gitlab/ci/variables/helpers_spec.rb b/spec/lib/gitlab/ci/variables/helpers_spec.rb index fc1055751bd..fb1e66bd605 100644 --- a/spec/lib/gitlab/ci/variables/helpers_spec.rb +++ b/spec/lib/gitlab/ci/variables/helpers_spec.rb @@ -15,21 +15,21 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do end let(:result) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value22', public: true }, - { key: 'key3', value: 'value3', public: true }] + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] end subject { described_class.merge_variables(current_variables, new_variables) } - it { is_expected.to eq(result) } + it { is_expected.to match_array(result) } context 'when new variables is a hash' do let(:new_variables) do { 'key2' => 'value22', 'key3' => 'value3' } end - it { is_expected.to eq(result) } + it { is_expected.to match_array(result) } end context 'when new variables is a hash with symbol keys' do @@ -37,79 +37,68 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do { key2: 'value22', key3: 'value3' } end - it { is_expected.to eq(result) } + it { is_expected.to match_array(result) } end context 'when new variables is nil' do let(:new_variables) {} let(:result) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value2', public: true }] + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }] end - it { is_expected.to eq(result) } + it { is_expected.to match_array(result) } end end - describe '.transform_to_yaml_variables' do - let(:variables) do - { 'key1' => 'value1', 'key2' => 'value2' } - end - - let(:result) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value2', public: true }] - end - - subject { described_class.transform_to_yaml_variables(variables) } - - it { is_expected.to eq(result) } + describe '.transform_to_array' do + subject { described_class.transform_to_array(variables) } - context 'when variables is nil' do - let(:variables) {} - - it { is_expected.to eq([]) } - end - end + context 'when values are strings' do + let(:variables) do + { 'key1' => 'value1', 'key2' => 'value2' } + end - describe '.transform_from_yaml_variables' do - let(:variables) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value2', public: true }] - end + let(:result) do + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }] + end - let(:result) do - { 'key1' => 'value1', 'key2' => 'value2' } + it { is_expected.to match_array(result) } end - subject { described_class.transform_from_yaml_variables(variables) } - - it { is_expected.to eq(result) } - context 'when variables is nil' do let(:variables) {} - it { is_expected.to eq({}) } + it { is_expected.to match_array([]) } end - context 'when variables is a hash' do + context 'when values are hashes' do let(:variables) do - { key1: 'value1', 'key2' => 'value2' } + { 'key1' => { value: 'value1', description: 'var 1' }, 'key2' => { value: 'value2' } } end - it { is_expected.to eq(result) } - end - - context 'when variables contain integers and symbols' do - let(:variables) do - { key1: 1, key2: :value2 } + let(:result) do + [{ key: 'key1', value: 'value1', description: 'var 1' }, + { key: 'key2', value: 'value2' }] end - let(:result1) do - { 'key1' => '1', 'key2' => 'value2' } - end + it { is_expected.to match_array(result) } + + context 'when a value data has `key` as a key' do + let(:variables) do + { 'key1' => { value: 'value1', key: 'new_key1' }, 'key2' => { value: 'value2' } } + end + + let(:result) do + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }] + end - it { is_expected.to eq(result1) } + it 'ignores the key set with "key"' do + is_expected.to match_array(result) + end + end end end @@ -127,35 +116,35 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do let(:inheritance) { true } let(:result) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value22', public: true }, - { key: 'key3', value: 'value3', public: true }] + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] end subject { described_class.inherit_yaml_variables(from: from, to: to, inheritance: inheritance) } - it { is_expected.to eq(result) } + it { is_expected.to match_array(result) } context 'when inheritance is false' do let(:inheritance) { false } let(:result) do - [{ key: 'key2', value: 'value22', public: true }, - { key: 'key3', value: 'value3', public: true }] + [{ key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] end - it { is_expected.to eq(result) } + it { is_expected.to match_array(result) } end context 'when inheritance is array' do let(:inheritance) { ['key2'] } let(:result) do - [{ key: 'key2', value: 'value22', public: true }, - { key: 'key3', value: 'value3', public: true }] + [{ key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] end - it { is_expected.to eq(result) } + it { is_expected.to match_array(result) } end end end diff --git a/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb b/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb index f815f56543c..082febacbd7 100644 --- a/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fast_spec_helper' +require 'tsort' RSpec.describe Gitlab::Ci::YamlProcessor::Dag do let(:nodes) { {} } diff --git a/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb b/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb index 0bd9563d191..77346f328ca 100644 --- a/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::Ci::YamlProcessor::FeatureFlags do let(:feature_flag) { :my_feature_flag } @@ -48,20 +48,32 @@ RSpec.describe Gitlab::Ci::YamlProcessor::FeatureFlags do end context 'when feature flag is checked outside the "with_actor" block' do - it 'raises an error on dev/test environment' do - expect { described_class.enabled?(feature_flag) }.to raise_error(described_class::NoActorError) - end + context 'when yaml_processor_feature_flag_corectness is used', :yaml_processor_feature_flag_corectness do + it 'raises an error on dev/test environment' do + expect { described_class.enabled?(feature_flag) }.to raise_error(described_class::NoActorError) + end + + context 'when on production' do + before do + allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false) + end - context 'when on production' do - before do - allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false) + it 'checks the feature flag without actor' do + expect(Feature).to receive(:enabled?).with(feature_flag, nil) + expect(Gitlab::ErrorTracking) + .to receive(:track_and_raise_for_dev_exception) + .and_call_original + + described_class.enabled?(feature_flag) + end end + end + context 'when yaml_processor_feature_flag_corectness is not used' do it 'checks the feature flag without actor' do expect(Feature).to receive(:enabled?).with(feature_flag, nil) expect(Gitlab::ErrorTracking) - .to receive(:track_and_raise_for_dev_exception) - .and_call_original + .to receive(:track_exception) described_class.enabled?(feature_flag) end diff --git a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb index 8416501e949..f7a0905d9da 100644 --- a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb @@ -72,8 +72,8 @@ module Gitlab it 'returns calculated variables with root and job variables' do is_expected.to match_array([ - { key: 'VAR1', value: 'value 11', public: true }, - { key: 'VAR2', value: 'value 2', public: true } + { key: 'VAR1', value: 'value 11' }, + { key: 'VAR2', value: 'value 2' } ]) end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 35af9ae6201..cc327f5b5f1 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -298,8 +298,8 @@ module Gitlab context 'when delayed is defined' do let(:config) do YAML.dump(rspec: { - script: 'rollout 10%', - when: 'delayed', + script: 'rollout 10%', + when: 'delayed', start_in: '1 day' }) end @@ -315,7 +315,7 @@ module Gitlab context 'when resource group is defined' do let(:config) do YAML.dump(rspec: { - script: 'test', + script: 'test', resource_group: 'iOS' }) end @@ -448,7 +448,7 @@ module Gitlab it 'parses the root:variables as #root_variables' do expect(subject.root_variables) - .to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true }) + .to contain_exactly({ key: 'SUPPORTED', value: 'parsed' }) end end @@ -490,7 +490,7 @@ module Gitlab it 'parses the root:variables as #root_variables' do expect(subject.root_variables) - .to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true }) + .to contain_exactly({ key: 'SUPPORTED', value: 'parsed' }) end end @@ -997,18 +997,6 @@ module Gitlab scheduling_type: :stage }) end - - context 'when the feature flag ci_docker_image_pull_policy is disabled' do - before do - stub_feature_flags(ci_docker_image_pull_policy: false) - end - - it { is_expected.not_to be_valid } - - it "returns no job" do - expect(processor.jobs).to eq({}) - end - end end context 'when a service has pull_policy' do @@ -1042,39 +1030,29 @@ module Gitlab scheduling_type: :stage }) end - - context 'when the feature flag ci_docker_image_pull_policy is disabled' do - before do - stub_feature_flags(ci_docker_image_pull_policy: false) - end - - it { is_expected.not_to be_valid } - - it "returns no job" do - expect(processor.jobs).to eq({}) - end - end end end - describe 'Variables' do - subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } + # Change this to a `describe` block when removing the FF ci_variables_refactoring_to_variable + shared_examples 'Variables' do + subject(:execute) { described_class.new(config).execute } - let(:build) { subject.builds.first } + let(:build) { execute.builds.first } let(:job_variables) { build[:job_variables] } let(:root_variables_inheritance) { build[:root_variables_inheritance] } context 'when global variables are defined' do - let(:variables) do - { 'VAR1' => 'value1', 'VAR2' => 'value2' } - end - let(:config) do - { - variables: variables, - before_script: ['pwd'], - rspec: { script: 'rspec' } - } + <<~YAML + variables: + VAR1: value1 + VAR2: value2 + + before_script: [pwd] + + rspec: + script: rspec + YAML end it 'returns global variables' do @@ -1084,22 +1062,23 @@ module Gitlab end context 'when job variables are defined' do - let(:config) do - { - before_script: ['pwd'], - rspec: { script: 'rspec', variables: variables } - } - end - context 'when syntax is correct' do - let(:variables) do - { 'VAR1' => 'value1', 'VAR2' => 'value2' } + let(:config) do + <<~YAML + before_script: [pwd] + + rspec: + script: rspec + variables: + VAR1: value1 + VAR2: value2 + YAML end it 'returns job variables' do expect(job_variables).to contain_exactly( - { key: 'VAR1', value: 'value1', public: true }, - { key: 'VAR2', value: 'value2', public: true } + { key: 'VAR1', value: 'value1' }, + { key: 'VAR2', value: 'value2' } ) expect(root_variables_inheritance).to eq(true) end @@ -1107,16 +1086,28 @@ module Gitlab context 'when syntax is incorrect' do context 'when variables defined but invalid' do - let(:variables) do - %w(VAR1 value1 VAR2 value2) + let(:config) do + <<~YAML + before_script: [pwd] + + rspec: + script: rspec + variables: [VAR1 value1 VAR2 value2] + YAML end - it_behaves_like 'returns errors', /jobs:rspec:variables config should be a hash of key value pairs/ + it_behaves_like 'returns errors', /jobs:rspec:variables config should be a hash/ end context 'when variables key defined but value not specified' do - let(:variables) do - nil + let(:config) do + <<~YAML + before_script: [pwd] + + rspec: + script: rspec + variables: null + YAML end it 'returns empty array' do @@ -1133,10 +1124,12 @@ module Gitlab context 'when job variables are not defined' do let(:config) do - { - before_script: ['pwd'], - rspec: { script: 'rspec' } - } + <<~YAML + before_script: ['pwd'] + + rspec: + script: rspec + YAML end it 'returns empty array' do @@ -1144,6 +1137,42 @@ module Gitlab expect(root_variables_inheritance).to eq(true) end end + + context 'when variables have different type of values' do + let(:config) do + <<~YAML + before_script: [pwd] + + rspec: + variables: + VAR1: value1 + VAR2: :value2 + VAR3: 123 + script: rspec + YAML + end + + it 'returns job variables' do + expect(job_variables).to contain_exactly( + { key: 'VAR1', value: 'value1' }, + { key: 'VAR2', value: 'value2' }, + { key: 'VAR3', value: '123' } + ) + expect(root_variables_inheritance).to eq(true) + end + end + end + + context 'when ci_variables_refactoring_to_variable is enabled' do + it_behaves_like 'Variables' + end + + context 'when ci_variables_refactoring_to_variable is disabled' do + before do + stub_feature_flags(ci_variables_refactoring_to_variable: false) + end + + it_behaves_like 'Variables' end context 'when using `extends`' do @@ -1203,21 +1232,21 @@ module Gitlab expect(config_processor.builds[0]).to include( name: 'test1', options: { script: ['test'] }, - job_variables: [{ key: 'VAR1', value: 'test1 var 1', public: true }, - { key: 'VAR2', value: 'test2 var 2', public: true }] + job_variables: [{ key: 'VAR1', value: 'test1 var 1' }, + { key: 'VAR2', value: 'test2 var 2' }] ) expect(config_processor.builds[1]).to include( name: 'test2', options: { script: ['test'] }, - job_variables: [{ key: 'VAR1', value: 'base var 1', public: true }, - { key: 'VAR2', value: 'test2 var 2', public: true }] + job_variables: [{ key: 'VAR1', value: 'base var 1' }, + { key: 'VAR2', value: 'test2 var 2' }] ) expect(config_processor.builds[2]).to include( name: 'test3', options: { script: ['test'] }, - job_variables: [{ key: 'VAR1', value: 'base var 1', public: true }] + job_variables: [{ key: 'VAR1', value: 'base var 1' }] ) expect(config_processor.builds[3]).to include( @@ -1647,10 +1676,10 @@ module Gitlab describe "Artifacts" do it "returns artifacts when defined" do config = YAML.dump({ - image: "image:1.0", - services: ["mysql"], + image: "image:1.0", + services: ["mysql"], before_script: ["pwd"], - rspec: { + rspec: { artifacts: { paths: ["logs/", "binaries/"], expose_as: "Exposed artifacts", @@ -1906,7 +1935,7 @@ module Gitlab let(:config) do { deploy_to_production: { - stage: 'deploy', + stage: 'deploy', script: 'test' } } @@ -2275,15 +2304,15 @@ module Gitlab let(:config) do { - var_default: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null' }] }, - var_when: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null', when: 'always' }] }, + var_default: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null' }] }, + var_when: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null', when: 'always' }] }, var_and_changes: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null', changes: %w[README], when: 'always' }] }, changes_not_var: { stage: 'test', script: 'test', rules: [{ if: '$VAR != null', changes: %w[README] }] }, var_not_changes: { stage: 'test', script: 'test', rules: [{ if: '$VAR == null', changes: %w[other/file.rb], when: 'always' }] }, - nothing: { stage: 'test', script: 'test', rules: [{ when: 'manual' }] }, - var_never: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'never' }] }, - var_delayed: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] }, - two_rules: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'on_success' }, { changes: %w[README], when: 'manual' }] } + nothing: { stage: 'test', script: 'test', rules: [{ when: 'manual' }] }, + var_never: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'never' }] }, + var_delayed: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] }, + two_rules: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'on_success' }, { changes: %w[README], when: 'manual' }] } } end @@ -2729,13 +2758,13 @@ module Gitlab context 'returns errors if variables is not a map' do let(:config) { YAML.dump({ variables: "test", rspec: { script: "test" } }) } - it_behaves_like 'returns errors', 'variables config should be a hash of key value pairs, value can be a hash' + it_behaves_like 'returns errors', 'variables config should be a hash' end context 'returns errors if variables is not a map of key-value strings' do let(:config) { YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) } - it_behaves_like 'returns errors', 'variables config should be a hash of key value pairs, value can be a hash' + it_behaves_like 'returns errors', 'variable definition must be either a string or a hash' end context 'returns errors if job when is not on_success, on_failure or always' do |