diff options
Diffstat (limited to 'spec/lib/gitlab/ci')
66 files changed, 1517 insertions, 616 deletions
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb index 27c2b005a93..30359a7170f 100644 --- a/spec/lib/gitlab/ci/ansi2html_spec.rb +++ b/spec/lib/gitlab/ci/ansi2html_spec.rb @@ -210,8 +210,8 @@ RSpec.describe Gitlab::Ci::Ansi2html do let(:section_start_time) { Time.new(2017, 9, 20).utc } let(:section_duration) { 3.seconds } let(:section_end_time) { section_start_time + section_duration } - let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"} - let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"} + let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K" } + let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K" } let(:section_start_html) do '<div class="section-start"' \ " data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{class_name(section_name)}\"" \ @@ -258,13 +258,13 @@ RSpec.describe Gitlab::Ci::Ansi2html do it_behaves_like 'a legit section' context 'section name includes $' do - let(:section_name) { 'my_$ection'} + let(:section_name) { 'my_$ection' } it_behaves_like 'forbidden char in section_name' end context 'section name includes <' do - let(:section_name) { '<a_tag>'} + let(:section_name) { '<a_tag>' } it_behaves_like 'forbidden char in section_name' end diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb index f9d23ff97bc..4b3b049176f 100644 --- a/spec/lib/gitlab/ci/ansi2json_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json_spec.rb @@ -78,8 +78,8 @@ RSpec.describe Gitlab::Ci::Ansi2json do let(:section_duration) { 63.seconds } let(:section_start_time) { Time.new(2019, 9, 17).utc } let(:section_end_time) { section_start_time + section_duration } - let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"} - let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"} + let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K" } + let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K" } it 'marks the first line of the section as header' do expect(convert_json("Hello#{section_start}world!")).to eq([ @@ -258,8 +258,8 @@ RSpec.describe Gitlab::Ci::Ansi2json do let(:nested_section_duration) { 2.seconds } let(:nested_section_start_time) { Time.new(2019, 9, 17).utc } let(:nested_section_end_time) { nested_section_start_time + nested_section_duration } - let(:nested_section_start) { "section_start:#{nested_section_start_time.to_i}:#{nested_section_name}\r\033[0K"} - let(:nested_section_end) { "section_end:#{nested_section_end_time.to_i}:#{nested_section_name}\r\033[0K"} + let(:nested_section_start) { "section_start:#{nested_section_start_time.to_i}:#{nested_section_name}\r\033[0K" } + let(:nested_section_end) { "section_end:#{nested_section_end_time.to_i}:#{nested_section_name}\r\033[0K" } it 'adds multiple sections to the lines inside the nested section' do trace = "Hello#{section_start}foo#{nested_section_start}bar#{nested_section_end}baz#{section_end}world" @@ -342,7 +342,7 @@ RSpec.describe Gitlab::Ci::Ansi2json do end context 'with section options' do - let(:option_section_start) { "section_start:#{section_start_time.to_i}:#{section_name}[collapsed=true,unused_option=123]\r\033[0K"} + let(:option_section_start) { "section_start:#{section_start_time.to_i}:#{section_name}[collapsed=true,unused_option=123]\r\033[0K" } it 'provides section options when set' do trace = "#{option_section_start}hello#{section_end}" @@ -463,8 +463,8 @@ RSpec.describe Gitlab::Ci::Ansi2json do let(:section_duration) { 63.seconds } let(:section_start_time) { Time.new(2019, 9, 17).utc } let(:section_end_time) { section_start_time + section_duration } - let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"} - let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"} + let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K" } + let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K" } context 'with split section body' do let(:pre_text) { "#{section_start}this is a header\nand " } diff --git a/spec/lib/gitlab/ci/artifacts/logger_spec.rb b/spec/lib/gitlab/ci/artifacts/logger_spec.rb new file mode 100644 index 00000000000..7753cb0d25e --- /dev/null +++ b/spec/lib/gitlab/ci/artifacts/logger_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Artifacts::Logger do + before do + Gitlab::ApplicationContext.push(feature_category: 'test', caller_id: 'caller') + end + + describe '.log_created' do + it 'logs information about created artifact' do + artifact = create(:ci_job_artifact, :archive) + + expect(Gitlab::AppLogger).to receive(:info).with( + hash_including( + message: 'Artifact created', + job_artifact_id: artifact.id, + size: artifact.size, + type: artifact.file_type, + build_id: artifact.job_id, + project_id: artifact.project_id, + 'correlation_id' => an_instance_of(String), + 'meta.feature_category' => 'test', + 'meta.caller_id' => 'caller' + ) + ) + + described_class.log_created(artifact) + end + end + + describe '.log_deleted' do + it 'logs information about deleted artifacts' do + artifact_1 = create(:ci_job_artifact, :archive, :expired) + artifact_2 = create(:ci_job_artifact, :archive) + artifacts = [artifact_1, artifact_2] + method = 'Foo#method' + + artifacts.each do |artifact| + expect(Gitlab::AppLogger).to receive(:info).with( + hash_including( + message: 'Artifact deleted', + job_artifact_id: artifact.id, + expire_at: artifact.expire_at, + size: artifact.size, + type: artifact.file_type, + build_id: artifact.job_id, + project_id: artifact.project_id, + method: method, + 'correlation_id' => an_instance_of(String), + 'meta.feature_category' => 'test', + 'meta.caller_id' => 'caller' + ) + ) + end + + described_class.log_deleted(artifacts, method) + end + end +end diff --git a/spec/lib/gitlab/ci/artifacts/metrics_spec.rb b/spec/lib/gitlab/ci/artifacts/metrics_spec.rb index 0ce76285b03..39e440f09e1 100644 --- a/spec/lib/gitlab/ci/artifacts/metrics_spec.rb +++ b/spec/lib/gitlab/ci/artifacts/metrics_spec.rb @@ -5,6 +5,25 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Artifacts::Metrics, :prometheus do let(:metrics) { described_class.new } + describe '.build_completed_report_type_counter' do + context 'when incrementing by more than one' do + let(:sast_counter) { described_class.send(:build_completed_report_type_counter, :sast) } + let(:dast_counter) { described_class.send(:build_completed_report_type_counter, :dast) } + + it 'increments a single counter' do + [dast_counter, sast_counter].each do |counter| + counter.increment(status: 'success') + counter.increment(status: 'success') + counter.increment(status: 'failed') + + expect(counter.get(status: 'success')).to eq 2.0 + expect(counter.get(status: 'failed')).to eq 1.0 + expect(counter.values.count).to eq 2 + end + end + end + end + describe '#increment_destroyed_artifacts' do context 'when incrementing by more than one' do let(:counter) { metrics.send(:destroyed_artifacts_counter) } 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 new file mode 100644 index 00000000000..2c236ba3726 --- /dev/null +++ b/spec/lib/gitlab/ci/build/artifacts/adapters/zip_stream_spec.rb @@ -0,0 +1,86 @@ +# 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/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb index c8ace28108b..7b35c9ba483 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb @@ -67,6 +67,7 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do subject { |example| path(example).children } it { is_expected.to all(be_an_instance_of(described_class)) } + it do is_expected.to contain_exactly entry('path/dir_1/file_1'), entry('path/dir_1/file_b'), @@ -79,6 +80,7 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do it { is_expected.to all(be_file) } it { is_expected.to all(be_an_instance_of(described_class)) } + it do is_expected.to contain_exactly entry('path/dir_1/file_1'), entry('path/dir_1/file_b') @@ -99,6 +101,7 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do it { is_expected.to all(be_directory) } it { is_expected.to all(be_an_instance_of(described_class)) } + it do is_expected.to contain_exactly entry('path/dir_1/subdir/'), entry('path/') diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index 94c14cfa479..baabab73ea2 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -74,7 +74,7 @@ RSpec.describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do end context 'kubernetes namespace does not exist' do - let(:namespace_builder) { double(execute: kubernetes_namespace)} + let(:namespace_builder) { double(execute: kubernetes_namespace) } before do allow(Clusters::KubernetesNamespaceFinder).to receive(:new) diff --git a/spec/lib/gitlab/ci/build/releaser_spec.rb b/spec/lib/gitlab/ci/build/releaser_spec.rb index 435f70e9ac5..ffa7073818a 100644 --- a/spec/lib/gitlab/ci/build/releaser_spec.rb +++ b/spec/lib/gitlab/ci/build/releaser_spec.rb @@ -13,6 +13,7 @@ RSpec.describe Gitlab::Ci::Build::Releaser do name: 'Release $CI_COMMIT_SHA', description: 'Created using the release-cli $EXTRA_DESCRIPTION', tag_name: 'release-$CI_COMMIT_SHA', + tag_message: 'Annotated tag message', ref: '$CI_COMMIT_SHA', milestones: %w[m1 m2 m3], released_at: '2020-07-15T08:00:00Z', @@ -27,7 +28,7 @@ RSpec.describe Gitlab::Ci::Build::Releaser do end it 'generates the script' do - expect(subject).to eq(['release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA" --released-at "2020-07-15T08:00:00Z" --milestone "m1" --milestone "m2" --milestone "m3" --assets-link "{\"name\":\"asset1\",\"url\":\"https://example.com/assets/1\",\"link_type\":\"other\",\"filepath\":\"/pretty/asset/1\"}" --assets-link "{\"name\":\"asset2\",\"url\":\"https://example.com/assets/2\"}"']) + expect(subject).to eq(['release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --tag-message "Annotated tag message" --ref "$CI_COMMIT_SHA" --released-at "2020-07-15T08:00:00Z" --milestone "m1" --milestone "m2" --milestone "m3" --assets-link "{\"name\":\"asset1\",\"url\":\"https://example.com/assets/1\",\"link_type\":\"other\",\"filepath\":\"/pretty/asset/1\"}" --assets-link "{\"name\":\"asset2\",\"url\":\"https://example.com/assets/2\"}"']) end end @@ -39,6 +40,7 @@ RSpec.describe Gitlab::Ci::Build::Releaser do :name | 'Release $CI_COMMIT_SHA' | 'release-cli create --name "Release $CI_COMMIT_SHA"' :description | 'Release-cli $EXTRA_DESCRIPTION' | 'release-cli create --description "Release-cli $EXTRA_DESCRIPTION"' :tag_name | 'release-$CI_COMMIT_SHA' | 'release-cli create --tag-name "release-$CI_COMMIT_SHA"' + :tag_message | 'Annotated tag message' | 'release-cli create --tag-message "Annotated tag message"' :ref | '$CI_COMMIT_SHA' | 'release-cli create --ref "$CI_COMMIT_SHA"' :milestones | %w[m1 m2 m3] | 'release-cli create --milestone "m1" --milestone "m2" --milestone "m3"' :released_at | '2020-07-15T08:00:00Z' | 'release-cli create --released-at "2020-07-15T08:00:00Z"' diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb index 3892b88598a..234ba68d627 100644 --- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb @@ -4,7 +4,9 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do describe '#satisfied_by?' do - subject { described_class.new(globs).satisfied_by?(pipeline, context) } + let(:context) { instance_double(Gitlab::Ci::Build::Context::Base) } + + subject(:satisfied_by) { described_class.new(globs).satisfied_by?(pipeline, context) } context 'a glob matching rule' do using RSpec::Parameterized::TableSyntax @@ -18,11 +20,9 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do # rubocop:disable Layout/LineLength where(:case_name, :globs, :files, :satisfied) do - 'exact top-level match' | ['Dockerfile'] | { 'Dockerfile' => '', 'Gemfile' => '' } | true 'exact top-level match' | { paths: ['Dockerfile'] } | { 'Dockerfile' => '', 'Gemfile' => '' } | true 'exact top-level no match' | { paths: ['Dockerfile'] } | { 'Gemfile' => '' } | false 'pattern top-level match' | { paths: ['Docker*'] } | { 'Dockerfile' => '', 'Gemfile' => '' } | true - 'pattern top-level no match' | ['Docker*'] | { 'Gemfile' => '' } | false 'pattern top-level no match' | { paths: ['Docker*'] } | { 'Gemfile' => '' } | false 'exact nested match' | { paths: ['project/build.properties'] } | { 'project/build.properties' => '' } | true 'exact nested no match' | { paths: ['project/build.properties'] } | { 'project/README.md' => '' } | false @@ -92,5 +92,97 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do it { is_expected.to be_truthy } end end + + context 'when using compare_to' do + let_it_be(:project) do + create(:project, :custom_repo, + files: { 'README.md' => 'readme' }) + end + + let_it_be(:user) { project.owner } + + before_all do + project.repository.add_branch(user, 'feature_1', 'master') + + project.repository.create_file( + user, 'file1.txt', 'file 1', message: 'Create file1.txt', branch_name: 'feature_1' + ) + project.repository.add_tag(user, 'tag_1', 'feature_1') + + project.repository.create_file( + user, 'file2.txt', 'file 2', message: 'Create file2.txt', branch_name: 'feature_1' + ) + project.repository.add_branch(user, 'feature_2', 'feature_1') + + project.repository.update_file( + user, 'file2.txt', 'file 2 updated', message: 'Update file2.txt', branch_name: 'feature_2' + ) + end + + context 'when compare_to is branch or tag' do + using RSpec::Parameterized::TableSyntax + + where(:pipeline_ref, :compare_to, :paths, :ff, :result) do + 'feature_1' | 'master' | ['file1.txt'] | true | true + 'feature_1' | 'master' | ['README.md'] | true | false + 'feature_1' | 'master' | ['xyz.md'] | true | false + 'feature_2' | 'master' | ['file1.txt'] | true | true + 'feature_2' | 'master' | ['file2.txt'] | true | true + 'feature_2' | 'feature_1' | ['file1.txt'] | true | false + 'feature_2' | 'feature_1' | ['file1.txt'] | false | true + 'feature_2' | 'feature_1' | ['file2.txt'] | true | true + 'feature_1' | 'tag_1' | ['file1.txt'] | true | false + 'feature_1' | 'tag_1' | ['file1.txt'] | false | true + 'feature_1' | 'tag_1' | ['file2.txt'] | true | true + 'feature_2' | 'tag_1' | ['file2.txt'] | true | true + end + + with_them do + let(:globs) { { paths: paths, compare_to: compare_to } } + + let(:pipeline) do + build(:ci_pipeline, project: project, ref: pipeline_ref, sha: project.commit(pipeline_ref).sha) + end + + before do + stub_feature_flags(ci_rules_changes_compare: ff) + end + + it { is_expected.to eq(result) } + end + end + + context 'when compare_to is a sha' do + let(:globs) { { paths: ['file2.txt'], compare_to: project.commit('tag_1').sha } } + + let(:pipeline) do + build(:ci_pipeline, project: project, ref: 'feature_2', sha: project.commit('feature_2').sha) + end + + it { is_expected.to be_truthy } + end + + context 'when compare_to is not a valid ref' do + let(:globs) { { paths: ['file1.txt'], compare_to: 'xyz' } } + + let(:pipeline) do + build(:ci_pipeline, project: project, ref: 'feature_2', sha: project.commit('feature_2').sha) + end + + it 'raises ParseError' do + expect { satisfied_by }.to raise_error( + ::Gitlab::Ci::Build::Rules::Rule::Clause::ParseError, 'rules:changes:compare_to is not a valid ref' + ) + end + + context 'when the FF ci_rules_changes_compare is disabled' do + before do + stub_feature_flags(ci_rules_changes_compare: false) + end + + it { is_expected.to be_truthy } + end + end + end end end diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb index 81bce989833..31c7437cfe0 100644 --- a/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb @@ -51,14 +51,6 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::If do end it { is_expected.to eq(true) } - - context 'when the FF ci_fix_rules_if_comparison_with_regexp_variable is disabled' do - before do - stub_feature_flags(ci_fix_rules_if_comparison_with_regexp_variable: false) - end - - it { is_expected.to eq(false) } - end end context 'when comparison is false' do diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb index 0fa6d4f8804..6121c28070f 100644 --- a/spec/lib/gitlab/ci/config/entry/image_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'support/helpers/stubbed_feature' -require 'support/helpers/stub_feature_flags' +require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Image do - include StubFeatureFlags - before do stub_feature_flags(ci_docker_image_pull_policy: true) diff --git a/spec/lib/gitlab/ci/config/entry/imageable_spec.rb b/spec/lib/gitlab/ci/config/entry/imageable_spec.rb new file mode 100644 index 00000000000..88f8e260611 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/imageable_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Config::Entry::Imageable do + let(:node_class) do + Class.new(::Gitlab::Config::Entry::Node) do + include ::Gitlab::Ci::Config::Entry::Imageable + + validations do + validates :config, allowed_keys: ::Gitlab::Ci::Config::Entry::Imageable::IMAGEABLE_ALLOWED_KEYS + end + + def self.name + 'node' + end + + def value + if string? + { name: @config } + elsif hash? + { + name: @config[:name] + }.compact + else + {} + end + end + end + end + + subject(:entry) { node_class.new(config) } + + before do + entry.compose! + end + + context 'when entry value is correct' do + let(:config) { 'image:1.0' } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + let(:config) { ['image:1.0'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors.first) + .to match /config should be a hash or a string/ + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + + context 'when unexpected key is specified' do + let(:config) { { name: 'image:1.0', non_existing: 'test' } } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors.first) + .to match /config contains unknown keys: non_existing/ + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb index 5b9337ede34..714b0a3b6aa 100644 --- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb @@ -212,7 +212,7 @@ 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(:variables) {} let(:deps) do double('deps', diff --git a/spec/lib/gitlab/ci/config/entry/release_spec.rb b/spec/lib/gitlab/ci/config/entry/release_spec.rb index e5155f91be4..7b6b31ca748 100644 --- a/spec/lib/gitlab/ci/config/entry/release_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/release_spec.rb @@ -128,25 +128,25 @@ RSpec.describe Gitlab::Ci::Config::Entry::Release do end context "when 'ref' is a short commit SHA" do - let(:ref) { 'b3235930'} + let(:ref) { 'b3235930' } it_behaves_like 'a valid entry' end context "when 'ref' is a branch name" do - let(:ref) { 'fix/123-branch-name'} + let(:ref) { 'fix/123-branch-name' } it_behaves_like 'a valid entry' end context "when 'ref' is a semantic versioning tag" do - let(:ref) { 'v1.2.3'} + let(:ref) { 'v1.2.3' } it_behaves_like 'a valid entry' end context "when 'ref' is a semantic versioning tag rc" do - let(:ref) { 'v1.2.3-rc'} + let(:ref) { 'v1.2.3-rc' } it_behaves_like 'a valid entry' end @@ -188,6 +188,30 @@ RSpec.describe Gitlab::Ci::Config::Entry::Release do end end + context "when value includes 'tag_message' keyword" do + let(:config) do + { + tag_name: 'v0.06', + description: "./release_changelog.txt", + tag_message: "Annotated tag message" + } + end + + it_behaves_like 'a valid entry' + end + + context "when 'tag_message' is nil" do + let(:config) do + { + tag_name: 'v0.06', + description: "./release_changelog.txt", + tag_message: nil + } + end + + it_behaves_like 'a valid entry' + end + context 'when entry value is not correct' do describe '#errors' do context 'when value of attribute is invalid' do @@ -231,6 +255,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Release do it_behaves_like 'reports error', 'release milestones should be an array of strings or a string' end + + context 'when `tag_message` is not a string' do + let(:config) { { tag_message: 100 } } + + it_behaves_like 'reports error', 'release tag message should be a string' + end end end end diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb index 051cccb4833..45aa859a356 100644 --- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb @@ -47,6 +47,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do :dotenv | 'build.dotenv' :terraform | 'tfplan.json' :accessibility | 'gl-accessibility.json' + :cyclonedx | 'gl-sbom.cdx.zip' end with_them 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..1f8543227c9 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -155,7 +155,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do 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' }, + job_variables: { 'VAR' => { value: 'job' } }, root_variables_inheritance: true, after_script: [], ignore: false, @@ -215,7 +215,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do 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' }, + job_variables: { 'VAR' => { value: 'job' } }, root_variables_inheritance: true, ignore: false, after_script: ['make clean'], diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb index 295561b3c4d..64f0a64074c 100644 --- a/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do let(:factory) do @@ -119,6 +119,23 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do end end end + + context 'with paths and compare_to' do + let(:config) { { paths: %w[app/ lib/], compare_to: 'branch1' } } + + it { is_expected.to be_valid } + + context 'when compare_to is not a string' do + let(:config) { { paths: %w[app/ lib/], compare_to: 1 } } + + it { is_expected.not_to be_valid } + + it 'returns information about errors' do + expect(entry.errors) + .to include(/should be a string/) + end + end + end end describe '#value' do @@ -137,5 +154,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do it { is_expected.to eq(config) } end + + context 'with paths and compare_to' do + let(:config) do + { paths: ['app/', 'lib/'], compare_to: 'branch1' } + end + + it { is_expected.to eq(config) } + end end end 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 93f4a66bfb6..c85fe366da6 100644 --- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb @@ -2,7 +2,6 @@ require 'fast_spec_helper' require 'gitlab_chronic_duration' -require 'support/helpers/stub_feature_flags' require_dependency 'active_model' RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do @@ -418,6 +417,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do it { is_expected.to eq(config) } end + + context 'when using changes with paths and compare_to' do + let(:config) { { changes: { paths: %w[app/ lib/ spec/ other/* paths/**/*.rb], compare_to: 'branch1' } } } + + it { is_expected.to eq(config) } + end end context 'when default value has been provided' do diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb index 3c000fd09ed..821ab442d61 100644 --- a/spec/lib/gitlab/ci/config/entry/service_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'support/helpers/stubbed_feature' -require 'support/helpers/stub_feature_flags' +require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Service do - include StubFeatureFlags - before do stub_feature_flags(ci_docker_image_pull_policy: true) entry.compose! diff --git a/spec/lib/gitlab/ci/config/entry/tags_spec.rb b/spec/lib/gitlab/ci/config/entry/tags_spec.rb index e05d4ae52b2..24efd08c855 100644 --- a/spec/lib/gitlab/ci/config/entry/tags_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/tags_spec.rb @@ -34,7 +34,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Tags do end context 'when tags limit is reached' do - let(:config) { Array.new(50) {|i| "tag-#{i}" } } + let(:config) { Array.new(50) { |i| "tag-#{i}" } } it 'reports error' do expect(entry.errors) diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb index 280bebe1a7c..1306d61d99c 100644 --- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Config::External::File::Base do - let(:variables) { } + let(:variables) {} let(:context_params) { { sha: 'HEAD', variables: variables } } let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } @@ -100,7 +100,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do describe '#to_hash' do context 'with includes' do let(:location) { 'some/file/config.yml' } - let(:content) { 'include: { template: Bash.gitlab-ci.yml }'} + let(:content) { 'include: { template: Bash.gitlab-ci.yml }' } before do allow_any_instance_of(test_class) diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb index 0e78498c98e..f5b36ebfa45 100644 --- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb @@ -167,7 +167,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do describe '#to_hash' do context 'properly includes another local file in the same repository' do let(:location) { 'some/file/config.yml' } - let(:content) { 'include: { local: another-config.yml }'} + let(:content) { 'include: { local: another-config.yml }' } let(:another_location) { 'another-config.yml' } let(:another_content) { 'rspec: JOB' } 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 3e1c4df4e32..45dfea636f3 100644 --- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Config::External::File::Remote do include StubRequests - let(:variables) {Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) } + let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) } let(:context_params) { { sha: '12345', variables: variables } } let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } let(:params) { { remote: location } } diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb index 354392eb42e..96ca5d98a6e 100644 --- a/spec/lib/gitlab/ci/config/normalizer_spec.rb +++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb @@ -232,7 +232,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do context 'when parallel config does not matches a factory' do let(:variables_config) { {} } - let(:parallel_config) { } + let(:parallel_config) {} it 'does not alter the job config' do is_expected.to match(config) diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 5eb04d969eb..055114769ea 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -872,4 +872,21 @@ RSpec.describe Gitlab::Ci::Config do end end end + + describe '#workflow_rules' do + subject(:workflow_rules) { config.workflow_rules } + + let(:yml) do + <<-EOS + workflow: + rules: + - if: $CI_COMMIT_REF_NAME == "master" + + rspec: + script: exit 0 + EOS + end + + it { is_expected.to eq([{ if: '$CI_COMMIT_REF_NAME == "master"' }]) } + end end diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index 4017accb462..33474865a93 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -178,7 +178,7 @@ RSpec.describe Gitlab::Ci::CronParser do end context 'when time crosses a Daylight Savings boundary' do - let(:cron) { '* 0 1 12 *'} + let(:cron) { '* 0 1 12 *' } # Note this previously only failed if the time zone is set # to a zone that observes Daylight Savings diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb new file mode 100644 index 00000000000..c99cfa94aa6 --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Parsers::Sbom::CyclonedxProperties do + subject(:parse_source) { described_class.parse_source(properties) } + + context 'when properties are nil' do + let(:properties) { nil } + + it { is_expected.to be_nil } + end + + context 'when report does not have gitlab properties' do + let(:properties) { ['name' => 'foo', 'value' => 'bar'] } + + it { is_expected.to be_nil } + end + + context 'when schema_version is missing' do + let(:properties) do + [ + { 'name' => 'gitlab:dependency_scanning:dependency_file', 'value' => 'package-lock.json' }, + { 'name' => 'gitlab:dependency_scanning:package_manager_name', 'value' => 'npm' }, + { 'name' => 'gitlab:dependency_scanning:language', 'value' => 'JavaScript' } + ] + end + + it { is_expected.to be_nil } + end + + context 'when schema version is unsupported' do + let(:properties) do + [ + { 'name' => 'gitlab:meta:schema_version', 'value' => '2' }, + { 'name' => 'gitlab:dependency_scanning:dependency_file', 'value' => 'package-lock.json' }, + { 'name' => 'gitlab:dependency_scanning:package_manager_name', 'value' => 'npm' }, + { 'name' => 'gitlab:dependency_scanning:language', 'value' => 'JavaScript' } + ] + end + + it { is_expected.to be_nil } + end + + context 'when no dependency_scanning properties are present' do + let(:properties) do + [ + { 'name' => 'gitlab:meta:schema_version', 'value' => '1' } + ] + end + + it 'does not call dependency_scanning parser' do + expect(Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning).not_to receive(:parse_source) + + parse_source + end + end + + context 'when dependency_scanning properties are present' do + let(:properties) do + [ + { 'name' => 'gitlab:meta:schema_version', 'value' => '1' }, + { 'name' => 'gitlab:dependency_scanning:category', 'value' => 'development' }, + { 'name' => 'gitlab:dependency_scanning:input_file:path', 'value' => 'package-lock.json' }, + { 'name' => 'gitlab:dependency_scanning:source_file:path', 'value' => 'package.json' }, + { 'name' => 'gitlab:dependency_scanning:package_manager:name', 'value' => 'npm' }, + { 'name' => 'gitlab:dependency_scanning:language:name', 'value' => 'JavaScript' }, + { 'name' => 'gitlab:dependency_scanning:unsupported_property', 'value' => 'Should be ignored' } + ] + end + + let(:expected_input) do + { + 'category' => 'development', + 'input_file' => { 'path' => 'package-lock.json' }, + 'source_file' => { 'path' => 'package.json' }, + 'package_manager' => { 'name' => 'npm' }, + 'language' => { 'name' => 'JavaScript' } + } + end + + it 'passes only supported properties to the dependency scanning parser' do + expect(Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning).to receive(:source).with(expected_input) + + parse_source + end + end +end diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb new file mode 100644 index 00000000000..431fe9f3591 --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Parsers::Sbom::Cyclonedx do + let(:report) { instance_double('Gitlab::Ci::Reports::Sbom::Report') } + let(:report_data) { base_report_data } + let(:raw_report_data) { report_data.to_json } + let(:report_valid?) { true } + let(:validator_errors) { [] } + let(:properties_parser) { class_double('Gitlab::Ci::Parsers::Sbom::CyclonedxProperties') } + + let(:base_report_data) do + { + 'bomFormat' => 'CycloneDX', + 'specVersion' => '1.4', + 'version' => 1 + } + end + + subject(:parse!) { described_class.new.parse!(raw_report_data, report) } + + before do + allow_next_instance_of(Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator) do |validator| + allow(validator).to receive(:valid?).and_return(report_valid?) + allow(validator).to receive(:errors).and_return(validator_errors) + end + + allow(properties_parser).to receive(:parse_source) + stub_const('Gitlab::Ci::Parsers::Sbom::CyclonedxProperties', properties_parser) + end + + context 'when report JSON is invalid' do + let(:raw_report_data) { '{ ' } + + it 'handles errors and adds them to the report' do + expect(report).to receive(:add_error).with(a_string_including("Report JSON is invalid:")) + + expect { parse! }.not_to raise_error + end + end + + context 'when report uses an unsupported spec version' do + let(:report_data) { base_report_data.merge({ 'specVersion' => '1.3' }) } + + it 'reports unsupported version as an error' do + expect(report).to receive(:add_error).with("Unsupported CycloneDX spec version. Must be one of: 1.4") + + parse! + end + end + + context 'when report does not conform to the CycloneDX schema' do + let(:report_valid?) { false } + let(:validator_errors) { %w[error1 error2] } + + it 'reports all errors returned by the validator' do + expect(report).to receive(:add_error).with("error1") + expect(report).to receive(:add_error).with("error2") + + parse! + end + end + + context 'when cyclonedx report has no components' do + it 'skips component processing' do + expect(report).not_to receive(:add_component) + + parse! + end + end + + context 'when report has components' do + let(:report_data) { base_report_data.merge({ 'components' => components }) } + let(:components) do + [ + { + "name" => "activesupport", + "version" => "5.1.4", + "purl" => "pkg:gem/activesupport@5.1.4", + "type" => "library", + "bom-ref" => "pkg:gem/activesupport@5.1.4" + }, + { + "name" => "byebug", + "version" => "10.0.0", + "purl" => "pkg:gem/byebug@10.0.0", + "type" => "library", + "bom-ref" => "pkg:gem/byebug@10.0.0" + }, + { + "name" => "minimal-component", + "type" => "library" + }, + { + # Should be skipped + "name" => "unrecognized-type", + "type" => "unknown" + } + ] + end + + it 'adds each component, ignoring unused attributes' do + expect(report).to receive(:add_component) + .with({ "name" => "activesupport", "version" => "5.1.4", "type" => "library" }) + expect(report).to receive(:add_component) + .with({ "name" => "byebug", "version" => "10.0.0", "type" => "library" }) + expect(report).to receive(:add_component) + .with({ "name" => "minimal-component", "type" => "library" }) + + parse! + end + end + + context 'when report has metadata properties' do + let(:report_data) { base_report_data.merge({ 'metadata' => { 'properties' => properties } }) } + + let(:properties) do + [ + { 'name' => 'gitlab:meta:schema_version', 'value' => '1' }, + { 'name' => 'gitlab:dependency_scanning:category', 'value' => 'development' }, + { 'name' => 'gitlab:dependency_scanning:input_file:path', 'value' => 'package-lock.json' }, + { 'name' => 'gitlab:dependency_scanning:source_file:path', 'value' => 'package.json' }, + { 'name' => 'gitlab:dependency_scanning:package_manager:name', 'value' => 'npm' }, + { 'name' => 'gitlab:dependency_scanning:language:name', 'value' => 'JavaScript' } + ] + end + + it 'passes them to the properties parser' do + expect(properties_parser).to receive(:parse_source).with(properties) + + parse! + end + end +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 new file mode 100644 index 00000000000..30114b17cac --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning do + subject { described_class.source(property_data) } + + context 'when all property data is present' do + let(:property_data) do + { + 'category' => 'development', + 'input_file' => { 'path' => 'package-lock.json' }, + 'source_file' => { 'path' => 'package.json' }, + 'package_manager' => { 'name' => 'npm' }, + 'language' => { 'name' => 'JavaScript' } + } + end + + it 'returns expected source data' do + is_expected.to eq({ + 'type' => :dependency_scanning, + 'data' => property_data, + 'fingerprint' => '4dbcb747e6f0fb3ed4f48d96b777f1d64acdf43e459fdfefad404e55c004a188' + }) + end + end + + context 'when required properties are missing' do + let(:property_data) do + { + 'category' => 'development', + 'source_file' => { 'path' => 'package.json' }, + 'package_manager' => { 'name' => 'npm' }, + 'language' => { 'name' => 'JavaScript' } + } + end + + it { is_expected.to be_nil } + end +end diff --git a/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb new file mode 100644 index 00000000000..c54a3268bbe --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator do + # Reports should be valid or invalid according to the specification at + # https://cyclonedx.org/docs/1.4/json/ + + subject(:validator) { described_class.new(report_data) } + + let_it_be(:required_attributes) do + { + "bomFormat" => "CycloneDX", + "specVersion" => "1.4", + "version" => 1 + } + end + + context "with minimally valid report" do + let_it_be(:report_data) { required_attributes } + + it { is_expected.to be_valid } + end + + context "when report has components" do + let(:report_data) { required_attributes.merge({ "components" => components }) } + + context "with minimally valid components" do + let(:components) do + [ + { + "type" => "library", + "name" => "activesupport" + }, + { + "type" => "library", + "name" => "byebug" + } + ] + end + + it { is_expected.to be_valid } + end + + context "when components have versions" do + let(:components) do + [ + { + "type" => "library", + "name" => "activesupport", + "version" => "5.1.4" + }, + { + "type" => "library", + "name" => "byebug", + "version" => "10.0.0" + } + ] + end + + it { is_expected.to be_valid } + end + + context "when components are not valid" do + let(:components) do + [ + { "type" => "foo" }, + { "name" => "activesupport" } + ] + end + + it { is_expected.not_to be_valid } + + it "outputs errors for each validation failure" do + expect(validator.errors).to match_array([ + "property '/components/0' is missing required keys: name", + "property '/components/0/type' is not one of: [\"application\", \"framework\"," \ + " \"library\", \"container\", \"operating-system\", \"device\", \"firmware\", \"file\"]", + "property '/components/1' is missing required keys: type" + ]) + end + end + end + + context "when report has metadata" do + let(:metadata) do + { + "timestamp" => "2022-02-23T08:02:39Z", + "tools" => [{ "vendor" => "GitLab", "name" => "Gemnasium", "version" => "2.34.0" }], + "authors" => [{ "name" => "GitLab", "email" => "support@gitlab.com" }] + } + end + + let(:report_data) { required_attributes.merge({ "metadata" => metadata }) } + + it { is_expected.to be_valid } + + context "when metadata has properties" do + before do + metadata.merge!({ "properties" => properties }) + end + + context "when properties are valid" do + let(:properties) do + [ + { "name" => "gitlab:dependency_scanning:input_file", "value" => "Gemfile.lock" }, + { "name" => "gitlab:dependency_scanning:package_manager", "value" => "bundler" } + ] + end + + it { is_expected.to be_valid } + end + + context "when properties are invalid" do + let(:properties) do + [ + { "name" => ["gitlab:meta:schema_version"], "value" => 1 } + ] + end + + it { is_expected.not_to be_valid } + + it "outputs errors for each validation failure" do + expect(validator.errors).to match_array([ + "property '/metadata/properties/0/name' is not of type: string", + "property '/metadata/properties/0/value' is not of type: string" + ]) + end + end + end + end +end 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 d06077d69b6..7828aa99f6a 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 @@ -6,6 +6,10 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let_it_be(:project) { create(:project) } let(:supported_dast_versions) { described_class::SUPPORTED_VERSIONS[:dast].join(', ') } + let(:deprecated_schema_version_message) {} + let(:missing_schema_version_message) do + "Report version not provided, dast report type supports versions: #{supported_dast_versions}" + end let(:scanner) do { @@ -24,7 +28,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do expect(described_class::SUPPORTED_VERSIONS.keys).to eq(described_class::DEPRECATED_VERSIONS.keys) end - context 'when a schema JSON file exists for a particular report type version' do + context 'when all files under schema path are explicitly listed' do # We only care about the part that comes before report-format.json # https://rubular.com/r/N8Juz7r8hYDYgD filename_regex = /(?<report_type>[-\w]*)\-report-format.json/ @@ -38,7 +42,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do matches = filename_regex.match(file) report_type = matches[:report_type].tr("-", "_").to_sym - it "#{report_type} #{version} is in the constant" do + it "#{report_type} #{version}" do expect(described_class::SUPPORTED_VERSIONS[report_type]).to include(version) end end @@ -64,11 +68,54 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do describe '#valid?' do subject { validator.valid? } + context 'when given a supported MAJOR.MINOR schema version' do + let(:report_type) { :dast } + let(:report_version) do + latest_vendored_version = described_class::SUPPORTED_VERSIONS[report_type].last.split(".") + (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 + end + context 'when given a supported schema version' do let(:report_type) { :dast } let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } - context 'when the report is valid' do + context 'and the report is valid' do let(:report_data) do { 'version' => report_version, @@ -79,7 +126,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_truthy } end - context 'when the report is invalid' do + context 'and the report is invalid' do let(:report_data) do { 'version' => report_version @@ -118,7 +165,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash) end - context 'when the report passes schema validation' do + context 'and the report passes schema validation' do let(:report_data) do { 'version' => '10.0.0', @@ -143,34 +190,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do end end - context 'when the report does not pass schema validation' do - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) - end - - let(:report_data) do - { - 'version' => 'V2.7.0' - } - end - - it { is_expected.to be_falsey } + context 'and the report does not pass schema validation' do + let(:report_data) do + { + 'version' => 'V2.7.0' + } end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - let(:report_data) do - { - 'version' => 'V2.7.0' - } - end - - it { is_expected.to be_truthy } - end + it { is_expected.to be_falsey } end end @@ -178,20 +205,40 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:report_type) { :dast } let(:report_version) { "12.37.0" } - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } end - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - 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: 'using_unsupported_schema_version', + security_report_scanner_id: 'gemnasium', + security_report_scanner_version: '2.1.0' + ) - it { is_expected.to be_falsey } + subject + end + end + + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } + end + + context 'and scanner information is empty' do + let(:scanner) { {} } it 'logs related information' do expect(Gitlab::AppLogger).to receive(:info).with( @@ -199,79 +246,26 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do security_report_type: report_type, security_report_version: report_version, project_id: project.id, + security_report_failure: 'schema_validation_fails', + security_report_scanner_id: nil, + security_report_scanner_version: nil + ) + + 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' + security_report_scanner_id: nil, + security_report_scanner_version: nil ) subject end end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - context 'when scanner information is empty' do - let(:scanner) { {} } - - 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: nil, - security_report_scanner_version: nil - ) - - 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: nil, - security_report_scanner_version: nil - ) - - subject - end - end - - it { is_expected.to be_falsey } - end - end - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_truthy } - end - - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - it { is_expected.to be_truthy } - end + it { is_expected.to be_falsey } end end @@ -284,19 +278,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end - before do - stub_feature_flags(enforce_security_report_validation: true) - end - it { is_expected.to be_falsey } - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - it { is_expected.to be_truthy } - end end end @@ -307,7 +289,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 'when the report is valid' do + context 'and the report is valid' do let(:report_data) do { 'version' => report_version, @@ -318,34 +300,20 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_empty } end - context 'when the report is invalid' do + context 'and the report is invalid' do let(:report_data) do { 'version' => report_version } end - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: project) - end - - let(:expected_errors) do - [ - 'root is missing required keys: vulnerabilities' - ] - end - - it { is_expected.to match_array(expected_errors) } + let(:expected_errors) do + [ + 'root is missing required keys: vulnerabilities' + ] end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - it { is_expected.to be_empty } - end + it { is_expected.to match_array(expected_errors) } end end @@ -363,7 +331,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash) end - context 'when the report passes schema validation' do + context 'and the report passes schema validation' do let(:report_data) do { 'version' => '10.0.0', @@ -374,119 +342,77 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_empty } end - context 'when the report does not pass schema validation' do - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) - end - - let(:report_data) do - { - 'version' => 'V2.7.0' - } - end - - let(:expected_errors) do - [ - "property '/version' does not match pattern: ^[0-9]+\\.[0-9]+\\.[0-9]+$", - "root is missing required keys: vulnerabilities" - ] - end - - it { is_expected.to match_array(expected_errors) } + context 'and the report does not pass schema validation' do + let(:report_data) do + { + 'version' => 'V2.7.0' + } end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - let(:report_data) do - { - 'version' => 'V2.7.0' - } - end - - it { is_expected.to be_empty } + let(:expected_errors) do + [ + "property '/version' does not match pattern: ^[0-9]+\\.[0-9]+\\.[0-9]+$", + "root is missing required keys: vulnerabilities" + ] end + + it { is_expected.to match_array(expected_errors) } end end context 'when given an unsupported schema version' do let(:report_type) { :dast } let(:report_version) { "12.37.0" } + let(:expected_unsupported_message) do + "Version #{report_version} for report type #{report_type} is unsupported, supported versions for this report type are: "\ + "#{supported_dast_versions}. GitLab will attempt to validate this report against the earliest supported "\ + "versions of this report type, to show all the errors but will not ingest the report" + end - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } end - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - let(:expected_errors) do - [ - "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}" - ] - end - - it { is_expected.to match_array(expected_errors) } + let(:expected_errors) do + [ + expected_unsupported_message + ] end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - let(:expected_errors) do - [ - "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}", - "root is missing required keys: vulnerabilities" - ] - end - - it { is_expected.to match_array(expected_errors) } - end + it { is_expected.to match_array(expected_errors) } end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } end - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_empty } + let(:expected_errors) do + [ + expected_unsupported_message, + "root is missing required keys: vulnerabilities" + ] end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - it { is_expected.to be_empty } - end + it { is_expected.to match_array(expected_errors) } end end context 'when not given a schema version' do let(:report_type) { :dast } let(:report_version) { nil } + let(:expected_missing_version_message) do + "Report version not provided, #{report_type} report type supports versions: #{supported_dast_versions}. GitLab "\ + "will attempt to validate this report against the earliest supported versions of this report type, to show all "\ + "the errors but will not ingest the report" + end + let(:report_data) do { 'vulnerabilities' => [] @@ -496,19 +422,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:expected_errors) do [ "root is missing required keys: version", - "Report version not provided, dast report type supports versions: #{supported_dast_versions}" + expected_missing_version_message ] end it { is_expected.to match_array(expected_errors) } - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - it { is_expected.to be_empty } - end end end @@ -519,7 +437,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 'when the report is valid' do + context 'and the report is valid' do let(:report_data) do { 'version' => report_version, @@ -530,7 +448,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_empty } end - context 'when the report is invalid' do + context 'and the report is invalid' do let(:report_data) do { 'version' => report_version @@ -550,9 +468,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do end let(:report_version) { described_class::DEPRECATED_VERSIONS[report_type].last } + let(:expected_deprecation_message) do + "Version #{report_version} for report type #{report_type} has been deprecated, supported versions for this "\ + "report type are: #{supported_dast_versions}. GitLab will attempt to parse and ingest this report if valid." + end + let(:expected_deprecation_warnings) do [ - "Version V2.7.0 for report type dast has been deprecated, supported versions for this report type are: #{supported_dast_versions}" + expected_deprecation_message ] end @@ -560,7 +483,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash) end - context 'when the report passes schema validation' do + context 'and the report passes schema validation' do let(:report_data) do { 'version' => report_version, @@ -571,7 +494,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to match_array(expected_deprecation_warnings) } end - context 'when the report does not pass schema validation' do + context 'and the report does not pass schema validation' do let(:report_data) do { 'version' => 'V2.7.0' @@ -600,11 +523,27 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do describe '#warnings' do subject { validator.warnings } - context 'when given a supported schema version' do + context 'when given a supported MAJOR.MINOR schema version' do let(:report_type) { :dast } - let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } + let(:report_version) do + latest_vendored_version = described_class::SUPPORTED_VERSIONS[report_type].last.split(".") + (latest_vendored_version[0...2] << "34").join(".") + end + + let(:latest_patch_version) do + ::Security::ReportSchemaVersionMatcher.new( + report_declared_version: report_version, + supported_versions: described_class::SUPPORTED_VERSIONS[report_type] + ).call + end + + let(:message) do + "This report uses a supported MAJOR.MINOR schema version but the PATCH version doesn't match"\ + " any vendored schema version. Validation will be attempted against version"\ + " #{latest_patch_version}" + end - context 'when the report is valid' do + context 'and the report is valid' do let(:report_data) do { 'version' => report_version, @@ -612,37 +551,57 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do } end - it { is_expected.to be_empty } + it { is_expected.to match_array([message]) } end - context 'when the report is invalid' do + context 'and the report is invalid' do let(:report_data) do { 'version' => report_version } end - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: project) - end + 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' + ) - it { is_expected.to be_empty } + subject end + end + end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end + context 'when given a supported schema version' do + let(:report_type) { :dast } + let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last } - let(:expected_warnings) do - [ - 'root is missing required keys: vulnerabilities' - ] - end + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } + end + + it { is_expected.to be_empty } + end - it { is_expected.to match_array(expected_warnings) } + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } end + + it { is_expected.to be_empty } end end @@ -660,7 +619,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash) end - context 'when the report passes schema validation' do + context 'and the report passes schema validation' do let(:report_data) do { 'vulnerabilities' => [] @@ -670,35 +629,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do it { is_expected.to be_empty } end - context 'when the report does not pass schema validation' do + context 'and the report does not pass schema validation' do let(:report_data) do { 'version' => 'V2.7.0' } end - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) - end - - it { is_expected.to be_empty } - end - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - let(:expected_warnings) do - [ - "property '/version' does not match pattern: ^[0-9]+\\.[0-9]+\\.[0-9]+$", - "root is missing required keys: vulnerabilities" - ] - end - - it { is_expected.to match_array(expected_warnings) } - end + it { is_expected.to be_empty } end end @@ -706,71 +644,25 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do let(:report_type) { :dast } let(:report_version) { "12.37.0" } - context 'when enforce_security_report_validation is enabled' do - before do - stub_feature_flags(enforce_security_report_validation: true) - end - - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - it { is_expected.to be_empty } + context 'and the report is valid' do + let(:report_data) do + { + 'version' => report_version, + 'vulnerabilities' => [] + } end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - it { is_expected.to be_empty } - end + it { is_expected.to be_empty } end - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - context 'when the report is valid' do - let(:report_data) do - { - 'version' => report_version, - 'vulnerabilities' => [] - } - end - - let(:expected_warnings) do - [ - "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}" - ] - end - - it { is_expected.to match_array(expected_warnings) } + context 'and the report is invalid' do + let(:report_data) do + { + 'version' => report_version + } end - context 'when the report is invalid' do - let(:report_data) do - { - 'version' => report_version - } - end - - let(:expected_warnings) do - [ - "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}", - "root is missing required keys: vulnerabilities" - ] - end - - it { is_expected.to match_array(expected_warnings) } - end + it { is_expected.to be_empty } end end @@ -784,21 +676,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do end it { is_expected.to be_empty } - - context 'when enforce_security_report_validation is disabled' do - before do - stub_feature_flags(enforce_security_report_validation: false) - end - - let(:expected_warnings) do - [ - "root is missing required keys: version", - "Report version not provided, dast report type supports versions: #{supported_dast_versions}" - ] - end - - it { is_expected.to match_array(expected_warnings) } - 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 0d78ce3440a..de43e759193 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb @@ -282,7 +282,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do subject { command.ambiguous_ref? } context 'when ref is not ambiguous' do - it { is_expected. to eq(false) } + it { is_expected.to eq(false) } end context 'when ref is ambiguous' do @@ -291,7 +291,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do project.repository.add_branch(project.creator, 'ref', 'master') end - it { is_expected. to eq(true) } + it { is_expected.to eq(true) } end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb index cbf92f8fa83..be5d3a96126 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb @@ -39,7 +39,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments do end context 'when the corresponding environment does not exist' do - let!(:environment) { } + let!(:environment) {} it 'does not create a deployment record' do expect { subject }.not_to change { Deployment.count } diff --git a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb index e30a78546af..eb5a37f19f4 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb @@ -45,7 +45,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do before do allow(step).to receive(:workflow_rules_result) .and_return( - double(pass?: true, variables: { 'VAR1' => 'val2' }) + double(pass?: true, variables: { 'VAR1' => 'val2', 'VAR2' => 3 }) ) step.perform! @@ -65,7 +65,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do end it 'saves workflow_rules_result' do - expect(command.workflow_rules_result.variables).to eq({ 'VAR1' => 'val2' }) + expect(command.workflow_rules_result.variables).to eq({ 'VAR1' => 'val2', 'VAR2' => 3 }) end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb index fabfbd779f3..5ee96b0baa8 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Chain::SeedBlock do let(:project) { create(:project, :repository) } let(:user) { create(:user, developer_projects: [project]) } - let(:seeds_block) { } + let(:seeds_block) {} let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb index 687bb82a8ef..f7774e199fb 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user, developer_projects: [project]) } - let(:seeds_block) { } + let(:seeds_block) {} let(:command) { initialize_command } let(:pipeline) { build(:ci_pipeline, project: project) } @@ -205,6 +205,30 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do end end + describe '#rule_variables' do + let(:config) do + { + variables: { VAR1: 11 }, + workflow: { + rules: [{ if: '$CI_PIPELINE_SOURCE', + variables: { SYMBOL: :symbol, STRING: "string", INTEGER: 1 } }, + { when: 'always' }] + }, + rspec: { script: 'rake' } + } + end + + let(:rspec_variables) { command.pipeline_seed.stages[0].statuses[0].variables.to_hash } + + it 'correctly parses rule variables' do + run_chain + + expect(rspec_variables['SYMBOL']).to eq("symbol") + expect(rspec_variables['STRING']).to eq("string") + expect(rspec_variables['INTEGER']).to eq("1") + end + end + context 'N+1 queries' do it 'avoids N+1 queries when calculating variables of jobs', :use_sql_query_cache do warm_up_pipeline, warm_up_command = prepare_pipeline1 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 eeac0c85a77..fb1a360a4b7 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb @@ -148,6 +148,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do expect(::Gitlab::HTTP).to receive(:post) do |_url, params| payload = Gitlab::Json.parse(params[:body]) + expect(payload['total_builds_count']).to eq(0) + builds = payload['builds'] expect(builds.count).to eq(2) expect(builds[0]['services']).to be_nil @@ -160,6 +162,23 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do perform! end + + context "with existing jobs from other project's alive pipelines" do + before do + create(:ci_pipeline, :with_job, user: user) + create(:ci_pipeline, :with_job) + end + + it 'returns the expected total_builds_count' do + expect(::Gitlab::HTTP).to receive(:post) do |_url, params| + payload = Gitlab::Json.parse(params[:body]) + + expect(payload['total_builds_count']).to eq(1) + end + + perform! + end + end end context 'when EXTERNAL_VALIDATION_SERVICE_TOKEN is set' do @@ -243,7 +262,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do end context 'when save_incompleted is false' do - let(:save_incompleted) { false} + let(:save_incompleted) { false } it 'adds errors to the pipeline without dropping it' do perform! diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb index 83742699d3d..47f172922a5 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb @@ -160,14 +160,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:left_value) { 'abcde' } it { is_expected.to eq(true) } - - context 'when the FF ci_fix_rules_if_comparison_with_regexp_variable is disabled' do - before do - stub_feature_flags(ci_fix_rules_if_comparison_with_regexp_variable: false) - end - - it { is_expected.to eq(false) } - end end context 'when not matching' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb index aad33106647..9e7ea3e4ea4 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb @@ -160,14 +160,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do let(:left_value) { 'abcde' } it { is_expected.to eq(false) } - - context 'when the FF ci_fix_rules_if_comparison_with_regexp_variable is disabled' do - before do - stub_feature_flags(ci_fix_rules_if_comparison_with_regexp_variable: false) - end - - it { is_expected.to eq(true) } - end end context 'when not matching' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index bbd11a00149..acaec07f95b 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -179,24 +179,16 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do .to_hash end - where(:expression, :ff, :result) do - '$teststring =~ "abcde"' | true | true - '$teststring =~ "abcde"' | false | true - '$teststring =~ $teststring' | true | true - '$teststring =~ $teststring' | false | true - '$teststring =~ $pattern1' | true | true - '$teststring =~ $pattern1' | false | false - '$teststring =~ $pattern2' | true | false - '$teststring =~ $pattern2' | false | false + where(:expression, :result) do + '$teststring =~ "abcde"' | true + '$teststring =~ $teststring' | true + '$teststring =~ $pattern1' | true + '$teststring =~ $pattern2' | false end with_them do let(:text) { expression } - before do - stub_feature_flags(ci_fix_rules_if_comparison_with_regexp_variable: ff) - end - it { is_expected.to eq(result) } end end diff --git a/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb index 8f727749ee2..a742c619584 100644 --- a/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Quota::Deployments do let(:pipeline) { build_stubbed(:ci_pipeline, project: project) } - let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2)} + let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2) } let(:command) do double(:command, diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 040f3ab5830..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 @@ -769,7 +769,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do with_them do it { is_expected.not_to be_included } - it 'correctly populates when:' do + it 'still correctly populates when:' do expect(seed_build.attributes).to include(when: 'never') end end @@ -958,6 +958,26 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do expect(seed_build.attributes).to include(when: 'never') end end + + context 'with invalid rules raising error' do + let(:rule_set) do + [ + { changes: { paths: ['README.md'], compare_to: 'invalid-ref' }, when: 'never' } + ] + end + + it { is_expected.not_to be_included } + + it 'correctly populates when:' do + expect(seed_build.attributes).to include(when: 'never') + end + + it 'returns an error' do + expect(seed_build.errors).to contain_exactly( + 'Failed to parse rule for rspec: rules:changes:compare_to is not a valid ref' + ) + end + end end end diff --git a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb new file mode 100644 index 00000000000..672117c311f --- /dev/null +++ b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Reports::Sbom::Component do + let(:attributes) do + { + 'type' => 'library', + 'name' => 'component-name', + 'version' => 'v0.0.1' + } + end + + 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' + ) + end +end diff --git a/spec/lib/gitlab/ci/reports/sbom/report_spec.rb b/spec/lib/gitlab/ci/reports/sbom/report_spec.rb new file mode 100644 index 00000000000..d7a285ab13c --- /dev/null +++ b/spec/lib/gitlab/ci/reports/sbom/report_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Reports::Sbom::Report do + subject(:report) { described_class.new } + + describe '#add_error' do + it 'appends errors to a list' do + report.add_error('error1') + report.add_error('error2') + + expect(report.errors).to match_array(%w[error1 error2]) + end + 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 + + it 'stores the source' do + report.set_source(source) + + expect(report.source).to be_a(Gitlab::Ci::Reports::Sbom::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 + + 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)) + 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 new file mode 100644 index 00000000000..97d8d7abb33 --- /dev/null +++ b/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Reports::Sbom::Reports do + subject(:reports_list) { described_class.new } + + describe '#add_report' do + let(:rep1) { Gitlab::Ci::Reports::Sbom::Report.new } + let(:rep2) { Gitlab::Ci::Reports::Sbom::Report.new } + + it 'appends the report to the report list' do + reports_list.add_report(rep1) + reports_list.add_report(rep2) + + expect(reports_list.reports.length).to eq(2) + expect(reports_list.reports.first).to eq(rep1) + expect(reports_list.reports.last).to eq(rep2) + end + end +end diff --git a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb new file mode 100644 index 00000000000..2d6434534a0 --- /dev/null +++ b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Reports::Sbom::Source do + let(:attributes) do + { + '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' + } + end + + 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'] + ) + end +end diff --git a/spec/lib/gitlab/ci/reports/security/reports_spec.rb b/spec/lib/gitlab/ci/reports/security/reports_spec.rb index 79eee642552..e240edc4a12 100644 --- a/spec/lib/gitlab/ci/reports/security/reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/reports_spec.rb @@ -57,7 +57,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do let(:high_severity_dast) { build(:ci_reports_security_finding, severity: 'high', report_type: 'dast') } let(:vulnerabilities_allowed) { 0 } let(:severity_levels) { %w(critical high) } - let(:vulnerability_states) { %w(newly_detected)} + let(:vulnerability_states) { %w(newly_detected) } subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states) } diff --git a/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb index 44e66fd9028..6f75e2c55e8 100644 --- a/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do end describe '#added' do - let(:new_location) {build(:ci_reports_security_locations_sast, :dynamic) } + let(:new_location) { build(:ci_reports_security_locations_sast, :dynamic) } let(:vul_params) { vuln_params(project.id, [identifier], confidence: :high) } let(:vuln) { build(:ci_reports_security_finding, severity: Enums::Vulnerability.severity_levels[:critical], location: new_location, **vul_params) } let(:low_vuln) { build(:ci_reports_security_finding, severity: Enums::Vulnerability.severity_levels[:low], location: new_location, **vul_params) } diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb index 1d6b39a7831..4a1f77bed65 100644 --- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb @@ -91,7 +91,7 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do subject { test_suite.with_attachment! } context 'when test cases do not contain an attachment' do - let(:test_case) { build(:report_test_case, :failed)} + let(:test_case) { build(:report_test_case, :failed) } before do test_suite.add_test_case(test_case) @@ -103,7 +103,7 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do end context 'when test cases contain an attachment' do - let(:test_case_with_attachment) { build(:report_test_case, :failed_with_attachment)} + let(:test_case_with_attachment) { build(:report_test_case, :failed_with_attachment) } before do test_suite.add_test_case(test_case_with_attachment) diff --git a/spec/lib/gitlab/ci/runner_releases_spec.rb b/spec/lib/gitlab/ci/runner_releases_spec.rb index 576eb02ad83..ad1e9b12b8a 100644 --- a/spec/lib/gitlab/ci/runner_releases_spec.rb +++ b/spec/lib/gitlab/ci/runner_releases_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::RunnerReleases do subject { described_class.instance } - let(:runner_releases_url) { 'the release API URL' } + let(:runner_releases_url) { 'http://testurl.com/runner_public_releases' } def releases subject.releases @@ -18,7 +18,7 @@ RSpec.describe Gitlab::Ci::RunnerReleases do before do subject.reset_backoff! - stub_application_setting(public_runner_releases_url: runner_releases_url) + allow(subject).to receive(:runner_releases_url).and_return(runner_releases_url) end describe 'caching behavior', :use_clean_rails_memory_store_caching do @@ -77,7 +77,8 @@ RSpec.describe Gitlab::Ci::RunnerReleases do allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything) do http_call_timestamp_offsets << Time.now.utc - start_time - raise Net::OpenTimeout if opts&.dig(:raise_timeout) + err_class = opts&.dig(:raise_error) + raise err_class if err_class mock_http_response(response) end @@ -113,12 +114,13 @@ RSpec.describe Gitlab::Ci::RunnerReleases do end context 'when request results in timeout' do - let(:response) { } + let(:response) {} let(:expected_releases) { nil } let(:expected_releases_by_minor) { nil } it_behaves_like 'requests that follow cache status', 5.seconds - it_behaves_like 'a service implementing exponential backoff', raise_timeout: true + it_behaves_like 'a service implementing exponential backoff', raise_error: Net::OpenTimeout + it_behaves_like 'a service implementing exponential backoff', raise_error: Errno::ETIMEDOUT end context 'when response is nil' do diff --git a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb index f2507a24b10..55c3834bfa7 100644 --- a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb +++ b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb @@ -5,36 +5,35 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do using RSpec::Parameterized::TableSyntax - describe '#check_runner_upgrade_status' do - subject(:result) { described_class.instance.check_runner_upgrade_status(runner_version) } + subject(:instance) { described_class.new(gitlab_version, runner_releases) } + + describe '#check_runner_upgrade_suggestion' do + subject(:result) { instance.check_runner_upgrade_suggestion(runner_version) } let(:gitlab_version) { '14.1.1' } let(:parsed_runner_version) { ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true) } - - before do - allow(described_class.instance).to receive(:gitlab_version) - .and_return(::Gitlab::VersionInfo.parse(gitlab_version)) - end + let(:runner_releases) { instance_double(Gitlab::Ci::RunnerReleases) } context 'with failing Gitlab::Ci::RunnerReleases request' do let(:runner_version) { '14.1.123' } - let(:runner_releases_double) { instance_double(Gitlab::Ci::RunnerReleases) } before do - allow(Gitlab::Ci::RunnerReleases).to receive(:instance).and_return(runner_releases_double) - allow(runner_releases_double).to receive(:releases).and_return(nil) + allow(runner_releases).to receive(:releases).and_return(nil) end it 'returns :error' do - is_expected.to eq({ error: parsed_runner_version }) + is_expected.to eq([parsed_runner_version, :error]) end end context 'with available_runner_releases configured' do - before do - url = ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url + let(:runner_releases) { Gitlab::Ci::RunnerReleases.instance } + let(:runner_releases_url) do + ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url + end - WebMock.stub_request(:get, url).to_return( + before do + WebMock.stub_request(:get, runner_releases_url).to_return( body: available_runner_releases.map { |v| { name: v } }.to_json, status: 200, headers: { 'Content-Type' => 'application/json' } @@ -53,7 +52,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do let(:runner_version) { 'v14.0.1' } it 'returns :not_available' do - is_expected.to eq({ not_available: parsed_runner_version }) + is_expected.to eq([parsed_runner_version, :not_available]) end end end @@ -68,7 +67,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do let(:runner_version) { nil } it 'returns :invalid_version' do - is_expected.to match({ invalid_version: anything }) + is_expected.to match([anything, :invalid_version]) end end @@ -76,7 +75,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do let(:runner_version) { 'junk' } it 'returns :invalid_version' do - is_expected.to match({ invalid_version: anything }) + is_expected.to match([anything, :invalid_version]) end end @@ -87,7 +86,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do let(:runner_version) { 'v14.2.0' } it 'returns :not_available' do - is_expected.to eq({ not_available: parsed_runner_version }) + is_expected.to eq([parsed_runner_version, :not_available]) end end end @@ -96,7 +95,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do let(:gitlab_version) { '14.0.1' } context 'with valid params' do - where(:runner_version, :expected_result, :expected_suggested_version) do + where(:runner_version, :expected_status, :expected_suggested_version) do 'v15.0.0' | :not_available | '15.0.0' # not available since the GitLab instance is still on 14.x, a major version might be incompatible, and a patch upgrade is not available 'v14.1.0-rc3' | :recommended | '14.1.1' # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes 'v14.1.0~beta.1574.gf6ea9389' | :recommended | '14.1.1' # suffixes are correctly handled @@ -116,7 +115,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do end with_them do - it { is_expected.to eq({ expected_result => Gitlab::VersionInfo.parse(expected_suggested_version) }) } + it { is_expected.to eq([Gitlab::VersionInfo.parse(expected_suggested_version), expected_status]) } end end end @@ -125,7 +124,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do let(:gitlab_version) { '13.9.0' } context 'with valid params' do - where(:runner_version, :expected_result, :expected_suggested_version) do + where(:runner_version, :expected_status, :expected_suggested_version) do 'v14.0.0' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available, even though the GitLab instance is still on 13.x and a major version might be incompatible 'v13.10.1' | :not_available | '13.10.1' # not available since 13.10.1 is already ahead of GitLab instance version and is the latest patch update for 13.10.x 'v13.10.0' | :recommended | '13.10.1' # recommended upgrade since 13.10.1 is available @@ -136,7 +135,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do end with_them do - it { is_expected.to eq({ expected_result => Gitlab::VersionInfo.parse(expected_suggested_version) }) } + it { is_expected.to eq([Gitlab::VersionInfo.parse(expected_suggested_version), expected_status]) } end end end @@ -152,7 +151,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do let(:runner_version) { '14.11.0~beta.29.gd0c550e3' } it 'recommends 15.1.0 since 14.11 is an unknown release and 15.1.0 is available' do - is_expected.to eq({ recommended: Gitlab::VersionInfo.new(15, 1, 0) }) + is_expected.to eq([Gitlab::VersionInfo.new(15, 1, 0), :recommended]) end end end diff --git a/spec/lib/gitlab/ci/status/bridge/common_spec.rb b/spec/lib/gitlab/ci/status/bridge/common_spec.rb index 30e6ad234a0..37524afc83d 100644 --- a/spec/lib/gitlab/ci/status/bridge/common_spec.rb +++ b/spec/lib/gitlab/ci/status/bridge/common_spec.rb @@ -29,15 +29,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Common do end it { expect(subject).to have_details } - it { expect(subject.details_path).to include "jobs/#{bridge.id}" } - - context 'with ci_retry_downstream_pipeline ff disabled' do - before do - stub_feature_flags(ci_retry_downstream_pipeline: false) - end - - it { expect(subject.details_path).to include "pipelines/#{downstream_pipeline.id}" } - end + it { expect(subject.details_path).to include "pipelines/#{downstream_pipeline.id}" } end context 'when user does not have access to read downstream pipeline' do diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb index e30a2211c8f..519b970ca5e 100644 --- a/spec/lib/gitlab/ci/status/build/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Canceled do end describe '.matches?' do - subject {described_class.matches?(build, user) } + subject { described_class.matches?(build, user) } context 'when build is canceled' do let(:build) { create(:ci_build, :canceled) } diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb index 49468674140..9738b3c1f36 100644 --- a/spec/lib/gitlab/ci/status/build/created_spec.rb +++ b/spec/lib/gitlab/ci/status/build/created_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Created do end describe '.matches?' do - subject {described_class.matches?(build, user) } + subject { described_class.matches?(build, user) } context 'when build is created' do let(:build) { create(:ci_build, :created) } diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb index 150705c1e36..a1152cb77e3 100644 --- a/spec/lib/gitlab/ci/status/build/manual_spec.rb +++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Manual do end describe '.matches?' do - subject {described_class.matches?(build, user) } + subject { described_class.matches?(build, user) } context 'when build is manual' do let(:build) { create(:ci_build, :manual) } diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb index 7b695d33877..b7dda9ce9c9 100644 --- a/spec/lib/gitlab/ci/status/build/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Pending do end describe '.matches?' do - subject {described_class.matches?(build, user) } + subject { described_class.matches?(build, user) } context 'when build is pending' do let(:build) { create(:ci_build, :pending) } diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb index 0b998a52a57..4437ac0089f 100644 --- a/spec/lib/gitlab/ci/status/build/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Skipped do end describe '.matches?' do - subject {described_class.matches?(build, user) } + subject { described_class.matches?(build, user) } context 'when build is skipped' do let(:build) { create(:ci_build, :skipped) } diff --git a/spec/lib/gitlab/ci/status/processable/waiting_for_resource_spec.rb b/spec/lib/gitlab/ci/status/processable/waiting_for_resource_spec.rb index 91a9724d043..26087fd771c 100644 --- a/spec/lib/gitlab/ci/status/processable/waiting_for_resource_spec.rb +++ b/spec/lib/gitlab/ci/status/processable/waiting_for_resource_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Gitlab::Ci::Status::Processable::WaitingForResource do end describe '.matches?' do - subject {described_class.matches?(processable, user) } + subject { described_class.matches?(processable, user) } context 'when processable is waiting for resource' do let(:processable) { create(:ci_build, :waiting_for_resource) } diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb index 0f97bc06a4e..5ff179b6fee 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb @@ -36,9 +36,10 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do let(:merge_request) { create(:merge_request, :simple, source_project: project) } let(:pipeline) { service.execute(merge_request).payload } - it 'has no jobs' do + it 'creates a pipeline with the expected jobs' do expect(pipeline).to be_merge_request_event - expect(build_names).to be_empty + expect(pipeline.errors.full_messages).to be_empty + expect(build_names).to match_array(%w(kics-iac-sast)) end end diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb index 78d3982a79f..1a909f52ec3 100644 --- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb @@ -44,7 +44,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do context 'when the project is set for deployment to AWS' do let(:platform_value) { 'ECS' } - let(:review_prod_build_names) { build_names.select {|n| n.include?('review') || n.include?('production')} } + let(:review_prod_build_names) { build_names.select { |n| n.include?('review') || n.include?('production') } } before do create(:ci_variable, project: project, key: 'AUTO_DEVOPS_PLATFORM_TARGET', value: platform_value) diff --git a/spec/lib/gitlab/ci/trace/remote_checksum_spec.rb b/spec/lib/gitlab/ci/trace/remote_checksum_spec.rb index 1cd88034166..be29543676f 100644 --- a/spec/lib/gitlab/ci/trace/remote_checksum_spec.rb +++ b/spec/lib/gitlab/ci/trace/remote_checksum_spec.rb @@ -47,7 +47,7 @@ RSpec.describe Gitlab::Ci::Trace::RemoteChecksum do end context 'when the response does not include :content_md5' do - let(:metadata) {{}} + let(:metadata) { {} } it 'raises an exception' do expect { subject }.to raise_error KeyError, /content_md5/ @@ -55,7 +55,7 @@ RSpec.describe Gitlab::Ci::Trace::RemoteChecksum do end context 'when the response include :content_md5' do - let(:metadata) {{ content_md5: base64checksum }} + let(:metadata) { { content_md5: base64checksum } } it { is_expected.to eq(checksum) } end diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb index 8ec0846bdca..6ab2089cce8 100644 --- a/spec/lib/gitlab/ci/variables/builder_spec.rb +++ b/spec/lib/gitlab/ci/variables/builder_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Variables::Builder do + include Ci::TemplateHelpers let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :repository, namespace: group) } let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project) } @@ -92,6 +93,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder do value: project.pages_url }, { key: 'CI_API_V4_URL', value: API::Helpers::Version.new('v4').root_url }, + { key: 'CI_TEMPLATE_REGISTRY_HOST', + value: template_registry_host }, { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s }, { key: 'CI_PIPELINE_SOURCE', diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index 26c560565e0..8ac03301322 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -302,6 +302,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection do .append(key: 'CI_BUILD_ID', value: '1') .append(key: 'RAW_VAR', value: '$TEST1', raw: true) .append(key: 'TEST1', value: 'test-3') + .append(key: 'FILEVAR1', value: 'file value 1', file: true) end context 'table tests' do @@ -311,28 +312,23 @@ RSpec.describe Gitlab::Ci::Variables::Collection do { "empty value": { value: '', - result: '', - keep_undefined: false + result: '' }, "simple expansions": { value: 'key$TEST1-$CI_BUILD_ID', - result: 'keytest-3-1', - keep_undefined: false + result: 'keytest-3-1' }, "complex expansion": { value: 'key${TEST1}-${CI_JOB_NAME}', - result: 'keytest-3-test-1', - keep_undefined: false + result: 'keytest-3-test-1' }, "complex expansions with raw variable": { value: 'key${RAW_VAR}-${CI_JOB_NAME}', - result: 'key$TEST1-test-1', - keep_undefined: false + result: 'key$TEST1-test-1' }, "missing variable not keeping original": { value: 'key${MISSING_VAR}-${CI_JOB_NAME}', - result: 'key-test-1', - keep_undefined: false + result: 'key-test-1' }, "missing variable keeping original": { value: 'key${MISSING_VAR}-${CI_JOB_NAME}', @@ -341,14 +337,24 @@ RSpec.describe Gitlab::Ci::Variables::Collection do }, "escaped characters are kept intact": { value: 'key-$TEST1-%%HOME%%-$${HOME}', - result: 'key-test-3-%%HOME%%-$${HOME}', - keep_undefined: false + result: 'key-test-3-%%HOME%%-$${HOME}' + }, + "file variable with expand_file_vars: true": { + value: 'key-$FILEVAR1-$TEST1', + result: 'key-file value 1-test-3' + }, + "file variable with expand_file_vars: false": { + value: 'key-$FILEVAR1-$TEST1', + result: 'key-$FILEVAR1-test-3', + expand_file_vars: false } } end with_them do - subject { collection.expand_value(value, keep_undefined: keep_undefined) } + let(:options) { { keep_undefined: keep_undefined, expand_file_vars: expand_file_vars }.compact } + + subject(:result) { collection.expand_value(value, **options) } it 'matches expected expansion' do is_expected.to eq(result) diff --git a/spec/lib/gitlab/ci/variables/helpers_spec.rb b/spec/lib/gitlab/ci/variables/helpers_spec.rb index f13b334c10e..2a1cdaeb3a7 100644 --- a/spec/lib/gitlab/ci/variables/helpers_spec.rb +++ b/spec/lib/gitlab/ci/variables/helpers_spec.rb @@ -15,21 +15,27 @@ 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) } + let(:result) do + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] + end + + it { is_expected.to match_array(result) } end context 'when new variables is a hash with symbol keys' do @@ -37,67 +43,72 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do { key2: 'value22', key3: 'value3' } end - it { is_expected.to eq(result) } + let(:result) do + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value22' }, + { key: 'key3', value: 'value3' }] + end + + 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 + describe '.transform_to_array' do + subject { described_class.transform_to_array(variables) } - let(:result) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value2', public: true }] - end + context 'when values are strings' do + let(:variables) do + { 'key1' => 'value1', 'key2' => 'value2' } + end - subject { described_class.transform_to_yaml_variables(variables) } + let(:result) do + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }] + end - it { is_expected.to eq(result) } + it { is_expected.to match_array(result) } + end context 'when variables is nil' do let(:variables) {} - it { is_expected.to eq([]) } - end - end - - describe '.transform_from_yaml_variables' do - let(:variables) do - [{ key: 'key1', value: 'value1', public: true }, - { key: 'key2', value: 'value2', public: true }] + it { is_expected.to match_array([]) } end - let(:result) do - { 'key1' => 'value1', 'key2' => 'value2' } - end + context 'when values are hashes' do + let(:variables) do + { 'key1' => { value: 'value1', description: 'var 1' }, 'key2' => { value: 'value2' } } + end - subject { described_class.transform_from_yaml_variables(variables) } + let(:result) do + [{ key: 'key1', value: 'value1', description: 'var 1' }, + { key: 'key2', value: 'value2' }] + end - it { is_expected.to eq(result) } + it { is_expected.to match_array(result) } - context 'when variables is nil' do - let(:variables) {} + context 'when a value data has `key` as a key' do + let(:variables) do + { 'key1' => { value: 'value1', key: 'new_key1' }, 'key2' => { value: 'value2' } } + end - it { is_expected.to eq({}) } - end + let(:result) do + [{ key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }] + end - context 'when variables is a hash' do - let(:variables) do - { key1: 'value1', 'key2' => 'value2' } + it { is_expected.to match_array(result) } end - - it { is_expected.to eq(result) } end end @@ -115,35 +126,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/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 22bc6b0db59..3477fe837b4 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -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 @@ -1098,8 +1098,8 @@ module Gitlab 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 @@ -1203,21 +1203,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( @@ -1425,7 +1425,7 @@ module Gitlab it 'returns the parallel config' do build_options = builds.map { |build| build[:options] } parallel_config = { - matrix: parallel[:matrix].map { |var| var.transform_values { |v| Array(v).flatten }}, + matrix: parallel[:matrix].map { |var| var.transform_values { |v| Array(v).flatten } }, total: build_options.size } @@ -1766,6 +1766,7 @@ module Gitlab script: ["make changelog | tee release_changelog.txt"], release: { tag_name: "$CI_COMMIT_TAG", + tag_message: "Annotated tag message", name: "Release $CI_TAG_NAME", description: "./release_changelog.txt", ref: 'b3235930aa443112e639f941c69c578912189bdd', @@ -1956,7 +1957,7 @@ module Gitlab subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } context 'no dependencies' do - let(:dependencies) { } + let(:dependencies) {} it { is_expected.to be_valid } end @@ -2012,8 +2013,8 @@ module Gitlab end describe "Job Needs" do - let(:needs) { } - let(:dependencies) { } + let(:needs) {} + let(:dependencies) {} let(:config) do { @@ -2893,7 +2894,7 @@ module Gitlab end end - describe 'Rules' do + describe 'Job rules' do context 'changes' do let(:config) do <<~YAML @@ -2938,6 +2939,49 @@ module Gitlab end end + describe 'Workflow rules' do + context 'changes' do + let(:config) do + <<~YAML + workflow: + rules: + - changes: [README.md] + + rspec: + script: exit 0 + YAML + end + + it 'returns pipeline with correct rules' do + expect(processor.builds.size).to eq(1) + expect(processor.workflow_rules).to eq( + [{ changes: { paths: ["README.md"] } }] + ) + end + + context 'with paths' do + let(:config) do + <<~YAML + workflow: + rules: + - changes: + paths: [README.md] + + rspec: + script: exit 0 + YAML + end + + it 'returns pipeline with correct rules' do + expect(processor.builds.size).to eq(1) + expect(processor.workflow_rules).to eq( + [{ changes: { paths: ["README.md"] } }] + ) + end + end + end + end + describe '#execute' do subject { Gitlab::Ci::YamlProcessor.new(content).execute } |