diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 04:45:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 04:45:44 +0300 |
commit | 85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch) | |
tree | 9160f299afd8c80c038f08e1545be119f5e3f1e1 /spec/support/helpers | |
parent | 15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff) |
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'spec/support/helpers')
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) |