Commit 250fede4 authored by Pedro Pombeiro's avatar Pedro Pombeiro

Address MR review comments

parent d565ff04
...@@ -16,18 +16,6 @@ module ExpandVariables ...@@ -16,18 +16,6 @@ module ExpandVariables
end end
end end
# expand_variables_collection expands a Gitlab::Ci::Variables::Collection, ignoring unknown variable references.
# If a circular variable reference is found, the original Collection is returned
def expand_variables_collection(variables, project)
return Gitlab::Ci::Variables::Collection.new(variables) if
Feature.disabled?(:variable_inside_variable, project)
sorted_variables = variables.sorted_collection(project)
return sorted_variables if sorted_variables.errors
expand_sorted_variables_collection(sorted_variables)
end
def possible_var_reference?(value) def possible_var_reference?(value)
return unless value return unless value
...@@ -74,16 +62,5 @@ module ExpandVariables ...@@ -74,16 +62,5 @@ module ExpandVariables
variables variables
end end
def expand_sorted_variables_collection(sorted_variables)
expanded_vars = {}
sorted_variables.each_with_object(Gitlab::Ci::Variables::Collection.new) do |item, collection|
item = item.merge(value: expand_existing(item.value, expanded_vars)) if item.depends_on
expanded_vars.store(item[:key], item)
collection.append(item)
end
end
end end
end end
...@@ -162,8 +162,8 @@ module Gitlab ...@@ -162,8 +162,8 @@ module Gitlab
end end
def variable_expansion_errors def variable_expansion_errors
sorted_collection = evaluate_context.variables.sorted_collection(@pipeline.project) expanded_collection = evaluate_context.variables.expand_all(@pipeline.project)
errors = sorted_collection.errors errors = expanded_collection.errors
["#{name}: #{errors}"] if errors ["#{name}: #{errors}"] if errors
end end
......
...@@ -63,16 +63,36 @@ module Gitlab ...@@ -63,16 +63,36 @@ module Gitlab
Collection.new(@variables.reject(&block)) Collection.new(@variables.reject(&block))
end end
def ==(other) def expand_value(value, keep_undefined: false)
return @variables == other if other.is_a?(Array) value.gsub(ExpandVariables::VARIABLES_REGEXP) do
return false unless other.class == self.class match = Regexp.last_match
result = @variables_by_key[match[1] || match[2]]&.value
result ||= match[0] if keep_undefined
result
end
end
def expand_all(project, keep_undefined: false)
return self if Feature.disabled?(:variable_inside_variable, project)
sorted = Sort.new(self)
return self.class.new(self, sorted.errors) unless sorted.valid?
new_collection = self.class.new
sorted.tsort.each do |item|
unless item.depends_on
new_collection.append(item)
next
end
@variables == other.variables # expand variables as they are added
variable = item.to_runner_variable
variable[:value] = new_collection.expand_value(variable[:value], keep_undefined: keep_undefined)
new_collection.append(variable)
end end
# Returns a sorted Collection object, and sets errors property in case of an error new_collection
def sorted_collection(project)
Sort.new(self, project).collection
end end
protected protected
......
...@@ -7,26 +7,23 @@ module Gitlab ...@@ -7,26 +7,23 @@ module Gitlab
class Item class Item
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
attr_reader :raw
def initialize(key:, value:, public: true, file: false, masked: false, raw: false) def initialize(key:, value:, public: true, file: false, masked: false, raw: false)
raise ArgumentError, "`#{key}` must be of type String or nil value, while it was: #{value.class}" unless raise ArgumentError, "`#{key}` must be of type String or nil value, while it was: #{value.class}" unless
value.is_a?(String) || value.nil? value.is_a?(String) || value.nil?
@variable = { key: key, value: value, public: public, file: file, masked: masked } @variable = { key: key, value: value, public: public, file: file, masked: masked, raw: raw }
@raw = raw
end end
def value def value
@variable.fetch(:value) @variable.fetch(:value)
end end
def [](key) def raw
@variable.fetch(key) @variable.fetch(:raw)
end end
def merge(*other_hashes) def [](key)
self.class.fabricate(@variable.merge(*other_hashes)) @variable.fetch(key)
end end
def ==(other) def ==(other)
...@@ -50,7 +47,7 @@ module Gitlab ...@@ -50,7 +47,7 @@ module Gitlab
# #
def to_runner_variable def to_runner_variable
@variable.reject do |hash_key, hash_value| @variable.reject do |hash_key, hash_value|
hash_key == :file && hash_value == false (hash_key == :file || hash_key == :raw) && hash_value == false
end end
end end
......
...@@ -8,12 +8,11 @@ module Gitlab ...@@ -8,12 +8,11 @@ module Gitlab
include TSort include TSort
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
def initialize(collection, project) def initialize(collection)
raise(ArgumentError, "A Gitlab::Ci::Variables::Collection object was expected") unless raise(ArgumentError, "A Gitlab::Ci::Variables::Collection object was expected") unless
collection.is_a?(Collection) collection.is_a?(Collection)
@collection = collection @collection = collection
@project = project
end end
def valid? def valid?
...@@ -23,8 +22,6 @@ module Gitlab ...@@ -23,8 +22,6 @@ module Gitlab
# errors sorts an array of variables, ignoring unknown variable references, # errors sorts an array of variables, ignoring unknown variable references,
# and returning an error string if a circular variable reference is found # and returning an error string if a circular variable reference is found
def errors def errors
return if Feature.disabled?(:variable_inside_variable, @project)
strong_memoize(:errors) do strong_memoize(:errors) do
# Check for cyclic dependencies and build error message in that case # Check for cyclic dependencies and build error message in that case
cyclic_vars = each_strongly_connected_component.filter_map do |component| cyclic_vars = each_strongly_connected_component.filter_map do |component|
...@@ -35,16 +32,6 @@ module Gitlab ...@@ -35,16 +32,6 @@ module Gitlab
end end
end end
# collection sorts a collection of variables, ignoring unknown variable references.
# If a circular variable reference is found, a new collection with the original array and an error is returned
def collection
return @collection if Feature.disabled?(:variable_inside_variable, @project)
return Gitlab::Ci::Variables::Collection.new(@collection, errors) if errors
Gitlab::Ci::Variables::Collection.new(tsort)
end
private private
def tsort_each_node(&block) def tsort_each_node(&block)
......
...@@ -268,236 +268,4 @@ RSpec.describe ExpandVariables do ...@@ -268,236 +268,4 @@ RSpec.describe ExpandVariables do
end end
end end
end end
describe '#expand_variables_collection' do
context 'when FF :variable_inside_variable is disabled' do
let_it_be(:project_with_flag_disabled) { create(:project) }
let_it_be(:project_with_flag_enabled) { create(:project) }
before do
stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
end
context 'table tests' do
using RSpec::Parameterized::TableSyntax
where do
{
"empty array": {
variables: []
},
"simple expansions": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key$variable$variable2' }
]
},
"complex expansion": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'key${variable}' }
]
},
"out-of-order variable reference": {
variables: [
{ key: 'variable2', value: 'key${variable}' },
{ key: 'variable', value: 'value' }
]
},
"complex expansions with raw variable": {
variables: [
{ key: 'variable3', value: 'key_${variable}_${variable2}' },
{ key: 'variable', value: '$variable2', raw: true },
{ key: 'variable2', value: 'value2' }
]
},
"array with cyclic dependency": {
variables: [
{ key: 'variable', value: '$variable2' },
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' }
]
}
}
end
with_them do
subject { ExpandVariables.expand_variables_collection(variables, project_with_flag_disabled) }
it 'returns Collection' do
is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection)
end
it 'does not expand variables' do
is_expected.to eq(variables)
end
end
end
end
context 'when FF :variable_inside_variable is enabled' do
let_it_be(:project_with_flag_disabled) { create(:project) }
let_it_be(:project_with_flag_enabled) { create(:project) }
before do
stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
end
context 'table tests' do
using RSpec::Parameterized::TableSyntax
where do
{
"empty array": {
variables: [],
result: []
},
"simple expansions": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key$variable$variable2' },
{ key: 'variable4', value: 'key$variable$variable3' }
],
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'keyvalueresult' },
{ key: 'variable4', value: 'keyvaluekeyvalueresult' }
]
},
"complex expansion": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'key${variable}' }
],
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'keyvalue' }
]
},
"unused variables": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result2' },
{ key: 'variable3', value: 'result3' },
{ key: 'variable4', value: 'key$variable$variable3' }
],
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result2' },
{ key: 'variable3', value: 'result3' },
{ key: 'variable4', value: 'keyvalueresult3' }
]
},
"complex expansions": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key${variable}${variable2}' }
],
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'keyvalueresult' }
]
},
"out-of-order expansion": {
variables: [
{ key: 'variable3', value: 'key$variable2$variable' },
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' }
],
result: [
{ key: 'variable2', value: 'result' },
{ key: 'variable', value: 'value' },
{ key: 'variable3', value: 'keyresultvalue' }
]
},
"out-of-order complex expansion": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key${variable2}${variable}' }
],
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'keyresultvalue' }
]
},
"missing variable": {
variables: [
{ key: 'variable2', value: 'key$variable' }
],
result: [
{ key: 'variable2', value: 'key$variable' }
]
},
"complex expansions with missing variable": {
variables: [
{ key: 'variable4', value: 'key${variable}${variable2}${variable3}' },
{ key: 'variable', value: 'value' },
{ key: 'variable3', value: 'value3' }
],
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable3', value: 'value3' },
{ key: 'variable4', value: 'keyvalue${variable2}value3' }
]
},
"complex expansions with raw variable": {
variables: [
{ key: 'variable3', value: 'key_${variable}_${variable2}' },
{ key: 'variable', value: '$variable2', raw: true },
{ key: 'variable2', value: 'value2' }
],
result: [
{ key: 'variable', value: '$variable2', raw: true },
{ key: 'variable2', value: 'value2' },
{ key: 'variable3', value: 'key_$variable2_value2' }
]
},
"cyclic dependency causes original array to be returned": {
variables: [
{ key: 'variable', value: '$variable2' },
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' }
],
result: [
{ key: 'variable', value: '$variable2' },
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' }
]
}
}
end
with_them do
let!(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
subject! do
ExpandVariables.expand_variables_collection(collection, project_with_flag_enabled)
end
it 'returns Collection' do
is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection)
end
it 'expands variables' do
is_expected.to eq(result)
end
it 'preserves raw attribute' do
collection.each do |v|
k = v[:key]
subject_item = subject.find { |sv| sv[:key] == k }
expect(subject_item.raw).to eq(v.raw)
end
end
end
end
end
end
end end
...@@ -180,16 +180,6 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do ...@@ -180,16 +180,6 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do
end end
end end
describe '#merge' do
it 'behaves like hash merge' do
item = described_class.new(**variable)
subject = item.merge(value: 'another thing')
expect(subject).not_to eq item
expect(subject[:value]).to eq 'another thing'
end
end
describe '#to_runner_variable' do describe '#to_runner_variable' do
context 'when variable is not a file-related' do context 'when variable is not a file-related' do
it 'returns a runner-compatible hash representation' do it 'returns a runner-compatible hash representation' do
...@@ -212,6 +202,26 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do ...@@ -212,6 +202,26 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do
end end
end end
context 'when variable is raw' do
it 'does not export raw value when it is false' do
runner_variable = described_class
.new(key: 'VAR', value: 'value', raw: false)
.to_runner_variable
expect(runner_variable)
.to eq(key: 'VAR', value: 'value', public: true, masked: false)
end
it 'exports raw value when it is true' do
runner_variable = described_class
.new(key: 'VAR', value: 'value', raw: true)
.to_runner_variable
expect(runner_variable)
.to eq(key: 'VAR', value: 'value', public: true, raw: true, masked: false)
end
end
context 'when referencing a variable' do context 'when referencing a variable' do
it '#depends_on contains names of dependencies' do it '#depends_on contains names of dependencies' do
runner_variable = described_class.new(key: 'CI_VAR', value: '${CI_VAR_2}-123-$CI_VAR_3') runner_variable = described_class.new(key: 'CI_VAR', value: '${CI_VAR_2}-123-$CI_VAR_3')
......
...@@ -4,15 +4,8 @@ require 'spec_helper' ...@@ -4,15 +4,8 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Variables::Collection::Sort do RSpec.describe Gitlab::Ci::Variables::Collection::Sort do
describe '#initialize with non-Collection value' do describe '#initialize with non-Collection value' do
let_it_be(:project_with_flag_disabled) { create(:project) }
let_it_be(:project_with_flag_enabled) { create(:project) }
before do
stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
end
context 'when FF :variable_inside_variable is disabled' do context 'when FF :variable_inside_variable is disabled' do
subject { Gitlab::Ci::Variables::Collection::Sort.new([], project_with_flag_disabled) } subject { Gitlab::Ci::Variables::Collection::Sort.new([]) }
it 'raises ArgumentError' do it 'raises ArgumentError' do
expect { subject }.to raise_error(ArgumentError, /Collection object was expected/) expect { subject }.to raise_error(ArgumentError, /Collection object was expected/)
...@@ -20,7 +13,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do ...@@ -20,7 +13,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do
end end
context 'when FF :variable_inside_variable is enabled' do context 'when FF :variable_inside_variable is enabled' do
subject { Gitlab::Ci::Variables::Collection::Sort.new([], project_with_flag_enabled) } subject { Gitlab::Ci::Variables::Collection::Sort.new([]) }
it 'raises ArgumentError' do it 'raises ArgumentError' do
expect { subject }.to raise_error(ArgumentError, /Collection object was expected/) expect { subject }.to raise_error(ArgumentError, /Collection object was expected/)
...@@ -29,88 +22,6 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do ...@@ -29,88 +22,6 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do
end end
describe '#errors' do describe '#errors' do
context 'when FF :variable_inside_variable is disabled' do
let_it_be(:project_with_flag_disabled) { create(:project) }
let_it_be(:project_with_flag_enabled) { create(:project) }
before do
stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
end
context 'table tests' do
using RSpec::Parameterized::TableSyntax
where do
{
"empty array": {
variables: []
},
"simple expansions": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key$variable$variable2' }
]
},
"complex expansion": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'key${variable}' }
]
},
"complex expansions with missing variable for Windows": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable3', value: 'key%variable%%variable2%' }
]
},
"out-of-order variable reference": {
variables: [
{ key: 'variable2', value: 'key${variable}' },
{ key: 'variable', value: 'value' }
]
},
"array with cyclic dependency": {
variables: [
{ key: 'variable', value: '$variable2' },
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' }
]
},
"array with raw variable": {
variables: [
{ key: 'variable', value: '$variable2' },
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2', raw: true }
]
}
}
end
with_them do
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
subject { Gitlab::Ci::Variables::Collection::Sort.new(collection, project_with_flag_disabled) }
it 'does not report error' do
expect(subject.errors).to eq(nil)
end
it 'valid? reports true' do
expect(subject.valid?).to eq(true)
end
end
end
end
context 'when FF :variable_inside_variable is enabled' do
let_it_be(:project_with_flag_disabled) { create(:project) }
let_it_be(:project_with_flag_enabled) { create(:project) }
before do
stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
end
context 'table tests' do context 'table tests' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
...@@ -158,7 +69,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do ...@@ -158,7 +69,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do
with_them do with_them do
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) } let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
subject { Gitlab::Ci::Variables::Collection::Sort.new(collection, project_with_flag_enabled) } subject { Gitlab::Ci::Variables::Collection::Sort.new(collection) }
it 'errors matches expected validation result' do it 'errors matches expected validation result' do
expect(subject.errors).to eq(validation_result) expect(subject.errors).to eq(validation_result)
...@@ -167,79 +78,15 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do ...@@ -167,79 +78,15 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do
it 'valid? matches expected validation result' do it 'valid? matches expected validation result' do
expect(subject.valid?).to eq(validation_result.nil?) expect(subject.valid?).to eq(validation_result.nil?)
end end
end
end
end
end
describe '#collection' do
context 'when FF :variable_inside_variable is disabled' do
before do
stub_feature_flags(variable_inside_variable: false)
end
context 'table tests' do
using RSpec::Parameterized::TableSyntax
where do
{
"empty array": {
variables: []
},
"simple expansions": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key$variable$variable2' }
]
},
"complex expansion": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'key${variable}' }
]
},
"complex expansions with missing variable for Windows": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable3', value: 'key%variable%%variable2%' }
]
},
"out-of-order variable reference": {
variables: [
{ key: 'variable2', value: 'key${variable}' },
{ key: 'variable', value: 'value' }
]
},
"array with cyclic dependency": {
variables: [
{ key: 'variable', value: '$variable2' },
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' }
]
}
}
end
with_them do it 'does not raise' do
let_it_be(:project) { create(:project) } expect { subject }.not_to raise_error
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
subject { Gitlab::Ci::Variables::Collection::Sort.new(collection, project).collection }
it 'does not expand variables' do
is_expected.to be(collection)
end end
end end
end end
end end
context 'when FF :variable_inside_variable is enabled' do describe '#tsort' do
before do
stub_licensed_features(group_saml_group_sync: true)
stub_feature_flags(variable_inside_variable: true)
end
context 'table tests' do context 'table tests' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
...@@ -287,14 +134,6 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do ...@@ -287,14 +134,6 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do
], ],
result: %w[variable variable3 variable4] result: %w[variable variable3 variable4]
}, },
"cyclic dependency causes original array to be returned": {
variables: [
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' },
{ key: 'variable', value: '$variable2' }
],
result: %w[variable2 variable3 variable]
},
"raw variable does not get resolved": { "raw variable does not get resolved": {
variables: [ variables: [
{ key: 'variable', value: '$variable2' }, { key: 'variable', value: '$variable2' },
...@@ -315,16 +154,32 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do ...@@ -315,16 +154,32 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sort do
end end
with_them do with_them do
let_it_be(:project) { create(:project) }
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) } let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
subject { Gitlab::Ci::Variables::Collection::Sort.new(collection, project).collection } subject { Gitlab::Ci::Variables::Collection::Sort.new(collection).tsort }
it 'returns correctly sorted variables' do it 'returns correctly sorted variables' do
expect(subject.map { |var| var[:key] }).to eq(result) expect(subject.map { |var| var[:key] }).to eq(result)
end end
end end
end end
context 'cyclic dependency' do
let(:variables) do
[
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' },
{ key: 'variable', value: '$variable2' }
]
end
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
subject { Gitlab::Ci::Variables::Collection::Sort.new(collection).tsort }
it 'raises TSort::Cyclic' do
expect { subject }.to raise_error(TSort::Cyclic)
end
end end
end end
end end
...@@ -113,67 +113,6 @@ RSpec.describe Gitlab::Ci::Variables::Collection do ...@@ -113,67 +113,6 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
end end
end end
describe '#==' do
variable = { key: 'VAR', value: 'value', public: true, masked: false }
context 'on empty collection' do
collection = described_class.new([])
it 'returns false for an array containing variable hash' do
expect(collection == [variable]).to eq(false)
end
it 'returns false for an unexpected type' do
expect(collection == variable).to eq(false)
end
it 'returns true for an empty array' do
expect(collection == []).to eq(true)
end
it 'returns true for the same object' do
expect(collection).to eq(collection)
end
it 'returns true for a similar object' do
expect(collection == described_class.new([])).to eq(true)
end
end
context 'on collection with a variable' do
collection = described_class.new([variable])
it 'returns false for an array containing other variable' do
expect(collection == [{ key: 'VAR', value: 'different value' }]).to eq(false)
end
it 'returns false for an empty array' do
expect(collection == []).to eq(false)
end
it 'returns false for an unexpected type' do
expect(collection == variable).to eq(false)
end
it 'returns false for a Collection with a variable with different attribute value' do
other = described_class.new([{ key: 'VAR', value: 'value', public: false, masked: false }])
expect(collection == other).to eq(false)
end
it 'returns true for an array containing variable hash' do
expect(collection == [variable]).to eq(true)
end
it 'returns true for the same object' do
expect(collection).to eq(collection)
end
it 'returns true for a similar object' do
expect(collection == described_class.new([variable])).to eq(true)
end
end
end
describe '#size' do describe '#size' do
it 'returns zero for empty collection' do it 'returns zero for empty collection' do
collection = described_class.new([]) collection = described_class.new([])
...@@ -227,68 +166,342 @@ RSpec.describe Gitlab::Ci::Variables::Collection do ...@@ -227,68 +166,342 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
end end
end end
describe '#sorted_collection' do describe '#reject' do
let!(:project) { create(:project) }
subject { collection.sorted_collection(project) }
context 'when FF :variable_inside_variable is disabled' do
before do
stub_feature_flags(variable_inside_variable: false)
end
let(:collection) do let(:collection) do
described_class.new described_class.new
.append(key: 'A', value: 'test-$B') .append(key: 'CI_JOB_NAME', value: 'test-1')
.append(key: 'B', value: 'test-$C') .append(key: 'CI_BUILD_ID', value: '1')
.append(key: 'C', value: 'test') .append(key: 'TEST1', value: 'test-3')
end
it { is_expected.to be(collection) }
end end
context 'when FF :variable_inside_variable is enabled' do subject { collection.reject { |var| var[:key] =~ /\ACI_(JOB|BUILD)/ } }
before do
stub_feature_flags(variable_inside_variable: [project])
end
let(:collection) do it 'returns a Collection instance' do
described_class.new is_expected.to be_an_instance_of(described_class)
.append(key: 'A', value: 'test-$B')
.append(key: 'B', value: 'test-$C')
.append(key: 'C', value: 'test')
end end
it { is_expected.to be_a(Gitlab::Ci::Variables::Collection) } it 'returns correctly filtered Collection' do
comp = collection.to_runner_variables.reject { |var| var[:key] =~ /\ACI_(JOB|BUILD)/ }
it 'returns sorted collection' do expect(subject.to_runner_variables).to eq(comp)
expect(subject.to_a).to eq(
[
{ key: 'C', value: 'test', masked: false, public: true },
{ key: 'B', value: 'test-$C', masked: false, public: true },
{ key: 'A', value: 'test-$B', masked: false, public: true }
])
end
end end
end end
describe '#reject' do describe '#expand_value' do
let(:collection) do let(:collection) do
described_class.new Gitlab::Ci::Variables::Collection.new
.append(key: 'CI_JOB_NAME', value: 'test-1') .append(key: 'CI_JOB_NAME', value: 'test-1')
.append(key: 'CI_BUILD_ID', value: '1') .append(key: 'CI_BUILD_ID', value: '1')
.append(key: 'RAW_VAR', value: '$TEST1', raw: true)
.append(key: 'TEST1', value: 'test-3') .append(key: 'TEST1', value: 'test-3')
end end
subject { collection.reject { |var| var[:key] =~ /\ACI_(JOB|BUILD)/ } } context 'table tests' do
using RSpec::Parameterized::TableSyntax
where do
{
"empty value": {
value: '',
result: '',
keep_undefined: false
},
"simple expansions": {
value: 'key$TEST1-$CI_BUILD_ID',
result: 'keytest-3-1',
keep_undefined: false
},
"complex expansion": {
value: 'key${TEST1}-${CI_JOB_NAME}',
result: 'keytest-3-test-1',
keep_undefined: false
},
"complex expansions with raw variable": {
value: 'key${RAW_VAR}-${CI_JOB_NAME}',
result: 'key$TEST1-test-1',
keep_undefined: false
},
"missing variable not keeping original": {
value: 'key${MISSING_VAR}-${CI_JOB_NAME}',
result: 'key-test-1',
keep_undefined: false
},
"missing variable keeping original": {
value: 'key${MISSING_VAR}-${CI_JOB_NAME}',
result: 'key${MISSING_VAR}-test-1',
keep_undefined: true
}
}
end
with_them do
subject { collection.expand_value(value, keep_undefined: keep_undefined) }
it 'matches expected expansion' do
is_expected.to eq(result)
end
end
end
end
describe '#expand_all' do
context 'when FF :variable_inside_variable is disabled' do
let_it_be(:project_with_flag_disabled) { create(:project) }
let_it_be(:project_with_flag_enabled) { create(:project) }
it 'returns a Collection instance' do before do
is_expected.to be_an_instance_of(described_class) stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
end
context 'table tests' do
using RSpec::Parameterized::TableSyntax
where do
{
"empty array": {
variables: [],
keep_undefined: false
},
"simple expansions": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key$variable$variable2' }
],
keep_undefined: false
},
"complex expansion": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'key${variable}' }
],
keep_undefined: false
},
"out-of-order variable reference": {
variables: [
{ key: 'variable2', value: 'key${variable}' },
{ key: 'variable', value: 'value' }
],
keep_undefined: false
},
"complex expansions with raw variable": {
variables: [
{ key: 'variable3', value: 'key_${variable}_${variable2}' },
{ key: 'variable', value: '$variable2', raw: true },
{ key: 'variable2', value: 'value2' }
],
keep_undefined: false
},
"array with cyclic dependency": {
variables: [
{ key: 'variable', value: '$variable2' },
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' }
],
keep_undefined: true
}
}
end
with_them do
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables, keep_undefined: keep_undefined) }
subject { collection.expand_all(project_with_flag_disabled) }
it 'returns Collection' do
is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection)
end
it 'does not expand variables' do
var_hash = variables.to_h { |env| [env.fetch(:key), env.fetch(:value)] }
.with_indifferent_access
expect(subject.to_hash).to eq(var_hash)
end
end
end
end end
it 'returns correctly filtered Collection' do context 'when FF :variable_inside_variable is enabled' do
comp = collection.to_runner_variables.reject { |var| var[:key] =~ /\ACI_(JOB|BUILD)/ } let_it_be(:project_with_flag_disabled) { create(:project) }
expect(subject.to_runner_variables).to eq(comp) let_it_be(:project_with_flag_enabled) { create(:project) }
before do
stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
end
context 'table tests' do
using RSpec::Parameterized::TableSyntax
where do
{
"empty array": {
variables: [],
keep_undefined: false,
result: []
},
"simple expansions": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key$variable$variable2' },
{ key: 'variable4', value: 'key$variable$variable3' }
],
keep_undefined: false,
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'keyvalueresult' },
{ key: 'variable4', value: 'keyvaluekeyvalueresult' }
]
},
"complex expansion": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'key${variable}' }
],
keep_undefined: false,
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'keyvalue' }
]
},
"unused variables": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result2' },
{ key: 'variable3', value: 'result3' },
{ key: 'variable4', value: 'key$variable$variable3' }
],
keep_undefined: false,
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result2' },
{ key: 'variable3', value: 'result3' },
{ key: 'variable4', value: 'keyvalueresult3' }
]
},
"complex expansions": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key${variable}${variable2}' }
],
keep_undefined: false,
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'keyvalueresult' }
]
},
"out-of-order expansion": {
variables: [
{ key: 'variable3', value: 'key$variable2$variable' },
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' }
],
keep_undefined: false,
result: [
{ key: 'variable2', value: 'result' },
{ key: 'variable', value: 'value' },
{ key: 'variable3', value: 'keyresultvalue' }
]
},
"out-of-order complex expansion": {
variables: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'key${variable2}${variable}' }
],
keep_undefined: false,
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' },
{ key: 'variable3', value: 'keyresultvalue' }
]
},
"missing variable": {
variables: [
{ key: 'variable2', value: 'key$variable' }
],
keep_undefined: false,
result: [
{ key: 'variable2', value: 'key' }
]
},
"missing variable keeping original": {
variables: [
{ key: 'variable2', value: 'key$variable' }
],
keep_undefined: true,
result: [
{ key: 'variable2', value: 'key$variable' }
]
},
"complex expansions with missing variable keeping original": {
variables: [
{ key: 'variable4', value: 'key${variable}${variable2}${variable3}' },
{ key: 'variable', value: 'value' },
{ key: 'variable3', value: 'value3' }
],
keep_undefined: true,
result: [
{ key: 'variable', value: 'value' },
{ key: 'variable3', value: 'value3' },
{ key: 'variable4', value: 'keyvalue${variable2}value3' }
]
},
"complex expansions with raw variable": {
variables: [
{ key: 'variable3', value: 'key_${variable}_${variable2}' },
{ key: 'variable', value: '$variable2', raw: true },
{ key: 'variable2', value: 'value2' }
],
keep_undefined: false,
result: [
{ key: 'variable', value: '$variable2', raw: true },
{ key: 'variable2', value: 'value2' },
{ key: 'variable3', value: 'key_$variable2_value2' }
]
},
"cyclic dependency causes original array to be returned": {
variables: [
{ key: 'variable', value: '$variable2' },
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' }
],
keep_undefined: false,
result: [
{ key: 'variable', value: '$variable2' },
{ key: 'variable2', value: '$variable3' },
{ key: 'variable3', value: 'key$variable$variable2' }
]
}
}
end
with_them do
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
subject { collection.expand_all(project_with_flag_enabled, keep_undefined: keep_undefined) }
it 'returns Collection' do
is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection)
end
it 'expands variables' do
var_hash = result.to_h { |env| [env.fetch(:key), env.fetch(:value)] }
.with_indifferent_access
expect(subject.to_hash).to eq(var_hash)
end
it 'preserves raw attribute' do
collection.each do |v|
k = v[:key]
subject_item = subject.find { |sv| sv[:key] == k }
expect(subject_item.raw).to eq(v.raw)
end
end
end
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment