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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/ci')
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/build/auto_retry_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb90
-rw-r--r--spec/lib/gitlab/ci/config/entry/include/rules_spec.rb98
-rw-r--r--spec/lib/gitlab/ci/config/entry/include_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb71
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules_spec.rb72
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/config/external/rules_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/normalizer_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb88
-rw-r--r--spec/lib/gitlab/ci/lint_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/parsers/security/common_spec.rb350
-rw-r--r--spec/lib/gitlab/ci/parsers/security/sast_spec.rb57
-rw-r--r--spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/reports/security/aggregated_report_spec.rb45
-rw-r--r--spec/lib/gitlab/ci/reports/security/finding_key_spec.rb41
-rw-r--r--spec/lib/gitlab/ci/reports/security/finding_signature_spec.rb59
-rw-r--r--spec/lib/gitlab/ci/reports/security/locations/sast_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/reports/security/locations/secret_detection_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/reports/security/report_spec.rb224
-rw-r--r--spec/lib/gitlab/ci/reports/security/reports_spec.rb113
-rw-r--r--spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb163
-rw-r--r--spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/npm_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb116
49 files changed, 1776 insertions, 337 deletions
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index bf1f2bae7da..27c2b005a93 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -150,6 +150,10 @@ RSpec.describe Gitlab::Ci::Ansi2html do
expect(convert_html("\r\n")).to eq('<span><br/></span>')
end
+ it 'replaces invalid UTF-8 data' do
+ expect(convert_html("UTF-8 dashes here: ───\n🐤🐤🐤🐤\xF0\x9F\x90\n")).to eq("<span>UTF-8 dashes here: ───<br/>🐤🐤🐤🐤�<br/></span>")
+ end
+
describe "incremental update" do
shared_examples 'stateable converter' do
let(:pass1_stream) { StringIO.new(pre_text) }
diff --git a/spec/lib/gitlab/ci/build/auto_retry_spec.rb b/spec/lib/gitlab/ci/build/auto_retry_spec.rb
index b107553bbce..e83e1326206 100644
--- a/spec/lib/gitlab/ci/build/auto_retry_spec.rb
+++ b/spec/lib/gitlab/ci/build/auto_retry_spec.rb
@@ -53,24 +53,8 @@ RSpec.describe Gitlab::Ci::Build::AutoRetry do
context 'with retries max config option' do
let(:build) { create(:ci_build, options: { retry: { max: 1 } }) }
- context 'when build_metadata_config is set' do
- before do
- stub_feature_flags(ci_build_metadata_config: true)
- end
-
- it 'returns the number of configured max retries' do
- expect(result).to eq 1
- end
- end
-
- context 'when build_metadata_config is not set' do
- before do
- stub_feature_flags(ci_build_metadata_config: false)
- end
-
- it 'returns the number of configured max retries' do
- expect(result).to eq 1
- end
+ it 'returns the number of configured max retries' do
+ expect(result).to eq 1
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index d294eca7f15..6c9c8fa5df5 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -106,7 +106,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] },
- variables: {},
job_variables: {},
root_variables_inheritance: true,
scheduling_type: :stage)
@@ -131,7 +130,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] },
- variables: {},
job_variables: {},
root_variables_inheritance: true,
scheduling_type: :stage)
@@ -287,7 +285,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
only: { refs: %w[branches tags] },
parallel: { matrix: [{ 'PROVIDER' => ['aws'], 'STACK' => %w(monitoring app1) },
{ 'PROVIDER' => ['gcp'], 'STACK' => %w(data) }] },
- variables: {},
job_variables: {},
root_variables_inheritance: true,
scheduling_type: :stage
diff --git a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
new file mode 100644
index 00000000000..b99048e2c18
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
+ let(:factory) do
+ Gitlab::Config::Entry::Factory.new(described_class)
+ .value(config)
+ end
+
+ subject(:entry) { factory.create! }
+
+ describe '.new' do
+ shared_examples 'an invalid config' do |error_message|
+ it { is_expected.not_to be_valid }
+
+ it 'has errors' do
+ expect(entry.errors).to include(error_message)
+ end
+ end
+
+ context 'when specifying an if: clause' do
+ let(:config) { { if: '$THIS || $THAT' } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'using a list of multiple expressions' do
+ let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
+
+ it_behaves_like 'an invalid config', /invalid expression syntax/
+ end
+
+ context 'when specifying an invalid if: clause expression' do
+ let(:config) { { if: ['$MY_VAR =='] } }
+
+ it_behaves_like 'an invalid config', /invalid expression syntax/
+ end
+
+ context 'when specifying an if: clause expression with an invalid token' do
+ let(:config) { { if: ['$MY_VAR == 123'] } }
+
+ it_behaves_like 'an invalid config', /invalid expression syntax/
+ end
+
+ context 'when using invalid regex in an if: clause' do
+ let(:config) { { if: ['$MY_VAR =~ /some ( thing/'] } }
+
+ it_behaves_like 'an invalid config', /invalid expression syntax/
+ end
+
+ context 'when using an if: clause with lookahead regex character "?"' do
+ let(:config) { { if: '$CI_COMMIT_REF =~ /^(?!master).+/' } }
+
+ context 'when allow_unsafe_ruby_regexp is disabled' do
+ it_behaves_like 'an invalid config', /invalid expression syntax/
+ end
+ end
+
+ context 'when specifying unknown policy' do
+ let(:config) { { invalid: :something } }
+
+ it_behaves_like 'an invalid config', /unknown keys: invalid/
+ end
+
+ context 'when clause is empty' do
+ let(:config) { {} }
+
+ it_behaves_like 'an invalid config', /can't be blank/
+ end
+
+ context 'when policy strategy does not match' do
+ let(:config) { 'string strategy' }
+
+ it_behaves_like 'an invalid config', /should be a hash/
+ end
+ end
+
+ describe '#value' do
+ subject(:value) { entry.value }
+
+ context 'when specifying an if: clause' do
+ let(:config) { { if: '$THIS || $THAT' } }
+
+ it 'returns the config' do
+ expect(subject).to eq(if: '$THIS || $THAT')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb
new file mode 100644
index 00000000000..c255d6e9dd6
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules do
+ let(:factory) do
+ Gitlab::Config::Entry::Factory.new(described_class)
+ .value(config)
+ end
+
+ subject(:entry) { factory.create! }
+
+ describe '.new' do
+ shared_examples 'a valid config' do
+ it { is_expected.to be_valid }
+
+ context 'when composed' do
+ before do
+ entry.compose!
+ end
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ shared_examples 'an invalid config' do |error_message|
+ it { is_expected.not_to be_valid }
+
+ it 'has errors' do
+ expect(entry.errors).to include(error_message)
+ end
+ end
+
+ context 'with an "if"' do
+ let(:config) do
+ [{ if: '$THIS == "that"' }]
+ end
+
+ it_behaves_like 'a valid config'
+ end
+
+ context 'with a "changes"' do
+ let(:config) do
+ [{ changes: ['filename.txt'] }]
+ end
+
+ context 'when composed' do
+ before do
+ entry.compose!
+ end
+
+ it_behaves_like 'an invalid config', /contains unknown keys: changes/
+ end
+ end
+
+ context 'with a list of two rules' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP' }
+ ]
+ end
+
+ it_behaves_like 'a valid config'
+ end
+
+ context 'without an array' do
+ let(:config) do
+ { if: '$SKIP' }
+ end
+
+ it_behaves_like 'an invalid config', /should be a array/
+ end
+ end
+
+ describe '#value' do
+ subject(:value) { entry.value }
+
+ context 'with an "if"' do
+ let(:config) do
+ [{ if: '$THIS == "that"' }]
+ end
+
+ it { is_expected.to eq(config) }
+ end
+
+ context 'with a list of two rules' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP' }
+ ]
+ end
+
+ it { is_expected.to eq(config) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/include_spec.rb b/spec/lib/gitlab/ci/config/entry/include_spec.rb
index 59f0b0e7a48..275cdcddeb0 100644
--- a/spec/lib/gitlab/ci/config/entry/include_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
RSpec.describe ::Gitlab::Ci::Config::Entry::Include do
subject(:include_entry) { described_class.new(config) }
@@ -86,6 +86,22 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Include do
end
end
end
+
+ context 'when using with "rules"' do
+ let(:config) { { local: 'test.yml', rules: [{ if: '$VARIABLE' }] } }
+
+ it { is_expected.to be_valid }
+
+ context 'when rules is not an array of hashes' do
+ let(:config) { { local: 'test.yml', rules: ['$VARIABLE'] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'has specific error' do
+ expect(include_entry.errors).to include('include rules should be an array of hashes')
+ end
+ end
+ end
end
context 'when value is something else' do
@@ -94,4 +110,26 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Include do
it { is_expected.not_to be_valid }
end
end
+
+ describe '#value' do
+ subject(:value) { include_entry.value }
+
+ context 'when config is a string' do
+ let(:config) { 'test.yml' }
+
+ it { is_expected.to eq('test.yml') }
+ end
+
+ context 'when config is a hash' do
+ let(:config) { { local: 'test.yml' } }
+
+ it { is_expected.to eq(local: 'test.yml') }
+ end
+
+ context 'when config has "rules"' do
+ let(:config) { { local: 'test.yml', rules: [{ if: '$VARIABLE' }] } }
+
+ it { is_expected.to eq(local: 'test.yml', rules: [{ if: '$VARIABLE' }]) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
index b1a8fbcdbe0..bdb4d25c142 100644
--- a/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
@@ -24,19 +24,4 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Inherit::Variables do
end
end
end
-
- describe '#inherit?' do
- where(:config, :inherit) do
- true | true
- false | false
- %w[A] | true
- %w[B] | false
- end
-
- with_them do
- it do
- expect(subject.inherit?('A')).to eq(inherit)
- end
- end
- end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 1d23ab0c2c7..5b47d3a3922 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -434,20 +434,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
expect(entry.errors).to include 'job dependencies the another-job should be part of needs'
end
end
-
- context 'when stage: is missing' do
- let(:config) do
- {
- script: 'echo',
- needs: ['build-job']
- }
- end
-
- it 'returns error about invalid data' do
- expect(entry).not_to be_valid
- expect(entry.errors).to include 'job config missing required keys: stage'
- end
- end
end
context 'when timeout value is not correct' do
@@ -626,7 +612,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
ignore: false,
after_script: %w[cleanup],
only: { refs: %w[branches tags] },
- variables: {},
job_variables: {},
root_variables_inheritance: true,
scheduling_type: :stage)
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index cb73044b62b..9a2a67389fc 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -99,7 +99,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do
only: { refs: %w[branches tags] },
stage: 'test',
trigger: { project: 'my/project' },
- variables: {},
job_variables: {},
root_variables_inheritance: true,
scheduling_type: :stage
@@ -110,7 +109,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do
only: { refs: %w[branches tags] },
script: ['something'],
stage: 'test',
- variables: {},
job_variables: {},
root_variables_inheritance: true,
scheduling_type: :stage
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index f98a6a869d6..b872f6644a2 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -362,76 +362,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
end
context 'with inheritance' do
- context 'of variables' do
- let(:config) do
- { variables: { A: 'job', B: 'job' } }
- end
-
- before do
- entry.compose!(deps)
- end
-
- context 'with only job variables' do
- it 'does return defined variables' do
- expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job' },
- job_variables: { 'A' => 'job', 'B' => 'job' },
- root_variables_inheritance: true
- )
- end
- end
-
- context 'when root yaml variables are used' do
- let(:variables) do
- Gitlab::Ci::Config::Entry::Variables.new(
- { A: 'root', C: 'root', D: 'root' }
- ).value
- end
-
- it 'does return job and root variables' do
- expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root', 'D' => 'root' },
- job_variables: { 'A' => 'job', 'B' => 'job' },
- root_variables_inheritance: true
- )
- end
-
- context 'when inherit of defaults is disabled' do
- let(:config) do
- {
- variables: { A: 'job', B: 'job' },
- inherit: { variables: false }
- }
- end
-
- it 'does return job and root variables' do
- expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job' },
- job_variables: { 'A' => 'job', 'B' => 'job' },
- root_variables_inheritance: false
- )
- end
- end
-
- context 'when inherit of only specific variable is enabled' do
- let(:config) do
- {
- variables: { A: 'job', B: 'job' },
- inherit: { variables: ['D'] }
- }
- end
-
- it 'does return job and root variables' do
- expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job', 'D' => 'root' },
- job_variables: { 'A' => 'job', 'B' => 'job' },
- root_variables_inheritance: ['D']
- )
- end
- end
- end
- end
-
context 'of default:tags' do
using RSpec::Parameterized::TableSyntax
@@ -493,7 +423,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
name: :rspec,
stage: 'test',
only: { refs: %w[branches tags] },
- variables: {},
job_variables: {},
root_variables_inheritance: true
)
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 31e3545e8d8..d862fbf5b78 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -132,7 +132,6 @@ 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' }],
- variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
job_variables: {},
root_variables_inheritance: true,
ignore: false,
@@ -148,7 +147,6 @@ 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' }],
- variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
job_variables: {},
root_variables_inheritance: true,
ignore: false,
@@ -166,7 +164,6 @@ 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) },
- variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
job_variables: { 'VAR' => 'job' },
root_variables_inheritance: true,
after_script: [],
@@ -214,7 +211,6 @@ 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' }],
- variables: { 'VAR' => 'root' },
job_variables: {},
root_variables_inheritance: true,
ignore: false,
@@ -228,7 +224,6 @@ 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' }],
- variables: { 'VAR' => 'job' },
job_variables: { 'VAR' => 'job' },
root_variables_inheritance: true,
ignore: false,
diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
index 7d26365e7b3..91252378541 100644
--- a/spec/lib/gitlab/ci/config/entry/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
@@ -17,6 +17,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do
describe '.new' do
subject { entry }
+ before do
+ subject.compose!
+ end
+
context 'with a list of rule rule' do
let(:config) do
[{ if: '$THIS == "that"', when: 'never' }]
@@ -24,14 +28,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do
it { is_expected.to be_a(described_class) }
it { is_expected.to be_valid }
-
- context 'when composed' do
- before do
- subject.compose!
- end
-
- it { is_expected.to be_valid }
- end
end
context 'with a list of two rules' do
@@ -42,21 +38,34 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do
]
end
- it { is_expected.to be_a(described_class) }
it { is_expected.to be_valid }
+ end
- context 'when composed' do
- before do
- subject.compose!
- end
+ context 'with a single rule object' do
+ let(:config) do
+ { if: '$SKIP', when: 'never' }
+ end
- it { is_expected.to be_valid }
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'with nested rules' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"', when: 'always' },
+ [{ if: '$SKIP', when: 'never' }]
+ ]
end
+
+ it { is_expected.to be_valid }
end
- context 'with a single rule object' do
+ context 'with rules nested more than one level' do
let(:config) do
- { if: '$SKIP', when: 'never' }
+ [
+ { if: '$THIS == "that"', when: 'always' },
+ [{ if: '$SKIP', when: 'never' }, [{ if: '$THIS == "other"', when: 'aways' }]]
+ ]
end
it { is_expected.not_to be_valid }
@@ -90,7 +99,36 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do
{ if: '$SKIP', when: 'never' }
end
- it { is_expected.to eq(config) }
+ it { is_expected.to eq([config]) }
+ end
+
+ context 'with nested rules' do
+ let(:first_rule) { { if: '$THIS == "that"', when: 'always' } }
+ let(:second_rule) { { if: '$SKIP', when: 'never' } }
+
+ let(:config) do
+ [
+ first_rule,
+ [second_rule]
+ ]
+ end
+
+ it { is_expected.to contain_exactly(first_rule, second_rule) }
+ end
+
+ context 'with rules nested more than one level' do
+ let(:first_rule) { { if: '$THIS == "that"', when: 'always' } }
+ let(:second_rule) { { if: '$SKIP', when: 'never' } }
+ let(:third_rule) { { if: '$THIS == "other"', when: 'aways' } }
+
+ let(:config) do
+ [
+ first_rule,
+ [second_rule, [third_rule]]
+ ]
+ end
+
+ it { is_expected.to contain_exactly(first_rule, second_rule, third_rule) }
end
end
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 88097f3f56a..a471997e43a 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
- let(:context_params) { { project: project, sha: '123456', user: user, variables: project.predefined_variables.to_runner_variables } }
+ let(:context_params) { { project: project, sha: '123456', user: user, variables: project.predefined_variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:file_content) do
@@ -347,15 +347,51 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
expect(subject.map(&:location)).to contain_exactly('myfolder/file1.yml', 'myfolder/file2.yml')
end
+ end
+
+ context "when 'include' has rules" do
+ let(:values) do
+ { include: [{ remote: remote_url },
+ { local: local_file, rules: [{ if: "$CI_PROJECT_ID == '#{project_id}'" }] }],
+ image: 'ruby:2.7' }
+ end
- context 'when the FF ci_wildcard_file_paths is disabled' do
- before do
- stub_feature_flags(ci_wildcard_file_paths: false)
+ context 'when the rules matches' do
+ let(:project_id) { project.id }
+
+ it 'includes the file' do
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Remote),
+ an_instance_of(Gitlab::Ci::Config::External::File::Local))
end
- it 'cannot find any file returns an error message' do
- expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Local))
- expect(subject[0].errors).to eq(['Local file `myfolder/*.yml` does not exist!'])
+ context 'when the FF ci_include_rules is disabled' do
+ before do
+ stub_feature_flags(ci_include_rules: false)
+ end
+
+ it 'includes the file' do
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Remote),
+ an_instance_of(Gitlab::Ci::Config::External::File::Local))
+ end
+ end
+ end
+
+ context 'when the rules does not match' do
+ let(:project_id) { non_existing_record_id }
+
+ it 'does not include the file' do
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Remote))
+ end
+
+ context 'when the FF ci_include_rules is disabled' do
+ before do
+ stub_feature_flags(ci_include_rules: false)
+ end
+
+ it 'includes the file' do
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Remote),
+ an_instance_of(Gitlab::Ci::Config::External::File::Local))
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb
new file mode 100644
index 00000000000..89ea13d710d
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::Rules do
+ let(:rule_hashes) {}
+
+ subject(:rules) { described_class.new(rule_hashes) }
+
+ describe '#evaluate' do
+ let(:context) { double(variables: {}) }
+
+ subject(:result) { rules.evaluate(context).pass? }
+
+ context 'when there is no rule' do
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when there is a rule' do
+ let(:rule_hashes) { [{ if: '$MY_VAR == "hello"' }] }
+
+ context 'when the rule matches' do
+ let(:context) { double(variables: { MY_VAR: 'hello' }) }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when the rule does not match' do
+ let(:context) { double(variables: { MY_VAR: 'invalid' }) }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb
index e5f0341c5fe..a29471706cc 100644
--- a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb
@@ -50,10 +50,6 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
name: 'test: [aws, app1]',
instance: 1,
parallel: { total: 4 },
- variables: {
- 'PROVIDER' => 'aws',
- 'STACK' => 'app1'
- },
job_variables: {
'PROVIDER' => 'aws',
'STACK' => 'app1'
@@ -63,10 +59,6 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
name: 'test: [aws, app2]',
instance: 2,
parallel: { total: 4 },
- variables: {
- 'PROVIDER' => 'aws',
- 'STACK' => 'app2'
- },
job_variables: {
'PROVIDER' => 'aws',
'STACK' => 'app2'
@@ -76,10 +68,6 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
name: 'test: [ovh, app]',
instance: 3,
parallel: { total: 4 },
- variables: {
- 'PROVIDER' => 'ovh',
- 'STACK' => 'app'
- },
job_variables: {
'PROVIDER' => 'ovh',
'STACK' => 'app'
@@ -89,10 +77,6 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
name: 'test: [gcp, app]',
instance: 4,
parallel: { total: 4 },
- variables: {
- 'PROVIDER' => 'gcp',
- 'STACK' => 'app'
- },
job_variables: {
'PROVIDER' => 'gcp',
'STACK' => 'app'
diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb
index 4c19657413c..354392eb42e 100644
--- a/spec/lib/gitlab/ci/config/normalizer_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb
@@ -4,7 +4,7 @@ require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Config::Normalizer do
let(:job_name) { :rspec }
- let(:job_config) { { script: 'rspec', parallel: parallel_config, name: 'rspec', variables: variables_config } }
+ let(:job_config) { { script: 'rspec', parallel: parallel_config, name: 'rspec', job_variables: variables_config } }
let(:config) { { job_name => job_config } }
describe '.normalize_jobs' do
@@ -202,21 +202,21 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
it 'sets job variables', :aggregate_failures do
expect(subject.values[0]).to match(
- a_hash_including(variables: { VAR_1: 'A', VAR_2: 'B', USER_VARIABLE: 'user value' })
+ a_hash_including(job_variables: { VAR_1: 'A', VAR_2: 'B', USER_VARIABLE: 'user value' })
)
expect(subject.values[1]).to match(
- a_hash_including(variables: { VAR_1: 'A', VAR_2: 'C', USER_VARIABLE: 'user value' })
+ a_hash_including(job_variables: { VAR_1: 'A', VAR_2: 'C', USER_VARIABLE: 'user value' })
)
end
it 'parallelizes jobs with original config' do
configs = subject.values.map do |config|
- config.except(:name, :instance, :variables)
+ config.except(:name, :instance, :job_variables)
end
original_config = config[job_name]
- .except(:name, :variables)
+ .except(:name, :job_variables)
.deep_merge(parallel: { total: 2 })
expect(configs).to all(match(a_hash_including(original_config)))
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 45ce4cac6c4..3ec4519748f 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -286,7 +286,9 @@ RSpec.describe Gitlab::Ci::Config do
end
context "when using 'include' directive" do
- let(:project) { create(:project, :repository) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :repository, group: group) }
+ let(:main_project) { create(:project, :repository, :public, group: group) }
let(:remote_location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:local_location) { 'spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml' }
@@ -317,7 +319,9 @@ RSpec.describe Gitlab::Ci::Config do
include:
- #{local_location}
- #{remote_location}
-
+ - project: '$MAIN_PROJECT'
+ ref: '$REF'
+ file: '$FILENAME'
image: ruby:2.7
HEREDOC
end
@@ -331,6 +335,26 @@ RSpec.describe Gitlab::Ci::Config do
allow(project.repository)
.to receive(:blob_data_at).and_return(local_file_content)
+
+ main_project.repository.create_file(
+ main_project.creator,
+ '.gitlab-ci.yml',
+ local_file_content,
+ message: 'Add README.md',
+ branch_name: 'master'
+ )
+
+ main_project.repository.create_file(
+ main_project.creator,
+ '.another-ci-file.yml',
+ local_file_content,
+ message: 'Add README.md',
+ branch_name: 'master'
+ )
+
+ create(:ci_variable, project: project, key: "REF", value: "HEAD")
+ create(:ci_group_variable, group: group, key: "FILENAME", value: ".gitlab-ci.yml")
+ create(:ci_instance_variable, key: 'MAIN_PROJECT', value: main_project.full_path)
end
context "when gitlab_ci_yml has valid 'include' defined" do
@@ -344,6 +368,38 @@ RSpec.describe Gitlab::Ci::Config do
expect(config.to_hash).to eq(composed_hash)
end
+
+ context 'handling variables' do
+ it 'contains all project variables' do
+ ref = config.context.variables.find { |v| v[:key] == 'REF' }
+
+ expect(ref[:value]).to eq("HEAD")
+ end
+
+ it 'contains all group variables' do
+ filename = config.context.variables.find { |v| v[:key] == 'FILENAME' }
+
+ expect(filename[:value]).to eq(".gitlab-ci.yml")
+ end
+
+ it 'contains all instance variables' do
+ project = config.context.variables.find { |v| v[:key] == 'MAIN_PROJECT' }
+
+ expect(project[:value]).to eq(main_project.full_path)
+ end
+
+ context 'overriding a group variable at project level' do
+ before do
+ create(:ci_variable, project: project, key: "FILENAME", value: ".another-ci-file.yml")
+ end
+
+ it 'successfully overrides' do
+ filename = config.context.variables.to_hash[:FILENAME]
+
+ expect(filename).to eq('.another-ci-file.yml')
+ end
+ end
+ end
end
context "when gitlab_ci.yml has invalid 'include' defined" do
@@ -667,5 +723,33 @@ RSpec.describe Gitlab::Ci::Config do
expect(config.to_hash).to eq(composed_hash)
end
end
+
+ context "when an 'include' has rules" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - local: #{local_location}
+ rules:
+ - if: $CI_PROJECT_ID == "#{project_id}"
+ image: ruby:2.7
+ HEREDOC
+ end
+
+ context 'when the rules condition is satisfied' do
+ let(:project_id) { project.id }
+
+ it 'includes the file' do
+ expect(config.to_hash).to include(local_location_hash)
+ end
+ end
+
+ context 'when the rules condition is satisfied' do
+ let(:project_id) { non_existing_record_id }
+
+ it 'does not include the file' do
+ expect(config.to_hash).not_to include(local_location_hash)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb
index 77f6608eb85..1e433d7854a 100644
--- a/spec/lib/gitlab/ci/lint_spec.rb
+++ b/spec/lib/gitlab/ci/lint_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Lint do
- let(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:lint) { described_class.new(project: project, current_user: user) }
@@ -89,6 +89,15 @@ RSpec.describe Gitlab::Ci::Lint do
)
end
+ after do
+ project.repository.delete_file(
+ project.creator,
+ 'another-gitlab-ci.yml',
+ message: 'Remove another-gitlab-ci.yml',
+ branch_name: 'master'
+ )
+ end
+
it 'sets merged_config' do
root_config = YAML.safe_load(content, [Symbol])
included_config = YAML.safe_load(included_content, [Symbol])
diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb
new file mode 100644
index 00000000000..c6387bf615b
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb
@@ -0,0 +1,350 @@
+# frozen_string_literal: true
+
+# TODO remove duplication from spec/lib/gitlab/ci/parsers/security/common_spec.rb and spec/lib/gitlab/ci/parsers/security/common_spec.rb
+# See https://gitlab.com/gitlab-org/gitlab/-/issues/336589
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Security::Common do
+ describe '#parse!' do
+ where(vulnerability_finding_signatures_enabled: [true, false])
+ with_them do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ let(:artifact) { build(:ci_job_artifact, :common_security_report) }
+ let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) }
+ # The path 'yarn.lock' was initially used by DependencyScanning, it is okay for SAST locations to use it, but this could be made better
+ let(:location) { ::Gitlab::Ci::Reports::Security::Locations::Sast.new(file_path: 'yarn.lock', start_line: 1, end_line: 1) }
+ let(:tracking_data) { nil }
+
+ before do
+ allow_next_instance_of(described_class) do |parser|
+ allow(parser).to receive(:create_location).and_return(location)
+ allow(parser).to receive(:tracking_data).and_return(tracking_data)
+ end
+
+ artifact.each_blob { |blob| described_class.parse!(blob, report, vulnerability_finding_signatures_enabled) }
+ end
+
+ describe 'schema validation' do
+ let(:validator_class) { Gitlab::Ci::Parsers::Security::Validators::SchemaValidator }
+ let(:parser) { described_class.new('{}', report, vulnerability_finding_signatures_enabled, validate: validate) }
+
+ subject(:parse_report) { parser.parse! }
+
+ before do
+ allow(validator_class).to receive(:new).and_call_original
+ end
+
+ context 'when the validate flag is set as `false`' do
+ let(:validate) { false }
+
+ it 'does not run the validation logic' do
+ parse_report
+
+ expect(validator_class).not_to have_received(:new)
+ end
+ end
+
+ context 'when the validate flag is set as `true`' do
+ let(:validate) { true }
+ let(:valid?) { false }
+
+ before do
+ allow_next_instance_of(validator_class) do |instance|
+ allow(instance).to receive(:valid?).and_return(valid?)
+ allow(instance).to receive(:errors).and_return(['foo'])
+ end
+
+ allow(parser).to receive_messages(create_scanner: true, create_scan: true)
+ end
+
+ it 'instantiates the validator with correct params' do
+ parse_report
+
+ expect(validator_class).to have_received(:new).with(report.type, {})
+ end
+
+ context 'when the report data is not valid according to the schema' do
+ it 'adds errors to the report' do
+ expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }])
+ end
+
+ it 'does not try to create report entities' do
+ parse_report
+
+ expect(parser).not_to have_received(:create_scanner)
+ expect(parser).not_to have_received(:create_scan)
+ end
+ end
+
+ context 'when the report data is valid according to the schema' do
+ let(:valid?) { true }
+
+ it 'does not add errors to the report' do
+ expect { parse_report }.not_to change { report.errors }.from([])
+ end
+
+ it 'keeps the execution flow as normal' do
+ parse_report
+
+ expect(parser).to have_received(:create_scanner)
+ expect(parser).to have_received(:create_scan)
+ end
+ end
+ end
+ end
+
+ describe 'parsing finding.name' do
+ let(:artifact) { build(:ci_job_artifact, :common_security_report_with_blank_names) }
+
+ context 'when message is provided' do
+ it 'sets message from the report as a finding name' do
+ finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
+ expected_name = Gitlab::Json.parse(finding.raw_metadata)['message']
+
+ expect(finding.name).to eq(expected_name)
+ end
+ end
+
+ context 'when message is not provided' do
+ context 'and name is provided' do
+ it 'sets name from the report as a name' do
+ finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
+ expected_name = Gitlab::Json.parse(finding.raw_metadata)['name']
+
+ expect(finding.name).to eq(expected_name)
+ end
+ end
+
+ context 'and name is not provided' do
+ context 'when CVE identifier exists' do
+ it 'combines identifier with location to create name' do
+ finding = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
+ expect(finding.name).to eq("CVE-2017-11429 in yarn.lock")
+ end
+ end
+
+ context 'when CWE identifier exists' do
+ it 'combines identifier with location to create name' do
+ finding = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
+ expect(finding.name).to eq("CWE-2017-11429 in yarn.lock")
+ end
+ end
+
+ context 'when neither CVE nor CWE identifier exist' do
+ it 'combines identifier with location to create name' do
+ finding = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
+ expect(finding.name).to eq("other-2017-11429 in yarn.lock")
+ end
+ end
+ end
+ end
+ end
+
+ describe 'parsing finding.details' do
+ context 'when details are provided' do
+ it 'sets details from the report' do
+ finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
+ expected_details = Gitlab::Json.parse(finding.raw_metadata)['details']
+
+ expect(finding.details).to eq(expected_details)
+ end
+ end
+
+ context 'when details are not provided' do
+ it 'sets empty hash' do
+ finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
+ expect(finding.details).to eq({})
+ end
+ end
+ end
+
+ describe 'top-level scanner' do
+ it 'is the primary scanner' do
+ expect(report.primary_scanner.external_id).to eq('gemnasium')
+ expect(report.primary_scanner.name).to eq('Gemnasium')
+ expect(report.primary_scanner.vendor).to eq('GitLab')
+ expect(report.primary_scanner.version).to eq('2.18.0')
+ end
+
+ it 'returns nil report has no scanner' do
+ empty_report = Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago)
+ described_class.parse!({}.to_json, empty_report)
+
+ expect(empty_report.primary_scanner).to be_nil
+ end
+ end
+
+ describe 'parsing scanners' do
+ subject(:scanner) { report.findings.first.scanner }
+
+ context 'when vendor is not missing in scanner' do
+ it 'returns scanner with parsed vendor value' do
+ expect(scanner.vendor).to eq('GitLab')
+ end
+ end
+ end
+
+ describe 'parsing scan' do
+ it 'returns scan object for each finding' do
+ scans = report.findings.map(&:scan)
+
+ expect(scans.map(&:status).all?('success')).to be(true)
+ expect(scans.map(&:start_time).all?('placeholder-value')).to be(true)
+ expect(scans.map(&:end_time).all?('placeholder-value')).to be(true)
+ expect(scans.size).to eq(3)
+ expect(scans.first).to be_a(::Gitlab::Ci::Reports::Security::Scan)
+ end
+
+ it 'returns nil when scan is not a hash' do
+ empty_report = Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago)
+ described_class.parse!({}.to_json, empty_report)
+
+ expect(empty_report.scan).to be(nil)
+ end
+ end
+
+ describe 'parsing schema version' do
+ it 'parses the version' do
+ expect(report.version).to eq('14.0.2')
+ end
+
+ it 'returns nil when there is no version' do
+ empty_report = Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago)
+ described_class.parse!({}.to_json, empty_report)
+
+ expect(empty_report.version).to be_nil
+ end
+ end
+
+ describe 'parsing analyzer' do
+ it 'associates analyzer with report' do
+ expect(report.analyzer.id).to eq('common-analyzer')
+ expect(report.analyzer.name).to eq('Common Analyzer')
+ expect(report.analyzer.version).to eq('2.0.1')
+ expect(report.analyzer.vendor).to eq('Common')
+ end
+
+ it 'returns nil when analyzer data is not available' do
+ empty_report = Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago)
+ described_class.parse!({}.to_json, empty_report)
+
+ expect(empty_report.analyzer).to be_nil
+ end
+ end
+
+ describe 'parsing links' do
+ it 'returns links object for each finding', :aggregate_failures do
+ links = report.findings.flat_map(&:links)
+
+ expect(links.map(&:url)).to match_array(['https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020', 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030'])
+ expect(links.map(&:name)).to match_array([nil, 'CVE-1030'])
+ expect(links.size).to eq(2)
+ expect(links.first).to be_a(::Gitlab::Ci::Reports::Security::Link)
+ end
+ end
+
+ describe 'setting the uuid' do
+ let(:finding_uuids) { report.findings.map(&:uuid) }
+ let(:uuid_1) do
+ Security::VulnerabilityUUID.generate(
+ report_type: "sast",
+ primary_identifier_fingerprint: report.findings[0].identifiers.first.fingerprint,
+ location_fingerprint: location.fingerprint,
+ project_id: pipeline.project_id
+ )
+ end
+
+ let(:uuid_2) do
+ Security::VulnerabilityUUID.generate(
+ report_type: "sast",
+ primary_identifier_fingerprint: report.findings[1].identifiers.first.fingerprint,
+ location_fingerprint: location.fingerprint,
+ project_id: pipeline.project_id
+ )
+ end
+
+ let(:expected_uuids) { [uuid_1, uuid_2, nil] }
+
+ it 'sets the UUIDv5 for findings', :aggregate_failures do
+ allow_next_instance_of(Gitlab::Ci::Reports::Security::Report) do |report|
+ allow(report).to receive(:type).and_return('sast')
+
+ expect(finding_uuids).to match_array(expected_uuids)
+ end
+ end
+ end
+
+ describe 'parsing tracking' do
+ let(:tracking_data) do
+ {
+ 'type' => 'source',
+ 'items' => [
+ 'signatures' => [
+ { 'algorithm' => 'hash', 'value' => 'hash_value' },
+ { 'algorithm' => 'location', 'value' => 'location_value' },
+ { 'algorithm' => 'scope_offset', 'value' => 'scope_offset_value' }
+ ]
+ ]
+ }
+ end
+
+ context 'with valid tracking information' do
+ it 'creates signatures for each algorithm' do
+ finding = report.findings.first
+ expect(finding.signatures.size).to eq(3)
+ expect(finding.signatures.map(&:algorithm_type).to_set).to eq(Set['hash', 'location', 'scope_offset'])
+ end
+ end
+
+ context 'with invalid tracking information' do
+ let(:tracking_data) do
+ {
+ 'type' => 'source',
+ 'items' => [
+ 'signatures' => [
+ { 'algorithm' => 'hash', 'value' => 'hash_value' },
+ { 'algorithm' => 'location', 'value' => 'location_value' },
+ { 'algorithm' => 'INVALID', 'value' => 'scope_offset_value' }
+ ]
+ ]
+ }
+ end
+
+ it 'ignores invalid algorithm types' do
+ finding = report.findings.first
+ expect(finding.signatures.size).to eq(2)
+ expect(finding.signatures.map(&:algorithm_type).to_set).to eq(Set['hash', 'location'])
+ end
+ end
+
+ context 'with valid tracking information' do
+ it 'creates signatures for each signature algorithm' do
+ finding = report.findings.first
+ expect(finding.signatures.size).to eq(3)
+ expect(finding.signatures.map(&:algorithm_type)).to eq(%w[hash location scope_offset])
+
+ signatures = finding.signatures.index_by(&:algorithm_type)
+ expected_values = tracking_data['items'][0]['signatures'].index_by { |x| x['algorithm'] }
+ expect(signatures['hash'].signature_value).to eq(expected_values['hash']['value'])
+ expect(signatures['location'].signature_value).to eq(expected_values['location']['value'])
+ expect(signatures['scope_offset'].signature_value).to eq(expected_values['scope_offset']['value'])
+ end
+
+ it 'sets the uuid according to the higest priority signature' do
+ finding = report.findings.first
+ highest_signature = finding.signatures.max_by(&:priority)
+
+ identifiers = if vulnerability_finding_signatures_enabled
+ "#{finding.report_type}-#{finding.primary_identifier.fingerprint}-#{highest_signature.signature_hex}-#{report.project_id}"
+ else
+ "#{finding.report_type}-#{finding.primary_identifier.fingerprint}-#{finding.location.fingerprint}-#{report.project_id}"
+ end
+
+ expect(finding.uuid).to eq(Gitlab::UUID.v5(identifiers))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/security/sast_spec.rb b/spec/lib/gitlab/ci/parsers/security/sast_spec.rb
new file mode 100644
index 00000000000..4bc48f6611a
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/security/sast_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#parse!' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ let(:created_at) { 2.weeks.ago }
+
+ context "when parsing valid reports" do
+ where(:report_format, :report_version, :scanner_length, :finding_length, :identifier_length, :file_path, :line) do
+ :sast | '14.0.0' | 1 | 5 | 6 | 'groovy/src/main/java/com/gitlab/security_products/tests/App.groovy' | 47
+ :sast_deprecated | '1.2' | 3 | 33 | 17 | 'python/hardcoded/hardcoded-tmp.py' | 1
+ end
+
+ with_them do
+ let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, created_at) }
+ let(:artifact) { create(:ci_job_artifact, report_format) }
+
+ before do
+ artifact.each_blob { |blob| described_class.parse!(blob, report) }
+ end
+
+ it "parses all identifiers and findings" do
+ expect(report.findings.length).to eq(finding_length)
+ expect(report.identifiers.length).to eq(identifier_length)
+ expect(report.scanners.length).to eq(scanner_length)
+ end
+
+ it 'generates expected location' do
+ location = report.findings.first.location
+
+ expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::Sast)
+ expect(location).to have_attributes(
+ file_path: file_path,
+ end_line: line,
+ start_line: line
+ )
+ end
+
+ it "generates expected metadata_version" do
+ expect(report.findings.first.metadata_version).to eq(report_version)
+ end
+ end
+ end
+
+ context "when parsing an empty report" do
+ let(:report) { Gitlab::Ci::Reports::Security::Report.new('sast', pipeline, created_at) }
+ let(:blob) { Gitlab::Json.generate({}) }
+
+ it { expect(described_class.parse!(blob, report)).to be_empty }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb b/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb
new file mode 100644
index 00000000000..1d361e16aad
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do
+ describe '#parse!' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ let(:created_at) { 2.weeks.ago }
+
+ context "when parsing valid reports" do
+ where(report_format: %i(secret_detection))
+
+ with_them do
+ let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, created_at) }
+ let(:artifact) { create(:ci_job_artifact, report_format) }
+
+ before do
+ artifact.each_blob { |blob| described_class.parse!(blob, report) }
+ end
+
+ it "parses all identifiers and findings" do
+ expect(report.findings.length).to eq(1)
+ expect(report.identifiers.length).to eq(1)
+ expect(report.scanners.length).to eq(1)
+ end
+
+ it 'generates expected location' do
+ location = report.findings.first.location
+
+ expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::SecretDetection)
+ expect(location).to have_attributes(
+ file_path: 'aws-key.py',
+ start_line: nil,
+ end_line: nil,
+ class_name: nil,
+ method_name: nil
+ )
+ end
+
+ it "generates expected metadata_version" do
+ expect(report.findings.first.metadata_version).to eq('3.0')
+ end
+ end
+ end
+
+ context "when parsing an empty report" do
+ let(:report) { Gitlab::Ci::Reports::Security::Report.new('secret_detection', pipeline, created_at) }
+ let(:blob) { Gitlab::Json.generate({}) }
+
+ it { expect(described_class.parse!(blob, report)).to be_empty }
+ 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
new file mode 100644
index 00000000000..f434ffd12bf
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:report_type, :expected_errors, :valid_data) do
+ :sast | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] }
+ :secret_detection | ['root is missing required keys: vulnerabilities'] | { 'version' => '10.0.0', 'vulnerabilities' => [] }
+ end
+
+ with_them do
+ let(:validator) { described_class.new(report_type, report_data) }
+
+ describe '#valid?' do
+ subject { validator.valid? }
+
+ context 'when given data is invalid according to the schema' do
+ let(:report_data) { {} }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when given data is valid according to the schema' do
+ let(:report_data) { valid_data }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#errors' do
+ let(:report_data) { { 'version' => '10.0.0' } }
+
+ subject { validator.errors }
+
+ it { is_expected.to eq(expected_errors) }
+ 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 2e73043e309..c22a0e23794 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -295,31 +295,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
end
end
- describe '#dangling_build?' do
- let(:project) { create(:project, :repository) }
- let(:command) { described_class.new(project: project, source: source) }
-
- subject { command.dangling_build? }
-
- context 'when source is :webide' do
- let(:source) { :webide }
-
- it { is_expected.to eq(true) }
- end
-
- context 'when source is :ondemand_dast_scan' do
- let(:source) { :ondemand_dast_scan }
-
- it { is_expected.to eq(true) }
- end
-
- context 'when source something else' do
- let(:source) { :web }
-
- it { is_expected.to eq(false) }
- end
- end
-
describe '#creates_child_pipeline?' do
let(:command) { described_class.new(bridge: bridge) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
index 499dc3554a3..1aa104310af 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
@@ -85,7 +85,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
end
it 'logs the error' do
- expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
instance_of(Gitlab::Ci::Limit::LimitExceededError),
project_id: project.id, plan: namespace.actual_plan_name
)
diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
index cc4aaffb0a4..83d47ae6819 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Sequence do
let_it_be(:user) { create(:user) }
let(:pipeline) { build_stubbed(:ci_pipeline) }
- let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new }
+ let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project) }
let(:first_step) { spy('first step') }
let(:second_step) { spy('second step') }
let(:sequence) { [first_step, second_step] }
@@ -71,5 +71,20 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Sequence do
expect(histogram).to have_received(:observe)
.with({ source: 'push' }, 0)
end
+
+ it 'records active jobs by pipeline plan in a histogram' do
+ allow(command.metrics)
+ .to receive(:active_jobs_histogram)
+ .and_return(histogram)
+
+ pipeline = create(:ci_pipeline, project: project, status: :running)
+ create(:ci_build, :finished, project: project, pipeline: pipeline)
+ create(:ci_build, :failed, project: project, pipeline: pipeline)
+ create(:ci_build, :running, project: project, pipeline: pipeline)
+ subject.build!
+
+ expect(histogram).to have_received(:observe)
+ .with(hash_including(plan: project.actual_plan_name), 3)
+ end
end
end
diff --git a/spec/lib/gitlab/ci/reports/security/aggregated_report_spec.rb b/spec/lib/gitlab/ci/reports/security/aggregated_report_spec.rb
new file mode 100644
index 00000000000..c56177a6453
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/security/aggregated_report_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Security::AggregatedReport do
+ subject { described_class.new(reports, findings) }
+
+ let(:reports) { build_list(:ci_reports_security_report, 1) }
+ let(:findings) { build_list(:ci_reports_security_finding, 1) }
+
+ describe '#created_at' do
+ context 'no reports' do
+ let(:reports) { [] }
+
+ it 'has no created date' do
+ expect(subject.created_at).to be_nil
+ end
+ end
+
+ context 'report with no created date' do
+ let(:reports) { build_list(:ci_reports_security_report, 1, created_at: nil) }
+
+ it 'has no created date' do
+ expect(subject.created_at).to be_nil
+ end
+ end
+
+ context 'has reports' do
+ let(:a_long_time_ago) { 2.months.ago }
+ let(:a_while_ago) { 2.weeks.ago }
+ let(:yesterday) { 1.day.ago }
+
+ let(:reports) do
+ [build(:ci_reports_security_report, created_at: a_while_ago),
+ build(:ci_reports_security_report, created_at: a_long_time_ago),
+ build(:ci_reports_security_report, created_at: nil),
+ build(:ci_reports_security_report, created_at: yesterday)]
+ end
+
+ it 'has oldest created date' do
+ expect(subject.created_at).to eq(a_long_time_ago)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb b/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb
new file mode 100644
index 00000000000..784c1183320
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Security::FindingKey do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#==' do
+ where(:location_fp_1, :location_fp_2, :identifier_fp_1, :identifier_fp_2, :equals?) do
+ nil | 'different location fp' | 'identifier fp' | 'different identifier fp' | false
+ 'location fp' | nil | 'identifier fp' | 'different identifier fp' | false
+ 'location fp' | 'different location fp' | nil | 'different identifier fp' | false
+ 'location fp' | 'different location fp' | 'identifier fp' | nil | false
+ nil | nil | 'identifier fp' | 'identifier fp' | false
+ 'location fp' | 'location fp' | nil | nil | false
+ nil | nil | nil | nil | false
+ 'location fp' | 'different location fp' | 'identifier fp' | 'different identifier fp' | false
+ 'location fp' | 'different location fp' | 'identifier fp' | 'identifier fp' | false
+ 'location fp' | 'location fp' | 'identifier fp' | 'different identifier fp' | false
+ 'location fp' | 'location fp' | 'identifier fp' | 'identifier fp' | true
+ end
+
+ with_them do
+ let(:finding_key_1) do
+ build(:ci_reports_security_finding_key,
+ location_fingerprint: location_fp_1,
+ identifier_fingerprint: identifier_fp_1)
+ end
+
+ let(:finding_key_2) do
+ build(:ci_reports_security_finding_key,
+ location_fingerprint: location_fp_2,
+ identifier_fingerprint: identifier_fp_2)
+ end
+
+ subject { finding_key_1 == finding_key_2 }
+
+ it { is_expected.to be(equals?) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/security/finding_signature_spec.rb b/spec/lib/gitlab/ci/reports/security/finding_signature_spec.rb
new file mode 100644
index 00000000000..23e6b40a039
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/security/finding_signature_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Security::FindingSignature do
+ subject { described_class.new(params.with_indifferent_access) }
+
+ let(:params) do
+ {
+ algorithm_type: 'hash',
+ signature_value: 'SIGNATURE'
+ }
+ end
+
+ describe '#initialize' do
+ context 'when a supported algorithm type is given' do
+ it 'allows itself to be created' do
+ expect(subject.algorithm_type).to eq(params[:algorithm_type])
+ expect(subject.signature_value).to eq(params[:signature_value])
+ end
+
+ describe '#valid?' do
+ it 'returns true' do
+ expect(subject.valid?).to eq(true)
+ end
+ end
+ end
+ end
+
+ describe '#valid?' do
+ context 'when supported algorithm_type is given' do
+ it 'is valid' do
+ expect(subject.valid?).to eq(true)
+ end
+ end
+
+ context 'when an unsupported algorithm_type is given' do
+ let(:params) do
+ {
+ algorithm_type: 'INVALID',
+ signature_value: 'SIGNATURE'
+ }
+ end
+
+ it 'is not valid' do
+ expect(subject.valid?).to eq(false)
+ end
+ end
+ end
+
+ describe '#to_hash' do
+ it 'returns a hash representation of the signature' do
+ expect(subject.to_hash).to eq(
+ algorithm_type: params[:algorithm_type],
+ signature_sha: Digest::SHA1.digest(params[:signature_value])
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/security/locations/sast_spec.rb b/spec/lib/gitlab/ci/reports/security/locations/sast_spec.rb
new file mode 100644
index 00000000000..effa7a60400
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/security/locations/sast_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Security::Locations::Sast do
+ let(:params) do
+ {
+ file_path: 'src/main/App.java',
+ start_line: 29,
+ end_line: 31,
+ class_name: 'com.gitlab.security_products.tests.App',
+ method_name: 'insecureCypher'
+ }
+ end
+
+ let(:mandatory_params) { %i[file_path start_line] }
+ let(:expected_fingerprint) { Digest::SHA1.hexdigest('src/main/App.java:29:31') }
+ let(:expected_fingerprint_path) { 'App.java' }
+
+ it_behaves_like 'vulnerability location'
+end
diff --git a/spec/lib/gitlab/ci/reports/security/locations/secret_detection_spec.rb b/spec/lib/gitlab/ci/reports/security/locations/secret_detection_spec.rb
new file mode 100644
index 00000000000..3b84a548713
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/security/locations/secret_detection_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Security::Locations::SecretDetection do
+ let(:params) do
+ {
+ file_path: 'src/main/App.java',
+ start_line: 29,
+ end_line: 31,
+ class_name: 'com.gitlab.security_products.tests.App',
+ method_name: 'insecureCypher'
+ }
+ end
+
+ let(:mandatory_params) { %i[file_path start_line] }
+ let(:expected_fingerprint) { Digest::SHA1.hexdigest('src/main/App.java:29:31') }
+ let(:expected_fingerprint_path) { 'App.java' }
+
+ it_behaves_like 'vulnerability location'
+end
diff --git a/spec/lib/gitlab/ci/reports/security/report_spec.rb b/spec/lib/gitlab/ci/reports/security/report_spec.rb
new file mode 100644
index 00000000000..5a85c3f19fc
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/security/report_spec.rb
@@ -0,0 +1,224 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Security::Report do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ let(:created_at) { 2.weeks.ago }
+
+ subject(:report) { described_class.new('sast', pipeline, created_at) }
+
+ it { expect(report.type).to eq('sast') }
+ it { is_expected.to delegate_method(:project_id).to(:pipeline) }
+
+ describe '#add_scanner' do
+ let(:scanner) { create(:ci_reports_security_scanner, external_id: 'find_sec_bugs') }
+
+ subject { report.add_scanner(scanner) }
+
+ it 'stores given scanner params in the map' do
+ subject
+
+ expect(report.scanners).to eq({ 'find_sec_bugs' => scanner })
+ end
+
+ it 'returns the added scanner' do
+ expect(subject).to eq(scanner)
+ end
+ end
+
+ describe '#add_identifier' do
+ let(:identifier) { create(:ci_reports_security_identifier) }
+
+ subject { report.add_identifier(identifier) }
+
+ it 'stores given identifier params in the map' do
+ subject
+
+ expect(report.identifiers).to eq({ identifier.fingerprint => identifier })
+ end
+
+ it 'returns the added identifier' do
+ expect(subject).to eq(identifier)
+ end
+ end
+
+ describe '#add_finding' do
+ let(:finding) { create(:ci_reports_security_finding) }
+
+ it 'enriches given finding and stores it in the collection' do
+ report.add_finding(finding)
+
+ expect(report.findings).to eq([finding])
+ end
+ end
+
+ describe '#clone_as_blank' do
+ let(:report) do
+ create(
+ :ci_reports_security_report,
+ findings: [create(:ci_reports_security_finding)],
+ scanners: [create(:ci_reports_security_scanner)],
+ identifiers: [create(:ci_reports_security_identifier)]
+ )
+ end
+
+ it 'creates a blank report with copied type and pipeline' do
+ clone = report.clone_as_blank
+
+ expect(clone.type).to eq(report.type)
+ expect(clone.pipeline).to eq(report.pipeline)
+ expect(clone.created_at).to eq(report.created_at)
+ expect(clone.findings).to eq([])
+ expect(clone.scanners).to eq({})
+ expect(clone.identifiers).to eq({})
+ end
+ end
+
+ describe '#replace_with!' do
+ let(:report) do
+ create(
+ :ci_reports_security_report,
+ findings: [create(:ci_reports_security_finding)],
+ scanners: [create(:ci_reports_security_scanner)],
+ identifiers: [create(:ci_reports_security_identifier)]
+ )
+ end
+
+ let(:other_report) do
+ create(
+ :ci_reports_security_report,
+ findings: [create(:ci_reports_security_finding, compare_key: 'other_finding')],
+ scanners: [create(:ci_reports_security_scanner, external_id: 'other_scanner', name: 'Other Scanner')],
+ identifiers: [create(:ci_reports_security_identifier, external_id: 'other_id', name: 'other_scanner')]
+ )
+ end
+
+ before do
+ report.replace_with!(other_report)
+ end
+
+ it 'replaces report contents with other reports contents' do
+ expect(report.findings).to eq(other_report.findings)
+ expect(report.scanners).to eq(other_report.scanners)
+ expect(report.identifiers).to eq(other_report.identifiers)
+ end
+ end
+
+ describe '#merge!' do
+ let(:merged_report) { double('Report') }
+
+ before do
+ merge_reports_service = double('MergeReportsService')
+
+ allow(::Security::MergeReportsService).to receive(:new).and_return(merge_reports_service)
+ allow(merge_reports_service).to receive(:execute).and_return(merged_report)
+ allow(report).to receive(:replace_with!)
+ end
+
+ subject { report.merge!(described_class.new('sast', pipeline, created_at)) }
+
+ it 'invokes the merge with other report and then replaces this report contents by merge result' do
+ subject
+
+ expect(report).to have_received(:replace_with!).with(merged_report)
+ end
+ end
+
+ describe '#primary_scanner' do
+ let(:scanner_1) { create(:ci_reports_security_scanner, external_id: 'external_id_1') }
+ let(:scanner_2) { create(:ci_reports_security_scanner, external_id: 'external_id_2') }
+
+ subject { report.primary_scanner }
+
+ before do
+ report.add_scanner(scanner_1)
+ report.add_scanner(scanner_2)
+ end
+
+ it { is_expected.to eq(scanner_1) }
+ end
+
+ describe '#add_error' do
+ context 'when the message is not given' do
+ it 'adds a new error to report with the generic error message' do
+ expect { report.add_error('foo') }.to change { report.errors }
+ .from([])
+ .to([{ type: 'foo', message: 'An unexpected error happened!' }])
+ end
+ end
+
+ context 'when the message is given' do
+ it 'adds a new error to report' do
+ expect { report.add_error('foo', 'bar') }.to change { report.errors }
+ .from([])
+ .to([{ type: 'foo', message: 'bar' }])
+ end
+ end
+ end
+
+ describe 'errored?' do
+ subject { report.errored? }
+
+ context 'when the report does not have any errors' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the report has errors' do
+ before do
+ report.add_error('foo', 'bar')
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#primary_scanner_order_to' do
+ let(:scanner_1) { build(:ci_reports_security_scanner) }
+ let(:scanner_2) { build(:ci_reports_security_scanner) }
+ let(:report_1) { described_class.new('sast', pipeline, created_at) }
+ let(:report_2) { described_class.new('sast', pipeline, created_at) }
+
+ subject(:compare_based_on_primary_scanners) { report_1.primary_scanner_order_to(report_2) }
+
+ context 'when the primary scanner of the receiver is nil' do
+ context 'when the primary scanner of the other is nil' do
+ it { is_expected.to be(1) }
+ end
+
+ context 'when the primary scanner of the other is not nil' do
+ before do
+ report_2.add_scanner(scanner_2)
+ end
+
+ it { is_expected.to be(1) }
+ end
+ end
+
+ context 'when the primary scanner of the receiver is not nil' do
+ before do
+ report_1.add_scanner(scanner_1)
+ end
+
+ context 'when the primary scanner of the other is nil' do
+ let(:scanner_2) { nil }
+
+ it { is_expected.to be(-1) }
+ end
+
+ context 'when the primary scanner of the other is not nil' do
+ before do
+ report_2.add_scanner(scanner_2)
+
+ allow(scanner_1).to receive(:<=>).and_return(0)
+ end
+
+ it 'compares two scanners' do
+ expect(compare_based_on_primary_scanners).to be(0)
+ expect(scanner_1).to have_received(:<=>).with(scanner_2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/security/reports_spec.rb b/spec/lib/gitlab/ci/reports/security/reports_spec.rb
new file mode 100644
index 00000000000..9b1e02f1418
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/security/reports_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Security::Reports do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+ let_it_be(:artifact) { create(:ci_job_artifact, :sast) }
+
+ let(:security_reports) { described_class.new(pipeline) }
+
+ describe '#get_report' do
+ subject { security_reports.get_report(report_type, artifact) }
+
+ context 'when report type is sast' do
+ let(:report_type) { 'sast' }
+
+ it { expect(subject.type).to eq('sast') }
+ it { expect(subject.created_at).to eq(artifact.created_at) }
+
+ it 'initializes a new report and returns it' do
+ expect(Gitlab::Ci::Reports::Security::Report).to receive(:new)
+ .with('sast', pipeline, artifact.created_at).and_call_original
+
+ is_expected.to be_a(Gitlab::Ci::Reports::Security::Report)
+ end
+
+ context 'when report type is already allocated' do
+ before do
+ subject
+ end
+
+ it 'does not initialize a new report' do
+ expect(Gitlab::Ci::Reports::Security::Report).not_to receive(:new)
+
+ is_expected.to be_a(Gitlab::Ci::Reports::Security::Report)
+ end
+ end
+ end
+ end
+
+ describe '#findings' do
+ let(:finding_1) { build(:ci_reports_security_finding, severity: 'low') }
+ let(:finding_2) { build(:ci_reports_security_finding, severity: 'high') }
+ let!(:expected_findings) { [finding_1, finding_2] }
+
+ subject { security_reports.findings }
+
+ before do
+ security_reports.get_report('sast', artifact).add_finding(finding_1)
+ security_reports.get_report('dependency_scanning', artifact).add_finding(finding_2)
+ end
+
+ it { is_expected.to match_array(expected_findings) }
+ end
+
+ describe "#violates_default_policy_against?" 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) }
+
+ subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels) }
+
+ before do
+ security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
+ end
+
+ context 'when the target_reports is `nil`' do
+ let(:target_reports) { nil }
+
+ context 'with severity levels matching the existing vulnerabilities' do
+ it { is_expected.to be(true) }
+ end
+
+ context "without any severity levels matching the existing vulnerabilities" do
+ let(:severity_levels) { %w(critical) }
+
+ it { is_expected.to be(false) }
+ end
+ end
+
+ context 'when the target_reports is not `nil`' do
+ let(:target_reports) { described_class.new(pipeline) }
+
+ context "when a report has a new unsafe vulnerability" do
+ context 'with severity levels matching the existing vulnerabilities' do
+ it { is_expected.to be(true) }
+ end
+
+ it { is_expected.to be(true) }
+
+ context 'with vulnerabilities_allowed higher than the number of new vulnerabilities' do
+ let(:vulnerabilities_allowed) { 10000 }
+
+ it { is_expected.to be(false) }
+ end
+
+ context "without any severity levels matching the existing vulnerabilities" do
+ let(:severity_levels) { %w(critical) }
+
+ it { is_expected.to be(false) }
+ end
+ end
+
+ context "when none of the reports have a new unsafe vulnerability" do
+ before do
+ target_reports.get_report('sast', artifact).add_finding(high_severity_dast)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+ end
+end
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
new file mode 100644
index 00000000000..44e66fd9028
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do
+ let(:identifier) { build(:ci_reports_security_identifier) }
+
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:location_param) { build(:ci_reports_security_locations_sast, :dynamic) }
+ let(:vulnerability_params) { vuln_params(project.id, [identifier], confidence: :low, severity: :critical) }
+ let(:base_vulnerability) { build(:ci_reports_security_finding, location: location_param, **vulnerability_params) }
+ let(:base_report) { build(:ci_reports_security_aggregated_reports, findings: [base_vulnerability]) }
+
+ let(:head_vulnerability) { build(:ci_reports_security_finding, location: location_param, uuid: base_vulnerability.uuid, **vulnerability_params) }
+ let(:head_report) { build(:ci_reports_security_aggregated_reports, findings: [head_vulnerability]) }
+
+ shared_context 'comparing reports' do
+ let(:vul_params) { vuln_params(project.id, [identifier]) }
+ let(:base_vulnerability) { build(:ci_reports_security_finding, :dynamic, **vul_params) }
+ let(:head_vulnerability) { build(:ci_reports_security_finding, :dynamic, **vul_params) }
+ let(:head_vul_findings) { [head_vulnerability, vuln] }
+ end
+
+ subject { described_class.new(project, base_report, head_report) }
+
+ where(vulnerability_finding_signatures: [true, false])
+
+ with_them do
+ before do
+ stub_licensed_features(vulnerability_finding_signatures: vulnerability_finding_signatures)
+ end
+
+ describe '#base_report_out_of_date' do
+ context 'no base report' do
+ let(:base_report) { build(:ci_reports_security_aggregated_reports, reports: [], findings: []) }
+
+ it 'is not out of date' do
+ expect(subject.base_report_out_of_date).to be false
+ end
+ end
+
+ context 'base report older than one week' do
+ let(:report) { build(:ci_reports_security_report, created_at: 1.week.ago - 60.seconds) }
+ let(:base_report) { build(:ci_reports_security_aggregated_reports, reports: [report]) }
+
+ it 'is not out of date' do
+ expect(subject.base_report_out_of_date).to be true
+ end
+ end
+
+ context 'base report less than one week old' do
+ let(:report) { build(:ci_reports_security_report, created_at: 1.week.ago + 60.seconds) }
+ let(:base_report) { build(:ci_reports_security_aggregated_reports, reports: [report]) }
+
+ it 'is not out of date' do
+ expect(subject.base_report_out_of_date).to be false
+ end
+ end
+ end
+
+ describe '#added' do
+ 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) }
+
+ context 'with new vulnerability' do
+ let(:head_report) { build(:ci_reports_security_aggregated_reports, findings: [head_vulnerability, vuln]) }
+
+ it 'points to source tree' do
+ expect(subject.added).to eq([vuln])
+ end
+ end
+
+ context 'when comparing reports with different fingerprints' do
+ include_context 'comparing reports'
+
+ let(:head_report) { build(:ci_reports_security_aggregated_reports, findings: head_vul_findings) }
+
+ it 'does not find any overlap' do
+ expect(subject.added).to eq(head_vul_findings)
+ end
+ end
+
+ context 'order' do
+ let(:head_report) { build(:ci_reports_security_aggregated_reports, findings: [head_vulnerability, vuln, low_vuln]) }
+
+ it 'does not change' do
+ expect(subject.added).to eq([vuln, low_vuln])
+ end
+ end
+ end
+
+ describe '#fixed' do
+ let(:vul_params) { vuln_params(project.id, [identifier]) }
+ let(:vuln) { build(:ci_reports_security_finding, :dynamic, **vul_params ) }
+ let(:medium_vuln) { build(:ci_reports_security_finding, confidence: ::Enums::Vulnerability.confidence_levels[:high], severity: Enums::Vulnerability.severity_levels[:medium], uuid: vuln.uuid, **vul_params) }
+
+ context 'with fixed vulnerability' do
+ let(:base_report) { build(:ci_reports_security_aggregated_reports, findings: [base_vulnerability, vuln]) }
+
+ it 'points to base tree' do
+ expect(subject.fixed).to eq([vuln])
+ end
+ end
+
+ context 'when comparing reports with different fingerprints' do
+ include_context 'comparing reports'
+
+ let(:base_report) { build(:ci_reports_security_aggregated_reports, findings: [base_vulnerability, vuln]) }
+
+ it 'does not find any overlap' do
+ expect(subject.fixed).to eq([base_vulnerability, vuln])
+ end
+ end
+
+ context 'order' do
+ let(:vul_findings) { [vuln, medium_vuln] }
+ let(:base_report) { build(:ci_reports_security_aggregated_reports, findings: [*vul_findings, base_vulnerability]) }
+
+ it 'does not change' do
+ expect(subject.fixed).to eq(vul_findings)
+ end
+ end
+ end
+
+ describe 'with empty vulnerabilities' do
+ let(:empty_report) { build(:ci_reports_security_aggregated_reports, reports: [], findings: []) }
+
+ it 'returns empty array when reports are not present' do
+ comparer = described_class.new(project, empty_report, empty_report)
+
+ expect(comparer.fixed).to eq([])
+ expect(comparer.added).to eq([])
+ end
+
+ it 'returns added vulnerability when base is empty and head is not empty' do
+ comparer = described_class.new(project, empty_report, head_report)
+
+ expect(comparer.fixed).to eq([])
+ expect(comparer.added).to eq([head_vulnerability])
+ end
+
+ it 'returns fixed vulnerability when head is empty and base is not empty' do
+ comparer = described_class.new(project, base_report, empty_report)
+
+ expect(comparer.fixed).to eq([base_vulnerability])
+ expect(comparer.added).to eq([])
+ end
+ end
+ end
+
+ def vuln_params(project_id, identifiers, confidence: :high, severity: :critical)
+ {
+ project_id: project_id,
+ report_type: :sast,
+ identifiers: identifiers,
+ confidence: ::Enums::Vulnerability.confidence_levels[confidence],
+ severity: ::Enums::Vulnerability.severity_levels[severity]
+ }
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb
index 6bc8e261640..f8df2266689 100644
--- a/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe '5-Minute-Production-App.gitlab-ci.yml' do
let(:default_branch) { 'master' }
let(:pipeline_branch) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
diff --git a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb
index e8aeb93a2ba..ca6f6872f89 100644
--- a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe 'Deploy-ECS.gitlab-ci.yml' do
let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
let(:platform_target) { 'ECS' }
diff --git a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
index 053499344e1..bd701aec8fc 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe 'Jobs/Build.gitlab-ci.yml' do
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
@@ -47,7 +47,7 @@ RSpec.describe 'Jobs/Build.gitlab-ci.yml' do
context 'on merge request' do
let(:service) { MergeRequests::CreatePipelineService.new(project: project, current_user: user) }
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
- let(:pipeline) { service.execute(merge_request) }
+ let(:pipeline) { service.execute(merge_request).payload }
it 'has no jobs' do
expect(pipeline).to be_merge_request_event
diff --git a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
index b23457315cc..64243f2d205 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
@@ -47,7 +47,7 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
context 'on merge request' do
let(:service) { MergeRequests::CreatePipelineService.new(project: project, current_user: user) }
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
- let(:pipeline) { service.execute(merge_request) }
+ let(:pipeline) { service.execute(merge_request).payload }
it 'has no jobs' do
expect(pipeline).to be_merge_request_event
diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
index 1d137ef89e1..d377cf0c735 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
@@ -210,7 +210,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
context 'on merge request' do
let(:service) { MergeRequests::CreatePipelineService.new(project: project, current_user: user) }
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
- let(:pipeline) { service.execute(merge_request) }
+ let(:pipeline) { service.execute(merge_request).payload }
it 'has no jobs' do
expect(pipeline).to be_merge_request_event
diff --git a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
index 7fa8d906d07..db9d7496251 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
@@ -47,7 +47,7 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
context 'on merge request' do
let(:service) { MergeRequests::CreatePipelineService.new(project: project, current_user: user) }
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
- let(:pipeline) { service.execute(merge_request) }
+ let(:pipeline) { service.execute(merge_request).payload }
it 'has no jobs' do
expect(pipeline).to be_merge_request_event
diff --git a/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb
index 0811c07e896..4685d843ce0 100644
--- a/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-RSpec.describe 'Terraform/Base.latest.gitlab-ci.yml' do
- subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform/Base.latest') }
+RSpec.describe 'Terraform/Base.gitlab-ci.yml' do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform/Base') }
describe 'the created pipeline' do
let(:default_branch) { 'master' }
@@ -11,7 +11,7 @@ RSpec.describe 'Terraform/Base.latest.gitlab-ci.yml' do
let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
diff --git a/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb
new file mode 100644
index 00000000000..e35f2eabe8e
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Terraform/Base.latest.gitlab-ci.yml' do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform/Base.latest') }
+
+ describe 'the created pipeline' do
+ let(:default_branch) { 'master' }
+ let(:pipeline_branch) { default_branch }
+ let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
+ let(:user) { project.owner }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
+ let(:pipeline) { service.execute!(:push).payload }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ allow(project).to receive(:default_branch).and_return(default_branch)
+ end
+
+ it 'does not create any jobs' do
+ expect(build_names).to be_empty
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
index e53d2f4f975..004261bc617 100644
--- a/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe 'Verify/Load-Performance-Testing.gitlab-ci.yml' do
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
@@ -64,7 +64,7 @@ RSpec.describe 'Verify/Load-Performance-Testing.gitlab-ci.yml' do
context 'on merge request' do
let(:service) { MergeRequests::CreatePipelineService.new(project: project, current_user: user) }
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
- let(:pipeline) { service.execute(merge_request) }
+ let(:pipeline) { service.execute(merge_request).payload }
it 'has no jobs' do
expect(pipeline).to be_merge_request_event
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 b40b4f5645f..7602309627b 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
@@ -17,7 +17,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
@@ -264,7 +264,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
let(:project) { create(:project, :custom_repo, files: files) }
let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: default_branch ) }
- let(:pipeline) { service.execute(:push) }
+ let(:pipeline) { service.execute(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
diff --git a/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb
index 4e5fe622648..3d97b47473d 100644
--- a/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'Flutter.gitlab-ci.yml' do
let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
diff --git a/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb
index 151880e27a3..14aaf717453 100644
--- a/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'Managed-Cluster-Applications.gitlab-ci.yml' do
let(:project) { create(:project, :custom_repo, namespace: user.namespace, files: { 'README.md' => '' }) }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
let(:default_branch) { project.default_branch_or_main }
let(:pipeline_branch) { default_branch }
diff --git a/spec/lib/gitlab/ci/templates/npm_spec.rb b/spec/lib/gitlab/ci/templates/npm_spec.rb
index 2456c9ae545..ea954690133 100644
--- a/spec/lib/gitlab/ci/templates/npm_spec.rb
+++ b/spec/lib/gitlab/ci/templates/npm_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'npm.gitlab-ci.yml' do
let(:pipeline_tag) { 'v1.2.1' }
let(:pipeline_ref) { pipeline_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref ) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
def create_branch(name:)
diff --git a/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb
new file mode 100644
index 00000000000..936cd6ac8aa
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Terraform.gitlab-ci.yml' do
+ before do
+ allow(Gitlab::Template::GitlabCiYmlTemplate).to receive(:excluded_patterns).and_return([])
+ end
+
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform') }
+
+ describe 'the created pipeline' do
+ let(:default_branch) { project.default_branch_or_main }
+ let(:pipeline_branch) { default_branch }
+ let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
+ let(:user) { project.owner }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
+ let(:pipeline) { service.execute!(:push).payload }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ allow(project).to receive(:default_branch).and_return(default_branch)
+ end
+
+ context 'on master branch' do
+ it 'creates init, validate and build jobs', :aggregate_failures do
+ expect(pipeline.errors).to be_empty
+ expect(build_names).to include('init', 'validate', 'build', 'deploy')
+ end
+ end
+
+ context 'outside the master branch' do
+ let(:pipeline_branch) { 'patch-1' }
+
+ before do
+ project.repository.create_branch(pipeline_branch, default_branch)
+ end
+
+ it 'does not creates a deploy and a test job', :aggregate_failures do
+ expect(pipeline.errors).to be_empty
+ expect(build_names).not_to include('deploy')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb
index 5ab3035486f..3d1306e82a5 100644
--- a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'Terraform.latest.gitlab-ci.yml' do
let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
let(:user) { project.owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
- let(:pipeline) { service.execute!(:push) }
+ let(:pipeline) { service.execute!(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
@@ -25,7 +25,8 @@ RSpec.describe 'Terraform.latest.gitlab-ci.yml' do
end
context 'on master branch' do
- it 'creates init, validate and build jobs' do
+ it 'creates init, validate and build jobs', :aggregate_failures do
+ expect(pipeline.errors).to be_empty
expect(build_names).to include('init', 'validate', 'build', 'deploy')
end
end
@@ -37,7 +38,8 @@ RSpec.describe 'Terraform.latest.gitlab-ci.yml' do
project.repository.create_branch(pipeline_branch, default_branch)
end
- it 'does not creates a deploy and a test job' do
+ it 'does not creates a deploy and a test job', :aggregate_failures do
+ expect(pipeline.errors).to be_empty
expect(build_names).not_to include('deploy')
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 19c2e34a0f0..49a470f9e01 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -42,7 +42,6 @@ module Gitlab
interruptible: true,
allow_failure: false,
when: "on_success",
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -75,7 +74,6 @@ module Gitlab
],
allow_failure: false,
when: 'on_success',
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -115,7 +113,6 @@ module Gitlab
tag_list: %w[A B],
allow_failure: false,
when: "on_success",
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true
})
@@ -163,7 +160,6 @@ module Gitlab
interruptible: true,
allow_failure: false,
when: "on_success",
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -354,7 +350,6 @@ module Gitlab
name: "rspec",
allow_failure: false,
when: "on_success",
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage,
@@ -368,7 +363,6 @@ module Gitlab
name: "prod",
allow_failure: false,
when: "on_success",
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage,
@@ -847,7 +841,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -882,7 +875,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -913,7 +905,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -942,7 +933,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -955,7 +945,6 @@ module Gitlab
subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute }
let(:build) { subject.builds.first }
- let(:yaml_variables) { build[:yaml_variables] }
let(:job_variables) { build[:job_variables] }
let(:root_variables_inheritance) { build[:root_variables_inheritance] }
@@ -973,84 +962,11 @@ module Gitlab
end
it 'returns global variables' do
- expect(yaml_variables).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
expect(job_variables).to eq([])
expect(root_variables_inheritance).to eq(true)
end
end
- context 'when job and global variables are defined' do
- let(:global_variables) do
- { 'VAR1' => 'global1', 'VAR3' => 'global3', 'VAR4' => 'global4' }
- end
-
- let(:build_variables) do
- { 'VAR1' => 'value1', 'VAR2' => 'value2' }
- end
-
- let(:config) do
- {
- before_script: ['pwd'],
- variables: global_variables,
- rspec: { script: 'rspec', variables: build_variables, inherit: inherit }
- }
- end
-
- context 'when no inheritance is specified' do
- let(:inherit) { }
-
- it 'returns all variables' do
- expect(yaml_variables).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true },
- { key: 'VAR3', value: 'global3', public: true },
- { key: 'VAR4', value: 'global4', public: true }
- )
- expect(job_variables).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
- expect(root_variables_inheritance).to eq(true)
- end
- end
-
- context 'when inheritance is disabled' do
- let(:inherit) { { variables: false } }
-
- it 'does not inherit variables' do
- expect(yaml_variables).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
- expect(job_variables).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
- expect(root_variables_inheritance).to eq(false)
- end
- end
-
- context 'when specific variables are to inherited' do
- let(:inherit) { { variables: %w[VAR1 VAR4] } }
-
- it 'returns all variables and inherits only specified variables' do
- expect(yaml_variables).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true },
- { key: 'VAR4', value: 'global4', public: true }
- )
- expect(job_variables).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
- expect(root_variables_inheritance).to eq(%w[VAR1 VAR4])
- end
- end
- end
-
context 'when job variables are defined' do
let(:config) do
{
@@ -1065,10 +981,6 @@ module Gitlab
end
it 'returns job variables' do
- expect(yaml_variables).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
expect(job_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
@@ -1096,9 +1008,6 @@ module Gitlab
# When variables config is empty, we assume this is a valid
# configuration, see issue #18775
#
- expect(yaml_variables).to be_an_instance_of(Array)
- expect(yaml_variables).to be_empty
-
expect(job_variables).to eq([])
expect(root_variables_inheritance).to eq(true)
end
@@ -1115,9 +1024,6 @@ module Gitlab
end
it 'returns empty array' do
- expect(yaml_variables).to be_an_instance_of(Array)
- expect(yaml_variables).to be_empty
-
expect(job_variables).to eq([])
expect(root_variables_inheritance).to eq(true)
end
@@ -1246,6 +1152,10 @@ module Gitlab
end
it { is_expected.to be_valid }
+
+ it 'adds the job from the included file' do
+ expect(subject.builds.map { |build| build[:name] }).to contain_exactly('job1', 'rspec')
+ end
end
context "when the included internal file is not present" do
@@ -1349,7 +1259,7 @@ module Gitlab
end
it 'sets matrix variables' do
- build_variables = builds.map { |build| build[:yaml_variables] }
+ build_variables = builds.map { |build| build[:job_variables] }
expected_variables = [
[
{ key: 'VAR1', value: '1' },
@@ -1601,7 +1511,6 @@ module Gitlab
},
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -1972,7 +1881,6 @@ module Gitlab
},
when: 'on_success',
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -1988,7 +1896,6 @@ module Gitlab
],
when: 'on_success',
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :dag
@@ -2011,7 +1918,6 @@ module Gitlab
},
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -2028,7 +1934,6 @@ module Gitlab
],
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :dag
@@ -2057,7 +1962,6 @@ module Gitlab
},
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -2076,7 +1980,6 @@ module Gitlab
],
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :dag
@@ -2101,7 +2004,6 @@ module Gitlab
],
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :dag
@@ -2134,7 +2036,6 @@ module Gitlab
],
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :dag
@@ -2342,7 +2243,6 @@ module Gitlab
},
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -2391,7 +2291,6 @@ module Gitlab
},
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -2406,7 +2305,6 @@ module Gitlab
},
when: "on_success",
allow_failure: false,
- yaml_variables: [],
job_variables: [],
root_variables_inheritance: true,
scheduling_type: :stage
@@ -2851,7 +2749,7 @@ module Gitlab
YAML
end
- it_behaves_like 'returns errors', 'The pipeline has circular dependencies.'
+ it_behaves_like 'returns errors', 'The pipeline has circular dependencies'
end
end
@@ -2883,7 +2781,7 @@ module Gitlab
expect(subject.valid?).to eq(false)
expect(subject.errors).to contain_exactly(
'jobs:rspec config contains unknown keys: bad_tags',
- 'jobs:rspec rules should be an array of hashes')
+ 'jobs:rspec rules should be an array containing hashes and arrays of hashes')
end
end