473 lines
14 KiB
Ruby
473 lines
14 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe Gitlab::Ci::Config::Entry::Processable do
|
|
let(:node_class) do
|
|
Class.new(::Gitlab::Config::Entry::Node) do
|
|
include Gitlab::Ci::Config::Entry::Processable
|
|
|
|
entry :tags, ::Gitlab::Config::Entry::ArrayOfStrings,
|
|
description: 'Set the default tags.',
|
|
inherit: true
|
|
|
|
def self.name
|
|
'job'
|
|
end
|
|
end
|
|
end
|
|
|
|
let(:entry) { node_class.new(config, name: :rspec) }
|
|
|
|
describe 'validations' do
|
|
before do
|
|
entry.compose!
|
|
end
|
|
|
|
context 'when entry config value is correct' do
|
|
let(:config) { { stage: 'test' } }
|
|
|
|
describe '#valid?' do
|
|
it 'is valid' do
|
|
expect(entry).to be_valid
|
|
end
|
|
end
|
|
|
|
context 'when job name is empty' do
|
|
let(:entry) { node_class.new(config, name: ''.to_sym) }
|
|
|
|
it 'reports error' do
|
|
expect(entry.errors).to include "job name can't be blank"
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when entry value is not correct' do
|
|
context 'incorrect config value type' do
|
|
let(:config) { ['incorrect'] }
|
|
|
|
describe '#errors' do
|
|
it 'reports error about a config type' do
|
|
expect(entry.errors)
|
|
.to include 'job config should be a hash'
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when config is empty' do
|
|
let(:config) { {} }
|
|
|
|
describe '#valid' do
|
|
it 'is invalid' do
|
|
expect(entry).not_to be_valid
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when extends key is not a string' do
|
|
let(:config) { { extends: 123 } }
|
|
|
|
it 'returns error about wrong value type' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include "job extends should be an array of strings or a string"
|
|
end
|
|
end
|
|
|
|
context 'when it uses both "when:" and "rules:"' do
|
|
let(:config) do
|
|
{
|
|
script: 'echo',
|
|
when: 'on_failure',
|
|
rules: [{ if: '$VARIABLE', when: 'on_success' }]
|
|
}
|
|
end
|
|
|
|
it 'returns an error about when: being combined with rules' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include 'job config key may not be used with `rules`: when'
|
|
end
|
|
end
|
|
|
|
context 'when only: is used with rules:' do
|
|
let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } }
|
|
|
|
it 'returns error about mixing only: with rules:' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
end
|
|
|
|
context 'and only: is blank' do
|
|
let(:config) { { only: nil, rules: [{ if: '$THIS' }] } }
|
|
|
|
it 'returns error about mixing only: with rules:' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
end
|
|
end
|
|
|
|
context 'and rules: is blank' do
|
|
let(:config) { { only: ['merge_requests'], rules: nil } }
|
|
|
|
it 'returns error about mixing only: with rules:' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when except: is used with rules:' do
|
|
let(:config) { { except: { refs: %w[master] }, rules: [{ if: '$THIS' }] } }
|
|
|
|
it 'returns error about mixing except: with rules:' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
end
|
|
|
|
context 'and except: is blank' do
|
|
let(:config) { { except: nil, rules: [{ if: '$THIS' }] } }
|
|
|
|
it 'returns error about mixing except: with rules:' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
end
|
|
end
|
|
|
|
context 'and rules: is blank' do
|
|
let(:config) { { except: { refs: %w[master] }, rules: nil } }
|
|
|
|
it 'returns error about mixing except: with rules:' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when only: and except: are both used with rules:' do
|
|
let(:config) do
|
|
{
|
|
only: %w[merge_requests],
|
|
except: { refs: %w[master] },
|
|
rules: [{ if: '$THIS' }]
|
|
}
|
|
end
|
|
|
|
it 'returns errors about mixing both only: and except: with rules:' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
end
|
|
|
|
context 'when only: and except: as both blank' do
|
|
let(:config) do
|
|
{ only: nil, except: nil, rules: [{ if: '$THIS' }] }
|
|
end
|
|
|
|
it 'returns errors about mixing both only: and except: with rules:' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
end
|
|
end
|
|
|
|
context 'when rules: is blank' do
|
|
let(:config) do
|
|
{ only: %w[merge_requests], except: { refs: %w[master] }, rules: nil }
|
|
end
|
|
|
|
it 'returns errors about mixing both only: and except: with rules:' do
|
|
expect(entry).not_to be_valid
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
expect(entry.errors).to include /may not be used with `rules`/
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#relevant?' do
|
|
it 'is a relevant entry' do
|
|
entry = node_class.new({ stage: 'test' }, name: :rspec)
|
|
|
|
expect(entry).to be_relevant
|
|
end
|
|
end
|
|
|
|
describe '#compose!' do
|
|
let(:unspecified) { double('unspecified', 'specified?' => false) }
|
|
let(:default) { double('default', '[]' => unspecified) }
|
|
let(:workflow) { double('workflow', 'has_rules?' => false) }
|
|
let(:variables) { }
|
|
|
|
let(:deps) do
|
|
double('deps',
|
|
default_entry: default,
|
|
workflow_entry: workflow,
|
|
variables_value: variables)
|
|
end
|
|
|
|
context 'with workflow rules' do
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
where(:name, :has_workflow_rules?, :only, :rules, :result) do
|
|
"uses default only" | false | nil | nil | { refs: %w[branches tags] }
|
|
"uses user only" | false | %w[branches] | nil | { refs: %w[branches] }
|
|
"does not define only" | false | nil | [] | nil
|
|
"does not define only" | true | nil | nil | nil
|
|
"uses user only" | true | %w[branches] | nil | { refs: %w[branches] }
|
|
"does not define only" | true | nil | [] | nil
|
|
end
|
|
|
|
with_them do
|
|
let(:config) { { script: 'ls', rules: rules, only: only }.compact }
|
|
|
|
it "#{name}" do
|
|
expect(workflow).to receive(:has_rules?) { has_workflow_rules? }
|
|
|
|
entry.compose!(deps)
|
|
|
|
expect(entry.only_value).to eq(result)
|
|
end
|
|
end
|
|
end
|
|
|
|
shared_examples 'has no warnings' do
|
|
it 'does not raise the warning' do
|
|
expect(entry.warnings).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when workflow rules is used' do
|
|
let(:workflow) { double('workflow', 'has_rules?' => true) }
|
|
|
|
before do
|
|
entry.compose!(deps)
|
|
end
|
|
|
|
context 'when rules are used' do
|
|
let(:config) { { script: 'ls', cache: { key: 'test' }, rules: [] } }
|
|
|
|
it 'does not define only' do
|
|
expect(entry).not_to be_only_defined
|
|
end
|
|
end
|
|
|
|
context 'when rules are not used and only is defined' do
|
|
let(:config) { { script: 'ls', cache: { key: 'test' }, only: [] } }
|
|
|
|
it 'keeps only entry' do
|
|
expect(entry).to be_only_defined
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when workflow rules is not used' do
|
|
let(:workflow) { double('workflow', 'has_rules?' => false) }
|
|
let(:feature_flag_value) { true }
|
|
|
|
before do
|
|
stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: feature_flag_value)
|
|
entry.compose!(deps)
|
|
end
|
|
|
|
context 'when rules are valid' do
|
|
let(:config) do
|
|
{
|
|
script: 'ls',
|
|
rules: [
|
|
{ if: '$CI_COMMIT_BRANCH', when: 'on_success' },
|
|
last_rule
|
|
]
|
|
}
|
|
end
|
|
|
|
context 'when last rule contains only `when`' do
|
|
let(:last_rule) { { when: when_value } }
|
|
|
|
context 'and its value is not `never`' do
|
|
let(:when_value) { 'on_success' }
|
|
|
|
it 'raises a warning' do
|
|
expect(entry.warnings).to contain_exactly(/may allow multiple pipelines/)
|
|
end
|
|
|
|
context 'when feature flag is disabled' do
|
|
let(:feature_flag_value) { false }
|
|
|
|
it_behaves_like 'has no warnings'
|
|
end
|
|
end
|
|
|
|
context 'and its value is `never`' do
|
|
let(:when_value) { 'never' }
|
|
|
|
it_behaves_like 'has no warnings'
|
|
end
|
|
end
|
|
|
|
context 'when last rule does not contain only `when`' do
|
|
let(:last_rule) { { if: '$CI_MERGE_REQUEST_ID', when: 'always' } }
|
|
|
|
it_behaves_like 'has no warnings'
|
|
end
|
|
end
|
|
|
|
context 'when rules are invalid' do
|
|
let(:config) { { script: 'ls', rules: { when: 'always' } } }
|
|
|
|
it_behaves_like 'has no warnings'
|
|
end
|
|
end
|
|
|
|
context 'when workflow rules is used' do
|
|
let(:workflow) { double('workflow', 'has_rules?' => true) }
|
|
|
|
before do
|
|
entry.compose!(deps)
|
|
end
|
|
|
|
context 'when last rule contains only `when' do
|
|
let(:config) do
|
|
{
|
|
script: 'ls',
|
|
rules: [
|
|
{ if: '$CI_COMMIT_BRANCH', when: 'on_success' },
|
|
{ when: 'always' }
|
|
]
|
|
}
|
|
end
|
|
|
|
it_behaves_like 'has no warnings'
|
|
end
|
|
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' }
|
|
)
|
|
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 all variables and overwrite them' do
|
|
expect(entry.value).to include(
|
|
variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root', 'D' => 'root' }
|
|
)
|
|
end
|
|
|
|
context 'when inherit of defaults is disabled' do
|
|
let(:config) do
|
|
{
|
|
variables: { A: 'job', B: 'job' },
|
|
inherit: { variables: false }
|
|
}
|
|
end
|
|
|
|
it 'does return only job variables' do
|
|
expect(entry.value).to include(
|
|
variables: { 'A' => 'job', 'B' => 'job' }
|
|
)
|
|
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 only job variables' do
|
|
expect(entry.value).to include(
|
|
variables: { 'A' => 'job', 'B' => 'job', 'D' => 'root' }
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'of default:tags' do
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
where(:name, :default_tags, :tags, :inherit_default, :result) do
|
|
"only local tags" | nil | %w[a b] | nil | %w[a b]
|
|
"only local tags" | nil | %w[a b] | true | %w[a b]
|
|
"only local tags" | nil | %w[a b] | false | %w[a b]
|
|
"global and local tags" | %w[b c] | %w[a b] | nil | %w[a b]
|
|
"global and local tags" | %w[b c] | %w[a b] | true | %w[a b]
|
|
"global and local tags" | %w[b c] | %w[a b] | false | %w[a b]
|
|
"only global tags" | %w[b c] | nil | nil | %w[b c]
|
|
"only global tags" | %w[b c] | nil | true | %w[b c]
|
|
"only global tags" | %w[b c] | nil | false | nil
|
|
"only global tags" | %w[b c] | nil | %w[image] | nil
|
|
"only global tags" | %w[b c] | nil | %w[tags] | %w[b c]
|
|
end
|
|
|
|
with_them do
|
|
let(:config) do
|
|
{ tags: tags,
|
|
inherit: { default: inherit_default } }
|
|
end
|
|
|
|
let(:default_specified_tags) do
|
|
double('tags',
|
|
'specified?' => true,
|
|
'valid?' => true,
|
|
'value' => default_tags,
|
|
'errors' => [])
|
|
end
|
|
|
|
before do
|
|
allow(default).to receive('[]').with(:tags).and_return(default_specified_tags)
|
|
|
|
entry.compose!(deps)
|
|
|
|
expect(entry).to be_valid
|
|
end
|
|
|
|
it { expect(entry.tags_value).to eq(result) }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when composed' do
|
|
before do
|
|
entry.compose!
|
|
end
|
|
|
|
describe '#value' do
|
|
context 'when entry is correct' do
|
|
let(:config) do
|
|
{ stage: 'test' }
|
|
end
|
|
|
|
it 'returns correct value' do
|
|
expect(entry.value).to eq(
|
|
name: :rspec,
|
|
stage: 'test',
|
|
only: { refs: %w[branches tags] },
|
|
variables: {}
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|