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/scripts')
-rw-r--r--spec/scripts/changed-feature-flags_spec.rb118
-rw-r--r--spec/scripts/lib/glfm/shared_spec.rb36
-rw-r--r--spec/scripts/lib/glfm/update_example_snapshots_spec.rb316
-rw-r--r--spec/scripts/lib/glfm/update_specification_spec.rb196
-rw-r--r--spec/scripts/trigger-build_spec.rb970
5 files changed, 1621 insertions, 15 deletions
diff --git a/spec/scripts/changed-feature-flags_spec.rb b/spec/scripts/changed-feature-flags_spec.rb
index 5c858588c0c..bbae49a90e4 100644
--- a/spec/scripts/changed-feature-flags_spec.rb
+++ b/spec/scripts/changed-feature-flags_spec.rb
@@ -6,8 +6,8 @@ load File.expand_path('../../scripts/changed-feature-flags', __dir__)
RSpec.describe 'scripts/changed-feature-flags' do
describe GetFeatureFlagsFromFiles do
- let(:feature_flag_definition1) do
- file = Tempfile.new('foo.yml', ff_dir)
+ let!(:feature_flag_definition1) do
+ file = File.open(File.join(ff_dir, "#{file_name1}.yml"), 'w+')
file.write(<<~YAML)
---
name: foo_flag
@@ -17,8 +17,8 @@ RSpec.describe 'scripts/changed-feature-flags' do
file
end
- let(:feature_flag_definition2) do
- file = Tempfile.new('bar.yml', ff_dir)
+ let!(:feature_flag_definition2) do
+ file = File.open(File.join(ff_dir, "#{file_name2}.yml"), 'w+')
file.write(<<~YAML)
---
name: bar_flag
@@ -28,48 +28,136 @@ RSpec.describe 'scripts/changed-feature-flags' do
file
end
+ let!(:feature_flag_diff1) do
+ FileUtils.mkdir_p(File.join(diffs_dir, ff_sub_dir))
+ file = File.open(File.join(diffs_dir, ff_sub_dir, "#{file_name1}.yml.diff"), 'w+')
+ file.write(<<~YAML)
+ @@ -5,4 +5,4 @@
+ name: foo_flag
+ -default_enabled: false
+ +default_enabled: true
+ YAML
+ file.rewind
+ file
+ end
+
+ let!(:feature_flag_diff2) do
+ FileUtils.mkdir_p(File.join(diffs_dir, ff_sub_dir))
+ file = File.open(File.join(diffs_dir, ff_sub_dir, "#{file_name2}.yml.diff"), 'w+')
+ file.write(<<~YAML)
+ @@ -0,0 +0,0 @@
+ name: bar_flag
+ -default_enabled: true
+ +default_enabled: false
+ YAML
+ file.rewind
+ file
+ end
+
+ let!(:deleted_feature_flag_diff) do
+ FileUtils.mkdir_p(File.join(diffs_dir, ff_sub_dir))
+ file = File.open(File.join(diffs_dir, ff_sub_dir, "foobar_ff_#{SecureRandom.hex(8)}.yml.deleted.diff"), 'w+')
+ file.write(<<~YAML)
+ @@ -0,0 +0,0 @@
+ -name: foobar_flag
+ -default_enabled: true
+ YAML
+ file.rewind
+ file
+ end
+
+ before do
+ allow(Dir).to receive(:pwd).and_return(Dir.tmpdir)
+ end
+
after do
- FileUtils.remove_entry(ff_dir, true)
+ feature_flag_definition1.close
+ feature_flag_definition2.close
+ feature_flag_diff1.close
+ feature_flag_diff2.close
+ deleted_feature_flag_diff.close
+ FileUtils.rm_r(ff_dir)
+ FileUtils.rm_r(diffs_dir)
end
describe '.extracted_flags' do
+ let(:file_name1) { "foo_ff_#{SecureRandom.hex(8)}"}
+ let(:file_name2) { "bar_ff_#{SecureRandom.hex(8)}"}
+ let(:ff_dir) { FileUtils.mkdir_p(File.join(Dir.tmpdir, ff_sub_dir)) }
+ let(:diffs_dir) { FileUtils.mkdir_p(File.join(Dir.tmpdir, 'diffs')).first }
+
shared_examples 'extract feature flags' do
it 'returns feature flags on their own' do
- subject = described_class.new({ files: [feature_flag_definition1.path, feature_flag_definition2.path] })
+ subject = described_class.new({ files: diffs_dir })
- expect(subject.extracted_flags).to eq('foo_flag,bar_flag')
+ expect(subject.extracted_flags.split(',')).to include('foo_flag', 'bar_flag')
end
it 'returns feature flags and their state as enabled' do
- subject = described_class.new({ files: [feature_flag_definition1.path, feature_flag_definition2.path], state: 'enabled' })
+ subject = described_class.new({ files: diffs_dir, state: 'enabled' })
- expect(subject.extracted_flags).to eq('foo_flag=enabled,bar_flag=enabled')
+ expect(subject.extracted_flags.split(',')).to include('foo_flag=enabled', 'bar_flag=enabled')
end
it 'returns feature flags and their state as disabled' do
- subject = described_class.new({ files: [feature_flag_definition1.path, feature_flag_definition2.path], state: 'disabled' })
+ subject = described_class.new({ files: diffs_dir, state: 'disabled' })
+
+ expect(subject.extracted_flags.split(',')).to include('foo_flag=disabled', 'bar_flag=disabled')
+ end
+
+ it 'does not return feature flags when there are mixed deleted and non-deleted definition files' do
+ subject = described_class.new({ files: diffs_dir, state: 'deleted' })
- expect(subject.extracted_flags).to eq('foo_flag=disabled,bar_flag=disabled')
+ expect(subject.extracted_flags).to eq('')
end
end
context 'with definition files in the development directory' do
- let(:ff_dir) { FileUtils.mkdir_p(File.join(Dir.tmpdir, 'feature_flags', 'development')) }
+ let(:ff_sub_dir) { %w[feature_flags development] }
it_behaves_like 'extract feature flags'
end
context 'with definition files in the ops directory' do
- let(:ff_dir) { FileUtils.mkdir_p(File.join(Dir.tmpdir, 'feature_flags', 'ops')) }
+ let(:ff_sub_dir) { %w[feature_flags ops] }
it_behaves_like 'extract feature flags'
end
context 'with definition files in the experiment directory' do
- let(:ff_dir) { FileUtils.mkdir_p(File.join(Dir.tmpdir, 'feature_flags', 'experiment')) }
+ let(:ff_sub_dir) { %w[feature_flags experiment] }
it 'ignores the files' do
- subject = described_class.new({ files: [feature_flag_definition1.path, feature_flag_definition2.path] })
+ subject = described_class.new({ files: diffs_dir })
+
+ expect(subject.extracted_flags).to eq('')
+ end
+ end
+
+ context 'with only deleted definition files' do
+ let(:ff_sub_dir) { %w[feature_flags development] }
+
+ before do
+ feature_flag_diff1.close
+ feature_flag_diff2.close
+ FileUtils.rm_r(feature_flag_diff1)
+ FileUtils.rm_r(feature_flag_diff2)
+ end
+
+ it 'returns feature flags and their state as deleted' do
+ subject = described_class.new({ files: diffs_dir, state: 'deleted' })
+
+ expect(subject.extracted_flags).to eq('foobar_flag=deleted')
+ end
+
+ it 'does not return feature flags when the desired state is enabled' do
+ subject = described_class.new({ files: diffs_dir, state: 'enabled' })
+
+ expect(subject.extracted_flags).to eq('')
+ end
+
+ it 'does not return feature flags when the desired state is disabled' do
+ subject = described_class.new({ files: diffs_dir, state: 'disabled' })
expect(subject.extracted_flags).to eq('')
end
diff --git a/spec/scripts/lib/glfm/shared_spec.rb b/spec/scripts/lib/glfm/shared_spec.rb
new file mode 100644
index 00000000000..f6792b93718
--- /dev/null
+++ b/spec/scripts/lib/glfm/shared_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+require 'fast_spec_helper'
+require_relative '../../../../scripts/lib/glfm/shared'
+
+RSpec.describe Glfm::Shared do
+ let(:instance) do
+ Class.new do
+ include Glfm::Shared
+ end.new
+ end
+
+ describe '#run_external_cmd' do
+ it 'works' do
+ expect(instance.run_external_cmd('echo "hello"')).to eq("hello\n")
+ end
+
+ context 'when command fails' do
+ it 'raises error' do
+ invalid_cmd = 'ls nonexistent_file'
+ expect(instance).to receive(:warn).with(/Error running command `#{invalid_cmd}`/)
+ expect(instance).to receive(:warn).with(/nonexistent_file.*no such file/i)
+ expect { instance.run_external_cmd(invalid_cmd) }.to raise_error(RuntimeError)
+ end
+ end
+ end
+
+ describe '#output' do
+ # NOTE: The #output method is normally always mocked, to prevent output while the specs are
+ # running. However, in order to provide code coverage for the method, we have to invoke
+ # it at least once.
+ it 'has code coverage' do
+ allow(instance).to receive(:puts)
+ instance.output('')
+ end
+ end
+end
diff --git a/spec/scripts/lib/glfm/update_example_snapshots_spec.rb b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb
new file mode 100644
index 00000000000..169f5d1c5a6
--- /dev/null
+++ b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb
@@ -0,0 +1,316 @@
+# frozen_string_literal: true
+require 'fast_spec_helper'
+require_relative '../../../../scripts/lib/glfm/update_example_snapshots'
+
+RSpec.describe Glfm::UpdateExampleSnapshots, '#process' do
+ subject { described_class.new }
+
+ # GLFM input files
+ let(:glfm_spec_txt_path) { described_class::GLFM_SPEC_TXT_PATH }
+ let(:glfm_spec_txt_local_io) { StringIO.new(glfm_spec_txt_contents) }
+ let(:glfm_example_status_yml_path) { described_class::GLFM_EXAMPLE_STATUS_YML_PATH }
+ let(:glfm_example_status_yml_io) { StringIO.new(glfm_example_status_yml_contents) }
+
+ # Example Snapshot (ES) output files
+ let(:es_examples_index_yml_path) { described_class::ES_EXAMPLES_INDEX_YML_PATH }
+ let(:es_examples_index_yml_io) { StringIO.new }
+ let(:es_markdown_yml_path) { described_class::ES_MARKDOWN_YML_PATH }
+ let(:es_markdown_yml_io) { StringIO.new }
+ let(:es_html_yml_path) { described_class::ES_HTML_YML_PATH }
+ let(:es_html_yml_io) { StringIO.new }
+ let(:es_prosemirror_json_yml_path) { described_class::ES_PROSEMIRROR_JSON_YML_PATH }
+ let(:es_prosemirror_json_yml_io) { StringIO.new }
+
+ # Internal tempfiles
+ let(:static_html_tempfile_path) { Tempfile.new.path }
+
+ let(:glfm_spec_txt_contents) do
+ <<~GLFM_SPEC_TXT_CONTENTS
+ ---
+ title: GitLab Flavored Markdown Spec
+ ...
+
+ # Introduction
+
+ GLFM intro text...
+
+ # Inlines
+
+ ## Strong
+
+ ```````````````````````````````` example
+ __bold__
+ .
+ <p><strong>bold</strong></p>
+ ````````````````````````````````
+
+ ```````````````````````````````` example strikethrough
+ __bold with more text__
+ .
+ <p><strong>bold with more text</strong></p>
+ ````````````````````````````````
+
+ <div class="extension">
+
+ ## Strikethrough (extension)
+
+ GFM enables the `strikethrough` extension.
+
+ ```````````````````````````````` example strikethrough
+ ~~Hi~~ Hello, world!
+ .
+ <p><del>Hi</del> Hello, world!</p>
+ ````````````````````````````````
+
+ </div>
+
+ End of last GitHub examples section.
+
+ # First GitLab-Specific Section with Examples
+
+ ## Strong but with two asterisks
+
+ ```````````````````````````````` example gitlab strong
+ **bold**
+ .
+ <p><strong>bold</strong></p>
+ ````````````````````````````````
+
+ # Second GitLab-Specific Section with Examples
+
+ ## Strong but with HTML
+
+ ```````````````````````````````` example gitlab strong
+ <strong>
+ bold
+ </strong>
+ .
+ <p><strong>
+ bold
+ </strong></p>
+ ````````````````````````````````
+
+ <!-- END TESTS -->
+
+ # Appendix
+
+ Appendix text.
+ GLFM_SPEC_TXT_CONTENTS
+ end
+
+ let(:glfm_example_status_yml_contents) do
+ <<~GLFM_EXAMPLE_STATUS_YML_CONTENTS
+ ---
+ - 07_01_first_gitlab_specific_section_with_examples_strong_but_with_two_asterisks:
+ skip_update_example_snapshots: false
+ skip_running_snapshot_static_html_tests: false
+ skip_running_snapshot_wysiwyg_html_tests: false
+ skip_running_snapshot_prosemirror_json_tests: false
+ skip_running_conformance_static_tests: false
+ skip_running_conformance_wysiwyg_tests: false
+ - 07_02_first_gitlab_specific_section_with_examples_strong_but_with_html:
+ skip_update_example_snapshots: false
+ skip_running_snapshot_static_html_tests: false
+ skip_running_snapshot_wysiwyg_html_tests: false
+ skip_running_snapshot_prosemirror_json_tests: false
+ skip_running_conformance_static_tests: false
+ skip_running_conformance_wysiwyg_tests: false
+ GLFM_EXAMPLE_STATUS_YML_CONTENTS
+ end
+
+ before do
+ # We mock out the URI and local file IO objects with real StringIO, instead of just mock
+ # objects. This gives better and more realistic coverage, while still avoiding
+ # actual network and filesystem I/O during the spec run.
+
+ # input files
+ allow(File).to receive(:open).with(glfm_spec_txt_path) { glfm_spec_txt_local_io }
+ allow(File).to receive(:open).with(glfm_example_status_yml_path) { glfm_example_status_yml_io }
+
+ # output files
+ allow(File).to receive(:open).with(es_examples_index_yml_path, 'w') { es_examples_index_yml_io }
+ allow(File).to receive(:open).with(es_html_yml_path, 'w') { es_html_yml_io }
+ allow(File).to receive(:open).with(es_prosemirror_json_yml_path, 'w') { es_prosemirror_json_yml_io }
+
+ # output files which are also input files
+ allow(File).to receive(:open).with(es_markdown_yml_path, 'w') { es_markdown_yml_io }
+ allow(File).to receive(:open).with(es_markdown_yml_path) { es_markdown_yml_io }
+
+ # Allow normal opening of Tempfile files created during script execution.
+ tempfile_basenames = [
+ described_class::MARKDOWN_TEMPFILE_BASENAME[0],
+ described_class::STATIC_HTML_TEMPFILE_BASENAME[0],
+ described_class::WYSIWYG_HTML_AND_JSON_TEMPFILE_BASENAME[0]
+ ].join('|')
+ # NOTE: This approach with a single regex seems to be the only way this can work. If you
+ # attempt to have multiple `allow...and_call_original` with `any_args`, the mocked
+ # parameter matching will fail to match the second one.
+ tempfiles_regex = /(#{tempfile_basenames})/
+ allow(File).to receive(:open).with(tempfiles_regex, any_args).and_call_original
+
+ # Prevent console output when running tests
+ allow(subject).to receive(:output)
+ end
+
+ describe 'writing examples_index.yml' do
+ let(:es_examples_index_yml_contents) { reread_io(es_examples_index_yml_io) }
+
+ it 'writes the correct content' do
+ subject.process(skip_static_and_wysiwyg: true)
+
+ expected =
+ <<~ES_EXAMPLES_INDEX_YML_CONTENTS
+ ---
+ 02_01__inlines__strong__01:
+ spec_txt_example_position: 1
+ source_specification: commonmark
+ 02_01__inlines__strong__02:
+ spec_txt_example_position: 2
+ source_specification: github
+ 02_02__inlines__strikethrough_extension__01:
+ spec_txt_example_position: 3
+ source_specification: github
+ 03_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__01:
+ spec_txt_example_position: 4
+ source_specification: gitlab
+ 04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__01:
+ spec_txt_example_position: 5
+ source_specification: gitlab
+ ES_EXAMPLES_INDEX_YML_CONTENTS
+ expect(es_examples_index_yml_contents).to eq(expected)
+ end
+ end
+
+ describe 'writing markdown.yml' do
+ let(:es_markdown_yml_contents) { reread_io(es_markdown_yml_io) }
+
+ it 'writes the correct content' do
+ subject.process(skip_static_and_wysiwyg: true)
+
+ expected =
+ <<~ES_MARKDOWN_YML_CONTENTS
+ ---
+ 02_01__inlines__strong__01: |
+ __bold__
+ 02_01__inlines__strong__02: |
+ __bold with more text__
+ 02_02__inlines__strikethrough_extension__01: |
+ ~~Hi~~ Hello, world!
+ 03_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__01: |
+ **bold**
+ 04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__01: |
+ <strong>
+ bold
+ </strong>
+ ES_MARKDOWN_YML_CONTENTS
+
+ expect(es_markdown_yml_contents).to eq(expected)
+ end
+ end
+
+ describe 'writing html.yml and prosemirror_json.yml' do
+ let(:es_html_yml_contents) { reread_io(es_html_yml_io) }
+ let(:es_prosemirror_json_yml_contents) { reread_io(es_prosemirror_json_yml_io) }
+
+ let(:glfm_example_status_yml_contents) do
+ <<~GLFM_EXAMPLE_STATUS_YML_CONTENTS
+ ---
+ - 02_01_gitlab_specific_section_with_examples_strong_but_with_two_asterisks:
+ skip_update_example_snapshots: false
+ skip_running_snapshot_static_html_tests: false
+ skip_running_snapshot_wysiwyg_html_tests: false
+ skip_running_snapshot_prosemirror_json_tests: false
+ skip_running_conformance_static_tests: false
+ skip_running_conformance_wysiwyg_tests: false
+ GLFM_EXAMPLE_STATUS_YML_CONTENTS
+ end
+
+ let(:glfm_spec_txt_contents) do
+ <<~GLFM_SPEC_TXT_CONTENTS
+ ---
+ title: GitLab Flavored Markdown Spec
+ ...
+
+ # Introduction
+
+ # GitLab-Specific Section with Examples
+
+ ## Strong but with two asterisks
+
+ ```````````````````````````````` example gitlab strong
+ **bold**
+ .
+ <p><strong>bold</strong></p>
+ ````````````````````````````````
+ <!-- END TESTS -->
+
+ # Appendix
+
+ Appendix text.
+ GLFM_SPEC_TXT_CONTENTS
+ end
+
+ before do
+ # NOTE: This is a necessary to avoid an `error Couldn't find an integrity file` error
+ # when invoking `yarn jest ...` on CI from within an RSpec job. It could be solved by
+ # adding `.yarn-install` to be included in the RSpec CI job, but that would be a performance
+ # hit to all RSpec jobs. We could also make a dedicate job just for this spec. However,
+ # since this is just a single script, those options may not be justified.
+ described_class.new.run_external_cmd('yarn install') if ENV['CI']
+ end
+
+ # NOTE: Both `html.yml` and `prosemirror_json.yml` generation are tested in a single example, to
+ # avoid slower tests, because generating the static HTML is slow due to the need to invoke
+ # the rails environment. We could have separate sections, but this would require an extra flag
+ # to the `process` method to independently skip static vs. WYSIWYG, which is not worth the effort.
+ it 'writes the correct content' do
+ subject.process
+
+ expected_html =
+ <<~ES_HTML_YML_CONTENTS
+ ---
+ 02_01__gitlab_specific_section_with_examples__strong_but_with_two_asterisks__01:
+ canonical: |
+ <p><strong>bold</strong></p>
+ static: |-
+ <p data-sourcepos="1:1-1:8" dir="auto"><strong>bold</strong></p>
+ wysiwyg: |-
+ <p><strong>bold</strong></p>
+ ES_HTML_YML_CONTENTS
+
+ expected_prosemirror_json =
+ <<~ES_PROSEMIRROR_JSON_YML_CONTENTS
+ ---
+ 02_01__gitlab_specific_section_with_examples__strong_but_with_two_asterisks__01: |-
+ {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ],
+ "text": "bold"
+ }
+ ]
+ }
+ ]
+ }
+ ES_PROSEMIRROR_JSON_YML_CONTENTS
+
+ expect(es_html_yml_contents).to eq(expected_html)
+ expect(es_prosemirror_json_yml_contents).to eq(expected_prosemirror_json)
+ end
+ end
+
+ def reread_io(io)
+ # Reset the io StringIO to the beginning position of the buffer
+ io.seek(0)
+ io.read
+ end
+end
diff --git a/spec/scripts/lib/glfm/update_specification_spec.rb b/spec/scripts/lib/glfm/update_specification_spec.rb
new file mode 100644
index 00000000000..e8d34b13efa
--- /dev/null
+++ b/spec/scripts/lib/glfm/update_specification_spec.rb
@@ -0,0 +1,196 @@
+# frozen_string_literal: true
+require 'fast_spec_helper'
+require_relative '../../../../scripts/lib/glfm/update_specification'
+
+RSpec.describe Glfm::UpdateSpecification, '#process' do
+ subject { described_class.new }
+
+ let(:ghfm_spec_txt_uri) { described_class::GHFM_SPEC_TXT_URI }
+ let(:ghfm_spec_txt_uri_io) { StringIO.new(ghfm_spec_txt_contents) }
+ let(:ghfm_spec_txt_path) { described_class::GHFM_SPEC_TXT_PATH }
+ let(:ghfm_spec_txt_local_io) { StringIO.new(ghfm_spec_txt_contents) }
+
+ let(:glfm_intro_txt_path) { described_class::GLFM_INTRO_TXT_PATH }
+ let(:glfm_intro_txt_io) { StringIO.new(glfm_intro_txt_contents) }
+ let(:glfm_examples_txt_path) { described_class::GLFM_EXAMPLES_TXT_PATH }
+ let(:glfm_examples_txt_io) { StringIO.new(glfm_examples_txt_contents) }
+ let(:glfm_spec_txt_path) { described_class::GLFM_SPEC_TXT_PATH }
+ let(:glfm_spec_txt_io) { StringIO.new }
+
+ let(:ghfm_spec_txt_contents) do
+ <<~GHFM_SPEC_TXT_CONTENTS
+ ---
+ title: GitHub Flavored Markdown Spec
+ version: 0.29
+ date: '2019-04-06'
+ license: '[CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)'
+ ...
+
+ # Introduction
+
+ ## What is GitHub Flavored Markdown?
+
+ It's like GLFM, but with an H.
+
+ # Section with Examples
+
+ ## Strong
+
+ ```````````````````````````````` example
+ __bold__
+ .
+ <p><strong>bold</strong></p>
+ ````````````````````````````````
+
+ End of last GitHub examples section.
+
+ <!-- END TESTS -->
+
+ # Appendix
+
+ Appendix text.
+ GHFM_SPEC_TXT_CONTENTS
+ end
+
+ let(:glfm_intro_txt_contents) do
+ <<~GLFM_INTRO_TXT_CONTENTS
+ # Introduction
+
+ ## What is GitLab Flavored Markdown?
+
+ Intro text about GitLab Flavored Markdown.
+ GLFM_INTRO_TXT_CONTENTS
+ end
+
+ let(:glfm_examples_txt_contents) do
+ <<~GLFM_EXAMPLES_TXT_CONTENTS
+ # GitLab-Specific Section with Examples
+
+ Some examples.
+ GLFM_EXAMPLES_TXT_CONTENTS
+ end
+
+ before do
+ # Mock default ENV var values
+ allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return(nil)
+ allow(ENV).to receive(:[]).and_call_original
+
+ # We mock out the URI and local file IO objects with real StringIO, instead of just mock
+ # objects. This gives better and more realistic coverage, while still avoiding
+ # actual network and filesystem I/O during the spec run.
+ allow(URI).to receive(:open).with(ghfm_spec_txt_uri) { ghfm_spec_txt_uri_io }
+ allow(File).to receive(:open).with(ghfm_spec_txt_path) { ghfm_spec_txt_local_io }
+ allow(File).to receive(:open).with(glfm_intro_txt_path) { glfm_intro_txt_io }
+ allow(File).to receive(:open).with(glfm_examples_txt_path) { glfm_examples_txt_io }
+ allow(File).to receive(:open).with(glfm_spec_txt_path, 'w') { glfm_spec_txt_io }
+
+ # Prevent console output when running tests
+ allow(subject).to receive(:output)
+ end
+
+ describe 'retrieving latest GHFM spec.txt' do
+ context 'when UPDATE_GHFM_SPEC_TXT is not true (default)' do
+ it 'does not download' do
+ expect(URI).not_to receive(:open).with(ghfm_spec_txt_uri)
+
+ subject.process
+
+ expect(reread_io(ghfm_spec_txt_local_io)).to eq(ghfm_spec_txt_contents)
+ end
+ end
+
+ context 'when UPDATE_GHFM_SPEC_TXT is true' do
+ let(:ghfm_spec_txt_local_io) { StringIO.new }
+
+ before do
+ allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return('true')
+ allow(File).to receive(:open).with(ghfm_spec_txt_path, 'w') { ghfm_spec_txt_local_io }
+ end
+
+ context 'with success' do
+ it 'downloads and saves' do
+ subject.process
+
+ expect(reread_io(ghfm_spec_txt_local_io)).to eq(ghfm_spec_txt_contents)
+ end
+ end
+
+ context 'with error handling' do
+ context 'with a version mismatch' do
+ let(:ghfm_spec_txt_contents) do
+ <<~GHFM_SPEC_TXT_CONTENTS
+ ---
+ title: GitHub Flavored Markdown Spec
+ version: 0.30
+ ...
+ GHFM_SPEC_TXT_CONTENTS
+ end
+
+ it 'raises an error' do
+ expect { subject.process }.to raise_error /version mismatch.*expected.*29.*got.*30/i
+ end
+ end
+
+ context 'with a failed read of file lines' do
+ let(:ghfm_spec_txt_contents) { '' }
+
+ it 'raises an error if lines cannot be read' do
+ expect { subject.process }.to raise_error /unable to read lines/i
+ end
+ end
+
+ context 'with a failed re-read of file string' do
+ before do
+ allow(ghfm_spec_txt_uri_io).to receive(:read).and_return(nil)
+ end
+
+ it 'raises an error if file is blank' do
+ expect { subject.process }.to raise_error /unable to read string/i
+ end
+ end
+ end
+ end
+ end
+
+ describe 'writing GLFM spec.txt' do
+ let(:glfm_contents) { reread_io(glfm_spec_txt_io) }
+
+ before do
+ subject.process
+ end
+
+ it 'replaces the header text with the GitLab version' do
+ expect(glfm_contents).not_to match(/GitHub Flavored Markdown Spec/m)
+ expect(glfm_contents).not_to match(/^version: \d\.\d/m)
+ expect(glfm_contents).not_to match(/^date: /m)
+ expect(glfm_contents).not_to match(/^license: /m)
+ expect(glfm_contents).to match(/#{Regexp.escape(described_class::GLFM_SPEC_TXT_HEADER)}\n/mo)
+ end
+
+ it 'replaces the intro section with the GitLab version' do
+ expect(glfm_contents).not_to match(/What is GitHub Flavored Markdown/m)
+ expect(glfm_contents).to match(/#{Regexp.escape(glfm_intro_txt_contents)}/m)
+ end
+
+ it 'inserts the GitLab examples sections before the appendix section' do
+ expected = <<~GHFM_SPEC_TXT_CONTENTS
+ End of last GitHub examples section.
+
+ # GitLab-Specific Section with Examples
+
+ Some examples.
+
+ <!-- END TESTS -->
+
+ # Appendix
+ GHFM_SPEC_TXT_CONTENTS
+ expect(glfm_contents).to match(/#{Regexp.escape(expected)}/m)
+ end
+ end
+
+ def reread_io(io)
+ # Reset the io StringIO to the beginning position of the buffer
+ io.seek(0)
+ io.read
+ end
+end
diff --git a/spec/scripts/trigger-build_spec.rb b/spec/scripts/trigger-build_spec.rb
new file mode 100644
index 00000000000..76a3cdbeaa2
--- /dev/null
+++ b/spec/scripts/trigger-build_spec.rb
@@ -0,0 +1,970 @@
+# frozen_string_literal: true
+# rubocop:disable RSpec/VerifiedDoubles
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+require_relative '../../scripts/trigger-build'
+
+RSpec.describe Trigger do
+ let(:env) do
+ {
+ 'CI_JOB_URL' => 'ci_job_url',
+ 'CI_PROJECT_PATH' => 'ci_project_path',
+ 'CI_COMMIT_REF_NAME' => 'ci_commit_ref_name',
+ 'CI_COMMIT_REF_SLUG' => 'ci_commit_ref_slug',
+ 'CI_COMMIT_SHA' => 'ci_commit_sha',
+ 'CI_MERGE_REQUEST_PROJECT_ID' => 'ci_merge_request_project_id',
+ 'CI_MERGE_REQUEST_IID' => 'ci_merge_request_iid',
+ 'GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN' => 'bot-token',
+ 'CI_JOB_TOKEN' => 'job-token',
+ 'GITLAB_USER_NAME' => 'gitlab_user_name',
+ 'GITLAB_USER_LOGIN' => 'gitlab_user_login',
+ 'QA_IMAGE' => 'qa_image',
+ 'OMNIBUS_GITLAB_CACHE_UPDATE' => 'omnibus_gitlab_cache_update',
+ 'OMNIBUS_GITLAB_PROJECT_ACCESS_TOKEN' => nil,
+ 'DOCS_PROJECT_API_TOKEN' => nil
+ }
+ end
+
+ let(:com_api_endpoint) { 'https://gitlab.com/api/v4' }
+ let(:com_api_token) { env['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN'] }
+ let(:com_gitlab_client) { double('com_gitlab_client') }
+
+ let(:downstream_gitlab_client_endpoint) { com_api_endpoint }
+ let(:downstream_gitlab_client_token) { com_api_token }
+ let(:downstream_gitlab_client) { com_gitlab_client }
+
+ let(:stubbed_pipeline) { Struct.new(:id, :web_url).new(42, 'pipeline_url') }
+ let(:trigger_token) { env['CI_JOB_TOKEN'] }
+
+ before do
+ stub_env(env)
+ allow(subject).to receive(:puts)
+ allow(Gitlab).to receive(:client)
+ .with(
+ endpoint: downstream_gitlab_client_endpoint,
+ private_token: downstream_gitlab_client_token
+ )
+ .and_return(downstream_gitlab_client)
+ end
+
+ def expect_run_trigger_with_params(variables = {})
+ expect(downstream_gitlab_client).to receive(:run_trigger)
+ .with(
+ downstream_project_path,
+ trigger_token,
+ ref,
+ hash_including(variables)
+ )
+ .and_return(stubbed_pipeline)
+ end
+
+ describe Trigger::Base do
+ let(:ref) { 'main' }
+
+ describe '#invoke!' do
+ context "when required methods aren't defined" do
+ it 'raises a NotImplementedError' do
+ expect { described_class.new.invoke! }.to raise_error(NotImplementedError)
+ end
+ end
+
+ context "when required methods are defined" do
+ let(:downstream_project_path) { 'foo/bar' }
+ let(:subclass) do
+ Class.new(Trigger::Base) do
+ def downstream_project_path
+ 'foo/bar'
+ end
+
+ # Must be overridden
+ def ref_param_name
+ 'FOO_BAR_BRANCH'
+ end
+ end
+ end
+
+ subject { subclass.new }
+
+ context 'when env variable `FOO_BAR_BRANCH` does not exist' do
+ it 'triggers the pipeline on the correct project and branch' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+ end
+
+ context 'when env variable `FOO_BAR_BRANCH` exists' do
+ let(:ref) { 'foo_bar_branch' }
+
+ before do
+ stub_env('FOO_BAR_BRANCH', ref)
+ end
+
+ it 'triggers the pipeline on the correct project and branch' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+ end
+
+ it 'waits for downstream pipeline' do
+ expect_run_trigger_with_params
+ expect(Trigger::Pipeline).to receive(:new)
+ .with(downstream_project_path, stubbed_pipeline.id, downstream_gitlab_client)
+
+ subject.invoke!
+ end
+
+ context 'with downstream_job_name: "foo"' do
+ let(:downstream_job) { Struct.new(:id, :name).new(42, 'foo') }
+ let(:paginated_resources) { Struct.new(:auto_paginate).new([downstream_job]) }
+
+ before do
+ stub_env('CI_COMMIT_REF_NAME', "#{ref}-ee")
+ end
+
+ it 'fetches the downstream job' do
+ expect_run_trigger_with_params
+ expect(downstream_gitlab_client).to receive(:pipeline_jobs)
+ .with(downstream_project_path, stubbed_pipeline.id).and_return(paginated_resources)
+ expect(Trigger::Job).to receive(:new)
+ .with(downstream_project_path, downstream_job.id, downstream_gitlab_client)
+
+ subject.invoke!(downstream_job_name: 'foo')
+ end
+ end
+ end
+ end
+
+ describe '#variables' do
+ let(:simple_forwarded_variables) do
+ {
+ 'TRIGGER_SOURCE' => env['CI_JOB_URL'],
+ 'TOP_UPSTREAM_SOURCE_PROJECT' => env['CI_PROJECT_PATH'],
+ 'TOP_UPSTREAM_SOURCE_REF' => env['CI_COMMIT_REF_NAME'],
+ 'TOP_UPSTREAM_SOURCE_JOB' => env['CI_JOB_URL'],
+ 'TOP_UPSTREAM_MERGE_REQUEST_PROJECT_ID' => env['CI_MERGE_REQUEST_PROJECT_ID'],
+ 'TOP_UPSTREAM_MERGE_REQUEST_IID' => env['CI_MERGE_REQUEST_IID']
+ }
+ end
+
+ it 'includes simple forwarded variables' do
+ expect(subject.variables).to include(simple_forwarded_variables)
+ end
+
+ describe "#base_variables" do
+ context 'when CI_COMMIT_TAG is set' do
+ before do
+ stub_env('CI_COMMIT_TAG', 'v1.0')
+ end
+
+ it 'sets GITLAB_REF_SLUG to CI_COMMIT_REF_NAME' do
+ expect(subject.variables['GITLAB_REF_SLUG']).to eq(env['CI_COMMIT_REF_NAME'])
+ end
+ end
+
+ context 'when CI_COMMIT_TAG is nil' do
+ before do
+ stub_env('CI_COMMIT_TAG', nil)
+ end
+
+ it 'sets GITLAB_REF_SLUG to CI_COMMIT_REF_SLUG' do
+ expect(subject.variables['GITLAB_REF_SLUG']).to eq(env['CI_COMMIT_REF_SLUG'])
+ end
+ end
+
+ context 'when TRIGGERED_USER is set' do
+ before do
+ stub_env('TRIGGERED_USER', 'triggered_user')
+ end
+
+ it 'sets TRIGGERED_USER to triggered_user' do
+ expect(subject.variables['TRIGGERED_USER']).to eq('triggered_user')
+ end
+ end
+
+ context 'when TRIGGERED_USER is not set' do
+ before do
+ stub_env('TRIGGERED_USER', nil)
+ end
+
+ it 'sets TRIGGERED_USER to GITLAB_USER_NAME' do
+ expect(subject.variables['TRIGGERED_USER']).to eq(env['GITLAB_USER_NAME'])
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', 'ci_merge_request_source_branch_sha')
+ end
+
+ it 'sets TOP_UPSTREAM_SOURCE_SHA to ci_merge_request_source_branch_sha' do
+ expect(subject.variables['TOP_UPSTREAM_SOURCE_SHA']).to eq('ci_merge_request_source_branch_sha')
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set as empty' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', '')
+ end
+
+ it 'sets TOP_UPSTREAM_SOURCE_SHA to CI_COMMIT_SHA' do
+ expect(subject.variables['TOP_UPSTREAM_SOURCE_SHA']).to eq(env['CI_COMMIT_SHA'])
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is not set' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', nil)
+ end
+
+ it 'sets TOP_UPSTREAM_SOURCE_SHA to CI_COMMIT_SHA' do
+ expect(subject.variables['TOP_UPSTREAM_SOURCE_SHA']).to eq(env['CI_COMMIT_SHA'])
+ end
+ end
+ end
+
+ describe "#version_file_variables" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:version_file, :version) do
+ 'GITALY_SERVER_VERSION' | "1"
+ 'GITLAB_ELASTICSEARCH_INDEXER_VERSION' | "2"
+ 'GITLAB_KAS_VERSION' | "3"
+ 'GITLAB_PAGES_VERSION' | "4"
+ 'GITLAB_SHELL_VERSION' | "5"
+ 'GITLAB_WORKHORSE_VERSION' | "6"
+ end
+
+ with_them do
+ context "when set in ENV" do
+ before do
+ stub_env(version_file, version)
+ end
+
+ it 'includes the version from ENV' do
+ expect(subject.variables[version_file]).to eq(version)
+ end
+ end
+
+ context "when set in a file" do
+ before do
+ allow(File).to receive(:read).and_call_original
+ end
+
+ it 'includes the version from the file' do
+ expect(File).to receive(:read).with(version_file).and_return(version)
+ expect(subject.variables[version_file]).to eq(version)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe Trigger::Omnibus do
+ describe '#variables' do
+ it 'invokes the trigger with expected variables' do
+ expect(subject.variables).to include(
+ 'QA_IMAGE' => env['QA_IMAGE'],
+ 'SKIP_QA_DOCKER' => 'true',
+ 'ALTERNATIVE_SOURCES' => 'true',
+ 'CACHE_UPDATE' => env['OMNIBUS_GITLAB_CACHE_UPDATE'],
+ 'GITLAB_QA_OPTIONS' => env['GITLAB_QA_OPTIONS'],
+ 'QA_TESTS' => env['QA_TESTS'],
+ 'ALLURE_JOB_NAME' => env['ALLURE_JOB_NAME']
+ )
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', 'ci_merge_request_source_branch_sha')
+ end
+
+ it 'sets GITLAB_VERSION & IMAGE_TAG to ci_merge_request_source_branch_sha' do
+ expect(subject.variables).to include(
+ 'GITLAB_VERSION' => 'ci_merge_request_source_branch_sha',
+ 'IMAGE_TAG' => 'ci_merge_request_source_branch_sha'
+ )
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set as empty' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', '')
+ end
+
+ it 'sets GITLAB_VERSION & IMAGE_TAG to CI_COMMIT_SHA' do
+ expect(subject.variables).to include(
+ 'GITLAB_VERSION' => env['CI_COMMIT_SHA'],
+ 'IMAGE_TAG' => env['CI_COMMIT_SHA']
+ )
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is not set' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', nil)
+ end
+
+ it 'sets GITLAB_VERSION & IMAGE_TAG to CI_COMMIT_SHA' do
+ expect(subject.variables).to include(
+ 'GITLAB_VERSION' => env['CI_COMMIT_SHA'],
+ 'IMAGE_TAG' => env['CI_COMMIT_SHA']
+ )
+ end
+ end
+
+ context 'when Trigger.security? is true' do
+ before do
+ allow(Trigger).to receive(:security?).and_return(true)
+ end
+
+ it 'sets SECURITY_SOURCES to true' do
+ expect(subject.variables['SECURITY_SOURCES']).to eq('true')
+ end
+ end
+
+ context 'when Trigger.security? is false' do
+ before do
+ allow(Trigger).to receive(:security?).and_return(false)
+ end
+
+ it 'sets SECURITY_SOURCES to false' do
+ expect(subject.variables['SECURITY_SOURCES']).to eq('false')
+ end
+ end
+
+ context 'when Trigger.ee? is true' do
+ before do
+ allow(Trigger).to receive(:ee?).and_return(true)
+ end
+
+ it 'sets ee to true' do
+ expect(subject.variables['ee']).to eq('true')
+ end
+ end
+
+ context 'when Trigger.ee? is false' do
+ before do
+ allow(Trigger).to receive(:ee?).and_return(false)
+ end
+
+ it 'sets ee to false' do
+ expect(subject.variables['ee']).to eq('false')
+ end
+ end
+
+ context 'when QA_BRANCH is set' do
+ before do
+ stub_env('QA_BRANCH', 'qa_branch')
+ end
+
+ it 'sets QA_BRANCH to qa_branch' do
+ expect(subject.variables['QA_BRANCH']).to eq('qa_branch')
+ end
+ end
+ end
+
+ describe '.access_token' do
+ context 'when OMNIBUS_GITLAB_PROJECT_ACCESS_TOKEN is set' do
+ let(:omnibus_gitlab_project_access_token) { 'omnibus_gitlab_project_access_token' }
+
+ before do
+ stub_env('OMNIBUS_GITLAB_PROJECT_ACCESS_TOKEN', omnibus_gitlab_project_access_token)
+ end
+
+ it 'returns the omnibus-specific access token' do
+ expect(described_class.access_token).to eq(omnibus_gitlab_project_access_token)
+ end
+ end
+
+ context 'when OMNIBUS_GITLAB_PROJECT_ACCESS_TOKEN is not set' do
+ before do
+ stub_env('OMNIBUS_GITLAB_PROJECT_ACCESS_TOKEN', nil)
+ end
+
+ it 'returns the default access token' do
+ expect(described_class.access_token).to eq(Trigger::Base.access_token)
+ end
+ end
+ end
+
+ describe '#invoke!' do
+ let(:downstream_project_path) { 'gitlab-org/build/omnibus-gitlab-mirror' }
+ let(:ref) { 'master' }
+
+ let(:env) do
+ super().merge(
+ 'QA_IMAGE' => 'qa_image',
+ 'GITLAB_QA_OPTIONS' => 'gitlab_qa_options',
+ 'QA_TESTS' => 'qa_tests',
+ 'ALLURE_JOB_NAME' => 'allure_job_name'
+ )
+ end
+
+ describe '#downstream_project_path' do
+ context 'when OMNIBUS_PROJECT_PATH is set' do
+ let(:downstream_project_path) { 'omnibus_project_path' }
+
+ before do
+ stub_env('OMNIBUS_PROJECT_PATH', downstream_project_path)
+ end
+
+ it 'triggers the pipeline on the correct project' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+ end
+ end
+
+ describe '#ref' do
+ context 'when OMNIBUS_BRANCH is set' do
+ let(:ref) { 'omnibus_branch' }
+
+ before do
+ stub_env('OMNIBUS_BRANCH', ref)
+ end
+
+ it 'triggers the pipeline on the correct ref' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+ end
+ end
+
+ context 'when CI_COMMIT_REF_NAME is a stable branch' do
+ let(:ref) { '14-10-stable' }
+
+ before do
+ stub_env('CI_COMMIT_REF_NAME', "#{ref}-ee")
+ end
+
+ it 'triggers the pipeline on the correct ref' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+ end
+ end
+ end
+
+ describe Trigger::CNG do
+ describe '#variables' do
+ it 'does not include redundant variables' do
+ expect(subject.variables).not_to include('TRIGGER_SOURCE', 'TRIGGERED_USER')
+ end
+
+ it 'invokes the trigger with expected variables' do
+ expect(subject.variables).to include('FORCE_RAILS_IMAGE_BUILDS' => 'true')
+ end
+
+ describe "TRIGGER_BRANCH" do
+ context 'when CNG_BRANCH is not set' do
+ it 'sets TRIGGER_BRANCH to master' do
+ expect(subject.variables['TRIGGER_BRANCH']).to eq('master')
+ end
+ end
+
+ context 'when CNG_BRANCH is set' do
+ let(:ref) { 'cng_branch' }
+
+ before do
+ stub_env('CNG_BRANCH', ref)
+ end
+
+ it 'sets TRIGGER_BRANCH to cng_branch' do
+ expect(subject.variables['TRIGGER_BRANCH']).to eq(ref)
+ end
+ end
+
+ context 'when CI_COMMIT_REF_NAME is a stable branch' do
+ let(:ref) { '14-10-stable' }
+
+ before do
+ stub_env('CI_COMMIT_REF_NAME', "#{ref}-ee")
+ end
+
+ it 'sets TRIGGER_BRANCH to the corresponding stable branch' do
+ expect(subject.variables['TRIGGER_BRANCH']).to eq(ref)
+ end
+ end
+ end
+
+ describe "GITLAB_VERSION" do
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', 'ci_merge_request_source_branch_sha')
+ end
+
+ it 'sets GITLAB_VERSION to CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' do
+ expect(subject.variables['GITLAB_VERSION']).to eq('ci_merge_request_source_branch_sha')
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set as empty' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', '')
+ end
+
+ it 'sets GITLAB_VERSION to CI_COMMIT_SHA' do
+ expect(subject.variables['GITLAB_VERSION']).to eq(env['CI_COMMIT_SHA'])
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is not set' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', nil)
+ end
+
+ it 'sets GITLAB_VERSION to CI_COMMIT_SHA' do
+ expect(subject.variables['GITLAB_VERSION']).to eq(env['CI_COMMIT_SHA'])
+ end
+ end
+ end
+
+ describe "GITLAB_TAG" do
+ context 'when CI_COMMIT_TAG is set' do
+ before do
+ stub_env('CI_COMMIT_TAG', 'v1.0')
+ end
+
+ it 'sets GITLAB_TAG to true' do
+ expect(subject.variables['GITLAB_TAG']).to eq('v1.0')
+ end
+ end
+
+ context 'when CI_COMMIT_TAG is nil' do
+ before do
+ stub_env('CI_COMMIT_TAG', nil)
+ end
+
+ it 'sets GITLAB_TAG to nil' do
+ expect(subject.variables['GITLAB_TAG']).to eq(nil)
+ end
+ end
+ end
+
+ describe "GITLAB_ASSETS_TAG" do
+ context 'when CI_COMMIT_TAG is set' do
+ before do
+ stub_env('CI_COMMIT_TAG', 'v1.0')
+ end
+
+ it 'sets GITLAB_ASSETS_TAG to CI_COMMIT_REF_NAME' do
+ expect(subject.variables['GITLAB_ASSETS_TAG']).to eq(env['CI_COMMIT_REF_NAME'])
+ end
+ end
+
+ context 'when CI_COMMIT_TAG and CI_MERGE_REQUEST_SOURCE_BRANCH_SHA are nil' do
+ before do
+ stub_env('CI_COMMIT_TAG', nil)
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', nil)
+ end
+
+ it 'sets GITLAB_ASSETS_TAG to CI_COMMIT_SHA' do
+ expect(subject.variables['GITLAB_ASSETS_TAG']).to eq(env['CI_COMMIT_SHA'])
+ end
+ end
+ end
+
+ describe "CE_PIPELINE" do
+ context 'when Trigger.ee? is true' do
+ before do
+ allow(Trigger).to receive(:ee?).and_return(true)
+ end
+
+ it 'sets CE_PIPELINE to nil' do
+ expect(subject.variables['CE_PIPELINE']).to eq(nil)
+ end
+ end
+
+ context 'when Trigger.ee? is false' do
+ before do
+ allow(Trigger).to receive(:ee?).and_return(false)
+ end
+
+ it 'sets CE_PIPELINE to true' do
+ expect(subject.variables['CE_PIPELINE']).to eq('true')
+ end
+ end
+ end
+
+ describe "EE_PIPELINE" do
+ context 'when Trigger.ee? is true' do
+ before do
+ allow(Trigger).to receive(:ee?).and_return(true)
+ end
+
+ it 'sets EE_PIPELINE to true' do
+ expect(subject.variables['EE_PIPELINE']).to eq('true')
+ end
+ end
+
+ context 'when Trigger.ee? is false' do
+ before do
+ allow(Trigger).to receive(:ee?).and_return(false)
+ end
+
+ it 'sets EE_PIPELINE to nil' do
+ expect(subject.variables['EE_PIPELINE']).to eq(nil)
+ end
+ end
+ end
+
+ describe "#version_param_value" do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:version_file) { 'GITALY_SERVER_VERSION' }
+
+ where(:raw_version, :expected_version) do
+ "1.2.3" | "v1.2.3"
+ "1.2.3-rc1" | "v1.2.3-rc1"
+ "1.2.3-ee" | "v1.2.3-ee"
+ "1.2.3-rc1-ee" | "v1.2.3-rc1-ee"
+ end
+
+ with_them do
+ context "when set in ENV" do
+ before do
+ stub_env(version_file, raw_version)
+ end
+
+ it 'includes the version from ENV' do
+ expect(subject.variables[version_file]).to eq(expected_version)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe Trigger::Docs do
+ let(:downstream_project_path) { 'gitlab-org/gitlab-docs' }
+
+ describe '#variables' do
+ describe "BRANCH_CE" do
+ before do
+ stub_env('CI_PROJECT_PATH', 'gitlab-org/gitlab-foss')
+ end
+
+ context 'when CI_PROJECT_PATH is gitlab-org/gitlab-foss' do
+ it 'sets BRANCH_CE to CI_COMMIT_REF_NAME' do
+ expect(subject.variables['BRANCH_CE']).to eq(env['CI_COMMIT_REF_NAME'])
+ end
+ end
+ end
+
+ describe "BRANCH_EE" do
+ before do
+ stub_env('CI_PROJECT_PATH', 'gitlab-org/gitlab')
+ end
+
+ context 'when CI_PROJECT_PATH is gitlab-org/gitlab' do
+ it 'sets BRANCH_EE to CI_COMMIT_REF_NAME' do
+ expect(subject.variables['BRANCH_EE']).to eq(env['CI_COMMIT_REF_NAME'])
+ end
+ end
+ end
+
+ describe "BRANCH_RUNNER" do
+ before do
+ stub_env('CI_PROJECT_PATH', 'gitlab-org/gitlab-runner')
+ end
+
+ context 'when CI_PROJECT_PATH is gitlab-org/gitlab-runner' do
+ it 'sets BRANCH_RUNNER to CI_COMMIT_REF_NAME' do
+ expect(subject.variables['BRANCH_RUNNER']).to eq(env['CI_COMMIT_REF_NAME'])
+ end
+ end
+ end
+
+ describe "BRANCH_OMNIBUS" do
+ before do
+ stub_env('CI_PROJECT_PATH', 'gitlab-org/omnibus-gitlab')
+ end
+
+ context 'when CI_PROJECT_PATH is gitlab-org/omnibus-gitlab' do
+ it 'sets BRANCH_OMNIBUS to CI_COMMIT_REF_NAME' do
+ expect(subject.variables['BRANCH_OMNIBUS']).to eq(env['CI_COMMIT_REF_NAME'])
+ end
+ end
+ end
+
+ describe "BRANCH_CHARTS" do
+ before do
+ stub_env('CI_PROJECT_PATH', 'gitlab-org/charts/gitlab')
+ end
+
+ context 'when CI_PROJECT_PATH is gitlab-org/charts/gitlab' do
+ it 'sets BRANCH_CHARTS to CI_COMMIT_REF_NAME' do
+ expect(subject.variables['BRANCH_CHARTS']).to eq(env['CI_COMMIT_REF_NAME'])
+ end
+ end
+ end
+
+ describe "REVIEW_SLUG" do
+ before do
+ stub_env('CI_PROJECT_PATH', 'gitlab-org/gitlab-foss')
+ end
+
+ context 'when CI_MERGE_REQUEST_IID is set' do
+ it 'sets REVIEW_SLUG' do
+ expect(subject.variables['REVIEW_SLUG']).to eq("-ce-#{env['CI_MERGE_REQUEST_IID']}")
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_IID is not set' do
+ before do
+ stub_env('CI_MERGE_REQUEST_IID', nil)
+ end
+
+ it 'sets REVIEW_SLUG' do
+ expect(subject.variables['REVIEW_SLUG']).to eq("-ce-#{env['CI_COMMIT_REF_SLUG']}")
+ end
+ end
+ end
+ end
+
+ describe '.access_token' do
+ context 'when DOCS_PROJECT_API_TOKEN is set' do
+ let(:docs_project_api_token) { 'docs_project_api_token' }
+
+ before do
+ stub_env('DOCS_PROJECT_API_TOKEN', docs_project_api_token)
+ end
+
+ it 'returns the docs-specific access token' do
+ expect(described_class.access_token).to eq(docs_project_api_token)
+ end
+ end
+
+ context 'when DOCS_PROJECT_API_TOKEN is not set' do
+ before do
+ stub_env('DOCS_PROJECT_API_TOKEN', nil)
+ end
+
+ it 'returns the default access token' do
+ expect(described_class.access_token).to eq(Trigger::Base.access_token)
+ end
+ end
+ end
+
+ describe '#invoke!' do
+ let(:trigger_token) { 'docs_trigger_token' }
+ let(:ref) { 'main' }
+
+ let(:env) do
+ super().merge(
+ 'CI_PROJECT_PATH' => 'gitlab-org/gitlab-foss',
+ 'DOCS_TRIGGER_TOKEN' => trigger_token
+ )
+ end
+
+ describe '#downstream_project_path' do
+ context 'when DOCS_PROJECT_PATH is set' do
+ let(:downstream_project_path) { 'docs_project_path' }
+
+ before do
+ stub_env('DOCS_PROJECT_PATH', downstream_project_path)
+ end
+
+ it 'triggers the pipeline on the correct project' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+ end
+ end
+
+ describe '#ref' do
+ context 'when DOCS_BRANCH is set' do
+ let(:ref) { 'docs_branch' }
+
+ before do
+ stub_env('DOCS_BRANCH', ref)
+ end
+
+ it 'triggers the pipeline on the correct ref' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+ end
+ end
+ end
+
+ describe '#cleanup!' do
+ let(:downstream_environment_response) { double('downstream_environment', id: 42) }
+ let(:downstream_environments_response) { [downstream_environment_response] }
+
+ before do
+ expect(com_gitlab_client).to receive(:environments)
+ .with(downstream_project_path, name: subject.__send__(:downstream_environment))
+ .and_return(downstream_environments_response)
+ expect(com_gitlab_client).to receive(:stop_environment)
+ .with(downstream_project_path, downstream_environment_response.id)
+ .and_return(downstream_environment_stopping_response)
+ end
+
+ context "when stopping the environment succeeds" do
+ let(:downstream_environment_stopping_response) { double('downstream_environment', state: 'stopped') }
+
+ it 'displays a success message' do
+ expect(subject).to receive(:puts)
+ .with("=> Downstream environment '#{subject.__send__(:downstream_environment)}' stopped.")
+
+ subject.cleanup!
+ end
+ end
+
+ context "when stopping the environment fails" do
+ let(:downstream_environment_stopping_response) { double('downstream_environment', state: 'running') }
+
+ it 'displays a failure message' do
+ expect(subject).to receive(:puts)
+ .with("=> Downstream environment '#{subject.__send__(:downstream_environment)}' failed to stop.")
+
+ subject.cleanup!
+ end
+ end
+ end
+ end
+
+ describe Trigger::DatabaseTesting do
+ describe '#variables' do
+ it 'invokes the trigger with expected variables' do
+ expect(subject.variables).to include('TRIGGERED_USER_LOGIN' => env['GITLAB_USER_LOGIN'])
+ end
+
+ describe "GITLAB_COMMIT_SHA" do
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', 'ci_merge_request_source_branch_sha')
+ end
+
+ it 'sets GITLAB_COMMIT_SHA to ci_merge_request_source_branch_sha' do
+ expect(subject.variables['GITLAB_COMMIT_SHA']).to eq('ci_merge_request_source_branch_sha')
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is set as empty' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', '')
+ end
+
+ it 'sets GITLAB_COMMIT_SHA to CI_COMMIT_SHA' do
+ expect(subject.variables['GITLAB_COMMIT_SHA']).to eq(env['CI_COMMIT_SHA'])
+ end
+ end
+
+ context 'when CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is not set' do
+ before do
+ stub_env('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', nil)
+ end
+
+ it 'sets GITLAB_COMMIT_SHA to CI_COMMIT_SHA' do
+ expect(subject.variables['GITLAB_COMMIT_SHA']).to eq(env['CI_COMMIT_SHA'])
+ end
+ end
+ end
+ end
+
+ describe '#invoke!' do
+ let(:downstream_project_path) { 'gitlab-com/database-team/gitlab-com-database-testing' }
+ let(:trigger_token) { 'gitlabcom_database_testing_access_token' }
+ let(:ops_api_endpoint) { 'https://ops.gitlab.net/api/v4' }
+ let(:ops_api_token) { 'gitlabcom_database_testing_access_token' }
+ let(:ops_gitlab_client) { double('ops_gitlab_client') }
+
+ let(:downstream_gitlab_client_endpoint) { ops_api_endpoint }
+ let(:downstream_gitlab_client_token) { ops_api_token }
+ let(:downstream_gitlab_client) { ops_gitlab_client }
+
+ let(:ref) { 'master' }
+ let(:mr_notes) { [double(body: described_class::IDENTIFIABLE_NOTE_TAG)] }
+
+ let(:env) do
+ super().merge(
+ 'GITLABCOM_DATABASE_TESTING_ACCESS_TOKEN' => ops_api_token,
+ 'GITLABCOM_DATABASE_TESTING_TRIGGER_TOKEN' => trigger_token
+ )
+ end
+
+ before do
+ allow(Gitlab).to receive(:client)
+ .with(
+ endpoint: com_api_endpoint,
+ private_token: com_api_token
+ )
+ .and_return(com_gitlab_client)
+ allow(com_gitlab_client).to receive(:merge_request_notes)
+ .with(
+ env['CI_PROJECT_PATH'],
+ env['CI_MERGE_REQUEST_IID']
+ )
+ .and_return(double(auto_paginate: mr_notes))
+ end
+
+ it 'invokes the trigger with expected variables' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+
+ describe '#downstream_project_path' do
+ context 'when GITLABCOM_DATABASE_TESTING_PROJECT_PATH is set' do
+ let(:downstream_project_path) { 'gitlabcom_database_testing_project_path' }
+
+ before do
+ stub_env('GITLABCOM_DATABASE_TESTING_PROJECT_PATH', downstream_project_path)
+ end
+
+ it 'triggers the pipeline on the correct project' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+ end
+ end
+
+ describe '#ref' do
+ context 'when GITLABCOM_DATABASE_TESTING_TRIGGER_REF is set' do
+ let(:ref) { 'gitlabcom_database_testing_trigger_ref' }
+
+ before do
+ stub_env('GITLABCOM_DATABASE_TESTING_TRIGGER_REF', ref)
+ end
+
+ it 'triggers the pipeline on the correct ref' do
+ expect_run_trigger_with_params
+
+ subject.invoke!
+ end
+ end
+ end
+
+ context 'when no MR notes with the identifier exist yet' do
+ let(:mr_notes) { [double(body: 'hello world')] }
+
+ it 'posts a new note' do
+ expect_run_trigger_with_params
+ expect(com_gitlab_client).to receive(:create_merge_request_note)
+ .with(
+ env['CI_PROJECT_PATH'],
+ env['CI_MERGE_REQUEST_IID'],
+ instance_of(String)
+ )
+ .and_return(double(id: 42))
+
+ subject.invoke!
+ end
+ end
+ end
+ end
+end
+# rubocop:enable RSpec/VerifiedDoubles