summaryrefslogtreecommitdiff
path: root/spec/support/helpers
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 01:45:44 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 01:45:44 +0000
commit85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch)
tree9160f299afd8c80c038f08e1545be119f5e3f1e1 /spec/support/helpers
parent15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff)
downloadgitlab-ce-85dc423f7090da0a52c73eb66faf22ddb20efff9.tar.gz
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'spec/support/helpers')
-rw-r--r--spec/support/helpers/ci/source_pipeline_helpers.rb13
-rw-r--r--spec/support/helpers/dns_helpers.rb2
-rw-r--r--spec/support/helpers/docs_screenshot_helpers.rb39
-rw-r--r--spec/support/helpers/fake_u2f_device.rb3
-rw-r--r--spec/support/helpers/fake_webauthn_device.rb74
-rw-r--r--spec/support/helpers/feature_flag_helpers.rb95
-rw-r--r--spec/support/helpers/features/editor_lite_spec_helpers.rb29
-rw-r--r--spec/support/helpers/features/releases_helpers.rb117
-rw-r--r--spec/support/helpers/features/snippet_helpers.rb51
-rw-r--r--spec/support/helpers/features/two_factor_helpers.rb74
-rw-r--r--spec/support/helpers/graphql_helpers.rb33
-rw-r--r--spec/support/helpers/jira_service_helper.rb3
-rw-r--r--spec/support/helpers/login_helpers.rb5
-rw-r--r--spec/support/helpers/markdown_feature.rb8
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb2
-rw-r--r--spec/support/helpers/multipart_helpers.rb91
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb4
-rw-r--r--spec/support/helpers/next_found_instance_of.rb33
-rw-r--r--spec/support/helpers/project_forks_helper.rb16
-rw-r--r--spec/support/helpers/repo_helpers.rb2
-rw-r--r--spec/support/helpers/snowplow_helpers.rb53
-rw-r--r--spec/support/helpers/stub_object_storage.rb2
-rw-r--r--spec/support/helpers/stubbed_feature.rb5
-rw-r--r--spec/support/helpers/test_env.rb19
-rw-r--r--spec/support/helpers/usage_data_helpers.rb17
-rw-r--r--spec/support/helpers/wiki_helpers.rb5
-rw-r--r--spec/support/helpers/workhorse_helpers.rb18
27 files changed, 779 insertions, 34 deletions
diff --git a/spec/support/helpers/ci/source_pipeline_helpers.rb b/spec/support/helpers/ci/source_pipeline_helpers.rb
new file mode 100644
index 00000000000..b99f499cc16
--- /dev/null
+++ b/spec/support/helpers/ci/source_pipeline_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Ci
+ module SourcePipelineHelpers
+ def create_source_pipeline(upstream, downstream)
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: upstream),
+ source_project: upstream.project,
+ pipeline: downstream,
+ project: downstream.project)
+ end
+ end
+end
diff --git a/spec/support/helpers/dns_helpers.rb b/spec/support/helpers/dns_helpers.rb
index 29be4da6902..1795b0a9ac3 100644
--- a/spec/support/helpers/dns_helpers.rb
+++ b/spec/support/helpers/dns_helpers.rb
@@ -25,6 +25,6 @@ module DnsHelpers
def permit_local_dns!
local_addresses = /\A(127|10)\.0\.0\.\d{1,3}|(192\.168|172\.16)\.\d{1,3}\.\d{1,3}|0\.0\.0\.0|localhost\z/i
allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM).and_call_original
- allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM, anything, anything).and_call_original
+ allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM, anything, anything, any_args).and_call_original
end
end
diff --git a/spec/support/helpers/docs_screenshot_helpers.rb b/spec/support/helpers/docs_screenshot_helpers.rb
new file mode 100644
index 00000000000..aa3aad0a740
--- /dev/null
+++ b/spec/support/helpers/docs_screenshot_helpers.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'fileutils'
+require 'mini_magick'
+
+module DocsScreenshotHelpers
+ extend ActiveSupport::Concern
+
+ def set_crop_data(element, padding)
+ @crop_element = element
+ @crop_padding = padding
+ end
+
+ def crop_image_screenshot(path)
+ element_rect = @crop_element.evaluate_script("this.getBoundingClientRect()")
+
+ width = element_rect['width'] + (@crop_padding * 2)
+ height = element_rect['height'] + (@crop_padding * 2)
+
+ x = element_rect['x'] - @crop_padding
+ y = element_rect['y'] - @crop_padding
+
+ image = MiniMagick::Image.new(path)
+ image.crop "#{width}x#{height}+#{x}+#{y}"
+ end
+
+ included do |base|
+ after do |example|
+ filename = "#{example.description}.png"
+ path = File.expand_path(filename, 'doc/')
+ page.save_screenshot(path)
+
+ if @crop_element
+ crop_image_screenshot(path)
+ set_crop_data(nil, nil)
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/fake_u2f_device.rb b/spec/support/helpers/fake_u2f_device.rb
index f765b277175..2ed1222ebd3 100644
--- a/spec/support/helpers/fake_u2f_device.rb
+++ b/spec/support/helpers/fake_u2f_device.rb
@@ -3,9 +3,10 @@
class FakeU2fDevice
attr_reader :name
- def initialize(page, name)
+ def initialize(page, name, device = nil)
@page = page
@name = name
+ @u2f_device = device
end
def respond_to_u2f_registration
diff --git a/spec/support/helpers/fake_webauthn_device.rb b/spec/support/helpers/fake_webauthn_device.rb
new file mode 100644
index 00000000000..d2c2f7d6bf3
--- /dev/null
+++ b/spec/support/helpers/fake_webauthn_device.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+require 'webauthn/fake_client'
+
+class FakeWebauthnDevice
+ attr_reader :name
+
+ def initialize(page, name, device = nil)
+ @page = page
+ @name = name
+ @webauthn_device = device
+ end
+
+ def respond_to_webauthn_registration
+ app_id = @page.evaluate_script('gon.webauthn.app_id')
+ challenge = @page.evaluate_script('gon.webauthn.options.challenge')
+
+ json_response = webauthn_device(app_id).create(challenge: challenge).to_json # rubocop:disable Rails/SaveBang
+ @page.execute_script <<~JS
+ var result = #{json_response};
+ result.getClientExtensionResults = () => ({});
+ navigator.credentials.create = function(_) {
+ return Promise.resolve(result);
+ };
+ JS
+ end
+
+ def respond_to_webauthn_authentication
+ app_id = @page.evaluate_script('JSON.parse(gon.webauthn.options).extensions.appid')
+ challenge = @page.evaluate_script('JSON.parse(gon.webauthn.options).challenge')
+
+ begin
+ json_response = webauthn_device(app_id).get(challenge: challenge).to_json
+
+ rescue RuntimeError
+ # A runtime error is raised from fake webauthn if no credentials have been registered yet.
+ # To be able to test non registered devices, credentials are created ad-hoc
+ webauthn_device(app_id).create # rubocop:disable Rails/SaveBang
+ json_response = webauthn_device(app_id).get(challenge: challenge).to_json
+ end
+
+ @page.execute_script <<~JS
+ var result = #{json_response};
+ result.getClientExtensionResults = () => ({});
+ navigator.credentials.get = function(_) {
+ return Promise.resolve(result);
+ };
+ JS
+ @page.click_link('Try again?', href: false)
+ end
+
+ def fake_webauthn_authentication
+ @page.execute_script <<~JS
+ const mockResponse = {
+ type: 'public-key',
+ id: '',
+ rawId: '',
+ response: { clientDataJSON: '', authenticatorData: '', signature: '', userHandle: '' },
+ getClientExtensionResults: () => {},
+ };
+ window.gl.resolveWebauthn(mockResponse);
+ JS
+ end
+
+ def add_credential(app_id, credential_id, credential_key)
+ credentials = { URI.parse(app_id).host => { credential_id => { credential_key: credential_key, sign_count: 0 } } }
+ webauthn_device(app_id).send(:authenticator).instance_variable_set(:@credentials, credentials)
+ end
+
+ private
+
+ def webauthn_device(app_id)
+ @webauthn_device ||= WebAuthn::FakeClient.new(app_id)
+ end
+end
diff --git a/spec/support/helpers/feature_flag_helpers.rb b/spec/support/helpers/feature_flag_helpers.rb
new file mode 100644
index 00000000000..93cd915879b
--- /dev/null
+++ b/spec/support/helpers/feature_flag_helpers.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module FeatureFlagHelpers
+ def create_flag(project, name, active = true, description: nil, version: Operations::FeatureFlag.versions['legacy_flag'])
+ create(:operations_feature_flag, name: name, active: active, version: version,
+ description: description, project: project)
+ end
+
+ def create_scope(feature_flag, environment_scope, active = true, strategies = [{ name: "default", parameters: {} }])
+ create(:operations_feature_flag_scope,
+ feature_flag: feature_flag,
+ environment_scope: environment_scope,
+ active: active,
+ strategies: strategies)
+ end
+
+ def within_feature_flag_row(index)
+ within ".gl-responsive-table-row:nth-child(#{index + 1})" do
+ yield
+ end
+ end
+
+ def within_feature_flag_scopes
+ within '.js-feature-flag-environments' do
+ yield
+ end
+ end
+
+ def within_scope_row(index)
+ within ".gl-responsive-table-row:nth-child(#{index + 1})" do
+ yield
+ end
+ end
+
+ def within_strategy_row(index)
+ within ".feature-flags-form > fieldset > div[data-testid='feature-flag-strategies'] > div:nth-child(#{index})" do
+ yield
+ end
+ end
+
+ def within_environment_spec
+ within '.table-section:nth-child(1)' do
+ yield
+ end
+ end
+
+ def within_status
+ within '.table-section:nth-child(2)' do
+ yield
+ end
+ end
+
+ def within_delete
+ within '.table-section:nth-child(4)' do
+ yield
+ end
+ end
+
+ def edit_feature_flag_button
+ find('.js-feature-flag-edit-button')
+ end
+
+ def delete_strategy_button
+ find("button[data-testid='delete-strategy-button']")
+ end
+
+ def add_linked_issue_button
+ find('.js-issue-count-badge-add-button')
+ end
+
+ def remove_linked_issue_button
+ find('.js-issue-item-remove-button')
+ end
+
+ def status_toggle_button
+ find('[data-testid="feature-flag-status-toggle"] button')
+ end
+
+ def expect_status_toggle_button_to_be_checked
+ expect(page).to have_css('[data-testid="feature-flag-status-toggle"] button.is-checked')
+ end
+
+ def expect_status_toggle_button_not_to_be_checked
+ expect(page).to have_css('[data-testid="feature-flag-status-toggle"] button:not(.is-checked)')
+ end
+
+ def expect_status_toggle_button_to_be_disabled
+ expect(page).to have_css('[data-testid="feature-flag-status-toggle"] button.is-disabled')
+ end
+
+ def expect_user_to_see_feature_flags_index_page
+ expect(page).to have_text('Feature Flags')
+ expect(page).to have_text('Lists')
+ end
+end
diff --git a/spec/support/helpers/features/editor_lite_spec_helpers.rb b/spec/support/helpers/features/editor_lite_spec_helpers.rb
new file mode 100644
index 00000000000..0a67e753379
--- /dev/null
+++ b/spec/support/helpers/features/editor_lite_spec_helpers.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+# These helpers help you interact within the Editor Lite (single-file editor, snippets, etc.).
+#
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module EditorLiteSpecHelpers
+ include ActionView::Helpers::JavaScriptHelper
+
+ def editor_set_value(value)
+ editor = find('.monaco-editor')
+ uri = editor['data-uri']
+
+ execute_script("monaco.editor.getModel('#{uri}').setValue('#{escape_javascript(value)}')")
+ end
+
+ def editor_get_value
+ editor = find('.monaco-editor')
+ uri = editor['data-uri']
+
+ evaluate_script("monaco.editor.getModel('#{uri}').getValue()")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/features/releases_helpers.rb b/spec/support/helpers/features/releases_helpers.rb
new file mode 100644
index 00000000000..0d46918b05c
--- /dev/null
+++ b/spec/support/helpers/features/releases_helpers.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+# These helpers fill fields on the "New Release" and
+# "Edit Release" pages. They use the keyboard to navigate
+# from one field to the next and assume that when
+# they are called, the field to be filled out is already focused.
+#
+# Usage:
+# describe "..." do
+# include Spec::Support::Helpers::Features::ReleasesHelpers
+# ...
+#
+# fill_tag_name("v1.0")
+# select_create_from("my-feature-branch")
+#
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module ReleasesHelpers
+ # Returns the element that currently has keyboard focus.
+ # Reminder that this returns a Selenium::WebDriver::Element
+ # _not_ a Capybara::Node::Element
+ def focused_element
+ page.driver.browser.switch_to.active_element
+ end
+
+ def fill_tag_name(tag_name, and_tab: true)
+ expect(focused_element).to eq(find_field('Tag name').native)
+
+ focused_element.send_keys(tag_name)
+
+ focused_element.send_keys(:tab) if and_tab
+ end
+
+ def select_create_from(branch_name, and_tab: true)
+ expect(focused_element).to eq(find('[data-testid="create-from-field"] button').native)
+
+ focused_element.send_keys(:enter)
+
+ # Wait for the dropdown to be rendered
+ page.find('.ref-selector .dropdown-menu')
+
+ # Pressing Enter in the search box shouldn't submit the form
+ focused_element.send_keys(branch_name, :enter)
+
+ # Wait for the search to return
+ page.find('.ref-selector .dropdown-item', text: branch_name, match: :first)
+
+ focused_element.send_keys(:arrow_down, :enter)
+
+ focused_element.send_keys(:tab) if and_tab
+ end
+
+ def fill_release_title(release_title, and_tab: true)
+ expect(focused_element).to eq(find_field('Release title').native)
+
+ focused_element.send_keys(release_title)
+
+ focused_element.send_keys(:tab) if and_tab
+ end
+
+ def select_milestone(milestone_title, and_tab: true)
+ expect(focused_element).to eq(find('[data-testid="milestones-field"] button').native)
+
+ focused_element.send_keys(:enter)
+
+ # Wait for the dropdown to be rendered
+ page.find('.project-milestone-combobox .dropdown-menu')
+
+ # Clear any existing input
+ focused_element.attribute('value').length.times { focused_element.send_keys(:backspace) }
+
+ # Pressing Enter in the search box shouldn't submit the form
+ focused_element.send_keys(milestone_title, :enter)
+
+ # Wait for the search to return
+ page.find('.project-milestone-combobox .dropdown-item', text: milestone_title, match: :first)
+
+ focused_element.send_keys(:arrow_down, :arrow_down, :enter)
+
+ focused_element.send_keys(:tab) if and_tab
+ end
+
+ def fill_release_notes(release_notes, and_tab: true)
+ expect(focused_element).to eq(find_field('Release notes').native)
+
+ focused_element.send_keys(release_notes)
+
+ # Tab past the links at the bottom of the editor
+ focused_element.send_keys(:tab, :tab, :tab) if and_tab
+ end
+
+ def fill_asset_link(link, and_tab: true)
+ expect(focused_element['id']).to start_with('asset-url-')
+
+ focused_element.send_keys(link[:url], :tab, link[:title], :tab, link[:type])
+
+ # Tab past the "Remove asset link" button
+ focused_element.send_keys(:tab, :tab) if and_tab
+ end
+
+ # Click "Add another link" and tab back to the beginning of the new row
+ def add_another_asset_link
+ expect(focused_element).to eq(find_button('Add another link').native)
+
+ focused_element.send_keys(:enter,
+ [:shift, :tab],
+ [:shift, :tab],
+ [:shift, :tab],
+ [:shift, :tab])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/features/snippet_helpers.rb b/spec/support/helpers/features/snippet_helpers.rb
new file mode 100644
index 00000000000..c01d179770c
--- /dev/null
+++ b/spec/support/helpers/features/snippet_helpers.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+# These helpers help you interact within the Editor Lite (single-file editor, snippets, etc.).
+#
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module SnippetSpecHelpers
+ include ActionView::Helpers::JavaScriptHelper
+ include Spec::Support::Helpers::Features::EditorLiteSpecHelpers
+
+ def snippet_get_first_blob_path
+ page.find_field(snippet_blob_path_field, match: :first).value
+ end
+
+ def snippet_get_first_blob_value
+ page.find(snippet_blob_content_selector, match: :first)
+ end
+
+ def snippet_description_value
+ page.find_field(snippet_description_field).value
+ end
+
+ def snippet_fill_in_form(title:, content:, description: '')
+ # fill_in snippet_title_field, with: title
+ # editor_set_value(content)
+ fill_in snippet_title_field, with: title
+
+ if description
+ # Click placeholder first to expand full description field
+ description_field.click
+ fill_in snippet_description_field, with: description
+ end
+
+ page.within('.file-editor') do
+ el = find('.inputarea')
+ el.send_keys content
+ end
+ end
+
+ private
+
+ def description_field
+ find('.js-description-input').find('input,textarea')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/features/two_factor_helpers.rb b/spec/support/helpers/features/two_factor_helpers.rb
new file mode 100644
index 00000000000..08a7665201f
--- /dev/null
+++ b/spec/support/helpers/features/two_factor_helpers.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+# These helpers allow you to manage and register
+# U2F and WebAuthn devices
+#
+# Usage:
+# describe "..." do
+# include Spec::Support::Helpers::Features::TwoFactorHelpers
+# ...
+#
+# manage_two_factor_authentication
+#
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module TwoFactorHelpers
+ def manage_two_factor_authentication
+ click_on 'Manage two-factor authentication'
+ expect(page).to have_content("Set up new device")
+ wait_for_requests
+ end
+
+ def register_u2f_device(u2f_device = nil, name: 'My device')
+ u2f_device ||= FakeU2fDevice.new(page, name)
+ u2f_device.respond_to_u2f_registration
+ click_on 'Set up new device'
+ expect(page).to have_content('Your device was successfully set up')
+ fill_in "Pick a name", with: name
+ click_on 'Register device'
+ u2f_device
+ end
+
+ # Registers webauthn device via UI
+ def register_webauthn_device(webauthn_device = nil, name: 'My device')
+ webauthn_device ||= FakeWebauthnDevice.new(page, name)
+ webauthn_device.respond_to_webauthn_registration
+ click_on 'Set up new device'
+ expect(page).to have_content('Your device was successfully set up')
+ fill_in 'Pick a name', with: name
+ click_on 'Register device'
+ webauthn_device
+ end
+
+ # Adds webauthn device directly via database
+ def add_webauthn_device(app_id, user, fake_device = nil, name: 'My device')
+ fake_device ||= WebAuthn::FakeClient.new(app_id)
+
+ options_for_create = WebAuthn::Credential.options_for_create(
+ user: { id: user.webauthn_xid, name: user.username },
+ authenticator_selection: { user_verification: 'discouraged' },
+ rp: { name: 'GitLab' }
+ )
+ challenge = options_for_create.challenge
+
+ device_response = fake_device.create(challenge: challenge).to_json # rubocop:disable Rails/SaveBang
+ device_registration_params = { device_response: device_response,
+ name: name }
+
+ Webauthn::RegisterService.new(
+ user, device_registration_params, challenge).execute
+ FakeWebauthnDevice.new(page, name, fake_device)
+ end
+
+ def assert_fallback_ui(page)
+ expect(page).to have_button('Verify code')
+ expect(page).to have_css('#user_otp_attempt')
+ expect(page).not_to have_link('Sign in via 2FA code')
+ expect(page).not_to have_css("#js-authenticate-token-2fa")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 87525734490..5635ba3df05 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -241,6 +241,39 @@ module GraphqlHelpers
post_graphql(mutation.query, current_user: current_user, variables: mutation.variables)
end
+ def post_graphql_mutation_with_uploads(mutation, current_user: nil)
+ file_paths = file_paths_in_mutation(mutation)
+ params = mutation_to_apollo_uploads_param(mutation, files: file_paths)
+
+ workhorse_post_with_file(api('/', current_user, version: 'graphql'),
+ params: params,
+ file_key: '1'
+ )
+ end
+
+ def file_paths_in_mutation(mutation)
+ paths = []
+ find_uploads(paths, [], mutation.variables)
+
+ paths
+ end
+
+ # Depth first search for UploadedFile values
+ def find_uploads(paths, path, value)
+ case value
+ when Rack::Test::UploadedFile
+ paths << path
+ when Hash
+ value.each do |k, v|
+ find_uploads(paths, path + [k], v)
+ end
+ when Array
+ value.each_with_index do |v, i|
+ find_uploads(paths, path + [i], v)
+ end
+ end
+ end
+
# this implements GraphQL multipart request v2
# https://github.com/jaydenseric/graphql-multipart-request-spec/tree/v2.0.0-alpha.2
# this is simplified and do not support file deduplication
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index 4895bc3ba15..698490c8c92 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -78,8 +78,7 @@ module JiraServiceHelper
end
def stub_jira_service_test
- WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/serverInfo')
- .to_return(body: { url: 'http://url' }.to_json)
+ WebMock.stub_request(:get, /serverInfo/).to_return(body: { url: 'http://url' }.to_json)
end
def stub_jira_urls(issue_id)
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 1118cfcf7ac..e21d4497cda 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -111,6 +111,11 @@ module LoginHelpers
FakeU2fDevice.new(page, nil).fake_u2f_authentication
end
+ def fake_successful_webauthn_authentication
+ allow_any_instance_of(Webauthn::AuthenticateService).to receive(:execute).and_return(true)
+ FakeWebauthnDevice.new(page, nil).fake_webauthn_authentication
+ end
+
def mock_auth_hash_with_saml_xml(provider, uid, email, saml_response)
response_object = { document: saml_xml(saml_response) }
mock_auth_hash(provider, uid, email, response_object: response_object)
diff --git a/spec/support/helpers/markdown_feature.rb b/spec/support/helpers/markdown_feature.rb
index 40e0d4413e2..0cb2863dc2c 100644
--- a/spec/support/helpers/markdown_feature.rb
+++ b/spec/support/helpers/markdown_feature.rb
@@ -87,6 +87,10 @@ class MarkdownFeature
@group_milestone ||= create(:milestone, name: 'group-milestone', group: group)
end
+ def alert
+ @alert ||= create(:alert_management_alert, project: project)
+ end
+
# Cross-references -----------------------------------------------------------
def xproject
@@ -125,6 +129,10 @@ class MarkdownFeature
@xmilestone ||= create(:milestone, project: xproject)
end
+ def xalert
+ @xalert ||= create(:alert_management_alert, project: xproject)
+ end
+
def urls
Gitlab::Routing.url_helpers
end
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
index 7168079fead..a384e44f428 100644
--- a/spec/support/helpers/metrics_dashboard_helpers.rb
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -47,7 +47,7 @@ module MetricsDashboardHelpers
end
def business_metric_title
- PrometheusMetricEnums.group_details[:business][:group_title]
+ Enums::PrometheusMetric.group_details[:business][:group_title]
end
def self_monitoring_dashboard_path
diff --git a/spec/support/helpers/multipart_helpers.rb b/spec/support/helpers/multipart_helpers.rb
new file mode 100644
index 00000000000..043cb6e1420
--- /dev/null
+++ b/spec/support/helpers/multipart_helpers.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+module MultipartHelpers
+ include WorkhorseHelpers
+
+ def post_env(rewritten_fields:, params:, secret:, issuer:)
+ token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256')
+ Rack::MockRequest.env_for(
+ '/',
+ method: 'post',
+ params: params,
+ described_class::RACK_ENV_KEY => token
+ )
+ end
+
+ # This function assumes a `mode` variable to be set
+ def upload_parameters_for(filepath: nil, key: nil, filename: 'filename', remote_id: 'remote_id')
+ result = {
+ "#{key}.name" => filename,
+ "#{key}.type" => "application/octet-stream",
+ "#{key}.sha256" => "1234567890"
+ }
+
+ case mode
+ when :local
+ result["#{key}.path"] = filepath
+ when :remote
+ result["#{key}.remote_id"] = remote_id
+ result["#{key}.size"] = 3.megabytes
+ else
+ raise ArgumentError, "can't handle #{mode} mode"
+ end
+
+ return result if ::Feature.disabled?(:upload_middleware_jwt_params_handler)
+
+ # the HandlerForJWTParams expects a jwt token with the upload parameters
+ # *without* the "#{key}." prefix
+ result.deep_transform_keys! { |k| k.remove("#{key}.") }
+ {
+ "#{key}.gitlab-workhorse-upload" => jwt_token('upload' => result)
+ }
+ end
+
+ # This function assumes a `mode` variable to be set
+ def rewritten_fields_hash(hash)
+ if mode == :remote
+ # For remote uploads, workhorse still submits rewritten_fields,
+ # but all the values are empty strings.
+ hash.keys.each { |k| hash[k] = '' }
+ end
+
+ hash
+ end
+
+ def expect_uploaded_files(uploaded_file_expectations)
+ expect(app).to receive(:call) do |env|
+ Array.wrap(uploaded_file_expectations).each do |expectation|
+ file = get_params(env).dig(*expectation[:params_path])
+ expect_uploaded_file(file, expectation)
+ end
+ end
+ end
+
+ # This function assumes a `mode` variable to be set
+ def expect_uploaded_file(file, expectation)
+ expect(file).to be_a(::UploadedFile)
+ expect(file.original_filename).to eq(expectation[:original_filename])
+ expect(file.sha256).to eq('1234567890')
+
+ case mode
+ when :local
+ expect(file.path).to eq(File.realpath(expectation[:filepath]))
+ expect(file.remote_id).to be_nil
+ expect(file.size).to eq(expectation[:size])
+ when :remote
+ expect(file.remote_id).to eq(expectation[:remote_id])
+ expect(file.path).to be_nil
+ expect(file.size).to eq(3.megabytes)
+ else
+ raise ArgumentError, "can't handle #{mode} mode"
+ end
+ end
+
+ # Rails doesn't combine the GET/POST parameters in
+ # ActionDispatch::HTTP::Parameters if action_dispatch.request.parameters is set:
+ # https://github.com/rails/rails/blob/aea6423f013ca48f7704c70deadf2cd6ac7d70a1/actionpack/lib/action_dispatch/http/parameters.rb#L41
+ def get_params(env)
+ req = ActionDispatch::Request.new(env)
+ req.GET.merge(req.POST)
+ end
+end
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index c7aa2ffe536..11e67ba394c 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -4,13 +4,13 @@ module NavbarStructureHelper
def insert_after_nav_item(before_nav_item_name, new_nav_item:)
expect(structure).to include(a_hash_including(nav_item: before_nav_item_name))
- index = structure.find_index { |h| h[:nav_item] == before_nav_item_name }
+ index = structure.find_index { |h| h[:nav_item] == before_nav_item_name if h }
structure.insert(index + 1, new_nav_item)
end
def insert_after_sub_nav_item(before_sub_nav_item_name, within:, new_sub_nav_item_name:)
expect(structure).to include(a_hash_including(nav_item: within))
- hash = structure.find { |h| h[:nav_item] == within }
+ hash = structure.find { |h| h[:nav_item] == within if h }
expect(hash).to have_key(:nav_sub_items)
expect(hash[:nav_sub_items]).to include(before_sub_nav_item_name)
diff --git a/spec/support/helpers/next_found_instance_of.rb b/spec/support/helpers/next_found_instance_of.rb
new file mode 100644
index 00000000000..ff34fcdd1d3
--- /dev/null
+++ b/spec/support/helpers/next_found_instance_of.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module NextFoundInstanceOf
+ ERROR_MESSAGE = 'NextFoundInstanceOf mock helpers can only be used with ActiveRecord targets'
+
+ def expect_next_found_instance_of(klass)
+ check_if_active_record!(klass)
+
+ stub_allocate(expect(klass)) do |expectation|
+ yield(expectation)
+ end
+ end
+
+ def allow_next_found_instance_of(klass)
+ check_if_active_record!(klass)
+
+ stub_allocate(allow(klass)) do |allowance|
+ yield(allowance)
+ end
+ end
+
+ private
+
+ def check_if_active_record!(klass)
+ raise ArgumentError.new(ERROR_MESSAGE) unless klass < ActiveRecord::Base
+ end
+
+ def stub_allocate(target)
+ target.to receive(:allocate).and_wrap_original do |method|
+ method.call.tap { |allocation| yield(allocation) }
+ end
+ end
+end
diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb
index a32e39e52c8..4b4285f251e 100644
--- a/spec/support/helpers/project_forks_helper.rb
+++ b/spec/support/helpers/project_forks_helper.rb
@@ -17,14 +17,26 @@ module ProjectForksHelper
project.add_developer(user)
end
- unless params[:namespace] || params[:namespace_id]
+ unless params[:namespace]
params[:namespace] = create(:group)
params[:namespace].add_owner(user)
end
+ namespace = params[:namespace]
+ create_repository = params.delete(:repository)
+
+ unless params[:target_project] || params[:using_service]
+ target_level = [project.visibility_level, namespace.visibility_level].min
+ visibility_level = Gitlab::VisibilityLevel.closest_allowed_level(target_level)
+
+ params[:target_project] =
+ create(:project,
+ (:repository if create_repository),
+ visibility_level: visibility_level, creator: user, namespace: namespace)
+ end
+
service = Projects::ForkService.new(project, user, params)
- create_repository = params.delete(:repository)
# Avoid creating a repository
unless create_repository
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb
index 7741c805b37..bbba58d60d6 100644
--- a/spec/support/helpers/repo_helpers.rb
+++ b/spec/support/helpers/repo_helpers.rb
@@ -125,7 +125,7 @@ eos
end
def create_file_in_repo(
- project, start_branch, branch_name, filename, content,
+ project, start_branch, branch_name, filename, content,
commit_message: 'Add new content')
Files::CreateService.new(
project,
diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb
new file mode 100644
index 00000000000..83a5b7e48bc
--- /dev/null
+++ b/spec/support/helpers/snowplow_helpers.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module SnowplowHelpers
+ # Asserts call for one snowplow event from `Gitlab::Tracking#event`.
+ #
+ # @param [Hash]
+ #
+ # Examples:
+ #
+ # describe '#show', :snowplow do
+ # it 'tracks snowplow events' do
+ # get :show
+ #
+ # expect_snowplow_event(category: 'Experiment', action: 'start')
+ # end
+ # end
+ #
+ # describe '#create', :snowplow do
+ # it 'tracks snowplow events' do
+ # post :create
+ #
+ # expect_snowplow_event(
+ # category: 'Experiment',
+ # action: 'created',
+ # )
+ # expect_snowplow_event(
+ # category: 'Experiment',
+ # action: 'accepted',
+ # property: 'property',
+ # label: 'label'
+ # )
+ # end
+ # end
+ def expect_snowplow_event(category:, action:, **kwargs)
+ expect(Gitlab::Tracking).to have_received(:event)
+ .with(category, action, **kwargs).at_least(:once)
+ end
+
+ # Asserts that no call to `Gitlab::Tracking#event` was made.
+ #
+ # Example:
+ #
+ # describe '#show', :snowplow do
+ # it 'does not track any snowplow events' do
+ # get :show
+ #
+ # expect_no_snowplow_event
+ # end
+ # end
+ def expect_no_snowplow_event
+ expect(Gitlab::Tracking).not_to have_received(:event)
+ end
+end
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index 8a52a614821..476b7d34ee5 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -14,7 +14,7 @@ module StubObjectStorage
end
def stub_object_storage_uploader(
- config:,
+ config:,
uploader:,
remote_directory:,
enabled: true,
diff --git a/spec/support/helpers/stubbed_feature.rb b/spec/support/helpers/stubbed_feature.rb
index e78efcf6b75..d4e9af7a031 100644
--- a/spec/support/helpers/stubbed_feature.rb
+++ b/spec/support/helpers/stubbed_feature.rb
@@ -37,10 +37,7 @@ module StubbedFeature
# We do `m.call` as we want to validate the execution of method arguments
# and a feature flag state if it is not persisted
unless Feature.persisted_name?(args.first)
- # TODO: this is hack to support `promo_feature_available?`
- # We enable all feature flags by default unless they are `promo_`
- # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/218667
- feature_flag = true unless args.first.to_s.start_with?('promo_')
+ feature_flag = true
end
feature_flag
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 7dae960410d..641ed24207e 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -247,8 +247,9 @@ module TestEnv
'GitLab Workhorse',
install_dir: workhorse_dir,
version: Gitlab::Workhorse.version,
- task: "gitlab:workhorse:install[#{install_workhorse_args}]"
- )
+ task: "gitlab:workhorse:install[#{install_workhorse_args}]") do
+ Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil)
+ end
end
def workhorse_dir
@@ -259,16 +260,22 @@ module TestEnv
host = "[#{host}]" if host.include?(':')
listen_addr = [host, port].join(':')
+ config_path = Gitlab::SetupHelper::Workhorse.get_config_path(workhorse_dir)
+
+ # This should be set up in setup_workhorse, but since
+ # component_needs_update? only checks that versions are consistent,
+ # we need to ensure the config file exists. This line can be removed
+ # later after a new Workhorse version is updated.
+ Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil) unless File.exist?(config_path)
+
workhorse_pid = spawn(
+ { 'PATH' => "#{ENV['PATH']}:#{workhorse_dir}" },
File.join(workhorse_dir, 'gitlab-workhorse'),
'-authSocket', upstream,
'-documentRoot', Rails.root.join('public').to_s,
'-listenAddr', listen_addr,
'-secretPath', Gitlab::Workhorse.secret_path.to_s,
- # TODO: Needed for workhorse + redis features.
- # https://gitlab.com/gitlab-org/gitlab/-/issues/209245
- #
- # '-config', '',
+ '-config', config_path,
'-logFile', 'log/workhorse-test.log',
'-logFormat', 'structured',
'-developmentMode' # to serve assets and rich error messages
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index fab775dd404..d92fcdc2d4a 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -88,6 +88,8 @@ module UsageDataHelpers
projects_jira_active
projects_jira_server_active
projects_jira_cloud_active
+ projects_jira_dvcs_cloud_active
+ projects_jira_dvcs_server_active
projects_slack_active
projects_slack_slash_commands_active
projects_custom_issue_tracker_active
@@ -136,6 +138,7 @@ module UsageDataHelpers
USAGE_DATA_KEYS = %i(
active_user_count
counts
+ counts_monthly
recorded_at
edition
version
@@ -160,6 +163,7 @@ module UsageDataHelpers
web_ide_clientside_preview_enabled
ingress_modsecurity_enabled
object_store
+ topology
).freeze
def stub_usage_data_connections
@@ -220,17 +224,8 @@ module UsageDataHelpers
)
end
- def expect_prometheus_api_to(*receive_matchers)
- expect_next_instance_of(Gitlab::PrometheusClient) do |client|
- receive_matchers.each { |m| expect(client).to m }
- end
- end
-
- def allow_prometheus_queries
- allow_next_instance_of(Gitlab::PrometheusClient) do |client|
- allow(client).to receive(:aggregate).and_return({})
- allow(client).to receive(:query).and_return({})
- end
+ def expect_prometheus_client_to(*receive_matchers)
+ receive_matchers.each { |m| expect(prometheus_client).to m }
end
def for_defined_days_back(days: [29, 2])
diff --git a/spec/support/helpers/wiki_helpers.rb b/spec/support/helpers/wiki_helpers.rb
index ae0d53d1297..e59c6bde264 100644
--- a/spec/support/helpers/wiki_helpers.rb
+++ b/spec/support/helpers/wiki_helpers.rb
@@ -3,6 +3,11 @@
module WikiHelpers
extend self
+ def stub_group_wikis(enabled)
+ stub_feature_flags(group_wikis: enabled)
+ stub_licensed_features(group_wikis: enabled)
+ end
+
def wait_for_svg_to_be_loaded(example = nil)
# Ensure the SVG is loaded first before clicking the button
find('.svg-content .js-lazy-loaded') if example.nil? || example.metadata.key?(:js)
diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb
index f16b6c1e910..7e95f49aea2 100644
--- a/spec/support/helpers/workhorse_helpers.rb
+++ b/spec/support/helpers/workhorse_helpers.rb
@@ -3,6 +3,8 @@
module WorkhorseHelpers
extend self
+ UPLOAD_PARAM_NAMES = %w[name size path remote_id sha256 type].freeze
+
def workhorse_send_data
@_workhorse_send_data ||= begin
header = response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]
@@ -59,6 +61,7 @@ module WorkhorseHelpers
file = workhorse_params.delete(key)
rewritten_fields[key] = file.path if file
workhorse_params = workhorse_disk_accelerated_file_params(key, file).merge(workhorse_params)
+ workhorse_params = workhorse_params.merge(jwt_file_upload_param(key: key, params: workhorse_params))
end
headers = if send_rewritten_field
@@ -74,8 +77,19 @@ module WorkhorseHelpers
private
- def jwt_token(data = {})
- JWT.encode({ 'iss' => 'gitlab-workhorse' }.merge(data), Gitlab::Workhorse.secret, 'HS256')
+ def jwt_file_upload_param(key:, params:)
+ upload_params = UPLOAD_PARAM_NAMES.map do |file_upload_param|
+ [file_upload_param, params["#{key}.#{file_upload_param}"]]
+ end
+ upload_params = upload_params.to_h.compact
+
+ return {} if upload_params.empty?
+
+ { "#{key}.gitlab-workhorse-upload" => jwt_token('upload' => upload_params) }
+ end
+
+ def jwt_token(data = {}, issuer: 'gitlab-workhorse', secret: Gitlab::Workhorse.secret, algorithm: 'HS256')
+ JWT.encode({ 'iss' => issuer }.merge(data), secret, algorithm)
end
def workhorse_rewritten_fields_header(fields)