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:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-20 12:55:51 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-20 12:55:51 +0300
commite8d2c2579383897a1dd7f9debd359abe8ae8373d (patch)
treec42be41678c2586d49a75cabce89322082698334 /spec/lib/gitlab/template_parser
parentfc845b37ec3a90aaa719975f607740c22ba6a113 (diff)
Add latest changes from gitlab-org/gitlab@14-1-stable-eev14.1.0-rc42
Diffstat (limited to 'spec/lib/gitlab/template_parser')
-rw-r--r--spec/lib/gitlab/template_parser/ast_spec.rb246
-rw-r--r--spec/lib/gitlab/template_parser/parser_spec.rb78
2 files changed, 324 insertions, 0 deletions
diff --git a/spec/lib/gitlab/template_parser/ast_spec.rb b/spec/lib/gitlab/template_parser/ast_spec.rb
new file mode 100644
index 00000000000..27361ea8632
--- /dev/null
+++ b/spec/lib/gitlab/template_parser/ast_spec.rb
@@ -0,0 +1,246 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::TemplateParser::AST::Identifier do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates a selector' do
+ data = { 'number' => 10 }
+
+ expect(described_class.new('number').evaluate(state, data)).to eq(10)
+ end
+
+ it 'returns nil if the key is not set' do
+ expect(described_class.new('number').evaluate(state, {})).to be_nil
+ end
+
+ it 'returns nil if the input is not a Hash' do
+ expect(described_class.new('number').evaluate(state, 45)).to be_nil
+ end
+
+ it 'returns the current data when using the special identifier "it"' do
+ expect(described_class.new('it').evaluate(state, 45)).to eq(45)
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Integer do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates a selector' do
+ expect(described_class.new(0).evaluate(state, [10])).to eq(10)
+ end
+
+ it 'returns nil if the index is not set' do
+ expect(described_class.new(1).evaluate(state, [10])).to be_nil
+ end
+
+ it 'returns nil if the input is not an Array' do
+ expect(described_class.new(0).evaluate(state, {})).to be_nil
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Selector do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+ let(:data) { { 'numbers' => [10] } }
+
+ describe '#evaluate' do
+ it 'evaluates a selector' do
+ ident = Gitlab::TemplateParser::AST::Identifier.new('numbers')
+ int = Gitlab::TemplateParser::AST::Integer.new(0)
+
+ expect(described_class.new([ident, int]).evaluate(state, data)).to eq(10)
+ end
+
+ it 'evaluates a selector that returns nil' do
+ int = Gitlab::TemplateParser::AST::Integer.new(0)
+
+ expect(described_class.new([int]).evaluate(state, data)).to be_nil
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Variable do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+ let(:data) { { 'numbers' => [10] } }
+
+ describe '#evaluate' do
+ it 'evaluates a variable' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{{numbers.0}}')
+ .nodes[0]
+
+ expect(node.evaluate(state, data)).to eq('10')
+ end
+
+ it 'evaluates an undefined variable' do
+ node =
+ Gitlab::TemplateParser::Parser.new.parse_and_transform('{{foobar}}').nodes[0]
+
+ expect(node.evaluate(state, data)).to eq('')
+ end
+
+ it 'evaluates the special variable "it"' do
+ node =
+ Gitlab::TemplateParser::Parser.new.parse_and_transform('{{it}}').nodes[0]
+
+ expect(node.evaluate(state, data)).to eq(data.to_s)
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Expressions do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates all expressions' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{{number}}foo')
+
+ expect(node.evaluate(state, { 'number' => 10 })).to eq('10foo')
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Text do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'returns the text' do
+ expect(described_class.new('foo').evaluate(state, {})).to eq('foo')
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::If do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates a truthy if expression without an else clause' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% if thing %}foo{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, { 'thing' => true })).to eq('foo')
+ end
+
+ it 'evaluates a falsy if expression without an else clause' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% if thing %}foo{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, { 'thing' => false })).to eq('')
+ end
+
+ it 'evaluates a falsy if expression with an else clause' do
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% if thing %}foo{% else %}bar{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, { 'thing' => false })).to eq('bar')
+ end
+ end
+
+ describe '#truthy?' do
+ it 'returns true for a non-empty String' do
+ expect(described_class.new.truthy?('foo')).to eq(true)
+ end
+
+ it 'returns true for a non-empty Array' do
+ expect(described_class.new.truthy?([10])).to eq(true)
+ end
+
+ it 'returns true for a Boolean true' do
+ expect(described_class.new.truthy?(true)).to eq(true)
+ end
+
+ it 'returns false for an empty String' do
+ expect(described_class.new.truthy?('')).to eq(false)
+ end
+
+ it 'returns true for an empty Array' do
+ expect(described_class.new.truthy?([])).to eq(false)
+ end
+
+ it 'returns false for a Boolean false' do
+ expect(described_class.new.truthy?(false)).to eq(false)
+ end
+ end
+end
+
+RSpec.describe Gitlab::TemplateParser::AST::Each do
+ let(:state) { Gitlab::TemplateParser::EvalState.new }
+
+ describe '#evaluate' do
+ it 'evaluates the expression' do
+ data = { 'animals' => [{ 'name' => 'Cat' }, { 'name' => 'Dog' }] }
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% each animals %}{{name}}{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, data)).to eq('CatDog')
+ end
+
+ it 'returns an empty string when the input is not a collection' do
+ data = { 'animals' => 10 }
+ node = Gitlab::TemplateParser::Parser
+ .new
+ .parse_and_transform('{% each animals %}{{name}}{% end %}')
+ .nodes[0]
+
+ expect(node.evaluate(state, data)).to eq('')
+ end
+
+ it 'disallows too many nested loops' do
+ data = {
+ 'foo' => [
+ {
+ 'bar' => [
+ {
+ 'baz' => [
+ {
+ 'quix' => [
+ {
+ 'foo' => [{ 'name' => 'Alice' }]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+
+ template = <<~TPL
+ {% each foo %}
+ {% each bar %}
+ {% each baz %}
+ {% each quix %}
+ {% each foo %}
+ {{name}}
+ {% end %}
+ {% end %}
+ {% end %}
+ {% end %}
+ {% end %}
+ TPL
+
+ node =
+ Gitlab::TemplateParser::Parser.new.parse_and_transform(template).nodes[0]
+
+ expect { node.evaluate(state, data) }
+ .to raise_error(Gitlab::TemplateParser::Error)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/template_parser/parser_spec.rb b/spec/lib/gitlab/template_parser/parser_spec.rb
new file mode 100644
index 00000000000..22247cbb693
--- /dev/null
+++ b/spec/lib/gitlab/template_parser/parser_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::TemplateParser::Parser do
+ let(:parser) { described_class.new }
+
+ describe '#root' do
+ it 'parses an empty template' do
+ expect(parser.root).to parse('')
+ end
+
+ it 'parses a variable with a single identifier step' do
+ expect(parser.root).to parse('{{foo}}')
+ end
+
+ it 'parses a variable with a single integer step' do
+ expect(parser.root).to parse('{{0}}')
+ end
+
+ it 'parses a variable with multiple selector steps' do
+ expect(parser.root).to parse('{{foo.bar}}')
+ end
+
+ it 'parses a variable with an integer selector step' do
+ expect(parser.root).to parse('{{foo.bar.0}}')
+ end
+
+ it 'parses the special "it" variable' do
+ expect(parser.root).to parse('{{it}}')
+ end
+
+ it 'parses a text node' do
+ expect(parser.root).to parse('foo')
+ end
+
+ it 'parses an if expression' do
+ expect(parser.root).to parse('{% if foo %}bar{% end %}')
+ end
+
+ it 'parses an if-else expression' do
+ expect(parser.root).to parse('{% if foo %}bar{% else %}baz{% end %}')
+ end
+
+ it 'parses an each expression' do
+ expect(parser.root).to parse('{% each foo %}foo{% end %}')
+ end
+
+ it 'parses an escaped newline' do
+ expect(parser.root).to parse("foo\\\nbar")
+ end
+
+ it 'parses a regular newline' do
+ expect(parser.root).to parse("foo\nbar")
+ end
+
+ it 'parses the default changelog template' do
+ expect(parser.root).to parse(Gitlab::Changelog::Config::DEFAULT_TEMPLATE)
+ end
+
+ it 'raises an error when parsing an integer selector that is too large' do
+ expect(parser.root).not_to parse('{{100000000000}}')
+ end
+ end
+
+ describe '#parse_and_transform' do
+ it 'parses and transforms a template' do
+ node = parser.parse_and_transform('foo')
+
+ expect(node).to be_instance_of(Gitlab::TemplateParser::AST::Expressions)
+ end
+
+ it 'raises parsing errors using a custom error class' do
+ expect { parser.parse_and_transform('{% each') }
+ .to raise_error(Gitlab::TemplateParser::Error)
+ end
+ end
+end