diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 12:55:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 12:55:51 +0300 |
commit | e8d2c2579383897a1dd7f9debd359abe8ae8373d (patch) | |
tree | c42be41678c2586d49a75cabce89322082698334 /spec/lib/gitlab/template_parser | |
parent | fc845b37ec3a90aaa719975f607740c22ba6a113 (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.rb | 246 | ||||
-rw-r--r-- | spec/lib/gitlab/template_parser/parser_spec.rb | 78 |
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 |