diff options
Diffstat (limited to 'spec/support')
307 files changed, 2884 insertions, 2560 deletions
diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb index 592962ebf7c..b7aff32460d 100644 --- a/spec/support/api/boards_shared_examples.rb +++ b/spec/support/api/boards_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'group and project boards' do |route_definition, ee = false| let(:root_url) { route_definition.gsub(":id", board_parent.id.to_s) } @@ -14,6 +16,16 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false| end end + it 'avoids N+1 queries' do + pat = create(:personal_access_token, user: user) + control = ActiveRecord::QueryRecorder.new { get api(root_url, personal_access_token: pat) } + + create(:milestone, "#{board_parent.class.name.underscore}": board_parent) + create(:board, "#{board_parent.class.name.underscore}": board_parent) + + expect { get api(root_url, personal_access_token: pat) }.not_to exceed_query_limit(control) + end + describe "GET #{route_definition}" do context "when unauthenticated" do it "returns authentication error" do diff --git a/spec/support/api/issues_resolving_discussions_shared_examples.rb b/spec/support/api/issues_resolving_discussions_shared_examples.rb index d2d6260dfa8..4c44f1bd103 100644 --- a/spec/support/api/issues_resolving_discussions_shared_examples.rb +++ b/spec/support/api/issues_resolving_discussions_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'creating an issue resolving discussions through the API' do it 'creates a new project issue' do expect(response).to have_gitlab_http_status(:created) diff --git a/spec/support/api/members_shared_examples.rb b/spec/support/api/members_shared_examples.rb index 8d910e52eda..603efd4fc75 100644 --- a/spec/support/api/members_shared_examples.rb +++ b/spec/support/api/members_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'a 404 response when source is private' do before do source.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index 63b719be03e..d6439f77408 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'group and project milestones' do |route_definition| let(:resource_route) { "#{route}/#{milestone.id}" } let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } diff --git a/spec/support/api/repositories_shared_context.rb b/spec/support/api/repositories_shared_context.rb index f1341804e56..346015106e3 100644 --- a/spec/support/api/repositories_shared_context.rb +++ b/spec/support/api/repositories_shared_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'disabled repository' do before do project.project_feature.update!( diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb index 4cf34d43117..ebbd57c8115 100644 --- a/spec/support/api/schema_matcher.rb +++ b/spec/support/api/schema_matcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SchemaPath def self.expand(schema, dir = nil) if Gitlab.ee? && dir.nil? diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb index 683234264a8..3786a8012f9 100644 --- a/spec/support/api/scopes/read_user_shared_examples.rb +++ b/spec/support/api/scopes/read_user_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'allows the "read_user" scope' do |api_version| let(:version) { api_version || 'v4' } diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb index 15037222630..3bd1b145433 100644 --- a/spec/support/api/time_tracking_shared_examples.rb +++ b/spec/support/api/time_tracking_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'an unauthorized API user' do it { is_expected.to eq(403) } end diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb index 476d80f3a93..27765652f28 100644 --- a/spec/support/banzai/reference_filter_shared_examples.rb +++ b/spec/support/banzai/reference_filter_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specs for reference links containing HTML. # # Requires a reference: diff --git a/spec/support/batch_loader.rb b/spec/support/batch_loader.rb index bb790e660a6..0eb8f279d29 100644 --- a/spec/support/batch_loader.rb +++ b/spec/support/batch_loader.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.configure do |config| config.after do BatchLoader::Executor.clear_current diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 56ac208a025..8accc5c1df5 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # rubocop:disable Style/GlobalVars require 'capybara/rails' require 'capybara/rspec' @@ -58,6 +60,7 @@ Capybara.javascript_driver = :chrome Capybara.default_max_wait_time = timeout Capybara.ignore_hidden_elements = true Capybara.default_normalize_ws = true +Capybara.enable_aria_label = true # Keep only the screenshots generated from the last failing test suite Capybara::Screenshot.prune_strategy = :keep_last_run diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb index b376822d530..8da55514c78 100644 --- a/spec/support/carrierwave.rb +++ b/spec/support/carrierwave.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + CarrierWave.root = File.expand_path('tmp/tests/public', Rails.root) RSpec.configure do |config| diff --git a/spec/support/chunked_io/chunked_io_helpers.rb b/spec/support/chunked_io/chunked_io_helpers.rb index fec1f951563..278f577f3cb 100644 --- a/spec/support/chunked_io/chunked_io_helpers.rb +++ b/spec/support/chunked_io/chunked_io_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ChunkedIOHelpers def sample_trace_raw @sample_trace_raw ||= File.read(expand_fixture_path('trace/sample_trace')) diff --git a/spec/support/commit_trailers_spec_helper.rb b/spec/support/commit_trailers_spec_helper.rb index efa317fd2f9..a958e259368 100644 --- a/spec/support/commit_trailers_spec_helper.rb +++ b/spec/support/commit_trailers_spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module CommitTrailersSpecHelper extend ActiveSupport::Concern diff --git a/spec/support/controllers/githubish_import_controller_shared_context.rb b/spec/support/controllers/githubish_import_controller_shared_context.rb index e71994edec6..3706178ee34 100644 --- a/spec/support/controllers/githubish_import_controller_shared_context.rb +++ b/spec/support/controllers/githubish_import_controller_shared_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'a GitHub-ish import controller' do let(:user) { create(:user) } let(:token) { "asdasd12345" } diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb index 5bb1269a19d..718d9857b18 100644 --- a/spec/support/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specifications for behavior common to all objects with an email attribute. # Takes a list of email-format attributes and requires: # - subject { "the object with a attribute= setter" } @@ -321,7 +323,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end end - context 'user has chosen an existing nested namespace and name for the project', :postgresql do + context 'user has chosen an existing nested namespace and name for the project' do let(:parent_namespace) { create(:group, name: 'foo') } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:test_name) { 'test_name' } @@ -340,7 +342,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end end - context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do + context 'user has chosen a non-existent nested namespaces and name for the project' do let(:test_name) { 'test_name' } it 'takes the selected namespace and name' do @@ -371,7 +373,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do end end - context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do + context 'user has chosen existent and non-existent nested namespaces and name for the project' do let(:test_name) { 'test_name' } let!(:parent_namespace) { create(:group, name: 'foo') } diff --git a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb index a0c77eecb61..d636c1cf6cd 100644 --- a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb +++ b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_context 'Ldap::OmniauthCallbacksController' do diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb index 355555d9d19..b5149a0fcb1 100644 --- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb +++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'authenticates sessionless user' do |path, format, params| params ||= {} diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb index 19b32c84d81..c57abbd96c6 100644 --- a/spec/support/cycle_analytics_helpers/test_generation.rb +++ b/spec/support/cycle_analytics_helpers/test_generation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # rubocop:disable Metrics/AbcSize # Note: The ABC size is large here because we have a method generating test cases with @@ -50,7 +52,7 @@ module CycleAnalyticsHelpers end median_time_difference = time_differences.sort[2] - expect(subject[phase].median).to be_within(5).of(median_time_difference) + expect(subject[phase].project_median).to be_within(5).of(median_time_difference) end context "when the data belongs to another project" do @@ -80,7 +82,7 @@ module CycleAnalyticsHelpers # Turn off the stub before checking assertions allow(self).to receive(:project).and_call_original - expect(subject[phase].median).to be_nil + expect(subject[phase].project_median).to be_nil end end @@ -103,7 +105,7 @@ module CycleAnalyticsHelpers Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn - expect(subject[phase].median).to be_nil + expect(subject[phase].project_median).to be_nil end end end @@ -121,7 +123,7 @@ module CycleAnalyticsHelpers Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn - expect(subject[phase].median).to be_nil + expect(subject[phase].project_median).to be_nil end end end @@ -138,7 +140,7 @@ module CycleAnalyticsHelpers post_fn[self, data] if post_fn - expect(subject[phase].median).to be_nil + expect(subject[phase].project_median).to be_nil end end end @@ -146,7 +148,7 @@ module CycleAnalyticsHelpers context "when none of the start / end conditions are matched" do it "returns nil" do - expect(subject[phase].median).to be_nil + expect(subject[phase].project_median).to be_nil end end end diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb index edd7de94203..f0dd6c52b74 100644 --- a/spec/support/database_cleaner.rb +++ b/spec/support/database_cleaner.rb @@ -26,31 +26,22 @@ RSpec.configure do |config| end config.append_after(:context) do - DatabaseCleaner.clean_with(:deletion, cache_tables: false) + delete_from_all_tables! end - config.before do - setup_database_cleaner - DatabaseCleaner.strategy = :transaction - end + config.around(:each, :delete) do |example| + self.class.use_transactional_tests = false - config.before(:each, :js) do - DatabaseCleaner.strategy = :deletion, { except: deletion_except_tables, cache_tables: false } - end + example.run - config.before(:each, :delete) do - DatabaseCleaner.strategy = :deletion, { except: deletion_except_tables, cache_tables: false } + delete_from_all_tables!(except: deletion_except_tables) end - config.before(:each, :migration) do - DatabaseCleaner.strategy = :deletion, { cache_tables: false } - end + config.around(:each, :migration) do |example| + self.class.use_transactional_tests = false - config.before do - DatabaseCleaner.start - end + example.run - config.append_after do - DatabaseCleaner.clean + delete_from_all_tables! end end diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb index c69fa322073..041ffa25535 100644 --- a/spec/support/db_cleaner.rb +++ b/spec/support/db_cleaner.rb @@ -1,4 +1,10 @@ +# frozen_string_literal: true + module DbCleaner + def delete_from_all_tables!(except: nil) + DatabaseCleaner.clean_with(:deletion, cache_tables: false, except: except) + end + def deletion_except_tables [] end diff --git a/spec/support/external_authorization_service_helpers.rb b/spec/support/external_authorization_service_helpers.rb index 79dd9a3d58e..f4214d800cf 100644 --- a/spec/support/external_authorization_service_helpers.rb +++ b/spec/support/external_authorization_service_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ExternalAuthorizationServiceHelpers def enable_external_authorization_service_check stub_application_setting(external_authorization_service_enabled: true) diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 0f8af2c5d6d..5590bf0fb7e 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -1,4 +1,6 @@ -shared_examples 'discussion comments' do |resource_name| +# frozen_string_literal: true + +shared_examples 'thread comments' do |resource_name| let(:form_selector) { '.js-main-target-form' } let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" } let(:toggle_selector) { "#{dropdown_selector} .dropdown-toggle" } @@ -48,7 +50,7 @@ shared_examples 'discussion comments' do |resource_name| find(toggle_selector).click end - it 'has a "Comment" item (selected by default) and "Start discussion" item' do + it 'has a "Comment" item (selected by default) and "Start thread" item' do expect(page).to have_selector menu_selector find("#{menu_selector} li", match: :first) @@ -59,7 +61,7 @@ shared_examples 'discussion comments' do |resource_name| expect(items.first).to have_selector '.fa-check' expect(items.first['class']).to match 'droplab-item-selected' - expect(items.last).to have_content 'Start discussion' + expect(items.last).to have_content 'Start thread' expect(items.last).to have_content "Discuss a specific suggestion or question#{' that needs to be resolved' if resource_name == 'merge request'}." expect(items.last).not_to have_selector '.fa-check' expect(items.last['class']).not_to match 'droplab-item-selected' @@ -103,7 +105,7 @@ shared_examples 'discussion comments' do |resource_name| expect(find(dropdown_selector)).to have_content 'Comment' end - describe 'when selecting "Start discussion"' do + describe 'when selecting "Start thread"' do before do find("#{menu_selector} li", match: :first) all("#{menu_selector} li").last.click @@ -114,9 +116,9 @@ shared_examples 'discussion comments' do |resource_name| # on issues page, the submit input is a <button>, on other pages it is <input> if button.tag_name == 'button' - expect(find(submit_selector)).to have_content 'Start discussion' + expect(find(submit_selector)).to have_content 'Start thread' else - expect(find(submit_selector).value).to eq 'Start discussion' + expect(find(submit_selector).value).to eq 'Start thread' end expect(page).not_to have_selector menu_selector @@ -124,17 +126,17 @@ shared_examples 'discussion comments' do |resource_name| if resource_name =~ /(issue|merge request)/ it 'updates the close button text' do - expect(find(close_selector)).to have_content "Start discussion & close #{resource_name}" + expect(find(close_selector)).to have_content "Start thread & close #{resource_name}" end it 'typing does not change the close button text' do find("#{form_selector} .note-textarea").send_keys('b') - expect(find(close_selector)).to have_content "Start discussion & close #{resource_name}" + expect(find(close_selector)).to have_content "Start thread & close #{resource_name}" end end - describe 'creating a discussion' do + describe 'creating a thread' do before do find(submit_selector).click wait_for_requests @@ -150,7 +152,7 @@ shared_examples 'discussion comments' do |resource_name| wait_for_requests end - it 'clicking "Start discussion" will post a discussion' do + it 'clicking "Start thread" will post a thread' do new_comment = all(comments_selector).last expect(new_comment).to have_content 'a' @@ -176,10 +178,10 @@ shared_examples 'discussion comments' do |resource_name| if resource_name == 'merge request' let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] } - let(:reply_id) { find("#{comments_selector} .note:last-child", match: :first)['data-note-id'] } + let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] } it 'can be replied to after resolving' do - click_button "Resolve discussion" + click_button "Resolve thread" wait_for_requests refresh @@ -188,10 +190,10 @@ shared_examples 'discussion comments' do |resource_name| submit_reply('to reply or not reply') end - it 'shows resolved discussion when toggled' do + it 'shows resolved thread when toggled' do submit_reply('a') - click_button "Resolve discussion" + click_button "Resolve thread" wait_for_requests expect(page).to have_selector(".note-row-#{note_id}", visible: true) @@ -205,7 +207,7 @@ shared_examples 'discussion comments' do |resource_name| end if resource_name == 'issue' - it "clicking 'Start discussion & close #{resource_name}' will post a discussion and close the #{resource_name}" do + it "clicking 'Start thread & close #{resource_name}' will post a thread and close the #{resource_name}" do find(close_selector).click find(comments_selector, match: :first) @@ -224,7 +226,7 @@ shared_examples 'discussion comments' do |resource_name| find(toggle_selector).click end - it 'has "Start discussion" selected' do + it 'has "Start thread" selected' do find("#{menu_selector} li", match: :first) items = all("#{menu_selector} li") @@ -232,7 +234,7 @@ shared_examples 'discussion comments' do |resource_name| expect(items.first).not_to have_selector '.fa-check' expect(items.first['class']).not_to match 'droplab-item-selected' - expect(items.last).to have_content 'Start discussion' + expect(items.last).to have_content 'Start thread' expect(items.last).to have_selector '.fa-check' expect(items.last['class']).to match 'droplab-item-selected' end @@ -277,7 +279,7 @@ shared_examples 'discussion comments' do |resource_name| expect(items.first).to have_selector '.fa-check' expect(items.first['class']).to match 'droplab-item-selected' - expect(items.last).to have_content 'Start discussion' + expect(items.last).to have_content 'Start thread' expect(items.last).not_to have_selector '.fa-check' expect(items.last['class']).not_to match 'droplab-item-selected' end @@ -299,13 +301,13 @@ shared_examples 'discussion comments' do |resource_name| expect(find("#{form_selector} .js-note-target-reopen")).to have_content "Comment & reopen #{resource_name}" end - it "should show a 'Start discussion & reopen #{resource_name}' button when 'Start discussion' is selected" do + it "should show a 'Start thread & reopen #{resource_name}' button when 'Start thread' is selected" do find(toggle_selector).click find("#{menu_selector} li", match: :first) all("#{menu_selector} li").last.click - expect(find("#{form_selector} .js-note-target-reopen")).to have_content "Start discussion & reopen #{resource_name}" + expect(find("#{form_selector} .js-note-target-reopen")).to have_content "Start thread & reopen #{resource_name}" end end end diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb index 5d5a0a7b5d2..2f9208e6ed5 100644 --- a/spec/support/features/reportable_note_shared_examples.rb +++ b/spec/support/features/reportable_note_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'reportable note' do |type| diff --git a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb index 38e5fb155a4..d4f8a87d0d8 100644 --- a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb +++ b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb @@ -1,4 +1,6 @@ -shared_examples 'creating an issue for a discussion' do +# frozen_string_literal: true + +shared_examples 'creating an issue for a thread' do it 'shows an issue with the title filled in' do title_field = page.find_field('issue[title]') diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb index 0de92aedba5..c97eeba87db 100644 --- a/spec/support/features/rss_shared_examples.rb +++ b/spec/support/features/rss_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples "an autodiscoverable RSS feed with current_user's feed token" do it "has an RSS autodiscovery link tag with current_user's feed token" do expect(page).to have_css("link[type*='atom+xml'][href*='feed_token=#{user.feed_token}']", visible: false) diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 01531864c1f..0f8ad2c6536 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'variable list' do it 'shows list of variables' do page.within('.js-ci-variable-list-section') do diff --git a/spec/support/forgery_protection.rb b/spec/support/forgery_protection.rb index fa87d5fa881..1d6ea013292 100644 --- a/spec/support/forgery_protection.rb +++ b/spec/support/forgery_protection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ForgeryProtection def with_forgery_protection ActionController::Base.allow_forgery_protection = true diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb index 2fdbddd40c2..a1328ef0d13 100644 --- a/spec/support/google_api/cloud_platform_helpers.rb +++ b/spec/support/google_api/cloud_platform_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module GoogleApi module CloudPlatformHelpers def stub_google_api_validate_token diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb index 4a9ce9beb78..aff0f87b6e4 100644 --- a/spec/support/helpers/api_helpers.rb +++ b/spec/support/helpers/api_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ApiHelpers # Public: Prepend a request path with the path to the API # @@ -30,11 +32,12 @@ module ApiHelpers end if query_string - full_path << (path.index('?') ? '&' : '?') - full_path << query_string - end + separator = path.index('?') ? '&' : '?' - full_path + full_path + separator + query_string + else + full_path + end end def expect_paginated_array_response(items) diff --git a/spec/support/helpers/assets_helpers.rb b/spec/support/helpers/assets_helpers.rb index 09bbf451671..fa24ad9ad2a 100644 --- a/spec/support/helpers/assets_helpers.rb +++ b/spec/support/helpers/assets_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AssetsHelpers # In a CI environment the assets are not compiled, as there is a CI job # `compile-assets` that compiles them in the prepare stage for all following diff --git a/spec/support/helpers/bare_repo_operations.rb b/spec/support/helpers/bare_repo_operations.rb index 3f4a4243cb6..099610f087d 100644 --- a/spec/support/helpers/bare_repo_operations.rb +++ b/spec/support/helpers/bare_repo_operations.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'zlib' class BareRepoOperations diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb index b85fde222ea..683ee3e4bf2 100644 --- a/spec/support/helpers/board_helpers.rb +++ b/spec/support/helpers/board_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module BoardHelpers def click_card(card) within card do diff --git a/spec/support/helpers/capybara_helpers.rb b/spec/support/helpers/capybara_helpers.rb index bcc2df44708..5abbc1e2951 100644 --- a/spec/support/helpers/capybara_helpers.rb +++ b/spec/support/helpers/capybara_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module CapybaraHelpers # Execute a block a certain number of times before considering it a failure # diff --git a/spec/support/helpers/ci_artifact_metadata_generator.rb b/spec/support/helpers/ci_artifact_metadata_generator.rb index ef638d59d2d..e02501565a9 100644 --- a/spec/support/helpers/ci_artifact_metadata_generator.rb +++ b/spec/support/helpers/ci_artifact_metadata_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # frozen_sting_literal: true # This generates fake CI metadata .gz for testing diff --git a/spec/support/helpers/cookie_helper.rb b/spec/support/helpers/cookie_helper.rb index 5ff7b0b68c9..ea4be12355b 100644 --- a/spec/support/helpers/cookie_helper.rb +++ b/spec/support/helpers/cookie_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Helper for setting cookies in Selenium/WebDriver # module CookieHelper diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 33648292037..575b2e779c5 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module CycleAnalyticsHelpers include GitHelpers @@ -10,7 +12,7 @@ module CycleAnalyticsHelpers repository = project.repository oldrev = repository.commit(branch_name)&.sha || Gitlab::Git::BLANK_SHA - if Timecop.frozen? && Gitlab::GitalyClient.feature_enabled?(:operation_user_commit_files) + if Timecop.frozen? mock_gitaly_multi_action_dates(repository, commit_time) end diff --git a/spec/support/helpers/database_connection_helpers.rb b/spec/support/helpers/database_connection_helpers.rb index 763329499f0..10ea7b5de91 100644 --- a/spec/support/helpers/database_connection_helpers.rb +++ b/spec/support/helpers/database_connection_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DatabaseConnectionHelpers def run_with_new_database_connection pool = ActiveRecord::Base.connection_pool diff --git a/spec/support/helpers/devise_helpers.rb b/spec/support/helpers/devise_helpers.rb index d32bc2424c0..70fc6a48414 100644 --- a/spec/support/helpers/devise_helpers.rb +++ b/spec/support/helpers/devise_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeviseHelpers # explicitly tells Devise which mapping to use # this is needed when we are testing a Devise controller bypassing the router @@ -21,4 +23,16 @@ module DeviseHelpers context.env end end + + def with_omniauth_full_host(&block) + # The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost` + # here), and causes integration tests to fail with 404s. We set the `full_host` by removing the request path (and + # anything after it) from the request URI. + omniauth_config_full_host = OmniAuth.config.full_host + OmniAuth.config.full_host = ->(request) { ActionDispatch::Request.new(request).base_url } + + yield + + OmniAuth.config.full_host = omniauth_config_full_host + end end diff --git a/spec/support/helpers/drag_to_helper.rb b/spec/support/helpers/drag_to_helper.rb index 6d53ad0b602..6099f87323f 100644 --- a/spec/support/helpers/drag_to_helper.rb +++ b/spec/support/helpers/drag_to_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DragTo def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000) evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), duration: #{duration}, from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});") diff --git a/spec/support/helpers/dropzone_helper.rb b/spec/support/helpers/dropzone_helper.rb index fe72d320fcf..a0f261b312e 100644 --- a/spec/support/helpers/dropzone_helper.rb +++ b/spec/support/helpers/dropzone_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DropzoneHelper # Provides a way to perform `attach_file` for a Dropzone-based file input # diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb index ed049daba80..024340310a1 100644 --- a/spec/support/helpers/email_helpers.rb +++ b/spec/support/helpers/email_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module EmailHelpers def sent_to_user(user, recipients: email_recipients) recipients.count { |to| to == user.notification_email } @@ -29,6 +31,10 @@ module EmailHelpers expect(ActionMailer::Base.deliveries).to be_empty end + def should_email_anyone + expect(ActionMailer::Base.deliveries).not_to be_empty + end + def email_recipients(kind: :to) ActionMailer::Base.deliveries.flat_map(&kind) end diff --git a/spec/support/helpers/exclusive_lease_helpers.rb b/spec/support/helpers/exclusive_lease_helpers.rb index 383cc7dee81..77703e20602 100644 --- a/spec/support/helpers/exclusive_lease_helpers.rb +++ b/spec/support/helpers/exclusive_lease_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ExclusiveLeaseHelpers def stub_exclusive_lease(key = nil, uuid = 'uuid', renew: false, timeout: nil) key ||= instance_of(String) diff --git a/spec/support/helpers/expect_next_instance_of.rb b/spec/support/helpers/expect_next_instance_of.rb index b95046b2b42..749d2cb2a56 100644 --- a/spec/support/helpers/expect_next_instance_of.rb +++ b/spec/support/helpers/expect_next_instance_of.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ExpectNextInstanceOf def expect_next_instance_of(klass, *new_args) receive_new = receive(:new) diff --git a/spec/support/helpers/expect_offense.rb b/spec/support/helpers/expect_offense.rb index 35718ba90c5..76301fe19ff 100644 --- a/spec/support/helpers/expect_offense.rb +++ b/spec/support/helpers/expect_offense.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rubocop/rspec/support' # https://github.com/backus/rubocop-rspec/blob/master/spec/support/expect_offense.rb diff --git a/spec/support/helpers/expect_request_with_status.rb b/spec/support/helpers/expect_request_with_status.rb new file mode 100644 index 00000000000..0469a94e336 --- /dev/null +++ b/spec/support/helpers/expect_request_with_status.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ExpectRequestWithStatus + def expect_request_with_status(status) + expect do + yield + + expect(response).to have_gitlab_http_status(status) + end + end +end diff --git a/spec/support/helpers/fake_blob_helpers.rb b/spec/support/helpers/fake_blob_helpers.rb index 801ca8b7412..ef4740638ff 100644 --- a/spec/support/helpers/fake_blob_helpers.rb +++ b/spec/support/helpers/fake_blob_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module FakeBlobHelpers class FakeBlob include BlobLike diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb index c7766df7a52..6c066b3b199 100644 --- a/spec/support/helpers/fake_migration_classes.rb +++ b/spec/support/helpers/fake_migration_classes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration[4.2] include Gitlab::Database::RenameReservedPathsMigration::V1 diff --git a/spec/support/helpers/fake_u2f_device.rb b/spec/support/helpers/fake_u2f_device.rb index a7605cd483a..f765b277175 100644 --- a/spec/support/helpers/fake_u2f_device.rb +++ b/spec/support/helpers/fake_u2f_device.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class FakeU2fDevice attr_reader :name @@ -32,6 +34,10 @@ class FakeU2fDevice ") end + def fake_u2f_authentication + @page.execute_script("window.gl.u2fAuthenticate.renderAuthenticated('abc');") + end + private def u2f_device(app_id) diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb index df88fd425c9..2a50b41cb4e 100644 --- a/spec/support/helpers/features/branches_helpers.rb +++ b/spec/support/helpers/features/branches_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These helpers allow you to manipulate with sorting features. # # Usage: diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb index 8a139fafac2..8c27f81930d 100644 --- a/spec/support/helpers/features/notes_helpers.rb +++ b/spec/support/helpers/features/notes_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These helpers allow you to manipulate with notes. # # Usage: @@ -21,6 +23,14 @@ module Spec end end + def edit_note(note_text_to_edit, new_note_text) + page.within('#notes-list li.note', text: note_text_to_edit) do + find('.js-note-edit').click + fill_in('note[note]', with: new_note_text) + find('.js-comment-button').click + end + end + def preview_note(text) page.within('.js-main-target-form') do filled_text = fill_in('note[note]', with: text) diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb index 003ecb251fe..a6428bf8573 100644 --- a/spec/support/helpers/features/sorting_helpers.rb +++ b/spec/support/helpers/features/sorting_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These helpers allow you to manipulate with sorting features. # # Usage: diff --git a/spec/support/helpers/filter_spec_helper.rb b/spec/support/helpers/filter_spec_helper.rb index 721d359c2ee..95c24d76dcd 100644 --- a/spec/support/helpers/filter_spec_helper.rb +++ b/spec/support/helpers/filter_spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Helper methods for Banzai filter specs # # Must be included into specs manually diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 34ef185ea27..39c818b1763 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module FilteredSearchHelpers def filtered_search page.find('.filtered-search') diff --git a/spec/support/helpers/fixture_helpers.rb b/spec/support/helpers/fixture_helpers.rb index 611d19f36a0..7b3b8ae5f7a 100644 --- a/spec/support/helpers/fixture_helpers.rb +++ b/spec/support/helpers/fixture_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module FixtureHelpers def fixture_file(filename, dir: '') return '' if filename.blank? diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb index cd49bb148f2..de8bb9ac8e3 100644 --- a/spec/support/helpers/git_http_helpers.rb +++ b/spec/support/helpers/git_http_helpers.rb @@ -1,4 +1,10 @@ +# frozen_string_literal: true + +require_relative 'workhorse_helpers' + module GitHttpHelpers + include WorkhorseHelpers + def clone_get(project, options = {}) get "/#{project}/info/refs", params: { service: 'git-upload-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token)) end diff --git a/spec/support/helpers/gitlab_verify_helpers.rb b/spec/support/helpers/gitlab_verify_helpers.rb index 5df4bf24ec2..9901ce374ed 100644 --- a/spec/support/helpers/gitlab_verify_helpers.rb +++ b/spec/support/helpers/gitlab_verify_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module GitlabVerifyHelpers def collect_ranges(args = {}) verifier = described_class.new(args.merge(batch_size: 1)) diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index e95c7f2a6d6..d86371d70b9 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -1,13 +1,14 @@ +# frozen_string_literal: true + module GraphqlHelpers MutationDefinition = Struct.new(:query, :variables) + NoData = Class.new(StandardError) + # makes an underscored string look like a fieldname # "merge_request" => "mergeRequest" def self.fieldnamerize(underscored_field_name) - graphql_field_name = underscored_field_name.to_s.camelize - graphql_field_name[0] = graphql_field_name[0].downcase - - graphql_field_name + underscored_field_name.to_s.camelize(:lower) end # Run a loader's named resolver @@ -60,7 +61,8 @@ module GraphqlHelpers end def variables_for_mutation(name, input) - graphql_input = input.map { |name, value| [GraphqlHelpers.fieldnamerize(name), value] }.to_h + graphql_input = prepare_input_for_mutation(input) + result = { input_variable_name_for_mutation(name) => graphql_input } # Avoid trying to serialize multipart data into JSON @@ -71,6 +73,18 @@ module GraphqlHelpers end end + # Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys + # + # prepare_input_for_mutation({ 'my_key' => 1 }) + # => { 'myKey' => 1} + def prepare_input_for_mutation(input) + input.map do |name, value| + value = prepare_input_for_mutation(value) if value.is_a?(Hash) + + [GraphqlHelpers.fieldnamerize(name), value] + end.to_h + end + def input_variable_name_for_mutation(mutation_name) mutation_name = GraphqlHelpers.fieldnamerize(mutation_name) mutation_field = GitlabSchema.mutation.fields[mutation_name] @@ -146,8 +160,9 @@ module GraphqlHelpers post_graphql(mutation.query, current_user: current_user, variables: mutation.variables) end + # Raises an error if no data is found def graphql_data - json_response['data'] + json_response['data'] || (raise NoData, graphql_errors) end def graphql_errors @@ -157,12 +172,13 @@ module GraphqlHelpers when Array # multiplexed queries json_response.map { |response| response['errors'] } else - raise "Unkown GraphQL response type #{json_response.class}" + raise "Unknown GraphQL response type #{json_response.class}" end end + # Raises an error if no response is found def graphql_mutation_response(mutation_name) - graphql_data[GraphqlHelpers.fieldnamerize(mutation_name)] + graphql_data.fetch(GraphqlHelpers.fieldnamerize(mutation_name)) end def nested_fields?(field) diff --git a/spec/support/helpers/import_spec_helper.rb b/spec/support/helpers/import_spec_helper.rb index d4eced724fa..d8fb2ba08af 100644 --- a/spec/support/helpers/import_spec_helper.rb +++ b/spec/support/helpers/import_spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'ostruct' # Helper methods for controller specs in the Import namespace diff --git a/spec/support/helpers/input_helper.rb b/spec/support/helpers/input_helper.rb index acbb42274ec..5136f8e9cb8 100644 --- a/spec/support/helpers/input_helper.rb +++ b/spec/support/helpers/input_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # see app/assets/javascripts/test_utils/simulate_input.js module InputHelper diff --git a/spec/support/helpers/inspect_requests.rb b/spec/support/helpers/inspect_requests.rb index 88ddc5c7f6c..4a1d9cb8539 100644 --- a/spec/support/helpers/inspect_requests.rb +++ b/spec/support/helpers/inspect_requests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative './wait_for_requests' module InspectRequests diff --git a/spec/support/helpers/issue_helpers.rb b/spec/support/helpers/issue_helpers.rb index ffd72515f37..82b954a92e2 100644 --- a/spec/support/helpers/issue_helpers.rb +++ b/spec/support/helpers/issue_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module IssueHelpers def visit_issues(project, opts = {}) visit project_issues_path project, opts diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index cdd7724cc13..7ec65318ec5 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'action_dispatch/testing/test_request' require 'fileutils' @@ -19,7 +21,7 @@ module JavaScriptFixturesHelpers end def fixture_root_path - (Gitlab.ee? ? 'ee/' : '') + 'spec/javascripts/fixtures' + 'tmp/tests/frontend/fixtures' + (Gitlab.ee? ? '-ee' : '') end # Public: Removes all fixture files from given directory diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb index f4d5343c4ed..57c33c81ea3 100644 --- a/spec/support/helpers/jira_service_helper.rb +++ b/spec/support/helpers/jira_service_helper.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + module JiraServiceHelper JIRA_URL = "http://jira.example.net".freeze JIRA_API = JIRA_URL + "/rest/api/2" def jira_service_settings properties = { - title: "JIRA tracker", + title: "Jira tracker", url: JIRA_URL, username: 'jira-user', password: 'my-secret-password', @@ -25,7 +27,7 @@ module JiraServiceHelper \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"}, \"displayName\":\"GitLab\",\"active\":true}, - \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\", + \"body\":\"[Administrator|http://localhost:3000/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\", \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", @@ -40,7 +42,7 @@ module JiraServiceHelper \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, - \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\", + \"body\":\"[Administrator|http://localhost:3000/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\", \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", diff --git a/spec/support/helpers/key_generator_helper.rb b/spec/support/helpers/key_generator_helper.rb index d55d8312c65..59c8eeb3692 100644 --- a/spec/support/helpers/key_generator_helper.rb +++ b/spec/support/helpers/key_generator_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spec module Support module Helpers @@ -33,7 +35,7 @@ module Spec # Packs string components into an openssh-encoded pubkey. def pack_pubkey_components(strings) - (strings.map { |s| [s.length].pack('N') }).zip(strings).flatten.join + (strings.flat_map { |s| [s.length].pack('N') }).zip(strings).join end end end diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index 011c4df0fe5..538a5b8ef3c 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module KubernetesHelpers include Gitlab::Kubernetes @@ -104,6 +106,26 @@ module KubernetesHelpers .to_return(status: [status, "Internal Server Error"]) end + def stub_kubeclient_get_secret_not_found_then_found(api_url, **options) + options[:metadata_name] ||= "default-token-1" + options[:namespace] ||= "default" + + WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}") + .to_return(status: [404, "Not Found"]) + .then + .to_return(kube_response(kube_v1_secret_body(options))) + end + + def stub_kubeclient_get_secret_missing_token_then_with_token(api_url, **options) + options[:metadata_name] ||= "default-token-1" + options[:namespace] ||= "default" + + WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}") + .to_return(kube_response(kube_v1_secret_body(options.merge(token: nil)))) + .then + .to_return(kube_response(kube_v1_secret_body(options))) + end + def stub_kubeclient_get_service_account(api_url, name, namespace: 'default') WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts/#{name}") .to_return(kube_response({})) @@ -179,16 +201,21 @@ module KubernetesHelpers .to_return(kube_response({})) end + def stub_kubeclient_put_role(api_url, name, namespace: 'default') + WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{name}") + .to_return(kube_response({})) + end + def kube_v1_secret_body(**options) { "kind" => "SecretList", "apiVersion": "v1", "metadata": { - "name": options[:metadata_name] || "default-token-1", + "name": options.fetch(:metadata_name, "default-token-1"), "namespace": "kube-system" }, "data": { - "token": options[:token] || Base64.encode64('token-sample-123') + "token": options.fetch(:token, Base64.encode64('token-sample-123')) } } end diff --git a/spec/support/helpers/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb index 66ca5d7f0a3..dce8a3803f5 100644 --- a/spec/support/helpers/ldap_helpers.rb +++ b/spec/support/helpers/ldap_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module LdapHelpers def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap)) ::Gitlab::Auth::LDAP::Adapter.new(provider, ldap) diff --git a/spec/support/helpers/live_debugger.rb b/spec/support/helpers/live_debugger.rb index 911eb48a8ca..d6091035b59 100644 --- a/spec/support/helpers/live_debugger.rb +++ b/spec/support/helpers/live_debugger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'io/console' module LiveDebugger diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index 0bb2d2510c2..2b508ee6f2c 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'devise_helpers' module LoginHelpers @@ -87,12 +89,17 @@ module LoginHelpers click_link "oauth-login-#{provider}" end + def fake_successful_u2f_authentication + allow(U2fRegistration).to receive(:authenticate).and_return(true) + FakeU2fDevice.new(page, nil).fake_u2f_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) end - def mock_auth_hash(provider, uid, email, response_object: nil) + def configure_mock_auth(provider, uid, email, response_object: nil) # The mock_auth configuration allows you to set per-provider (or default) # authentication hashes to return during integration testing. OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({ @@ -118,6 +125,11 @@ module LoginHelpers response_object: response_object } }) + end + + def mock_auth_hash(provider, uid, email, response_object: nil) + configure_mock_auth(provider, uid, email, response_object: response_object) + original_env_config_omniauth_auth = Rails.application.env_config['omniauth.auth'] Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym] diff --git a/spec/support/helpers/markdown_feature.rb b/spec/support/helpers/markdown_feature.rb index 96401379cf0..eea03fb9325 100644 --- a/spec/support/helpers/markdown_feature.rb +++ b/spec/support/helpers/markdown_feature.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This is a helper class used by the GitLab Markdown feature spec # # Because the feature spec only cares about the output of the Markdown, and the diff --git a/spec/support/helpers/memory_usage_helper.rb b/spec/support/helpers/memory_usage_helper.rb new file mode 100644 index 00000000000..984ea8cc571 --- /dev/null +++ b/spec/support/helpers/memory_usage_helper.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module MemoryUsageHelper + extend ActiveSupport::Concern + + def gather_memory_data(csv_path) + write_csv_entry(csv_path, + { + example_group_path: TestEnv.topmost_example_group[:location], + example_group_description: TestEnv.topmost_example_group[:description], + time: Time.current, + job_name: ENV['CI_JOB_NAME'] + }.merge(get_memory_usage)) + end + + def write_csv_entry(path, entry) + CSV.open(path, "a", headers: entry.keys, write_headers: !File.exist?(path)) do |file| + file << entry.values + end + end + + def get_memory_usage + output, status = Gitlab::Popen.popen(%w(free -m)) + abort "`free -m` return code is #{status}: #{output}" unless status.zero? + + result = output.split("\n")[1].split(" ")[1..-1] + attrs = %i(m_total m_used m_free m_shared m_buffers_cache m_available).freeze + + attrs.zip(result).to_h + end + + included do |config| + config.after(:all) do + gather_memory_data(ENV['MEMORY_TEST_PATH']) if ENV['MEMORY_TEST_PATH'] + end + end +end diff --git a/spec/support/helpers/merge_request_diff_helpers.rb b/spec/support/helpers/merge_request_diff_helpers.rb index 3b49d0b3319..49beecc6d4b 100644 --- a/spec/support/helpers/merge_request_diff_helpers.rb +++ b/spec/support/helpers/merge_request_diff_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequestDiffHelpers def click_diff_line(line_holder, diff_side = nil) line = get_line_components(line_holder, diff_side) diff --git a/spec/support/helpers/merge_request_helpers.rb b/spec/support/helpers/merge_request_helpers.rb index 772adff4626..3c359bc9353 100644 --- a/spec/support/helpers/merge_request_helpers.rb +++ b/spec/support/helpers/merge_request_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequestHelpers def visit_merge_requests(project, opts = {}) visit project_merge_requests_path project, opts diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb index 1f36b0e217c..0e86b6dfda7 100644 --- a/spec/support/helpers/metrics_dashboard_helpers.rb +++ b/spec/support/helpers/metrics_dashboard_helpers.rb @@ -18,6 +18,14 @@ module MetricsDashboardHelpers project.repository.refresh_method_caches([:metrics_dashboard]) end + def system_dashboard_path + Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH + end + + def business_metric_title + PrometheusMetricEnums.group_details[:business][:group_title] + end + shared_examples_for 'misconfigured dashboard service response' do |status_code| it 'returns an appropriate message and status code' do result = service_call @@ -28,9 +36,7 @@ module MetricsDashboardHelpers end end - shared_examples_for 'valid dashboard service response' do - let(:dashboard_schema) { JSON.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/dashboard.json')) } - + shared_examples_for 'valid dashboard service response for schema' do it 'returns a json representation of the dashboard' do result = service_call @@ -40,4 +46,24 @@ module MetricsDashboardHelpers expect(JSON::Validator.fully_validate(dashboard_schema, result[:dashboard])).to be_empty end end + + shared_examples_for 'valid dashboard service response' do + let(:dashboard_schema) { JSON.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/dashboard.json')) } + + it_behaves_like 'valid dashboard service response for schema' + end + + shared_examples_for 'valid embedded dashboard service response' do + let(:dashboard_schema) { JSON.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/embedded_dashboard.json')) } + + it_behaves_like 'valid dashboard service response for schema' + end + + shared_examples_for 'raises error for users with insufficient permissions' do + context 'when the user does not have sufficient access' do + let(:user) { build(:user) } + + it_behaves_like 'misconfigured dashboard service response', :unauthorized + end + end end diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb index cc1a28cb264..2727ab7fb1e 100644 --- a/spec/support/helpers/migrations_helpers.rb +++ b/spec/support/helpers/migrations_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MigrationsHelpers def active_record_base ActiveRecord::Base @@ -18,8 +20,12 @@ module MigrationsHelpers ActiveRecord::Migrator.migrations_paths end + def migration_context + ActiveRecord::MigrationContext.new(migrations_paths) + end + def migrations - ActiveRecord::Migrator.migrations(migrations_paths) + migration_context.migrations end def clear_schema_cache! @@ -96,8 +102,7 @@ module MigrationsHelpers def schema_migrate_down! disable_migrations_output do - ActiveRecord::Migrator.migrate(migrations_paths, - migration_schema_version) + migration_context.down(migration_schema_version) end reset_column_in_all_models @@ -107,7 +112,7 @@ module MigrationsHelpers reset_column_in_all_models disable_migrations_output do - ActiveRecord::Migrator.migrate(migrations_paths) + migration_context.up end reset_column_in_all_models @@ -123,7 +128,7 @@ module MigrationsHelpers end def migrate! - ActiveRecord::Migrator.up(migrations_paths) do |migration| + migration_context.up do |migration| migration.name == described_class.name end end diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb index 4230d315d9b..94dbd2fb1b7 100644 --- a/spec/support/helpers/mobile_helpers.rb +++ b/spec/support/helpers/mobile_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MobileHelpers def resize_screen_xs resize_window(575, 768) diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb index 79a0aa174b1..a4322618cd3 100644 --- a/spec/support/helpers/note_interaction_helpers.rb +++ b/spec/support/helpers/note_interaction_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module NoteInteractionHelpers def open_more_actions_dropdown(note) note_element = find("#note_#{note.id}") diff --git a/spec/support/helpers/notification_helpers.rb b/spec/support/helpers/notification_helpers.rb index 44c2051598c..16ecb338f6e 100644 --- a/spec/support/helpers/notification_helpers.rb +++ b/spec/support/helpers/notification_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module NotificationHelpers extend self diff --git a/spec/support/helpers/position_tracer_helpers.rb b/spec/support/helpers/position_tracer_helpers.rb new file mode 100644 index 00000000000..bbf6e06dd40 --- /dev/null +++ b/spec/support/helpers/position_tracer_helpers.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module PositionTracerHelpers + def diff_refs(base_commit, head_commit) + Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id) + end + + def position(attrs = {}) + attrs.reverse_merge!( + diff_refs: old_diff_refs + ) + Gitlab::Diff::Position.new(attrs) + end + + def expect_new_position(attrs, result = subject) + aggregate_failures("expect new position #{attrs.inspect}") do + if attrs.nil? + expect(result[:outdated]).to be_truthy + else + new_position = result[:position] + + expect(result[:outdated]).to be_falsey + expect(new_position).not_to be_nil + expect(new_position.diff_refs).to eq(new_diff_refs) + + attrs.each do |attr, value| + expect(new_position.send(attr)).to eq(value) + end + end + end + end + + def expect_change_position(attrs, result = subject) + aggregate_failures("expect change position #{attrs.inspect}") do + change_position = result[:position] + + expect(result[:outdated]).to be_truthy + + if attrs.nil? || attrs.empty? + expect(change_position).to be_nil + else + expect(change_position).not_to be_nil + expect(change_position.diff_refs).to eq(change_diff_refs) + + attrs.each do |attr, value| + expect(change_position.send(attr)).to eq(value) + end + end + end + end + + def create_branch(new_name, branch_name) + CreateBranchService.new(project, current_user).execute(new_name, branch_name) + end + + def create_file(branch_name, file_name, content) + Files::CreateService.new( + project, + current_user, + start_branch: branch_name, + branch_name: branch_name, + commit_message: "Create file", + file_path: file_name, + file_content: content + ).execute + project.commit(branch_name) + end + + def update_file(branch_name, file_name, content) + Files::UpdateService.new( + project, + current_user, + start_branch: branch_name, + branch_name: branch_name, + commit_message: "Update file", + file_path: file_name, + file_content: content + ).execute + project.commit(branch_name) + end + + def delete_file(branch_name, file_name) + Files::DeleteService.new( + project, + current_user, + start_branch: branch_name, + branch_name: branch_name, + commit_message: "Delete file", + file_path: file_name + ).execute + project.commit(branch_name) + end +end diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb index bcb11a09b36..b2d22853e4c 100644 --- a/spec/support/helpers/project_forks_helper.rb +++ b/spec/support/helpers/project_forks_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ProjectForksHelper def fork_project(project, user = nil, params = {}) Gitlab::GitalyClient.allow_n_plus_1_calls do diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb index 87f825152cf..7c03746a395 100644 --- a/spec/support/helpers/prometheus_helpers.rb +++ b/spec/support/helpers/prometheus_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module PrometheusHelpers def prometheus_memory_query(environment_slug) %{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20} @@ -70,6 +72,18 @@ module PrometheusHelpers WebMock.stub_request(:get, url).to_raise(exception_type) end + def stub_any_prometheus_request + WebMock.stub_request(:any, /prometheus.example.com/) + end + + def stub_any_prometheus_request_with_response(status: 200, headers: {}, body: nil) + stub_any_prometheus_request.to_return({ + status: status, + headers: { 'Content-Type' => 'application/json' }.merge(headers), + body: body || prometheus_values_body.to_json + }) + end + def stub_all_prometheus_requests(environment_slug, body: nil, status: 200) stub_prometheus_request( prometheus_query_with_time_url(prometheus_memory_query(environment_slug), Time.now.utc), diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb index c4ae62b25e4..d936dc6de41 100644 --- a/spec/support/helpers/query_recorder.rb +++ b/spec/support/helpers/query_recorder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveRecord class QueryRecorder attr_reader :log, :skip_cached, :cached @@ -11,7 +13,7 @@ module ActiveRecord def show_backtrace(values) Rails.logger.debug("QueryRecorder SQL: #{values[:sql]}") - caller.each { |line| Rails.logger.debug(" --> #{line}") } + Gitlab::Profiler.clean_backtrace(caller).each { |line| Rails.logger.debug(" --> #{line}") } end def callback(name, start, finish, message_id, values) @@ -35,5 +37,9 @@ module ActiveRecord def log_message @log.join("\n\n") end + + def occurrences + @log.group_by(&:to_s).transform_values(&:count) + end end end diff --git a/spec/support/helpers/quick_actions_helpers.rb b/spec/support/helpers/quick_actions_helpers.rb index 361190aa352..cb853f5363f 100644 --- a/spec/support/helpers/quick_actions_helpers.rb +++ b/spec/support/helpers/quick_actions_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module QuickActionsHelpers def write_note(text) Sidekiq::Testing.fake! do diff --git a/spec/support/helpers/rake_helpers.rb b/spec/support/helpers/rake_helpers.rb index 7d8d7750bf3..d8f354a69da 100644 --- a/spec/support/helpers/rake_helpers.rb +++ b/spec/support/helpers/rake_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RakeHelpers def run_rake_task(task_name, *args) Rake::Task[task_name].reenable diff --git a/spec/support/helpers/reactive_caching_helpers.rb b/spec/support/helpers/reactive_caching_helpers.rb index b76b53db0b9..aa9d3b3a199 100644 --- a/spec/support/helpers/reactive_caching_helpers.rb +++ b/spec/support/helpers/reactive_caching_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ReactiveCachingHelpers def reactive_cache_key(subject, *qualifiers) ([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':') @@ -10,7 +12,7 @@ module ReactiveCachingHelpers def stub_reactive_cache(subject = nil, data = nil, *qualifiers) allow(ReactiveCachingWorker).to receive(:perform_async) allow(ReactiveCachingWorker).to receive(:perform_in) - write_reactive_cache(subject, data, *qualifiers) unless data.nil? + write_reactive_cache(subject, data, *qualifiers) unless subject.nil? end def synchronous_reactive_cache(subject) diff --git a/spec/support/helpers/redis_without_keys.rb b/spec/support/helpers/redis_without_keys.rb index 6220167dee6..e030f1028f7 100644 --- a/spec/support/helpers/redis_without_keys.rb +++ b/spec/support/helpers/redis_without_keys.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Redis ForbiddenCommand = Class.new(StandardError) diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb index 9f27502aa52..f96a01d15b5 100644 --- a/spec/support/helpers/reference_parser_helpers.rb +++ b/spec/support/helpers/reference_parser_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ReferenceParserHelpers def empty_html_link Nokogiri::HTML.fragment('<a></a>').children[0] diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb index 44d95a029af..ca4d2acbf2c 100644 --- a/spec/support/helpers/repo_helpers.rb +++ b/spec/support/helpers/repo_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RepoHelpers extend self diff --git a/spec/support/helpers/routes_helpers.rb b/spec/support/helpers/routes_helpers.rb index c4129606418..2a7cd81cbe3 100644 --- a/spec/support/helpers/routes_helpers.rb +++ b/spec/support/helpers/routes_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RoutesHelpers def fake_routes(&block) @routes = @routes.dup diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb index abbbb636d66..815337f8615 100644 --- a/spec/support/helpers/search_helpers.rb +++ b/spec/support/helpers/search_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SearchHelpers def select_filter(name) find(:xpath, "//ul[contains(@class, 'search-filter')]//a[contains(.,'#{name}')]").click diff --git a/spec/support/helpers/seed_repo.rb b/spec/support/helpers/seed_repo.rb index 71f1a86b0c1..20738b45129 100644 --- a/spec/support/helpers/seed_repo.rb +++ b/spec/support/helpers/seed_repo.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file is generated by generate-seed-repo-rb. Do not edit this file manually. # # Seed repo: diff --git a/spec/support/helpers/select2_helper.rb b/spec/support/helpers/select2_helper.rb index 87672c8896d..9c42c2b0d8b 100644 --- a/spec/support/helpers/select2_helper.rb +++ b/spec/support/helpers/select2_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'wait_for_requests' # Select2 ajax programmatic helper diff --git a/spec/support/helpers/selection_helper.rb b/spec/support/helpers/selection_helper.rb index b4725b137b2..a5f9ca76f6e 100644 --- a/spec/support/helpers/selection_helper.rb +++ b/spec/support/helpers/selection_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SelectionHelper def select_element(selector) find(selector) diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb index e505a6b7258..3801d25fb63 100644 --- a/spec/support/helpers/sorting_helper.rb +++ b/spec/support/helpers/sorting_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Helper allows you to sort items # # Params diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index f6c613ad5aa..c8b2bf040e6 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/core_ext/hash/transform_values' require 'active_support/hash_with_indifferent_access' require 'active_support/dependencies' @@ -65,6 +67,10 @@ module StubConfiguration allow(Gitlab.config.artifacts).to receive_messages(to_settings(messages)) end + def stub_pages_setting(messages) + allow(Gitlab.config.pages).to receive_messages(to_settings(messages)) + end + def stub_storage_settings(messages) messages.deep_stringify_keys! @@ -81,6 +87,12 @@ module StubConfiguration allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages)) end + def stub_sentry_settings + allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) + allow(Gitlab.config.sentry).to receive(:dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42') + allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/43') + end + def stub_kerberos_setting(messages) allow(Gitlab.config.kerberos).to receive_messages(to_settings(messages)) end @@ -89,6 +101,11 @@ module StubConfiguration allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages)) end + def stub_rack_attack_setting(messages) + allow(Gitlab.config.rack_attack).to receive(:git_basic_auth).and_return(messages) + allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages)) + end + private # Modifies stubbed messages to also stub possible predicate versions diff --git a/spec/support/helpers/stub_env.rb b/spec/support/helpers/stub_env.rb index 1c2f474a015..8107ffc939f 100644 --- a/spec/support/helpers/stub_env.rb +++ b/spec/support/helpers/stub_env.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb module StubENV def stub_env(key_or_hash, value = nil) diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb index 48258692304..6c3efff7262 100644 --- a/spec/support/helpers/stub_feature_flags.rb +++ b/spec/support/helpers/stub_feature_flags.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module StubFeatureFlags # Stub Feature flags with `flag_name: true/false` # diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb index 4cb3b18df85..badea94352a 100644 --- a/spec/support/helpers/stub_gitlab_calls.rb +++ b/spec/support/helpers/stub_gitlab_calls.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module StubGitlabCalls def stub_gitlab_calls stub_user diff --git a/spec/support/helpers/stub_gitlab_data.rb b/spec/support/helpers/stub_gitlab_data.rb index fa402f35b95..ed518393c03 100644 --- a/spec/support/helpers/stub_gitlab_data.rb +++ b/spec/support/helpers/stub_gitlab_data.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module StubGitlabData def gitlab_ci_yaml File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) diff --git a/spec/support/helpers/stub_metrics.rb b/spec/support/helpers/stub_metrics.rb index 64983fdf222..e347955efbb 100644 --- a/spec/support/helpers/stub_metrics.rb +++ b/spec/support/helpers/stub_metrics.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module StubMetrics def authentication_metrics Gitlab::Auth::Activity diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb index d31f9908714..e5b8bb712bb 100644 --- a/spec/support/helpers/stub_object_storage.rb +++ b/spec/support/helpers/stub_object_storage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module StubObjectStorage def stub_object_storage_uploader( config:, diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb index 5cad35282c0..473f07dd413 100644 --- a/spec/support/helpers/stub_requests.rb +++ b/spec/support/helpers/stub_requests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module StubRequests IP_ADDRESS_STUB = '8.8.8.9'.freeze @@ -26,6 +28,19 @@ module StubRequests .and_return([addr]) end + def stub_all_dns(url, ip_address:) + url = URI(url) + port = 80 # arbitarily chosen, does not matter as we are not going to connect + socket = Socket.sockaddr_in(port, ip_address) + addr = Addrinfo.new(socket) + + # See Gitlab::UrlBlocker + allow(Addrinfo).to receive(:getaddrinfo).and_call_original + allow(Addrinfo).to receive(:getaddrinfo) + .with(url.hostname, anything, nil, :STREAM) + .and_return([addr]) + end + def stubbed_hostname(url, hostname: IP_ADDRESS_STUB) url = parse_url(url) url.hostname = hostname diff --git a/spec/support/helpers/stub_worker.rb b/spec/support/helpers/stub_worker.rb index 58b7ee93dff..cac839ed5fe 100644 --- a/spec/support/helpers/stub_worker.rb +++ b/spec/support/helpers/stub_worker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb module StubWorker def stub_worker(queue:) diff --git a/spec/support/helpers/terms_helper.rb b/spec/support/helpers/terms_helper.rb index a00ec14138b..a61bae18f9a 100644 --- a/spec/support/helpers/terms_helper.rb +++ b/spec/support/helpers/terms_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module TermsHelper def enforce_terms stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 06b5ecdf150..a4acf76e1a3 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + require 'rspec/mocks' require 'toml-rb' module TestEnv + extend ActiveSupport::Concern extend self ComponentFailedToInstallError = Class.new(StandardError) @@ -64,7 +67,8 @@ module TestEnv 'with-codeowners' => '219560e', 'submodule_inside_folder' => 'b491b92', 'png-lfs' => 'fe42f41', - 'sha-starting-with-large-number' => '8426165' + 'sha-starting-with-large-number' => '8426165', + 'invalid-utf8-diff-paths' => '99e4853' }.freeze # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily @@ -107,6 +111,12 @@ module TestEnv setup_forked_repo end + included do |config| + config.append_before do + set_current_example_group + end + end + def disable_mailer allow_any_instance_of(NotificationService).to receive(:mailer) .and_return(double.as_null_object) @@ -122,7 +132,7 @@ module TestEnv # Keeps gitlab-shell and gitlab-test def clean_test_path Dir[TMP_TEST_PATH].each do |entry| - unless File.basename(entry) =~ /\A(gitaly|gitlab-(shell|test|test_bare|test-fork|test-fork_bare))\z/ + unless test_dirs.include?(File.basename(entry)) FileUtils.rm_rf(entry) end end @@ -133,32 +143,11 @@ module TestEnv FileUtils.mkdir_p(artifacts_path) end - def clean_gitlab_test_path - Dir[TMP_TEST_PATH].each do |entry| - unless test_dirs.include?(File.basename(entry)) - FileUtils.rm_rf(entry) - end - end - end - def setup_gitlab_shell component_timed_setup('GitLab Shell', install_dir: Gitlab.config.gitlab_shell.path, version: Gitlab::Shell.version_required, task: 'gitlab:shell:install') - - # gitlab-shell hooks don't work in our test environment because they try to make internal API calls - sabotage_gitlab_shell_hooks - end - - def sabotage_gitlab_shell_hooks - create_fake_git_hooks(Gitlab::Shell.new.hooks_path) - end - - def create_fake_git_hooks(hooks_dir) - %w[pre-receive post-receive update].each do |hook| - File.open(File.join(hooks_dir, hook), 'w', 0755) { |f| f.puts '#!/bin/sh' } - end end def setup_gitaly @@ -172,7 +161,6 @@ module TestEnv task: "gitlab:gitaly:install[#{install_gitaly_args}]") do Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, { 'default' => repos_path }, force: true) - create_fake_git_hooks(File.join(gitaly_dir, 'ruby/git-hooks')) start_gitaly(gitaly_dir) end end @@ -310,11 +298,27 @@ module TestEnv FileUtils.rm_rf(path) end + def current_example_group + Thread.current[:current_example_group] + end + + # looking for a top-level `describe` + def topmost_example_group + example_group = current_example_group + example_group = example_group[:parent_example_group] until example_group[:parent_example_group].nil? + example_group + end + private + def set_current_example_group + Thread.current[:current_example_group] = ::RSpec.current_example.metadata[:example_group] + end + # These are directories that should be preserved at cleanup time def test_dirs @test_dirs ||= %w[ + frontend gitaly gitlab-shell gitlab-test @@ -359,10 +363,7 @@ module TestEnv # Try to reset without fetching to avoid using the network. unless reset.call raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin)) - - # Before we used Git clone's --mirror option, bare repos could end up - # with missing refs, clearing them and retrying should fix the issue. - clean_gitlab_test_path && init unless reset.call + raise "Could not update test seed repository, please delete #{repo_path} and try again" unless reset.call end end diff --git a/spec/support/helpers/upload_helpers.rb b/spec/support/helpers/upload_helpers.rb index 5eead80c935..60e14a8673b 100644 --- a/spec/support/helpers/upload_helpers.rb +++ b/spec/support/helpers/upload_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' module UploadHelpers diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb index 45b9faa0fea..3bb2f7c5b51 100644 --- a/spec/support/helpers/wait_for_requests.rb +++ b/spec/support/helpers/wait_for_requests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module WaitForRequests extend self diff --git a/spec/support/helpers/wait_helpers.rb b/spec/support/helpers/wait_helpers.rb index 7e8e25798e8..a8c4408db59 100644 --- a/spec/support/helpers/wait_helpers.rb +++ b/spec/support/helpers/wait_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module WaitHelpers extend self diff --git a/spec/support/helpers/wiki_helpers.rb b/spec/support/helpers/wiki_helpers.rb index 8165403cb60..06cea728b42 100644 --- a/spec/support/helpers/wiki_helpers.rb +++ b/spec/support/helpers/wiki_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module WikiHelpers extend self diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb index ef1f9f68671..4488e5f227e 100644 --- a/spec/support/helpers/workhorse_helpers.rb +++ b/spec/support/helpers/workhorse_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module WorkhorseHelpers extend self diff --git a/spec/support/http_io/http_io_helpers.rb b/spec/support/http_io/http_io_helpers.rb index 42144870eb5..0193db81fa9 100644 --- a/spec/support/http_io/http_io_helpers.rb +++ b/spec/support/http_io/http_io_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module HttpIOHelpers def stub_remote_url_206(url, file_path) WebMock.stub_request(:get, url) diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb index 2542a59bb00..ac6840dbcfc 100644 --- a/spec/support/import_export/common_util.rb +++ b/spec/support/import_export/common_util.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ImportExport module CommonUtil def setup_symlink(tmpdir, symlink_name) diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb index b4164cff922..122df7f27f0 100644 --- a/spec/support/import_export/configuration_helper.rb +++ b/spec/support/import_export/configuration_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ConfigurationHelper # Returns a list of models from hashes/arrays contained in +project_tree+ def names_from_tree(project_tree) diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb index 388b88f0331..f862a9bc1a4 100644 --- a/spec/support/import_export/export_file_helper.rb +++ b/spec/support/import_export/export_file_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require './spec/support/import_export/configuration_helper' module ExportFileHelper diff --git a/spec/support/inspect_squelch.rb b/spec/support/inspect_squelch.rb new file mode 100644 index 00000000000..90475204889 --- /dev/null +++ b/spec/support/inspect_squelch.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# This class can generate a lot of output if it fails, +# so squelch the instance variable output. +class ActiveSupport::Cache::NullStore + def inspect + "<#{self.class}>" + end +end diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/issuables_requiring_filter_shared_examples.rb index 71bcc82ee58..ee25df00dfb 100644 --- a/spec/support/issuables_requiring_filter_shared_examples.rb +++ b/spec/support/issuables_requiring_filter_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuables requiring filter' do |action| it "doesn't load any issuables if no filter is set" do expect_any_instance_of(described_class).not_to receive(:issuables_collection) diff --git a/spec/support/json_response.rb b/spec/support/json_response.rb index 210b0e6d867..55bdce0cfe9 100644 --- a/spec/support/json_response.rb +++ b/spec/support/json_response.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + RSpec.configure do |config| - config.include_context 'JSON response' + config.include_context 'JSON response', type: :controller config.include_context 'JSON response', type: :request config.include_context 'JSON response', :api end diff --git a/spec/support/matchers/abort_matcher.rb b/spec/support/matchers/abort_matcher.rb new file mode 100644 index 00000000000..64fed2ca069 --- /dev/null +++ b/spec/support/matchers/abort_matcher.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :abort_execution do + match do |code_block| + @captured_stderr = StringIO.new + original_stderr = $stderr + $stderr = @captured_stderr + + code_block.call + + false + rescue SystemExit => e + captured = @captured_stderr.string.chomp + @actual_exit_code = e.status + break false unless e.status == 1 + + if @message + if @message.is_a? String + @message == captured + elsif @message.is_a? Regexp + @message.match?(captured) + else + raise ArgumentError, 'with_message must be either a String or a Regular Expression' + end + end + + ensure + $stderr = original_stderr + end + + chain :with_message do |message| + @message = message + end + + failure_message do |block| + unless @actual_exit_code + break "expected #{block} to abort with '#{@message}' but didnt call abort." + end + + if @actual_exit_code != 1 + break "expected #{block} to abort with: '#{@message}' but exited with success instead." + end + + "expected #{block} to abort with: '#{@message}' \n but received: '#{@captured_stderr.string.chomp}' instead." + end + + supports_block_expectations +end diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb index e6899e2d23c..c9ff777f604 100644 --- a/spec/support/matchers/access_matchers.rb +++ b/spec/support/matchers/access_matchers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # AccessMatchers # # The custom matchers contained in this module are used to test a user's access diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index 429401a5da8..401bf6c196e 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # AccessMatchersForController # # For testing authorize_xxx in controller. diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb index f4127efc6ae..c38aa7ad6a6 100644 --- a/spec/support/matchers/background_migrations_matchers.rb +++ b/spec/support/matchers/background_migrations_matchers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected| match do |migration| BackgroundMigrationWorker.jobs.any? do |job| diff --git a/spec/support/matchers/be_a_binary_string.rb b/spec/support/matchers/be_a_binary_string.rb index f041ae76167..6195c6c7554 100644 --- a/spec/support/matchers/be_a_binary_string.rb +++ b/spec/support/matchers/be_a_binary_string.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :be_a_binary_string do |_| match do |actual| actual.is_a?(String) && actual.encoding == Encoding.find('ASCII-8BIT') diff --git a/spec/support/matchers/be_like_time.rb b/spec/support/matchers/be_like_time.rb index 1f27390eab7..b449f7a7ffb 100644 --- a/spec/support/matchers/be_like_time.rb +++ b/spec/support/matchers/be_like_time.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :be_like_time do |expected| match do |actual| expect(actual).to be_within(1.second).of(expected) diff --git a/spec/support/matchers/be_n_plus_1_query.rb b/spec/support/matchers/be_n_plus_1_query.rb new file mode 100644 index 00000000000..bbfd1897f04 --- /dev/null +++ b/spec/support/matchers/be_n_plus_1_query.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Nplus1QueryHelpers + DEFAULT_THRESHOLD = 3 + + def with_threshold(threshold) + @threshold = threshold + self + end + + def for_query(query) + @query = query + self + end + + def threshold + @threshold || DEFAULT_THRESHOLD + end + + def occurrences + @occurrences ||= + if @query + recorder.occurrences.select { |recorded, count| recorded =~ @query } + else + recorder.occurrences + end + end + + def over_threshold + occurrences.select do |recorded, count| + count > threshold + end + end + + def recorder + @recorder ||= ActiveRecord::QueryRecorder.new(&@subject_block) + end + + def verify_count(&block) + @subject_block = block + over_threshold.present? + end + + def failure_message + log_message = over_threshold.to_yaml + "The following queries are executed more than #{threshold} time(s):\n#{log_message}" + end +end + +RSpec::Matchers.define :be_n_plus_1_query do + supports_block_expectations + + include Nplus1QueryHelpers + + match do |block| + verify_count(&block) + end + + failure_message_when_negated do |actual| + failure_message + end +end diff --git a/spec/support/matchers/be_url.rb b/spec/support/matchers/be_url.rb index f8096af1b22..69171f53891 100644 --- a/spec/support/matchers/be_url.rb +++ b/spec/support/matchers/be_url.rb @@ -1,5 +1,11 @@ +# frozen_string_literal: true + RSpec::Matchers.define :be_url do |_| match do |actual| URI.parse(actual) rescue false end end + +# looks better when used like: +# expect(thing).to receive(:method).with(a_valid_url) +RSpec::Matchers.alias_matcher :a_valid_url, :be_url diff --git a/spec/support/matchers/be_utf8.rb b/spec/support/matchers/be_utf8.rb index ea806352422..4fa55539a49 100644 --- a/spec/support/matchers/be_utf8.rb +++ b/spec/support/matchers/be_utf8.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :be_utf8 do |_| match do |actual| actual.is_a?(String) && actual.encoding == Encoding.find('UTF-8') diff --git a/spec/support/matchers/be_valid_commit.rb b/spec/support/matchers/be_valid_commit.rb index 3696e4d5f03..b59339de622 100644 --- a/spec/support/matchers/be_valid_commit.rb +++ b/spec/support/matchers/be_valid_commit.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :be_valid_commit do match do |actual| actual && diff --git a/spec/support/matchers/disallow_request_matchers.rb b/spec/support/matchers/disallow_request_matchers.rb index db4d90e4fd0..a161e3660cd 100644 --- a/spec/support/matchers/disallow_request_matchers.rb +++ b/spec/support/matchers/disallow_request_matchers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :disallow_request do match do |middleware| alert = middleware.env['rack.session'].to_hash diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb index cd042401f3a..40cf85eb8e5 100644 --- a/spec/support/matchers/exceed_query_limit.rb +++ b/spec/support/matchers/exceed_query_limit.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ExceedQueryLimitHelpers def with_threshold(threshold) @threshold = threshold diff --git a/spec/support/matchers/execute_check.rb b/spec/support/matchers/execute_check.rb index 7232fad52fb..d3c0751f0dc 100644 --- a/spec/support/matchers/execute_check.rb +++ b/spec/support/matchers/execute_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :execute_check do |expected| match do |actual| expect(actual).to eq(SystemCheck) diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb index ebfabcd8f24..c2b3ebe3422 100644 --- a/spec/support/matchers/gitaly_matchers.rb +++ b/spec/support/matchers/gitaly_matchers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :gitaly_request_with_path do |storage_name, relative_path| match do |actual| repository = actual.repository @@ -9,6 +11,6 @@ end RSpec::Matchers.define :gitaly_request_with_params do |params| match do |actual| - params.reduce(true) { |r, (key, val)| r && actual.send(key) == val } + params.reduce(true) { |r, (key, val)| r && actual[key.to_s] == val } end end diff --git a/spec/support/matchers/gitlab_git_matchers.rb b/spec/support/matchers/gitlab_git_matchers.rb index c840cd4bf2d..aea1603db05 100644 --- a/spec/support/matchers/gitlab_git_matchers.rb +++ b/spec/support/matchers/gitlab_git_matchers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :gitlab_git_repository_with do |values| match do |actual| actual.is_a?(Gitlab::Git::Repository) && diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index 7894484f590..4d48b4b5389 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :require_graphql_authorizations do |*expected| match do |field| expect(field.metadata[:authorize]).to eq(*expected) diff --git a/spec/support/matchers/have_emoji.rb b/spec/support/matchers/have_emoji.rb index 23fb8e9c1c4..273bd0b7f40 100644 --- a/spec/support/matchers/have_emoji.rb +++ b/spec/support/matchers/have_emoji.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :have_emoji do |emoji_name| match do |actual| expect(actual).to have_selector("gl-emoji[data-name='#{emoji_name}']") diff --git a/spec/support/matchers/have_gitlab_http_status.rb b/spec/support/matchers/have_gitlab_http_status.rb index e7e418cdde4..13a64a58218 100644 --- a/spec/support/matchers/have_gitlab_http_status.rb +++ b/spec/support/matchers/have_gitlab_http_status.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :have_gitlab_http_status do |expected| match do |actual| expect(actual).to have_http_status(expected) diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb index 92cf3de5448..049cfc022fb 100644 --- a/spec/support/matchers/have_issuable_counts.rb +++ b/spec/support/matchers/have_issuable_counts.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :have_issuable_counts do |opts| expected_counts = opts.map do |state, count| "#{state.to_s.humanize} #{count}" diff --git a/spec/support/matchers/include_module.rb b/spec/support/matchers/include_module.rb index 0a78af1e90e..9b6970cf061 100644 --- a/spec/support/matchers/include_module.rb +++ b/spec/support/matchers/include_module.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :include_module do |expected| match do described_class.included_modules.include?(expected) diff --git a/spec/support/matchers/issuable_matchers.rb b/spec/support/matchers/issuable_matchers.rb index 62f510b0fbd..743f0b8c932 100644 --- a/spec/support/matchers/issuable_matchers.rb +++ b/spec/support/matchers/issuable_matchers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"| match do |actual| node = find("#{parent} h#{level} a#user-content-#{id}") diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index ec4ec6f4038..12e8fa83a60 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # MarkdownMatchers # # Custom matchers for our custom HTML::Pipeline filters. These are used to test diff --git a/spec/support/matchers/match_file.rb b/spec/support/matchers/match_file.rb index d1888b3376a..4e522b52912 100644 --- a/spec/support/matchers/match_file.rb +++ b/spec/support/matchers/match_file.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :match_file do |expected| match do |actual| expect(Digest::MD5.hexdigest(actual)).to eq(Digest::MD5.hexdigest(File.read(expected))) diff --git a/spec/support/matchers/match_ids.rb b/spec/support/matchers/match_ids.rb index 1cb6b74acac..7bc41949937 100644 --- a/spec/support/matchers/match_ids.rb +++ b/spec/support/matchers/match_ids.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :match_ids do |*expected| match do |actual| actual_ids = map_ids(actual) diff --git a/spec/support/matchers/metric_counter_matcher.rb b/spec/support/matchers/metric_counter_matcher.rb index 22d5cd17e3f..f0d52b9b149 100644 --- a/spec/support/matchers/metric_counter_matcher.rb +++ b/spec/support/matchers/metric_counter_matcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :increment do |counter| match do |adapter| expect(adapter.send(counter)) diff --git a/spec/support/matchers/navigation_matcher.rb b/spec/support/matchers/navigation_matcher.rb index 63f59b9654c..ad73c96031e 100644 --- a/spec/support/matchers/navigation_matcher.rb +++ b/spec/support/matchers/navigation_matcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :have_active_navigation do |expected| match do |page| expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1) diff --git a/spec/support/matchers/pagination_matcher.rb b/spec/support/matchers/pagination_matcher.rb index 9a7697e2bfc..a3e9c3b8474 100644 --- a/spec/support/matchers/pagination_matcher.rb +++ b/spec/support/matchers/pagination_matcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :include_pagination_headers do |expected| match do |actual| expect(actual.headers).to include('X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link') diff --git a/spec/support/matchers/query_matcher.rb b/spec/support/matchers/query_matcher.rb index bb0feca7c43..3e47fe241bc 100644 --- a/spec/support/matchers/query_matcher.rb +++ b/spec/support/matchers/query_matcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :make_queries_matching do |matcher, expected_count = nil| supports_block_expectations diff --git a/spec/support/matchers/satisfy_matchers.rb b/spec/support/matchers/satisfy_matchers.rb index 585915bac93..dd1920ee2b2 100644 --- a/spec/support/matchers/satisfy_matchers.rb +++ b/spec/support/matchers/satisfy_matchers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These matchers are a syntactic hack to provide more readable expectations for # an Enumerable object. # diff --git a/spec/support/matchers/security_header_matcher.rb b/spec/support/matchers/security_header_matcher.rb index f8518d13ebb..760b1fddd06 100644 --- a/spec/support/matchers/security_header_matcher.rb +++ b/spec/support/matchers/security_header_matcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec::Matchers.define :include_security_headers do |expected| match do |actual| expect(actual.headers).to include('X-Content-Type-Options') diff --git a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb b/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb index 016bcfa9b1b..656be3b6d4d 100644 --- a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb +++ b/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MigrationsHelpers module TrackUntrackedUploadsHelpers PUBLIC_DIR = File.join(Rails.root, 'tmp', 'tests', 'public') diff --git a/spec/support/omni_auth.rb b/spec/support/omni_auth.rb index 0b1af4052ff..64aa3855a6f 100644 --- a/spec/support/omni_auth.rb +++ b/spec/support/omni_auth.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + OmniAuth.config.test_mode = true diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb index 8044b061ca5..4e006edb7da 100644 --- a/spec/support/prometheus/additional_metrics_shared_examples.rb +++ b/spec/support/prometheus/additional_metrics_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'additional metrics query' do include Prometheus::MetricBuilders @@ -44,9 +46,11 @@ RSpec.shared_examples 'additional metrics query' do end describe 'project has Kubernetes service' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do + context 'when user configured kubernetes from CI/CD > Clusters' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } let(:environment) { create(:environment, slug: 'environment-slug', project: project) } - let(:kube_namespace) { project.deployment_platform.kubernetes_namespace_for(project) } + let(:kube_namespace) { environment.deployment_namespace } it_behaves_like 'query context containing environment slug and filter' @@ -56,19 +60,6 @@ RSpec.shared_examples 'additional metrics query' do subject.query(*query_params) end end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end end describe 'project without Kubernetes service' do diff --git a/spec/support/prometheus/metric_builders.rb b/spec/support/prometheus/metric_builders.rb index c8d056d3fc8..512e32a44d0 100644 --- a/spec/support/prometheus/metric_builders.rb +++ b/spec/support/prometheus/metric_builders.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Prometheus module MetricBuilders def simple_query(suffix = 'a', **opts) diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb index 71eec9f3217..8666c19481c 100644 --- a/spec/support/protected_tags/access_control_ce_shared_examples.rb +++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples "protected tags > access control > CE" do ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)| it "allows creating protected tags that #{access_type_name} can create" do diff --git a/spec/support/redis/redis_helpers.rb b/spec/support/redis/redis_helpers.rb index 0457e8487d8..7c571738a01 100644 --- a/spec/support/redis/redis_helpers.rb +++ b/spec/support/redis/redis_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RedisHelpers # config/README.md diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb index 6aa59960092..7e47cdae866 100644 --- a/spec/support/redis/redis_shared_examples.rb +++ b/spec/support/redis/redis_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples "redis_shared_examples" do include StubENV diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb index b38c5dfe60b..1c9f9e5161e 100644 --- a/spec/support/rspec.rb +++ b/spec/support/rspec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "helpers/stub_configuration" require_relative "helpers/stub_metrics" require_relative "helpers/stub_object_storage" diff --git a/spec/support/seed.rb b/spec/support/seed.rb index bea2e9c3044..36cb819763b 100644 --- a/spec/support/seed.rb +++ b/spec/support/seed.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.configure do |config| config.include SeedHelper, :seed_helper diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb index b0bf942aa09..27f6d0570b6 100644 --- a/spec/support/services/clusters/create_service_shared.rb +++ b/spec/support/services/clusters/create_service_shared.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'valid cluster create params' do let(:params) do { @@ -30,23 +32,56 @@ shared_context 'invalid cluster create params' do end shared_examples 'create cluster service success' do - it 'creates a cluster object and performs a worker' do - expect(ClusterProvisionWorker).to receive(:perform_async) - - expect { subject } - .to change { Clusters::Cluster.count }.by(1) - .and change { Clusters::Providers::Gcp.count }.by(1) - - expect(subject.name).to eq('test-cluster') - expect(subject.user).to eq(user) - expect(subject.project).to eq(project) - expect(subject.provider.gcp_project_id).to eq('gcp-project') - expect(subject.provider.zone).to eq('us-central1-a') - expect(subject.provider.num_nodes).to eq(1) - expect(subject.provider.machine_type).to eq('machine_type-a') - expect(subject.provider.access_token).to eq(access_token) - expect(subject.provider).to be_legacy_abac - expect(subject.platform).to be_nil + context 'namespace per environment feature is enabled' do + before do + stub_feature_flags(kubernetes_namespace_per_environment: true) + end + + it 'creates a cluster object and performs a worker' do + expect(ClusterProvisionWorker).to receive(:perform_async) + + expect { subject } + .to change { Clusters::Cluster.count }.by(1) + .and change { Clusters::Providers::Gcp.count }.by(1) + + expect(subject.name).to eq('test-cluster') + expect(subject.user).to eq(user) + expect(subject.project).to eq(project) + expect(subject.provider.gcp_project_id).to eq('gcp-project') + expect(subject.provider.zone).to eq('us-central1-a') + expect(subject.provider.num_nodes).to eq(1) + expect(subject.provider.machine_type).to eq('machine_type-a') + expect(subject.provider.access_token).to eq(access_token) + expect(subject.provider).to be_legacy_abac + expect(subject.platform).to be_nil + expect(subject.namespace_per_environment).to eq true + end + end + + context 'namespace per environment feature is disabled' do + before do + stub_feature_flags(kubernetes_namespace_per_environment: false) + end + + it 'creates a cluster object and performs a worker' do + expect(ClusterProvisionWorker).to receive(:perform_async) + + expect { subject } + .to change { Clusters::Cluster.count }.by(1) + .and change { Clusters::Providers::Gcp.count }.by(1) + + expect(subject.name).to eq('test-cluster') + expect(subject.user).to eq(user) + expect(subject.project).to eq(project) + expect(subject.provider.gcp_project_id).to eq('gcp-project') + expect(subject.provider.zone).to eq('us-central1-a') + expect(subject.provider.num_nodes).to eq(1) + expect(subject.provider.machine_type).to eq('machine_type-a') + expect(subject.provider.access_token).to eq(access_token) + expect(subject.provider).to be_legacy_abac + expect(subject.platform).to be_nil + expect(subject.namespace_per_environment).to eq false + end end end diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb index 8b4cffaac19..4c3644e6724 100644 --- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb +++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specifications for behavior common to all objects with executable attributes. # It can take a `default_params`. diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb index ffbce6c42bf..5e5acd0e40a 100644 --- a/spec/support/services/issuable_update_service_shared_examples.rb +++ b/spec/support/services/issuable_update_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuable update service' do def update_issuable(opts) described_class.new(project, user, opts).execute(open_issuable) diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb index 1284415da1f..65236f13e27 100644 --- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb +++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields| diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb index 1d2a4856724..bbe442f07b0 100644 --- a/spec/support/setup_builds_storage.rb +++ b/spec/support/setup_builds_storage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.configure do |config| def builds_path Rails.root.join('tmp/tests/builds') diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb index 9d806fc524d..b4d061a8215 100644 --- a/spec/support/shared_contexts/email_shared_context.rb +++ b/spec/support/shared_contexts/email_shared_context.rb @@ -1,4 +1,4 @@ -require 'gitlab/email/receiver' +# frozen_string_literal: true shared_context :email_shared_context do let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb index a0d994c4d8d..38f6011646e 100644 --- a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.shared_context 'GroupProjectsFinder context' do diff --git a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb index b8a9554f55f..26ab6fbd400 100644 --- a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.shared_context 'IssuesFinder context' do diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb index ab6687f1d07..ef1e65d2577 100644 --- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests context' do diff --git a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb index 9e1f89ee0ed..d6404b2ee4b 100644 --- a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.shared_context 'UsersFinder#execute filter by project context' do diff --git a/spec/support/shared_contexts/json_response_shared_context.rb b/spec/support/shared_contexts/json_response_shared_context.rb index df5fc288089..bd37c97ed35 100644 --- a/spec/support/shared_contexts/json_response_shared_context.rb +++ b/spec/support/shared_contexts/json_response_shared_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'JSON response' do let(:json_response) { JSON.parse(response.body) } end diff --git a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb index 05424d08b9d..276ebf973c8 100644 --- a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb +++ b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'merge request allowing collaboration' do include ProjectForksHelper diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb index b4808ac0068..fd24c443288 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -7,7 +7,7 @@ RSpec.shared_context 'GroupPolicy context' do let(:maintainer) { create(:user) } let(:owner) { create(:user) } let(:admin) { create(:admin) } - let(:group) { create(:group, :private) } + let(:group) { create(:group, :private, :owner_subgroup_creation_only) } let(:guest_permissions) do %i[ @@ -16,7 +16,7 @@ RSpec.shared_context 'GroupPolicy context' do read_group_merge_requests ] end - let(:reporter_permissions) { [:admin_label] } + let(:reporter_permissions) { %i[admin_label read_container_image] } let(:developer_permissions) { [:admin_milestone] } let(:maintainer_permissions) do %i[ @@ -31,7 +31,7 @@ RSpec.shared_context 'GroupPolicy context' do :admin_group_member, :change_visibility_level, :set_note_created_at, - (Gitlab::Database.postgresql? ? :create_subgroup : nil) + :create_subgroup ].compact end diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb index 089f1798cd2..4d176ab5fca 100644 --- a/spec/support/shared_contexts/services_shared_context.rb +++ b/spec/support/shared_contexts/services_shared_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Service.available_services_names.each do |service| shared_context service do let(:dashed_service) { service.dasherize } @@ -37,8 +39,7 @@ Service.available_services_names.each do |service| def initialize_service(service) service_item = project.find_or_initialize_service(service) service_item.properties = service_attrs - service_item.active = true if service == "kubernetes" - service_item.save + service_item.save! service_item end end diff --git a/spec/support/shared_contexts/url_shared_context.rb b/spec/support/shared_contexts/url_shared_context.rb index 1b1f67daac3..560cd500ecd 100644 --- a/spec/support/shared_contexts/url_shared_context.rb +++ b/spec/support/shared_contexts/url_shared_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'invalid urls' do let(:urls_with_CRLF) do ["http://127.0.0.1:333/pa\rth", diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb index 421303c97be..a43d2a75082 100644 --- a/spec/support/shared_examples/application_setting_examples.rb +++ b/spec/support/shared_examples/application_setting_examples.rb @@ -1,62 +1,150 @@ # frozen_string_literal: true +RSpec.shared_examples 'string of domains' do |attribute| + it 'sets single domain' do + setting.method("#{attribute}_raw=").call('example.com') + expect(setting.method(attribute).call).to eq(['example.com']) + end + + it 'sets multiple domains with spaces' do + setting.method("#{attribute}_raw=").call('example.com *.example.com') + expect(setting.method(attribute).call).to eq(['example.com', '*.example.com']) + end + + it 'sets multiple domains with newlines and a space' do + setting.method("#{attribute}_raw=").call("example.com\n *.example.com") + expect(setting.method(attribute).call).to eq(['example.com', '*.example.com']) + end + + it 'sets multiple domains with commas' do + setting.method("#{attribute}_raw=").call("example.com, *.example.com") + expect(setting.method(attribute).call).to eq(['example.com', '*.example.com']) + end + + it 'sets multiple domains with semicolon' do + setting.method("#{attribute}_raw=").call("example.com; *.example.com") + expect(setting.method(attribute).call).to contain_exactly('example.com', '*.example.com') + end + + it 'sets multiple domains with mixture of everything' do + setting.method("#{attribute}_raw=").call("example.com; *.example.com\n test.com\sblock.com yes.com") + expect(setting.method(attribute).call).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com') + end + + it 'removes duplicates' do + setting.method("#{attribute}_raw=").call("example.com; example.com; 127.0.0.1; 127.0.0.1") + expect(setting.method(attribute).call).to contain_exactly('example.com', '127.0.0.1') + end + + it 'does not fail with garbage values' do + setting.method("#{attribute}_raw=").call("example;34543:garbage:fdh5654;") + expect(setting.method(attribute).call).to contain_exactly('example', '34543:garbage:fdh5654') + end + + it 'does not raise error with nil' do + setting.method("#{attribute}_raw=").call(nil) + expect(setting.method(attribute).call).to eq([]) + end +end + RSpec.shared_examples 'application settings examples' do context 'restricted signup domains' do - it 'sets single domain' do - setting.domain_whitelist_raw = 'example.com' - expect(setting.domain_whitelist).to eq(['example.com']) - end + it_behaves_like 'string of domains', :domain_whitelist + end - it 'sets multiple domains with spaces' do - setting.domain_whitelist_raw = 'example.com *.example.com' - expect(setting.domain_whitelist).to eq(['example.com', '*.example.com']) - end + context 'blacklisted signup domains' do + it_behaves_like 'string of domains', :domain_blacklist - it 'sets multiple domains with newlines and a space' do - setting.domain_whitelist_raw = "example.com\n *.example.com" - expect(setting.domain_whitelist).to eq(['example.com', '*.example.com']) + it 'sets multiple domain with file' do + setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt')) + expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar') end + end + + context 'outbound_local_requests_whitelist' do + it_behaves_like 'string of domains', :outbound_local_requests_whitelist + + it 'clears outbound_local_requests_whitelist_arrays memoization' do + setting.outbound_local_requests_whitelist_raw = 'example.com' - it 'sets multiple domains with commas' do - setting.domain_whitelist_raw = "example.com, *.example.com" - expect(setting.domain_whitelist).to eq(['example.com', '*.example.com']) + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], ['example.com'] + ) + + setting.outbound_local_requests_whitelist_raw = 'gitlab.com' + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], ['gitlab.com'] + ) end end - context 'blacklisted signup domains' do - it 'sets single domain' do - setting.domain_blacklist_raw = 'example.com' - expect(setting.domain_blacklist).to contain_exactly('example.com') + context 'outbound_local_requests_whitelist_arrays' do + it 'separates the IPs and domains' do + setting.outbound_local_requests_whitelist = [ + '192.168.1.1', '127.0.0.0/28', 'www.example.com', 'example.com', + '::ffff:a00:2', '1:0:0:0:0:0:0:0/124', 'subdomain.example.com' + ] + + ip_whitelist = [ + IPAddr.new('192.168.1.1'), IPAddr.new('127.0.0.0/8'), + IPAddr.new('::ffff:a00:2'), IPAddr.new('1:0:0:0:0:0:0:0/124') + ] + domain_whitelist = ['www.example.com', 'example.com', 'subdomain.example.com'] + + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + ip_whitelist, domain_whitelist + ) end + end - it 'sets multiple domains with spaces' do - setting.domain_blacklist_raw = 'example.com *.example.com' - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') - end + context 'add_to_outbound_local_requests_whitelist' do + it 'adds entry to outbound_local_requests_whitelist' do + setting.outbound_local_requests_whitelist = ['example.com'] - it 'sets multiple domains with newlines and a space' do - setting.domain_blacklist_raw = "example.com\n *.example.com" - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') - end + setting.add_to_outbound_local_requests_whitelist( + ['example.com', '127.0.0.1', 'gitlab.com'] + ) - it 'sets multiple domains with commas' do - setting.domain_blacklist_raw = "example.com, *.example.com" - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') + expect(setting.outbound_local_requests_whitelist).to contain_exactly( + 'example.com', + '127.0.0.1', + 'gitlab.com' + ) end - it 'sets multiple domains with semicolon' do - setting.domain_blacklist_raw = "example.com; *.example.com" - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') + it 'clears outbound_local_requests_whitelist_arrays memoization' do + setting.outbound_local_requests_whitelist = ['example.com'] + + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], + ['example.com'] + ) + + setting.add_to_outbound_local_requests_whitelist( + ['example.com', 'gitlab.com'] + ) + + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], + ['example.com', 'gitlab.com'] + ) end - it 'sets multiple domains with mixture of everything' do - setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com" - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com') + it 'does not raise error with nil' do + setting.outbound_local_requests_whitelist = nil + + setting.add_to_outbound_local_requests_whitelist(['gitlab.com']) + + expect(setting.outbound_local_requests_whitelist).to contain_exactly('gitlab.com') + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], ['gitlab.com'] + ) end - it 'sets multiple domain with file' do - setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt')) - expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar') + it 'does not raise error with nil' do + setting.outbound_local_requests_whitelist = nil + + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly([], []) end end @@ -249,43 +337,4 @@ RSpec.shared_examples 'application settings examples' do expect(setting.password_authentication_enabled_for_web?).to be_falsey end - - describe 'sentry settings' do - context 'when the sentry settings are not set in gitlab.yml' do - it 'fallbacks to the settings in the database' do - setting.sentry_enabled = true - setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' - setting.clientside_sentry_enabled = true - setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' - - allow(Gitlab.config.sentry).to receive(:enabled).and_return(false) - allow(Gitlab.config.sentry).to receive(:dsn).and_return(nil) - allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return(nil) - - expect(setting.sentry_enabled).to eq true - expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' - expect(setting.clientside_sentry_enabled).to eq true - expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' - end - end - - context 'when the sentry settings are set in gitlab.yml' do - it 'does not fallback to the settings in the database' do - setting.sentry_enabled = false - setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' - setting.clientside_sentry_enabled = false - setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' - - allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) - allow(Gitlab.config.sentry).to receive(:dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42') - allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/43') - - expect(setting).not_to receive(:read_attribute) - expect(setting.sentry_enabled).to eq true - expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42' - expect(setting.clientside_sentry_enabled).to eq true - expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/43' - end - end - end end diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb new file mode 100644 index 00000000000..76d82649c5f --- /dev/null +++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +shared_examples_for 'multiple issue boards' do + dropdown_selector = '.js-boards-selector .dropdown-menu' + + context 'authorized user' do + before do + parent.add_maintainer(user) + + login_as(user) + + visit boards_path + wait_for_requests + end + + it 'shows current board name' do + page.within('.boards-switcher') do + expect(page).to have_content(board.name) + end + end + + it 'shows a list of boards' do + click_button board.name + + page.within(dropdown_selector) do + expect(page).to have_content(board.name) + expect(page).to have_content(board2.name) + end + end + + it 'switches current board' do + click_button board.name + + page.within(dropdown_selector) do + click_link board2.name + end + + wait_for_requests + + page.within('.boards-switcher') do + expect(page).to have_content(board2.name) + end + end + + it 'creates new board without detailed configuration' do + click_button board.name + + page.within(dropdown_selector) do + click_button 'Create new board' + end + + fill_in 'board-new-name', with: 'This is a new board' + click_button 'Create board' + wait_for_requests + + expect(page).to have_button('This is a new board') + end + + it 'deletes board' do + click_button board.name + + wait_for_requests + + page.within(dropdown_selector) do + click_button 'Delete board' + end + + expect(page).to have_content('Are you sure you want to delete this board?') + click_button 'Delete' + + click_button board2.name + page.within(dropdown_selector) do + expect(page).not_to have_content(board.name) + expect(page).to have_content(board2.name) + end + end + + it 'adds a list to the none default board' do + click_button board.name + + page.within(dropdown_selector) do + click_link board2.name + end + + wait_for_requests + + page.within('.boards-switcher') do + expect(page).to have_content(board2.name) + end + + click_button 'Add list' + + wait_for_requests + + page.within '.dropdown-menu-issues-board-new' do + click_link planning.title + end + + wait_for_requests + + expect(page).to have_selector('.board', count: 3) + + click_button board2.name + + page.within(dropdown_selector) do + click_link board.name + end + + wait_for_requests + + expect(page).to have_selector('.board', count: 2) + end + + it 'maintains sidebar state over board switch' do + assert_boards_nav_active + + find('.boards-switcher').click + wait_for_requests + click_link board2.name + + assert_boards_nav_active + end + end + + context 'unauthorized user' do + before do + visit boards_path + wait_for_requests + end + + it 'does not show action links' do + click_button board.name + + page.within(dropdown_selector) do + expect(page).not_to have_content('Create new board') + expect(page).not_to have_content('Delete board') + end + end + end + + def assert_boards_nav_active + expect(find('.nav-sidebar .active .active')).to have_selector('a', text: 'Boards') + end +end diff --git a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb index dc97a39f051..dcc92dda950 100644 --- a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb +++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'chat slash commands service' do describe "Associations" do it { is_expected.to respond_to :token } @@ -91,6 +93,19 @@ RSpec.shared_examples 'chat slash commands service' do subject.trigger(params) end + + context 'when user is blocked' do + before do + chat_name.user.block + end + + it 'blocks command execution' do + expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute) + + result = subject.trigger(params) + expect(result).to include(text: /^Whoops! This action is not allowed/) + end + end end end end diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb index db935bcb388..e2b4b50d41d 100644 --- a/spec/support/shared_examples/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/ci_trace_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'common trace features' do describe '#html' do before do @@ -5,11 +7,11 @@ shared_examples_for 'common trace features' do end it "returns formatted html" do - expect(trace.html).to eq("12<br>34") + expect(trace.html).to eq("<span>12<br/>34</span>") end it "returns last line of formatted html" do - expect(trace.html(last_lines: 1)).to eq("34") + expect(trace.html(last_lines: 1)).to eq("<span>34</span>") end end @@ -270,7 +272,7 @@ shared_examples_for 'common trace features' do include ExclusiveLeaseHelpers before do - stub_exclusive_lease_taken("trace:write:lock:#{trace.job.id}", timeout: 1.minute) + stub_exclusive_lease_taken("trace:write:lock:#{trace.job.id}", timeout: 10.minutes) end it 'blocks concurrent archiving' do @@ -720,6 +722,58 @@ shared_examples_for 'trace with enabled live trace feature' do end end + describe '#archived_trace_exist?' do + subject { trace.archived_trace_exist? } + + context 'when trace does not exist' do + it { is_expected.to be_falsy } + end + + context 'when archived trace exists' do + before do + create(:ci_job_artifact, :trace, job: build) + end + + it { is_expected.to be_truthy } + end + + context 'when live trace exists' do + before do + Gitlab::Ci::Trace::ChunkedIO.new(build) do |stream| + stream.write('abc') + end + end + + it { is_expected.to be_falsy } + end + end + + describe '#live_trace_exist?' do + subject { trace.live_trace_exist? } + + context 'when trace does not exist' do + it { is_expected.to be_falsy } + end + + context 'when archived trace exists' do + before do + create(:ci_job_artifact, :trace, job: build) + end + + it { is_expected.to be_falsy } + end + + context 'when live trace exists' do + before do + Gitlab::Ci::Trace::ChunkedIO.new(build) do |stream| + stream.write('abc') + end + end + + it { is_expected.to be_truthy } + end + end + describe '#archive!' do subject { trace.archive! } diff --git a/spec/support/shared_examples/common_system_notes_examples.rb b/spec/support/shared_examples/common_system_notes_examples.rb index da5a4f3e319..75f93a32d78 100644 --- a/spec/support/shared_examples/common_system_notes_examples.rb +++ b/spec/support/shared_examples/common_system_notes_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'system note creation' do |update_params, note_text| subject { described_class.new(project, user).execute(issuable, old_labels: []) } diff --git a/spec/support/shared_examples/container_repositories_shared_examples.rb b/spec/support/shared_examples/container_repositories_shared_examples.rb new file mode 100644 index 00000000000..946b130fca2 --- /dev/null +++ b/spec/support/shared_examples/container_repositories_shared_examples.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +shared_examples 'rejected container repository access' do |user_type, status| + context "for #{user_type}" do + let(:api_user) { users[user_type] } + + it "returns #{status}" do + subject + + expect(response).to have_gitlab_http_status(status) + end + end +end + +shared_examples 'returns repositories for allowed users' do |user_type, scope| + context "for #{user_type}" do + it 'returns a list of repositories' do + subject + + expect(json_response.length).to eq(2) + expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + root_repository.id, test_repository.id) + expect(response.body).not_to include('tags') + end + + it 'returns a matching schema' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end + + context 'with tags param' do + let(:url) { "/#{scope}s/#{object.id}/registry/repositories?tags=true" } + + before do + stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest), with_manifest: true) + stub_container_registry_tags(repository: test_repository.path, tags: %w(rootA latest), with_manifest: true) + end + + it 'returns a list of repositories and their tags' do + subject + + expect(json_response.length).to eq(2) + expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + root_repository.id, test_repository.id) + expect(response.body).to include('tags') + end + + it 'returns a matching schema' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end + end + end +end diff --git a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb index 8dd78fd0a25..d8a1ae83f61 100644 --- a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb +++ b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'disabled when using an external authorization service' do @@ -6,7 +8,7 @@ shared_examples 'disabled when using an external authorization service' do it 'works when the feature is not enabled' do subject - expect(response).to be_success + expect(response).to be_successful end it 'renders a 404 with a message when the feature is enabled' do diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb index 0acc9e2a836..26ed86bfe26 100644 --- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb +++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuable notes filter' do let(:params) do if issuable_parent.is_a?(Project) @@ -39,14 +41,22 @@ shared_examples 'issuable notes filter' do get :discussions, params: params.merge(notes_filter: notes_filter) - expect(user.reload.notes_filter_for(issuable)).to eq(0) + expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes]) + end + + it 'does not set notes filter when persist_filter param is false' do + notes_filter = UserPreference::NOTES_FILTERS[:only_comments] + + get :discussions, params: params.merge(notes_filter: notes_filter, persist_filter: false) + + expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes]) end it 'returns only user comments' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable) get :discussions, params: params - discussions = JSON.parse(response.body) + discussions = json_response expect(discussions.count).to eq(1) expect(discussions.first["notes"].first["system"]).to be(false) @@ -56,7 +66,7 @@ shared_examples 'issuable notes filter' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable) get :discussions, params: params - discussions = JSON.parse(response.body) + discussions = json_response expect(discussions.count).to eq(1) expect(discussions.first["notes"].first["system"]).to be(true) diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb index b7080c68270..d3cadf2ba7c 100644 --- a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb +++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb @@ -9,7 +9,15 @@ # - `filepath`: path of the file (contains filename) # - `subject`: the request to be made to the controller. Example: # subject { get :show, namespace_id: project.namespace, project_id: project } -shared_examples 'a controller that can serve LFS files' do +# +# The LFS disabled scenario can be skipped by passing `skip_lfs_disabled_tests: true` +# when including the examples (Note, at time of writing this is only used by +# an EE-specific spec): +# +# it_behaves_like 'a controller that can serve LFS files', skip_lfs_disabled_tests: true do +# ... +# end +shared_examples 'a controller that can serve LFS files' do |options = {}| let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' } let(:lfs_size) { '1575078' } let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) } @@ -83,6 +91,8 @@ shared_examples 'a controller that can serve LFS files' do end it 'delivers ASCII file' do + skip 'Calling spec asked to skip testing LFS disabled scenario' if options[:skip_lfs_disabled_tests] + subject expect(response).to have_gitlab_http_status(200) diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb index eb051166a69..1cd14ea2251 100644 --- a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb +++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'set sort order from user preference' do describe '#set_sort_order_from_user_preference' do # There is no issuable_sorting_field defined in any CE controllers yet, diff --git a/spec/support/shared_examples/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb index bafd9bac8d0..f3f9abb7da2 100644 --- a/spec/support/shared_examples/controllers/todos_shared_examples.rb +++ b/spec/support/shared_examples/controllers/todos_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'todos actions' do context 'when authorized' do before do diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb index 59708173716..39d13cccb13 100644 --- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'handle uploads' do let(:user) { create(:user) } let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } @@ -74,6 +76,16 @@ shared_examples 'handle uploads' do UploadService.new(model, jpg, uploader_class).execute end + context 'when accessing a specific upload via different model' do + it 'responds with status 404' do + params.merge!(other_params) + + show_upload + + expect(response).to have_gitlab_http_status(404) + end + end + context "when the model is public" do before do model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb index e80722857ec..78666e677ef 100644 --- a/spec/support/shared_examples/controllers/variables_shared_examples.rb +++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'GET #show lists all variables' do it 'renders the variables as json' do subject diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb index 4e45e2921e7..60c8899d349 100644 --- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb +++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'dirty submit form' do |selector_args| selectors = selector_args.is_a?(Array) ? selector_args : [selector_args] diff --git a/spec/support/shared_examples/discussions_provider_shared_examples.rb b/spec/support/shared_examples/discussions_provider_shared_examples.rb new file mode 100644 index 00000000000..77cf1ac3f51 --- /dev/null +++ b/spec/support/shared_examples/discussions_provider_shared_examples.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +shared_examples 'discussions provider' do + it 'returns the expected discussions' do + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: requested_iid } + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('entities/discussions') + + expect(json_response.size).to eq(expected_discussion_count) + expect(json_response.pluck('id')).to eq(expected_discussion_ids) + end +end diff --git a/spec/support/shared_examples/email_format_shared_examples.rb b/spec/support/shared_examples/email_format_shared_examples.rb index b924a208e71..22d6c2b38e3 100644 --- a/spec/support/shared_examples/email_format_shared_examples.rb +++ b/spec/support/shared_examples/email_format_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specifications for behavior common to all objects with an email attribute. # Takes a list of email-format attributes and requires: # - subject { "the object with a attribute= setter" } diff --git a/spec/support/shared_examples/fast_destroy_all.rb b/spec/support/shared_examples/fast_destroy_all.rb index a8079b6d864..a64259c03f2 100644 --- a/spec/support/shared_examples/fast_destroy_all.rb +++ b/spec/support/shared_examples/fast_destroy_all.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'fast destroyable' do describe 'Forbid #destroy and #destroy_all' do it 'does not delete database rows and associted external data' do diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb index 221926aaf7e..f24e47f4638 100644 --- a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb +++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'comment on merge request file' do it 'adds a comment' do click_diff_line(find("[id='#{sample_commit.line_code}']")) @@ -16,7 +18,7 @@ shared_examples 'comment on merge request file' do visit(merge_request_path(merge_request)) page.within('.notes .discussion') do - expect(page).to have_content("#{user.name} #{user.to_reference} started a discussion") + expect(page).to have_content("#{user.name} #{user.to_reference} started a thread") expect(page).to have_content(sample_commit.line_code_path) expect(page).to have_content('Line is wrong') end diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb index ec1b1754cf0..c0db4cdde72 100644 --- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'a creatable merge request' do include WaitForRequests diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb index a6121fcc50a..964c80007b0 100644 --- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'an editable merge request' do it 'updates merge request', :js do find('.js-assignee-search').click diff --git a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb index 96c821b26f7..09a48533ee3 100644 --- a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb +++ b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issue sidebar stays collapsed on mobile' do before do resize_screen_xs diff --git a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb index c92c7f603d6..63ed37cde03 100644 --- a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb +++ b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuable user dropdown behaviors' do include FilteredSearchHelpers diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb index d87e5fcaa88..8e1d24c4be2 100644 --- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb +++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'Maintainer manages access requests' do let(:user) { create(:user) } let(:maintainer) { create(:user) } diff --git a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb index 64c3b80136d..51559c0b110 100644 --- a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb +++ b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'project features apply to issuables' do |klass| let(:described_class) { klass } diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb index a8f2c2e7a5a..db83d6f0793 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples "protected branches > access control > CE" do ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can push to" do @@ -6,7 +8,7 @@ shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.qa-allowed-to-merge-dropdown') do + within('.rspec-allowed-to-merge-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -32,13 +34,13 @@ shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.qa-allowed-to-merge-dropdown') do + within('.rspec-allowed-to-merge-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end find(".js-allowed-to-push").click - within('.qa-allowed-to-push-dropdown') do + within('.rspec-allowed-to-push-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -78,7 +80,7 @@ shared_examples "protected branches > access control > CE" do end find(".js-allowed-to-push").click - within('.qa-allowed-to-push-dropdown') do + within('.rspec-allowed-to-push-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -95,13 +97,13 @@ shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.qa-allowed-to-merge-dropdown') do + within('.rspec-allowed-to-merge-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end find(".js-allowed-to-push").click - within('.qa-allowed-to-push-dropdown') do + within('.rspec-allowed-to-push-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end diff --git a/spec/support/shared_examples/features/search_shared_examples.rb b/spec/support/shared_examples/features/search_shared_examples.rb index 25ebbf011d5..e27d6700cbf 100644 --- a/spec/support/shared_examples/features/search_shared_examples.rb +++ b/spec/support/shared_examples/features/search_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'top right search form' do it 'does not show top right search form' do expect(page).not_to have_selector('.search') diff --git a/spec/support/shared_examples/file_finder.rb b/spec/support/shared_examples/file_finder.rb index 0dc351b5149..984a06ccd1a 100644 --- a/spec/support/shared_examples/file_finder.rb +++ b/spec/support/shared_examples/file_finder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'file finder' do let(:query) { 'files' } let(:search_results) { subject.find(query) } diff --git a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb index d7e17cc0b70..b8b0079e36d 100644 --- a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb +++ b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'a finder with external authorization service' do diff --git a/spec/support/shared_examples/gitlab_verify.rb b/spec/support/shared_examples/gitlab_verify.rb index 560913ca92f..721ea3b4c88 100644 --- a/spec/support/shared_examples/gitlab_verify.rb +++ b/spec/support/shared_examples/gitlab_verify.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do describe 'batching' do let(:first_batch) { objects[0].id..objects[0].id } diff --git a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb index 713f0a879c1..145c476c7f7 100644 --- a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb +++ b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'issuable state' do it 'exposes all the existing issuable states' do expect(described_class.values.keys).to include(*%w[opened closed locked]) diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb new file mode 100644 index 00000000000..022d41c0bdd --- /dev/null +++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Shared example for expecting top-level errors. +# See https://graphql-ruby.org/mutations/mutation_errors#raising-errors +# +# { errors: [] } +# +# There must be a method or let called `mutation` defined that executes +# the mutation. +RSpec.shared_examples 'a mutation that returns top-level errors' do |errors:| + it do + post_graphql_mutation(mutation, current_user: current_user) + + error_messages = graphql_errors.map { |e| e['message'] } + + expect(error_messages).to eq(errors) + end +end + +# Shared example for expecting schema-level errors. +# See https://graphql-ruby.org/mutations/mutation_errors#errors-as-data +# +# { data: { mutationName: { errors: [] } } } +# +# There must be: +# - a method or let called `mutation` defined that executes the mutation +# - a `mutation_response` method defined that returns the data of the mutation response. +RSpec.shared_examples 'a mutation that returns errors in the response' do |errors:| + it do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['errors']).to eq(errors) + end +end diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb new file mode 100644 index 00000000000..f2e1a95345b --- /dev/null +++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a Note mutation that does not create a Note' do + it do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Note.count } + end +end + +RSpec.shared_examples 'a Note mutation that creates a Note' do + it do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change { Note.count }.by(1) + end +end + +RSpec.shared_examples 'a Note mutation when the user does not have permission' do + it_behaves_like 'a Note mutation that does not create a Note' + + it_behaves_like 'a mutation that returns top-level errors', + errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action'] +end + +RSpec.shared_examples 'a Note mutation when there are active record validation errors' do |model: Note| + before do + expect_next_instance_of(model) do |note| + expect(note).to receive(:valid?).at_least(:once).and_return(false) + expect(note).to receive_message_chain( + :errors, + :full_messages + ).and_return(['Error 1', 'Error 2']) + end + end + + it_behaves_like 'a Note mutation that does not create a Note' + + it_behaves_like 'a mutation that returns errors in the response', errors: ['Error 1', 'Error 2'] + + it 'returns an empty Note' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response).to have_key('note') + expect(mutation_response['note']).to be_nil + end +end + +RSpec.shared_examples 'a Note mutation when the given resource id is not for a Noteable' do + let(:noteable) { create(:label, project: project) } + + it_behaves_like 'a Note mutation that does not create a Note' + + it_behaves_like 'a mutation that returns top-level errors', errors: ['Cannot add notes to this resource'] +end + +RSpec.shared_examples 'a Note mutation when the given resource id is not for a Note' do + let(:note) { create(:issue) } + + it_behaves_like 'a mutation that returns top-level errors', errors: ['Resource is not a note'] +end diff --git a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb new file mode 100644 index 00000000000..323d1c51ffd --- /dev/null +++ b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true +require 'spec_helper' + +shared_context 'exposing regular notes on a noteable in GraphQL' do + include GraphqlHelpers + + let(:note) do + create(:note, + noteable: noteable, + project: (noteable.project if noteable.respond_to?(:project))) + end + let(:user) { note.author } + + context 'for regular notes' do + let(:query) do + note_fields = <<~NOTES + notes { + edges { + node { + #{all_graphql_fields_for('Note')} + } + } + } + NOTES + + noteable_query(note_fields) + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: user) + end + end + + it 'includes the note' do + post_graphql(query, current_user: user) + + expect(noteable_data['notes']['edges'].first['node']['body']) + .to eq(note.note) + end + end + + context "for discussions" do + let(:query) do + discussion_fields = <<~DISCUSSIONS + discussions { + edges { + node { + #{all_graphql_fields_for('Discussion')} + } + } + } + DISCUSSIONS + + noteable_query(discussion_fields) + end + + let!(:reply) { create(:note, noteable: noteable, in_reply_to: note, discussion_id: note.discussion_id) } + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: user) + end + end + + it 'includes all discussion notes' do + post_graphql(query, current_user: user) + + discussion = noteable_data['discussions']['edges'].first['node'] + ids = discussion['notes']['edges'].map { |note_edge| note_edge['node']['id'] } + + expect(ids).to eq([note.to_global_id.to_s, reply.to_global_id.to_s]) + end + end +end diff --git a/spec/support/shared_examples/group_members_shared_example.rb b/spec/support/shared_examples/group_members_shared_example.rb index 547c83c7955..4f7d496741d 100644 --- a/spec/support/shared_examples/group_members_shared_example.rb +++ b/spec/support/shared_examples/group_members_shared_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'members and requesters associations' do describe '#members_and_requesters' do it 'includes members and requesters' do diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb index 01bee603274..17f495ebe46 100644 --- a/spec/support/shared_examples/helm_generated_script.rb +++ b/spec/support/shared_examples/helm_generated_script.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'helm commands' do describe '#generate_script' do let(:helm_setup) do diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb index d97b21f71cd..3460a8ba297 100644 --- a/spec/support/shared_examples/issuable_shared_examples.rb +++ b/spec/support/shared_examples/issuable_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'cache counters invalidator' do it 'invalidates counter cache for assignees' do expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts) diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb index 244f4766a84..52d90b5f183 100644 --- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb +++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuables list meta-data' do |issuable_type, action = nil| include ProjectForksHelper diff --git a/spec/support/shared_examples/issue_tracker_service_shared_example.rb b/spec/support/shared_examples/issue_tracker_service_shared_example.rb index a6ab03cb808..0a483fd30ba 100644 --- a/spec/support/shared_examples/issue_tracker_service_shared_example.rb +++ b/spec/support/shared_examples/issue_tracker_service_shared_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr| it { is_expected.to allow_value('https://example.com').for(url_attr) } diff --git a/spec/support/shared_examples/ldap_shared_examples.rb b/spec/support/shared_examples/ldap_shared_examples.rb index 52c34e78965..0a70ce7ea0c 100644 --- a/spec/support/shared_examples/ldap_shared_examples.rb +++ b/spec/support/shared_examples/ldap_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'normalizes a DN' do using RSpec::Parameterized::TableSyntax diff --git a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb index f326e502092..22e5698825d 100644 --- a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb +++ b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'redirecting a legacy path' do |source, target| include RSpec::Rails::RequestExampleGroup diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb index dcf7c1a90c2..2cbc0c2bdf2 100644 --- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'backfill migration for project repositories' do |storage| describe '#perform' do let(:storage_versions) { storage == :legacy ? [nil, 0] : [1, 2] } diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb new file mode 100644 index 00000000000..91bf804978d --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +shared_examples 'a redis usage counter' do |thing, event| + describe ".count(#{event})", :clean_gitlab_redis_shared_state do + it "increments the #{thing} #{event} counter by 1" do + expect do + described_class.count(event) + end.to change { described_class.read(event) }.by 1 + end + end + + describe ".read(#{event})", :clean_gitlab_redis_shared_state do + event_count = 5 + + it "returns the total number of #{event} events" do + event_count.times do + described_class.count(event) + end + + expect(described_class.read(event)).to eq(event_count) + end + end +end + +shared_examples 'a redis usage counter with totals' do |prefix, events| + describe 'totals', :clean_gitlab_redis_shared_state do + before do + events.each do |k, n| + n.times do + described_class.count(k) + end + end + end + + let(:expected_totals) do + events.transform_keys { |k| "#{prefix}_#{k}".to_sym } + end + + it 'can report all totals' do + expect(described_class.totals).to include(expected_totals) + end + end + + # Override these let-bindings to adjust the unknown events tests + let(:unknown_event) { described_class::UnknownEvent } + let(:bad_event) { :wibble } + + describe 'unknown events' do + it 'cannot increment' do + expect { described_class.count(bad_event) }.to raise_error unknown_event + end + + it 'cannot read' do + expect { described_class.read(bad_event) }.to raise_error unknown_event + end + end +end diff --git a/spec/support/shared_examples/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb index a86050e2cf2..96c02260d53 100644 --- a/spec/support/shared_examples/malicious_regexp_shared_examples.rb +++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'timeout' shared_examples 'malicious regexp' do diff --git a/spec/support/shared_examples/mentionable_shared_examples.rb b/spec/support/shared_examples/mentionable_shared_examples.rb index 1226841f24c..93a8c4709a6 100644 --- a/spec/support/shared_examples/mentionable_shared_examples.rb +++ b/spec/support/shared_examples/mentionable_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specifications for behavior common to all Mentionable implementations. # Requires a shared context containing: # - subject { "the mentionable implementation" } @@ -76,6 +78,30 @@ shared_examples 'a mentionable' do expect(refs).to include(ext_commit) end + context 'when there are cached markdown fields' do + before do + if subject.is_a?(CacheMarkdownField) + subject.refresh_markdown_cache + end + end + + it 'sends in cached markdown fields when appropriate' do + if subject.is_a?(CacheMarkdownField) + expect_next_instance_of(Gitlab::ReferenceExtractor) do |ext| + attrs = subject.class.mentionable_attrs.collect(&:first) & subject.cached_markdown_fields.markdown_fields + attrs.each do |field| + expect(ext).to receive(:analyze).with(subject.send(field), hash_including(rendered: anything)) + end + end + + expect(subject).not_to receive(:refresh_markdown_cache) + expect(subject).to receive(:cached_markdown_fields).at_least(:once).and_call_original + + subject.all_references(author) + end + end + end + it 'creates cross-reference notes' do mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit, ext_issue, ext_mr, ext_commit] @@ -98,6 +124,33 @@ shared_examples 'an editable mentionable' do [create(:issue, project: project), create(:issue, project: ext_proj)] end + context 'when there are cached markdown fields' do + before do + if subject.is_a?(CacheMarkdownField) + subject.refresh_markdown_cache + end + end + + it 'refreshes markdown cache if necessary' do + subject.save! + + set_mentionable_text.call('This is a text') + + if subject.is_a?(CacheMarkdownField) + expect_next_instance_of(Gitlab::ReferenceExtractor) do |ext| + subject.cached_markdown_fields.markdown_fields.each do |field| + expect(ext).to receive(:analyze).with(subject.send(field), hash_including(rendered: anything)) + end + end + + expect(subject).to receive(:refresh_markdown_cache) + expect(subject).to receive(:cached_markdown_fields).at_least(:once).and_call_original + + subject.all_references(author) + end + end + end + it 'creates new cross-reference notes when the mentionable text is edited' do subject.save subject.create_cross_references! diff --git a/spec/support/shared_examples/milestone_tabs_examples.rb b/spec/support/shared_examples/milestone_tabs_examples.rb index 8b757586941..bda4b978737 100644 --- a/spec/support/shared_examples/milestone_tabs_examples.rb +++ b/spec/support/shared_examples/milestone_tabs_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'milestone tabs' do def go(path, extra_params = {}) params = diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb index a248f60d23e..b837ca87256 100644 --- a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb +++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples_for 'AtomicInternalId' do |validate_presence: true| diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb index 0a302e7d030..b6a3d50d14a 100644 --- a/spec/support/shared_examples/models/chat_service_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" shared_examples_for "chat service" do |service_name| @@ -220,7 +222,8 @@ shared_examples_for "chat service" do |service_name| context "with not default branch" do let(:pipeline) do - create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch") + create(:ci_pipeline, :failed, project: project, + sha: project.commit.sha, ref: "not-the-default-branch") end context "when notify_only_default_branch enabled" do diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb index d6490a808ce..8e58cc7ba22 100644 --- a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'cluster application core specs' do |application_name| it { is_expected.to belong_to(:cluster) } it { is_expected.to validate_presence_of(:cluster) } diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb index bd3661471f8..7ddb3b11c85 100644 --- a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'cluster application helm specs' do |application_name| let(:application) { create(application_name) } diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index 4525c03837f..5341aacb445 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'cluster application status specs' do |application_name| describe '#status' do let(:cluster) { create(:cluster, :provided_by_gcp) } diff --git a/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb new file mode 100644 index 00000000000..8b298c5c974 --- /dev/null +++ b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +shared_examples_for 'a valid diff positionable note' do |factory_on_commit| + context 'for commit' do + let(:project) { create(:project, :repository) } + let(:commit) { project.commit(sample_commit.id) } + let(:commit_id) { commit.id } + let(:diff_refs) { commit.diff_refs } + + let(:position) do + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: diff_refs + ) + end + + subject { build(factory_on_commit, commit_id: commit_id, position: position) } + + context 'position diff refs matches commit diff refs' do + it 'is valid' do + expect(subject).to be_valid + expect(subject.errors).not_to have_key(:commit_id) + end + end + + context 'position diff refs does not match commit diff refs' do + let(:diff_refs) do + Gitlab::Diff::DiffRefs.new( + base_sha: "not_existing_sha", + head_sha: "existing_sha" + ) + end + + it 'is invalid' do + expect(subject).to be_invalid + expect(subject.errors).to have_key(:commit_id) + end + end + + context 'commit does not exist' do + let(:commit_id) { 'non-existing' } + + it 'is invalid' do + expect(subject).to be_invalid + expect(subject.errors).to have_key(:commit_id) + end + end + end +end diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb index a4762b68858..7ea2bb265cc 100644 --- a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb +++ b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This shared example requires a `builder` and `user` variable shared_examples 'issuable hook data' do |kind| let(:data) { builder.build(user: user) } diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb index ef5cea3f2a5..050d710f1de 100644 --- a/spec/support/shared_examples/models/members_notifications_shared_example.rb +++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'members notifications' do |entity_type| let(:notification_service) { double('NotificationService').as_null_object } diff --git a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb index f0264878811..03d10c10e3c 100644 --- a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb +++ b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'project hook data with deprecateds' do |project_key: :project| it 'contains project data' do expect(data[project_key][:name]).to eq(project.name) diff --git a/spec/support/shared_examples/models/services_fields_shared_examples.rb b/spec/support/shared_examples/models/services_fields_shared_examples.rb new file mode 100644 index 00000000000..6fbd0da9383 --- /dev/null +++ b/spec/support/shared_examples/models/services_fields_shared_examples.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +shared_examples 'issue tracker fields' do + let(:title) { 'custom title' } + let(:description) { 'custom description' } + let(:url) { 'http://issue_tracker.example.com' } + + context 'when data are stored in the properties' do + describe '#update' do + before do + service.update(title: 'new_title', description: 'new description') + end + + it 'removes title and description from properties' do + expect(service.reload.properties).not_to include('title', 'description') + end + + it 'stores title & description in services table' do + expect(service.read_attribute(:title)).to eq('new_title') + expect(service.read_attribute(:description)).to eq('new description') + end + end + + describe 'reading fields' do + it 'returns correct values' do + expect(service.title).to eq(title) + expect(service.description).to eq(description) + end + end + end +end diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb index 1b09c3dd636..e03435cafe8 100644 --- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb +++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb @@ -25,16 +25,23 @@ shared_examples_for 'UpdateProjectStatistics' do .to change { reload_stat } .by(delta) end + + it 'schedules a namespace statistics worker' do + expect(Namespaces::ScheduleAggregationWorker) + .to receive(:perform_async).once + + subject.save! + end end context 'when updating' do + let(:delta) { 42 } + before do subject.save! end it 'updates project statistics' do - delta = 42 - expect(ProjectStatistics) .to receive(:increment_statistic) .and_call_original @@ -45,6 +52,28 @@ shared_examples_for 'UpdateProjectStatistics' do .to change { reload_stat } .by(delta) end + + it 'schedules a namespace statistics worker' do + expect(Namespaces::ScheduleAggregationWorker) + .to receive(:perform_async).once + + subject.write_attribute(statistic_attribute, read_attribute + delta) + subject.save! + end + + it 'avoids N + 1 queries' do + subject.write_attribute(statistic_attribute, read_attribute + delta) + + control_count = ActiveRecord::QueryRecorder.new do + subject.save! + end + + subject.write_attribute(statistic_attribute, read_attribute + delta) + + expect do + subject.save! + end.not_to exceed_query_limit(control_count) + end end context 'when destroying' do @@ -59,11 +88,18 @@ shared_examples_for 'UpdateProjectStatistics' do .to receive(:increment_statistic) .and_call_original - expect { subject.destroy } + expect { subject.destroy! } .to change { reload_stat } .by(delta) end + it 'schedules a namespace statistics worker' do + expect(Namespaces::ScheduleAggregationWorker) + .to receive(:perform_async).once + + subject.destroy! + end + context 'when it is destroyed from the project level' do it 'does not update the project statistics' do expect(ProjectStatistics) @@ -72,6 +108,14 @@ shared_examples_for 'UpdateProjectStatistics' do project.update(pending_delete: true) project.destroy! end + + it 'does not schedule a namespace statistics worker' do + expect(Namespaces::ScheduleAggregationWorker) + .not_to receive(:perform_async) + + project.update(pending_delete: true) + project.destroy! + end end end end diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb index 43033a2d256..eb1ade03017 100644 --- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb +++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples_for 'model with uploads' do |supports_fileuploads| diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index 897c9106d77..ca031df000e 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'gitlab email notification' do set(:group) { create(:group) } set(:subgroup) { create(:group, parent: group) } @@ -42,42 +44,17 @@ shared_examples 'an email sent from GitLab' do end shared_examples 'an email sent to a user' do - let(:group_notification_email) { 'user+group@example.com' } - it 'is sent to user\'s global notification email address' do - expect(subject).to deliver_to(test_recipient.notification_email) + expect(subject).to deliver_to(recipient.notification_email) end - context 'that is part of a project\'s group' do - it 'is sent to user\'s group notification email address when set' do - create(:notification_setting, user: test_recipient, source: project.group, notification_email: group_notification_email) - expect(subject).to deliver_to(group_notification_email) - end - - it 'is sent to user\'s global notification email address when no group email set' do - create(:notification_setting, user: test_recipient, source: project.group, notification_email: '') - expect(subject).to deliver_to(test_recipient.notification_email) - end - end - - context 'when project is in a sub-group', :nested_groups do - before do - project.update!(group: subgroup) - end - - it 'is sent to user\'s subgroup notification email address when set' do - # Set top-level group notification email address to make sure it doesn't get selected - create(:notification_setting, user: test_recipient, source: group, notification_email: group_notification_email) - - subgroup_notification_email = 'user+subgroup@example.com' - create(:notification_setting, user: test_recipient, source: subgroup, notification_email: subgroup_notification_email) + context 'with group notification email' do + it 'is sent to user\'s group notification email' do + group_notification_email = 'user+group@example.com' - expect(subject).to deliver_to(subgroup_notification_email) - end + create(:notification_setting, user: recipient, source: group, notification_email: group_notification_email) - it 'is sent to user\'s group notification email address when set and subgroup email address not set' do - create(:notification_setting, user: test_recipient, source: subgroup, notification_email: '') - expect(subject).to deliver_to(test_recipient.notification_email) + expect(subject).to deliver_to(group_notification_email) end end end @@ -281,18 +258,8 @@ shared_examples 'a note email' do is_expected.to have_body_text note.note end - it 'does not contain note author' do - is_expected.not_to have_body_text note.author_name - end - - context 'when enabled email_author_in_body' do - before do - stub_application_setting(email_author_in_body: true) - end - - it 'contains a link to note author' do - is_expected.to have_body_text note.author_name - end + it 'contains a link to note author' do + is_expected.to have_body_text note.author_name end end diff --git a/spec/support/shared_examples/policies/clusterable_shared_examples.rb b/spec/support/shared_examples/policies/clusterable_shared_examples.rb index d99f94c76c3..0b427c23256 100644 --- a/spec/support/shared_examples/policies/clusterable_shared_examples.rb +++ b/spec/support/shared_examples/policies/clusterable_shared_examples.rb @@ -13,7 +13,11 @@ shared_examples 'clusterable policies' do clusterable.add_developer(current_user) end + it { expect_disallowed(:read_cluster) } it { expect_disallowed(:add_cluster) } + it { expect_disallowed(:create_cluster) } + it { expect_disallowed(:update_cluster) } + it { expect_disallowed(:admin_cluster) } end context 'with a maintainer' do @@ -22,15 +26,11 @@ shared_examples 'clusterable policies' do end context 'with no clusters' do + it { expect_allowed(:read_cluster) } it { expect_allowed(:add_cluster) } - end - - context 'with an existing cluster' do - before do - cluster - end - - it { expect_disallowed(:add_cluster) } + it { expect_allowed(:create_cluster) } + it { expect_allowed(:update_cluster) } + it { expect_allowed(:admin_cluster) } end end end diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index 7a71e2ee370..13b7ade658b 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -17,6 +17,7 @@ RSpec.shared_examples 'archived project policies' do upload_file resolve_note award_emoji + admin_tag ] end diff --git a/spec/support/shared_examples/position_formatters.rb b/spec/support/shared_examples/position_formatters.rb index ffc9456dbc7..30b6b8d24f0 100644 --- a/spec/support/shared_examples/position_formatters.rb +++ b/spec/support/shared_examples/position_formatters.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for "position formatter" do let(:formatter) { described_class.new(attrs) } diff --git a/spec/support/shared_examples/project_latest_successful_build_for_examples.rb b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb new file mode 100644 index 00000000000..a9bd23e9fc9 --- /dev/null +++ b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +shared_examples 'latest successful build for sha or ref' do + context 'with many builds' do + let(:other_pipeline) { create_pipeline(project) } + let(:other_build) { create_build(other_pipeline, 'test') } + let(:build_name) { other_build.name } + + before do + pipeline1 = create_pipeline(project) + pipeline2 = create_pipeline(project) + create_build(pipeline1, 'test') + create_build(pipeline1, 'test2') + create_build(pipeline2, 'test2') + end + + it 'gives the latest builds from latest pipeline' do + expect(subject).to eq(other_build) + end + end + + context 'with succeeded pipeline' do + let!(:build) { create_build } + let(:build_name) { build.name } + + context 'standalone pipeline' do + it 'returns builds for ref for default_branch' do + expect(subject).to eq(build) + end + + context 'with nonexistent build' do + let(:build_name) { 'TAIL' } + + it 'returns empty relation if the build cannot be found' do + expect(subject).to be_nil + end + end + end + + context 'with some pending pipeline' do + before do + create_build(create_pipeline(project, 'pending')) + end + + it 'gives the latest build from latest pipeline' do + expect(subject).to eq(build) + end + end + end + + context 'with pending pipeline' do + let!(:pending_build) { create_build(pipeline) } + let(:build_name) { pending_build.name } + + before do + pipeline.update(status: 'pending') + end + + it 'returns empty relation' do + expect(subject).to be_nil + end + end +end diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb index b337a1c18d8..f5a86e4dc2c 100644 --- a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb @@ -5,7 +5,7 @@ shared_examples 'tag quick action' do it 'tags this commit' do add_note("/tag #{tag_name} #{tag_message}") - expect(page).to have_content 'Commands applied' + expect(page).to have_content %{Tagged this commit to #{tag_name} with "#{tag_message}".} expect(page).to have_content "tagged commit #{truncated_commit_sha}" expect(page).to have_content tag_name diff --git a/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb deleted file mode 100644 index d97da6be192..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'assign quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets assign quick action accordingly" do - assignee = create(:user, username: 'bob') - - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/assign @bob" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable.assignees).to eq [assignee] - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - end - - it "creates the #{issuable_type} and interprets assign quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/assign me" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable.assignees).to eq [maintainer] - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the assign quick action accordingly' do - assignee = create(:user, username: 'bob') - add_note("Awesome!\n\n/assign @bob") - - expect(page).to have_content 'Awesome!' - expect(page).not_to have_content '/assign @bob' - - wait_for_requests - issuable.reload - note = issuable.notes.user.first - - expect(note.note).to eq 'Awesome!' - expect(issuable.assignees).to eq [assignee] - end - - it "assigns the #{issuable_type} to the current user" do - add_note("/assign me") - - expect(page).not_to have_content '/assign me' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload.assignees).to eq [maintainer] - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains assign quick action to bob' do - create(:user, username: 'bob') - - visit public_send("project_#{issuable_type}_path", project, issuable) - - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "Awesome!\n/assign @bob " - click_on 'Preview' - - expect(page).not_to have_content '/assign @bob' - expect(page).to have_content 'Awesome!' - expect(page).to have_content 'Assigns @bob.' - end - end - - it 'explains assign quick action to me' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "Awesome!\n/assign me" - click_on 'Preview' - - expect(page).not_to have_content '/assign me' - expect(page).to have_content 'Awesome!' - expect(page).to have_content "Assigns @#{maintainer.username}." - end - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb deleted file mode 100644 index 74cbfa3f4b4..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'award quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets award quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/award :100:" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.award_emoji).to eq [] - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - expect(issuable.award_emoji).to eq [] - end - - it 'creates the note and interprets the award quick action accordingly' do - add_note("/award :100:") - - wait_for_requests - expect(page).not_to have_content '/award' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.award_emoji.last.name).to eq('100') - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains label quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/award :100:') - - expect(page).not_to have_content '/award' - expect(page).to have_selector "gl-emoji[data-name='100']" - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb index a79a61bc708..6e7eb78261a 100644 --- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb @@ -68,7 +68,7 @@ shared_examples 'close quick action' do |issuable_type| it "does not close the #{issuable_type}" do add_note('/close') - expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content "Closed this #{issuable.to_ability_name.humanize(capitalize: false)}." expect(issuable).to be_open end end diff --git a/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb deleted file mode 100644 index 1e1e3c7bc95..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'copy_metadata quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets copy_metadata quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/copy_metadata #{source_issuable.to_reference(project)}" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).last - - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - issuable.reload - expect(issuable.description).to eq 'bug description' - expect(issuable.milestone).to eq milestone - expect(issuable.labels).to match_array([label_bug, label_feature]) - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets copy_metadata quick action accordingly' do - add_note("/copy_metadata #{source_issuable.to_reference(project)}") - - wait_for_requests - expect(page).not_to have_content '/copy_metadata' - expect(page).to have_content 'Commands applied' - issuable.reload - expect(issuable.milestone).to eq milestone - expect(issuable.labels).to match_array([label_bug, label_feature]) - end - - context "when current user cannot copy_metadata" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not copy_metadata' do - add_note("/copy_metadata #{source_issuable.to_reference(project)}") - - wait_for_requests - expect(page).not_to have_content '/copy_metadata' - expect(page).not_to have_content 'Commands applied' - issuable.reload - expect(issuable.milestone).not_to eq milestone - expect(issuable.labels).to eq [] - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains copy_metadata quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note("/copy_metadata #{source_issuable.to_reference(project)}") - - expect(page).not_to have_content '/copy_metadata' - expect(page).to have_content "Copy labels and milestone from #{source_issuable.to_reference(project)}." - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb deleted file mode 100644 index 8a72bbc13bf..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb +++ /dev/null @@ -1,103 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'done quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets done quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/done" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - - todos = TodosFinder.new(maintainer).execute - expect(todos.size).to eq 0 - end - end - - context "post note to existing #{issuable_type}" do - before do - TodoService.new.mark_todo(issuable, maintainer) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the done quick action accordingly' do - todos = TodosFinder.new(maintainer).execute - todo = todos.first - expect(todo.reload).to be_pending - - expect(todos.size).to eq 1 - expect(todo.target).to eq issuable - expect(todo.author).to eq maintainer - expect(todo.user).to eq maintainer - - add_note('/done') - - wait_for_requests - expect(page).not_to have_content '/done' - expect(page).to have_content 'Commands applied' - expect(todo.reload).to be_done - end - - context "when current user cannot mark #{issuable_type} todo as done" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it "does not set the #{issuable_type} todo as done" do - todos = TodosFinder.new(maintainer).execute - todo = todos.first - expect(todo.reload).to be_pending - - expect(todos.size).to eq 1 - expect(todo.target).to eq issuable - expect(todo.author).to eq maintainer - expect(todo.user).to eq maintainer - - add_note('/done') - - expect(page).not_to have_content 'Commands applied' - expect(todo.reload).to be_pending - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains done quick action' do - TodoService.new.mark_todo(issuable, maintainer) - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/done') - - expect(page).not_to have_content '/done' - expect(page).to have_content "Marks todo as done." - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb deleted file mode 100644 index 648755d7e55..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'estimate quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets estimate quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/estimate 1d 2h 3m" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.time_estimate).to eq 36180 - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the estimate quick action accordingly' do - add_note("/estimate 1d 2h 3m") - - wait_for_requests - expect(page).not_to have_content '/estimate' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.time_estimate).to eq 36180 - end - - context "when current user cannot set estimate to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not set estimate' do - add_note("/estimate ~bug ~feature") - - wait_for_requests - expect(page).not_to have_content '/estimate' - expect(issuable.reload.time_estimate).to eq 0 - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains estimate quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/estimate 1d 2h 3m') - - expect(page).not_to have_content '/estimate' - expect(page).to have_content 'Sets time estimate to 1d 2h 3m.' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb new file mode 100644 index 00000000000..439c068471b --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb @@ -0,0 +1,273 @@ +# frozen_string_literal: true + +shared_examples 'issuable quick actions' do + QuickAction = Struct.new(:action_text, :expectation, :before_action, keyword_init: true) do + # Pass a block as :before_action if + # issuable state needs to be changed before + # the quick action is executed. + def call_before_action + before_action.call if before_action + end + + def skip_access_check + action_text["/todo"] || + action_text["/done"] || + action_text["/subscribe"] || + action_text["/shrug"] || + action_text["/tableflip"] + end + end + + # Quick actions shared by issues and merge requests + let(:issuable_quick_actions) do + [ + QuickAction.new( + action_text: "/subscribe", + expectation: ->(noteable, can_use_quick_action) { + if can_use_quick_action + expect(noteable).to be_subscribed(note_author, issuable.project) + else + expect(noteable).not_to be_subscribed(note_author, issuable.project) + end + } + ), + QuickAction.new( + action_text: "/unsubscribe", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable).not_to be_subscribed(note_author, issuable.project) + } + ), + QuickAction.new( + action_text: "/todo", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.todos.count == 1).to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/done", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.todos.last.done?).to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/close", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.closed?).to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/reopen", + before_action: -> { + issuable.close + }, + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.open?).to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/assign @#{user.username}", + expectation: ->(noteable, can_use_quick_action) { + if noteable.allows_multiple_assignees? + expect(noteable.assignees == [old_assignee, user]).to eq(can_use_quick_action) + else + expect(noteable.assignees == [user]).to eq(can_use_quick_action) + end + } + ), + QuickAction.new( + action_text: "/unassign", + expectation: ->(noteable, can_use_quick_action) { + if can_use_quick_action + expect(noteable.assignees).to be_empty + else + expect(noteable.assignees).not_to be_empty + end + } + ), + QuickAction.new( + action_text: "/title new title", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.title == "new title").to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/lock", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.discussion_locked?).to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/unlock", + before_action: -> { + issuable.update(discussion_locked: true) + }, + expectation: ->(noteable, can_use_quick_action) { + if can_use_quick_action + expect(noteable).not_to be_discussion_locked + else + expect(noteable).to be_discussion_locked + end + } + ), + QuickAction.new( + action_text: "/milestone %\"sprint\"", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.milestone == milestone).to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/remove_milestone", + before_action: -> { + issuable.update(milestone_id: milestone.id) + }, + expectation: ->(noteable, can_use_quick_action) { + if can_use_quick_action + expect(noteable.milestone_id).to be_nil + else + expect(noteable.milestone_id).to eq(milestone.id) + end + } + ), + QuickAction.new( + action_text: "/label ~feature", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.labels&.last&.id == feature_label.id).to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/unlabel", + expectation: ->(noteable, can_use_quick_action) { + if can_use_quick_action + expect(noteable.labels).to be_empty + else + expect(noteable.labels).not_to be_empty + end + } + ), + QuickAction.new( + action_text: "/award :100:", + expectation: ->(noteable, can_use_quick_action) { + if can_use_quick_action + expect(noteable.award_emoji.last.name).to eq("100") + else + expect(noteable.award_emoji).to be_empty + end + } + ), + QuickAction.new( + action_text: "/estimate 1d 2h 3m", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.time_estimate == 36180).to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/remove_estimate", + before_action: -> { + issuable.update(time_estimate: 30000) + }, + expectation: ->(noteable, can_use_quick_action) { + if can_use_quick_action + expect(noteable.time_estimate).to be_zero + else + expect(noteable.time_estimate).to eq(30000) + end + } + ), + QuickAction.new( + action_text: "/spend 1d 2h 3m", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.total_time_spent == 36180).to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/remove_time_spent", + expectation: ->(noteable, can_use_quick_action) { + if can_use_quick_action + expect(noteable.total_time_spent == 0) + else + expect(noteable.timelogs).to be_empty + end + } + ), + QuickAction.new( + action_text: "/shrug oops", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.notes&.last&.note == "HELLO\noops ¯\\_(ツ)_/¯\nWORLD").to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/tableflip oops", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.notes&.last&.note == "HELLO\noops (╯°□°)╯︵ ┻━┻\nWORLD").to eq(can_use_quick_action) + } + ), + QuickAction.new( + action_text: "/copy_metadata #{issue_2.to_reference}", + expectation: ->(noteable, can_use_quick_action) { + if can_use_quick_action + expect(noteable.labels).to eq(issue_2.labels) + expect(noteable.milestone).to eq(issue_2.milestone) + else + expect(noteable.labels).not_to eq(issue_2.labels) + expect(noteable.milestone).not_to eq(issue_2.milestone) + end + } + ) + ] + end + + let(:old_assignee) { create(:user) } + + before do + project.add_developer(old_assignee) + issuable.update(assignees: [old_assignee]) + end + + context 'when user can update issuable' do + set(:developer) { create(:user) } + let(:note_author) { developer } + + before do + project.add_developer(developer) + end + + it 'saves the note and updates the issue' do + quick_actions.each do |quick_action| + note_text = %(HELLO\n#{quick_action.action_text}\nWORLD) + quick_action.call_before_action + + note = described_class.new(project, developer, note_params.merge(note: note_text)).execute + noteable = note.noteable + + # shrug and tablefip quick actions modifies the note text + # on these cases we need to skip this assertion + if !quick_action.action_text["shrug"] && !quick_action.action_text["tableflip"] + expect(note.note).to eq "HELLO\nWORLD" + end + + quick_action.expectation.call(noteable, true) + end + end + end + + context 'when user cannot update issuable' do + set(:non_member) { create(:user) } + let(:note_author) { non_member } + + it 'applies commands that user can execute' do + quick_actions.each do |quick_action| + note_text = %(HELLO\n#{quick_action.action_text}\nWORLD) + quick_action.call_before_action + + note = described_class.new(project, non_member, note_params.merge(note: note_text)).execute + noteable = note.noteable + + if quick_action.skip_access_check + quick_action.expectation.call(noteable, true) + else + quick_action.expectation.call(noteable, false) + end + end + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb deleted file mode 100644 index 9066e382b70..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'label quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets label quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug ~feature" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.labels).to match_array([label_bug, label_feature]) - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - expect(issuable.labels).to eq [] - end - - it 'creates the note and interprets the label quick action accordingly' do - add_note("/label ~bug ~feature") - - wait_for_requests - expect(page).not_to have_content '/label' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.labels).to match_array([label_bug, label_feature]) - end - - context "when current user cannot set label to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not set label' do - add_note("/label ~bug ~feature") - - wait_for_requests - expect(page).not_to have_content '/label' - expect(issuable.labels).to eq [] - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains label quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/label ~bug ~feature') - - expect(page).not_to have_content '/label' - expect(page).to have_content 'Adds bug feature labels.' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb deleted file mode 100644 index d3197f2a459..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'lock quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets lock quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/lock" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable).not_to be_discussion_locked - end - end - - context "post note to existing #{issuable_type}" do - before do - issuable.update(discussion_locked: false) - expect(issuable).not_to be_discussion_locked - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the lock quick action accordingly' do - add_note('/lock') - - wait_for_requests - expect(page).not_to have_content '/lock' - expect(page).to have_content 'Commands applied' - expect(issuable.reload).to be_discussion_locked - end - - context "when current user cannot lock to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it "does not lock the #{issuable_type}" do - add_note('/lock') - - wait_for_requests - expect(page).not_to have_content '/lock' - expect(issuable).not_to be_discussion_locked - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains lock quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/lock') - - expect(page).not_to have_content '/lock' - expect(page).to have_content "Locks the discussion" - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb deleted file mode 100644 index 7f16ce93b6a..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'milestone quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets milestone quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/milestone %\"ASAP\"" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.milestone).to eq milestone - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - expect(issuable.milestone).to be_nil - end - - it 'creates the note and interprets the milestone quick action accordingly' do - add_note("/milestone %\"ASAP\"") - - wait_for_requests - expect(page).not_to have_content '/milestone' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.milestone).to eq milestone - end - - context "when current user cannot set milestone to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not set milestone' do - add_note('/milestone') - - wait_for_requests - expect(page).not_to have_content '/milestone' - expect(issuable.reload.milestone).to be_nil - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains milestone quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note("/milestone %\"ASAP\"") - - expect(page).not_to have_content '/milestone' - expect(page).to have_content 'Sets the milestone to %ASAP' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb deleted file mode 100644 index 643ae77516a..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'relabel quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets relabel quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /relabel ~feature" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.labels).to eq [label_bug, label_feature] - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - issuable.update(labels: [label_bug]) - end - - it 'creates the note and interprets the relabel quick action accordingly' do - add_note('/relabel ~feature') - - wait_for_requests - expect(page).not_to have_content '/relabel' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.labels).to match_array([label_feature]) - end - - it 'creates the note and interprets the relabel quick action with empty param' do - add_note('/relabel') - - wait_for_requests - expect(page).not_to have_content '/relabel' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.labels).to match_array([label_bug]) - end - - context "when current user cannot relabel to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not relabel' do - add_note('/relabel ~feature') - - wait_for_requests - expect(page).not_to have_content '/relabel' - expect(issuable.labels).to match_array([label_bug]) - end - end - end - - context "preview of note on #{issuable_type}", :js do - before do - issuable.update(labels: [label_bug]) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it 'explains relabel all quick action' do - preview_note('/relabel ~feature') - - expect(page).not_to have_content '/relabel' - expect(page).to have_content 'Replaces all labels with feature label.' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb deleted file mode 100644 index 24f6f8d5bf4..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'remove_estimate quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets estimate quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/remove_estimate" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.time_estimate).to eq 0 - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - issuable.update_attribute(:time_estimate, 36180) - end - - it 'creates the note and interprets the remove_estimate quick action accordingly' do - add_note("/remove_estimate") - - wait_for_requests - expect(page).not_to have_content '/remove_estimate' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.time_estimate).to eq 0 - end - - context "when current user cannot remove_estimate" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not remove_estimate' do - add_note('/remove_estimate') - - wait_for_requests - expect(page).not_to have_content '/remove_estimate' - expect(issuable.reload.time_estimate).to eq 36180 - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains remove_estimate quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/remove_estimate') - - expect(page).not_to have_content '/remove_estimate' - expect(page).to have_content 'Removes time estimate.' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb deleted file mode 100644 index edd92d5cdbc..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb +++ /dev/null @@ -1,86 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'remove_milestone quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets remove_milestone quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/remove_milestone" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.milestone).to be_nil - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - issuable.update(milestone: milestone) - expect(issuable.milestone).to eq(milestone) - end - - it 'creates the note and interprets the remove_milestone quick action accordingly' do - add_note("/remove_milestone") - - wait_for_requests - expect(page).not_to have_content '/remove_milestone' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.milestone).to be_nil - end - - context "when current user cannot remove milestone to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not remove milestone' do - add_note('/remove_milestone') - - wait_for_requests - expect(page).not_to have_content '/remove_milestone' - expect(issuable.reload.milestone).to eq(milestone) - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains remove_milestone quick action' do - issuable.update(milestone: milestone) - expect(issuable.milestone).to eq(milestone) - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note("/remove_milestone") - - expect(page).not_to have_content '/remove_milestone' - expect(page).to have_content 'Removes %ASAP milestone.' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb deleted file mode 100644 index 6d5894b2318..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'remove_time_spent quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets remove_time_spent quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/remove_time_spent" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.total_time_spent).to eq 0 - end - end - - context "post note to existing #{issuable_type}" do - before do - issuable.update!(spend_time: { duration: 36180, user_id: maintainer.id }) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the remove_time_spent quick action accordingly' do - add_note("/remove_time_spent") - - wait_for_requests - expect(page).not_to have_content '/remove_time_spent' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.total_time_spent).to eq 0 - end - - context "when current user cannot set remove_time_spent time" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not set remove_time_spent time' do - add_note("/remove_time_spent") - - wait_for_requests - expect(page).not_to have_content '/remove_time_spent' - expect(issuable.reload.total_time_spent).to eq 36180 - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains remove_time_spent quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/remove_time_spent') - - expect(page).not_to have_content '/remove_time_spent' - expect(page).to have_content 'Removes spent time.' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb deleted file mode 100644 index af173e93bb5..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb +++ /dev/null @@ -1,86 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'reopen quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets reopen quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/reopen" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - end - end - - context "post note to existing #{issuable_type}" do - before do - issuable.close - expect(issuable).to be_closed - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the reopen quick action accordingly' do - add_note('/reopen') - - wait_for_requests - expect(page).not_to have_content '/reopen' - expect(page).to have_content 'Commands applied' - - issuable.reload - expect(issuable).to be_opened - end - - context "when current user cannot reopen #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it "does not reopen the #{issuable_type}" do - add_note('/reopen') - - expect(page).not_to have_content 'Commands applied' - expect(issuable).to be_closed - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains reopen quick action' do - issuable.close - expect(issuable).to be_closed - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/reopen') - - expect(page).not_to have_content '/reopen' - expect(page).to have_content "Reopens this #{issuable_type.to_s.humanize.downcase}." - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb deleted file mode 100644 index b592cf16f67..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'shrug quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets shrug quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/shrug oops" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq "bug description\noops ¯\\_(ツ)_/¯" - expect(page).to have_content 'bug 345' - expect(page).to have_content "bug description oops ¯\\_(ツ)_/¯" - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets shrug quick action accordingly' do - add_note("/shrug oops") - - wait_for_requests - expect(page).not_to have_content '/shrug oops' - expect(page).to have_content "oops ¯\\_(ツ)_/¯" - expect(issuable.notes.last.note).to eq "oops ¯\\_(ツ)_/¯" - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains shrug quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/shrug oops') - - expect(page).not_to have_content '/shrug' - expect(page).to have_content "oops ¯\\_(ツ)_/¯" - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb deleted file mode 100644 index 97b4885eba0..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'spend quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets spend quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/spend 1d 2h 3m" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.total_time_spent).to eq 36180 - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the spend quick action accordingly' do - add_note("/spend 1d 2h 3m") - - wait_for_requests - expect(page).not_to have_content '/spend' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.total_time_spent).to eq 36180 - end - - context "when current user cannot set spend time" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not set spend time' do - add_note("/spend 1s 2h 3m") - - wait_for_requests - expect(page).not_to have_content '/spend' - expect(issuable.reload.total_time_spent).to eq 0 - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains spend quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/spend 1d 2h 3m') - - expect(page).not_to have_content '/spend' - expect(page).to have_content 'Adds 1d 2h 3m spent time.' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb deleted file mode 100644 index 15aefd511a5..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'subscribe quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets subscribe quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/subscribe" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.subscribed?(maintainer, project)).to be_truthy - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - expect(issuable.subscribed?(maintainer, project)).to be_falsy - end - - it 'creates the note and interprets the subscribe quick action accordingly' do - add_note('/subscribe') - - wait_for_requests - expect(page).not_to have_content '/subscribe' - expect(page).to have_content 'Commands applied' - expect(issuable.subscribed?(maintainer, project)).to be_truthy - end - - context "when current user cannot subscribe to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it "does not subscribe to the #{issuable_type}" do - add_note('/subscribe') - - wait_for_requests - expect(page).not_to have_content '/subscribe' - expect(page).to have_content 'Commands applied' - expect(issuable.subscribed?(maintainer, project)).to be_falsy - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains subscribe quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/subscribe') - - expect(page).not_to have_content '/subscribe' - expect(page).to have_content "Subscribes to this #{issuable_type.to_s.humanize.downcase}" - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb deleted file mode 100644 index c174127c4ff..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'tableflip quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets tableflip quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/tableflip oops" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq "bug description\noops (╯°□°)╯︵ ┻━┻" - expect(page).to have_content 'bug 345' - expect(page).to have_content "bug description oops (╯°□°)╯︵ ┻━┻" - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets tableflip quick action accordingly' do - add_note("/tableflip oops") - - wait_for_requests - expect(page).not_to have_content '/tableflip oops' - expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻" - expect(issuable.notes.last.note).to eq "oops (╯°□°)╯︵ ┻━┻" - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains tableflip quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/tableflip oops') - - expect(page).not_to have_content '/tableflip' - expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻" - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb deleted file mode 100644 index 93a69093dde..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'title quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets title quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/title new title" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(issuable.title).to eq 'bug 345' - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the title quick action accordingly' do - add_note('/title New title') - - wait_for_requests - expect(page).not_to have_content '/title new title' - expect(page).to have_content 'Commands applied' - expect(page).to have_content 'New title' - - issuable.reload - expect(issuable.title).to eq 'New title' - end - - context "when current user cannot set title #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it "does not set title to the #{issuable_type}" do - add_note('/title New title') - - expect(page).not_to have_content 'Commands applied' - expect(issuable.title).not_to eq 'New title' - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains title quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/title New title') - wait_for_requests - - expect(page).not_to have_content '/title New title' - expect(page).to have_content 'Changes the title to "New title".' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb deleted file mode 100644 index cccc28127ce..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'todo quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets todo quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/todo" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - - todos = TodosFinder.new(maintainer).execute - expect(todos.size).to eq 0 - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the todo quick action accordingly' do - add_note('/todo') - - wait_for_requests - expect(page).not_to have_content '/todo' - expect(page).to have_content 'Commands applied' - - todos = TodosFinder.new(maintainer).execute - todo = todos.first - - expect(todos.size).to eq 1 - expect(todo).to be_pending - expect(todo.target).to eq issuable - expect(todo.author).to eq maintainer - expect(todo.user).to eq maintainer - end - - context "when current user cannot add todo #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it "does not add todo the #{issuable_type}" do - add_note('/todo') - - expect(page).not_to have_content 'Commands applied' - todos = TodosFinder.new(maintainer).execute - expect(todos.size).to eq 0 - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains todo quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/todo') - - expect(page).not_to have_content '/todo' - expect(page).to have_content "Adds a todo." - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb deleted file mode 100644 index 0b1a52bc860..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb +++ /dev/null @@ -1,120 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'unassign quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets unassign quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/unassign @bob" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable.assignees).to eq [] - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - end - - it "creates the #{issuable_type} and interprets unassign quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/unassign me" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable.assignees).to eq [] - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the unassign quick action accordingly' do - assignee = create(:user, username: 'bob') - issuable.update(assignee_ids: [assignee.id]) - expect(issuable.assignees).to eq [assignee] - - add_note("Awesome!\n\n/unassign @bob") - - expect(page).to have_content 'Awesome!' - expect(page).not_to have_content '/unassign @bob' - - wait_for_requests - issuable.reload - note = issuable.notes.user.first - - expect(note.note).to eq 'Awesome!' - expect(issuable.assignees).to eq [] - end - - it "unassigns the #{issuable_type} from current user" do - issuable.update(assignee_ids: [maintainer.id]) - expect(issuable.reload.assignees).to eq [maintainer] - expect(issuable.assignees).to eq [maintainer] - - add_note("/unassign me") - - expect(page).not_to have_content '/unassign me' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload.assignees).to eq [] - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains unassign quick action: from bob' do - assignee = create(:user, username: 'bob') - issuable.update(assignee_ids: [assignee.id]) - expect(issuable.assignees).to eq [assignee] - - visit public_send("project_#{issuable_type}_path", project, issuable) - - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "Awesome!\n/unassign @bob " - click_on 'Preview' - - expect(page).not_to have_content '/unassign @bob' - expect(page).to have_content 'Awesome!' - expect(page).to have_content 'Removes assignee @bob.' - end - end - - it 'explains unassign quick action: from me' do - issuable.update(assignee_ids: [maintainer.id]) - expect(issuable.assignees).to eq [maintainer] - - visit public_send("project_#{issuable_type}_path", project, issuable) - - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "Awesome!\n/unassign me" - click_on 'Preview' - - expect(page).not_to have_content '/unassign me' - expect(page).to have_content 'Awesome!' - expect(page).to have_content "Removes assignee @#{maintainer.username}." - end - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb deleted file mode 100644 index 1a1ee05841f..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'unlabel quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets unlabel quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /unlabel" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.labels).to eq [label_bug] - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - issuable.update(labels: [label_bug, label_feature]) - end - - it 'creates the note and interprets the unlabel all quick action accordingly' do - add_note("/unlabel") - - wait_for_requests - expect(page).not_to have_content '/unlabel' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.labels).to eq [] - end - - it 'creates the note and interprets the unlabel some quick action accordingly' do - add_note("/unlabel ~bug") - - wait_for_requests - expect(page).not_to have_content '/unlabel' - expect(page).to have_content 'Commands applied' - expect(issuable.reload.labels).to match_array([label_feature]) - end - - context "when current user cannot unlabel to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'does not unlabel' do - add_note("/unlabel") - - wait_for_requests - expect(page).not_to have_content '/unlabel' - expect(issuable.labels).to match_array([label_bug, label_feature]) - end - end - end - - context "preview of note on #{issuable_type}", :js do - before do - issuable.update(labels: [label_bug, label_feature]) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it 'explains unlabel all quick action' do - preview_note('/unlabel') - - expect(page).not_to have_content '/unlabel' - expect(page).to have_content 'Removes all labels.' - end - - it 'explains unlabel some quick action' do - preview_note('/unlabel ~bug') - - expect(page).not_to have_content '/unlabel' - expect(page).to have_content 'Removes bug label.' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb deleted file mode 100644 index 998ff99b32e..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb +++ /dev/null @@ -1,87 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'unlock quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets unlock quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/unlock" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable).not_to be_discussion_locked - end - end - - context "post note to existing #{issuable_type}" do - before do - issuable.update(discussion_locked: true) - expect(issuable).to be_discussion_locked - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it 'creates the note and interprets the unlock quick action accordingly' do - add_note('/unlock') - - wait_for_requests - expect(page).not_to have_content '/unlock' - expect(page).to have_content 'Commands applied' - expect(issuable.reload).not_to be_discussion_locked - end - - context "when current user cannot unlock to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it "does not lock the #{issuable_type}" do - add_note('/unlock') - - wait_for_requests - expect(page).not_to have_content '/unlock' - expect(issuable).to be_discussion_locked - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains unlock quick action' do - issuable.update(discussion_locked: true) - expect(issuable).to be_discussion_locked - - visit public_send("project_#{issuable_type}_path", project, issuable) - - preview_note('/unlock') - - expect(page).not_to have_content '/unlock' - expect(page).to have_content 'Unlocks the discussion' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb deleted file mode 100644 index bd92f133889..00000000000 --- a/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb +++ /dev/null @@ -1,86 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'unsubscribe quick action' do |issuable_type| - before do - project.add_maintainer(maintainer) - gitlab_sign_in(maintainer) - end - - context "new #{issuable_type}", :js do - before do - case issuable_type - when :merge_request - visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) - wait_for_all_requests - when :issue - visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) - wait_for_all_requests - end - end - - it "creates the #{issuable_type} and interprets unsubscribe quick action accordingly" do - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/unsubscribe" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq 'bug description' - expect(issuable).to be_opened - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - expect(issuable.subscribed?(maintainer, project)).to be_truthy - end - end - - context "post note to existing #{issuable_type}" do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - issuable.subscribe(maintainer, project) - expect(issuable.subscribed?(maintainer, project)).to be_truthy - end - - it 'creates the note and interprets the unsubscribe quick action accordingly' do - add_note('/unsubscribe') - - wait_for_requests - expect(page).not_to have_content '/unsubscribe' - expect(page).to have_content 'Commands applied' - expect(issuable.subscribed?(maintainer, project)).to be_falsey - end - - context "when current user cannot unsubscribe to #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - wait_for_all_requests - end - - it "does not unsubscribe to the #{issuable_type}" do - add_note('/unsubscribe') - - wait_for_requests - expect(page).not_to have_content '/unsubscribe' - expect(issuable.subscribed?(maintainer, project)).to be_truthy - end - end - end - - context "preview of note on #{issuable_type}", :js do - it 'explains unsubscribe quick action' do - visit public_send("project_#{issuable_type}_path", project, issuable) - issuable.subscribe(maintainer, project) - expect(issuable.subscribed?(maintainer, project)).to be_truthy - - preview_note('/unsubscribe') - - expect(page).not_to have_content '/unsubscribe' - expect(page).to have_content "Unsubscribes from this #{issuable_type.to_s.humanize.downcase}." - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb deleted file mode 100644 index 336500487fe..00000000000 --- a/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'confidential quick action' do - context 'when the current user can update issues' do - it 'does not create a note, and marks the issue as confidential' do - add_note('/confidential') - - expect(page).not_to have_content '/confidential' - expect(page).to have_content 'Commands applied' - expect(page).to have_content 'made the issue confidential' - - expect(issue.reload).to be_confidential - end - end - - context 'when the current user cannot update the issue' do - let(:guest) { create(:user) } - - before do - project.add_guest(guest) - gitlab_sign_out - sign_in(guest) - visit project_issue_path(project, issue) - end - - it 'does not create a note, and does not mark the issue as confidential' do - add_note('/confidential') - - expect(page).not_to have_content 'Commands applied' - expect(page).not_to have_content 'made the issue confidential' - - expect(issue.reload).not_to be_confidential - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb index 34dba5dbc31..3e9ee9a633f 100644 --- a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb @@ -2,8 +2,14 @@ shared_examples 'create_merge_request quick action' do context 'create a merge request starting from an issue' do - def expect_mr_quickaction(success) - expect(page).to have_content 'Commands applied' + def expect_mr_quickaction(success, branch_name = nil) + command_message = if branch_name + "Created branch '#{branch_name}' and a merge request to resolve this issue" + else + "Created a branch and a merge request to resolve this issue" + end + + expect(page).to have_content command_message if success expect(page).to have_content 'created merge request' @@ -13,19 +19,21 @@ shared_examples 'create_merge_request quick action' do end it "doesn't create a merge request when the branch name is invalid" do - add_note("/create_merge_request invalid branch name") + branch_name = 'invalid branch name' + add_note("/create_merge_request #{branch_name}") wait_for_requests - expect_mr_quickaction(false) + expect_mr_quickaction(false, branch_name) end it "doesn't create a merge request when a branch with that name already exists" do - add_note("/create_merge_request feature") + branch_name = 'feature' + add_note("/create_merge_request #{branch_name}") wait_for_requests - expect_mr_quickaction(false) + expect_mr_quickaction(false, branch_name) end it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do @@ -46,7 +54,7 @@ shared_examples 'create_merge_request quick action' do branch_name = '1-feature' add_note("/create_merge_request #{branch_name}") - expect_mr_quickaction(true) + expect_mr_quickaction(true, branch_name) created_mr = project.merge_requests.last expect(created_mr.source_branch).to eq(branch_name) diff --git a/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb deleted file mode 100644 index ae78cd86cd5..00000000000 --- a/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'due quick action' do - context 'due quick action available and date can be added' do - it 'sets the due date accordingly' do - add_note('/due 2016-08-28') - - expect(page).not_to have_content '/due 2016-08-28' - expect(page).to have_content 'Commands applied' - - visit project_issue_path(project, issue) - - page.within '.due_date' do - expect(page).to have_content 'Aug 28, 2016' - end - end - end - - context 'due quick action not available' do - let(:guest) { create(:user) } - before do - project.add_guest(guest) - gitlab_sign_out - sign_in(guest) - visit project_issue_path(project, issue) - end - - it 'does not set the due date' do - add_note('/due 2016-08-28') - - expect(page).not_to have_content 'Commands applied' - expect(page).not_to have_content '/due 2016-08-28' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb index 633c7135fbc..3834b8b2b87 100644 --- a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb @@ -9,7 +9,6 @@ shared_examples 'duplicate quick action' do add_note("/duplicate ##{original_issue.to_reference}") expect(page).not_to have_content "/duplicate #{original_issue.to_reference}" - expect(page).to have_content 'Commands applied' expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" expect(issue.reload).to be_closed @@ -28,7 +27,6 @@ shared_examples 'duplicate quick action' do it 'does not create a note, and does not mark the issue as a duplicate' do add_note("/duplicate ##{original_issue.to_reference}") - expect(page).not_to have_content 'Commands applied' expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" expect(issue.reload).to be_open diff --git a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb index a0b0d888769..a37b2392d52 100644 --- a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb @@ -12,7 +12,7 @@ shared_examples 'move quick action' do it 'moves the issue' do add_note("/move #{target_project.full_path}") - expect(page).to have_content 'Commands applied' + expect(page).to have_content "Moved this issue to #{target_project.full_path}." expect(issue.reload).to be_closed visit project_issue_path(target_project, issue) @@ -29,7 +29,7 @@ shared_examples 'move quick action' do wait_for_requests - expect(page).to have_content 'Commands applied' + expect(page).to have_content "Moved this issue to #{project_unauthorized.full_path}." expect(issue.reload).to be_open end end @@ -40,7 +40,7 @@ shared_examples 'move quick action' do wait_for_requests - expect(page).to have_content 'Commands applied' + expect(page).to have_content "Failed to move this issue because target project doesn't exist." expect(issue.reload).to be_open end end @@ -56,7 +56,7 @@ shared_examples 'move quick action' do shared_examples 'applies the commands to issues in both projects, target and source' do it "applies quick actions" do - expect(page).to have_content 'Commands applied' + expect(page).to have_content "Moved this issue to #{target_project.full_path}." expect(issue.reload).to be_closed visit project_issue_path(target_project, issue) diff --git a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb deleted file mode 100644 index 657c2a60d24..00000000000 --- a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'remove_due_date quick action' do - context 'remove_due_date action available and due date can be removed' do - it 'removes the due date accordingly' do - add_note('/remove_due_date') - - expect(page).not_to have_content '/remove_due_date' - expect(page).to have_content 'Commands applied' - - visit project_issue_path(project, issue) - - page.within '.due_date' do - expect(page).to have_content 'None' - end - end - end - - context 'remove_due_date action not available' do - let(:guest) { create(:user) } - before do - project.add_guest(guest) - gitlab_sign_out - sign_in(guest) - visit project_issue_path(project, issue) - end - - it 'does not remove the due date' do - add_note("/remove_due_date") - - expect(page).not_to have_content 'Commands applied' - expect(page).not_to have_content '/remove_due_date' - end - end -end diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb index c454ddc4bba..ac7c17915de 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb @@ -10,7 +10,7 @@ shared_examples 'merge quick action' do it 'merges the MR' do add_note("/merge") - expect(page).to have_content 'Commands applied' + expect(page).to have_content 'Scheduled to merge this merge request when the pipeline succeeds.' expect(merge_request.reload).to be_merged end diff --git a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb deleted file mode 100644 index cf2bdb1dd68..00000000000 --- a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'target_branch quick action' do - describe '/target_branch command in merge request' do - let(:another_project) { create(:project, :public, :repository) } - let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } - - before do - another_project.add_maintainer(user) - sign_in(user) - end - - it 'changes target_branch in new merge_request' do - visit project_new_merge_request_path(another_project, new_url_opts) - - fill_in "merge_request_title", with: 'My brand new feature' - fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:" - click_button "Submit merge request" - - merge_request = another_project.merge_requests.first - expect(merge_request.description).to eq "le feature \nFeature description:" - expect(merge_request.target_branch).to eq 'fix' - end - - it 'does not change target branch when merge request is edited' do - new_merge_request = create(:merge_request, source_project: another_project) - - visit edit_project_merge_request_path(another_project, new_merge_request) - fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n" - click_button "Save changes" - - new_merge_request = another_project.merge_requests.first - expect(new_merge_request.description).to include('/target_branch') - expect(new_merge_request.target_branch).not_to eq('fix') - end - end - - describe '/target_branch command from note' do - context 'when the current user can change target branch' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - end - - it 'changes target branch from a note' do - add_note("message start \n/target_branch merge-test\n message end.") - - wait_for_requests - expect(page).not_to have_content('/target_branch') - expect(page).to have_content('message start') - expect(page).to have_content('message end.') - - expect(merge_request.reload.target_branch).to eq 'merge-test' - end - - it 'does not fail when target branch does not exists' do - add_note('/target_branch totally_not_existing_branch') - - expect(page).not_to have_content('/target_branch') - - expect(merge_request.target_branch).to eq 'feature' - end - end - - context 'when current user can not change target branch' do - before do - project.add_guest(guest) - sign_in(guest) - visit project_merge_request_path(project, merge_request) - end - - it 'does not change target branch' do - add_note('/target_branch merge-test') - - expect(page).not_to have_content '/target_branch merge-test' - - expect(merge_request.target_branch).to eq 'feature' - end - end - end -end diff --git a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb deleted file mode 100644 index 60d6e53e74f..00000000000 --- a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'wip quick action' do - context 'when the current user can toggle the WIP prefix' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - wait_for_requests - end - - it 'adds the WIP: prefix to the title' do - add_note('/wip') - - expect(page).not_to have_content '/wip' - expect(page).to have_content 'Commands applied' - - expect(merge_request.reload.work_in_progress?).to eq true - end - - it 'removes the WIP: prefix from the title' do - merge_request.update!(title: merge_request.wip_title) - add_note('/wip') - - expect(page).not_to have_content '/wip' - expect(page).to have_content 'Commands applied' - - expect(merge_request.reload.work_in_progress?).to eq false - end - end - - context 'when the current user cannot toggle the WIP prefix' do - before do - project.add_guest(guest) - sign_in(guest) - visit project_merge_request_path(project, merge_request) - end - - it 'does not change the WIP prefix' do - add_note('/wip') - - expect(page).not_to have_content '/wip' - expect(page).not_to have_content 'Commands applied' - - expect(merge_request.reload.work_in_progress?).to eq false - end - end -end diff --git a/spec/support/shared_examples/reference_parser_shared_examples.rb b/spec/support/shared_examples/reference_parser_shared_examples.rb index baf8bcc04b8..d903c0f10e0 100644 --- a/spec/support/shared_examples/reference_parser_shared_examples.rb +++ b/spec/support/shared_examples/reference_parser_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples "referenced feature visibility" do |*related_features| let(:feature_fields) do related_features.map { |feature| (feature + "_access_level").to_sym } diff --git a/spec/support/shared_examples/relative_positioning_shared_examples.rb b/spec/support/shared_examples/relative_positioning_shared_examples.rb new file mode 100644 index 00000000000..b7382cea93c --- /dev/null +++ b/spec/support/shared_examples/relative_positioning_shared_examples.rb @@ -0,0 +1,283 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a class that supports relative positioning' do + let(:item1) { create(factory, default_params) } + let(:item2) { create(factory, default_params) } + let(:new_item) { create(factory, default_params) } + + def create_item(params) + create(factory, params.merge(default_params)) + end + + def create_items_with_positions(positions) + positions.map do |position| + create_item(relative_position: position) + end + end + + describe '.move_nulls_to_end' do + it 'moves items with null relative_position to the end' do + item1.update!(relative_position: nil) + item2.update!(relative_position: nil) + + described_class.move_nulls_to_end([item1, item2]) + + expect(item2.prev_relative_position).to eq item1.relative_position + expect(item1.prev_relative_position).to eq nil + expect(item2.next_relative_position).to eq nil + end + + it 'moves the item near the start position when there are no existing positions' do + item1.update!(relative_position: nil) + + described_class.move_nulls_to_end([item1]) + + expect(item1.relative_position).to eq(described_class::START_POSITION + described_class::IDEAL_DISTANCE) + end + + it 'does not perform any moves if all items have their relative_position set' do + item1.update!(relative_position: 1) + + expect(item1).not_to receive(:save) + + described_class.move_nulls_to_end([item1]) + end + end + + describe '#max_relative_position' do + it 'returns maximum position' do + expect(item1.max_relative_position).to eq item2.relative_position + end + end + + describe '#prev_relative_position' do + it 'returns previous position if there is an item above' do + item1.update(relative_position: 5) + item2.update(relative_position: 15) + + expect(item2.prev_relative_position).to eq item1.relative_position + end + + it 'returns nil if there is no item above' do + expect(item1.prev_relative_position).to eq nil + end + end + + describe '#next_relative_position' do + it 'returns next position if there is an item below' do + item1.update(relative_position: 5) + item2.update(relative_position: 15) + + expect(item1.next_relative_position).to eq item2.relative_position + end + + it 'returns nil if there is no item below' do + expect(item2.next_relative_position).to eq nil + end + end + + describe '#move_before' do + it 'moves item before' do + [item2, item1].each(&:move_to_end) + + item1.move_before(item2) + + expect(item1.relative_position).to be < item2.relative_position + end + end + + describe '#move_after' do + it 'moves item after' do + [item1, item2].each(&:move_to_end) + + item1.move_after(item2) + + expect(item1.relative_position).to be > item2.relative_position + end + end + + describe '#move_to_end' do + before do + [item1, item2].each do |item1| + item1.move_to_end && item1.save + end + end + + it 'moves item to the end' do + new_item.move_to_end + + expect(new_item.relative_position).to be > item2.relative_position + end + end + + describe '#move_between' do + before do + [item1, item2].each do |item1| + item1.move_to_end && item1.save + end + end + + it 'positions item between two other' do + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to be > item1.relative_position + expect(new_item.relative_position).to be < item2.relative_position + end + + it 'positions item between on top' do + new_item.move_between(nil, item1) + + expect(new_item.relative_position).to be < item1.relative_position + end + + it 'positions item between to end' do + new_item.move_between(item2, nil) + + expect(new_item.relative_position).to be > item2.relative_position + end + + it 'positions items even when after and before positions are the same' do + item2.update relative_position: item1.relative_position + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to be > item1.relative_position + expect(item1.relative_position).to be < item2.relative_position + end + + it 'positions items between other two if distance is 1' do + item2.update relative_position: item1.relative_position + 1 + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to be > item1.relative_position + expect(item1.relative_position).to be < item2.relative_position + end + + it 'positions item in the middle of other two if distance is big enough' do + item1.update relative_position: 6000 + item2.update relative_position: 10000 + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to eq(8000) + end + + it 'positions item closer to the middle if we are at the very top' do + item2.update relative_position: 6000 + + new_item.move_between(nil, item2) + + expect(new_item.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE) + end + + it 'positions item closer to the middle if we are at the very bottom' do + new_item.update relative_position: 1 + item1.update relative_position: 6000 + item2.destroy + + new_item.move_between(item1, nil) + + expect(new_item.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE) + end + + it 'positions item in the middle of other two if distance is not big enough' do + item1.update relative_position: 100 + item2.update relative_position: 400 + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to eq(250) + end + + it 'positions item in the middle of other two is there is no place' do + item1.update relative_position: 100 + item2.update relative_position: 101 + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to be_between(item1.relative_position, item2.relative_position) + end + + it 'uses rebalancing if there is no place' do + item1.update relative_position: 100 + item2.update relative_position: 101 + item3 = create_item(relative_position: 102) + new_item.update relative_position: 103 + + new_item.move_between(item2, item3) + new_item.save! + + expect(new_item.relative_position).to be_between(item2.relative_position, item3.relative_position) + expect(item1.reload.relative_position).not_to eq(100) + end + + it 'positions item right if we pass none-sequential parameters' do + item1.update relative_position: 99 + item2.update relative_position: 101 + item3 = create_item(relative_position: 102) + new_item.update relative_position: 103 + + new_item.move_between(item1, item3) + new_item.save! + + expect(new_item.relative_position).to be(100) + end + + it 'avoids N+1 queries when rebalancing other items' do + items = create_items_with_positions([100, 101, 102]) + + count = ActiveRecord::QueryRecorder.new do + new_item.move_between(items[-2], items[-1]) + end + + items = create_items_with_positions([150, 151, 152, 153, 154]) + + expect { new_item.move_between(items[-2], items[-1]) }.not_to exceed_query_limit(count) + end + end + + describe '#move_sequence_before' do + it 'moves the whole sequence of items to the middle of the nearest gap' do + items = create_items_with_positions([90, 100, 101, 102]) + + items.last.move_sequence_before + items.last.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([90, 95, 96, 102]) + end + + it 'finds a gap if there are unused positions' do + items = create_items_with_positions([100, 101, 102]) + + items.last.move_sequence_before + items.last.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([50, 51, 102]) + end + end + + describe '#move_sequence_after' do + it 'moves the whole sequence of items to the middle of the nearest gap' do + items = create_items_with_positions([100, 101, 102, 110]) + + items.first.move_sequence_after + items.first.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([100, 105, 106, 110]) + end + + it 'finds a gap if there are unused positions' do + items = create_items_with_positions([100, 101, 102]) + + items.first.move_sequence_after + items.first.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([100, 601, 602]) + end + end +end diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb index 8a7fcf856a1..776a0bdd29e 100644 --- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'custom attributes endpoints' do |attributable_name| let!(:custom_attribute1) { attributable.custom_attributes.create key: 'foo', value: 'foo' } let!(:custom_attribute2) { attributable.custom_attributes.create key: 'bar', value: 'bar' } diff --git a/spec/support/shared_examples/requests/api/diff_discussions.rb b/spec/support/shared_examples/requests/api/diff_discussions.rb index 366c2955359..76c6c93964a 100644 --- a/spec/support/shared_examples/requests/api/diff_discussions.rb +++ b/spec/support/shared_examples/requests/api/diff_discussions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name| describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do it "includes diff discussions" do diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb index c3132c41f5b..fc72287f265 100644 --- a/spec/support/shared_examples/requests/api/discussions.rb +++ b/spec/support/shared_examples/requests/api/discussions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_individual_notes: false| describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do it "returns an array of discussions" do diff --git a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb index 96d59e0c472..9fe6288d53f 100644 --- a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb +++ b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuable participants endpoint' do let(:area) { entity.class.name.underscore.pluralize } diff --git a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb new file mode 100644 index 00000000000..90c1ed8d09b --- /dev/null +++ b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +def get_issue + json_response.is_a?(Array) ? json_response.detect {|issue| issue['id'] == target_issue.id} : json_response +end + +shared_examples 'accessible merge requests count' do + it 'returns anonymous accessible merge requests count' do + get api(api_url), params: { scope: 'all' } + + issue = get_issue + expect(issue).not_to be_nil + expect(issue['merge_requests_count']).to eq(1) + end + + it 'returns guest accessible merge requests count' do + get api(api_url, guest), params: { scope: 'all' } + + issue = get_issue + expect(issue).not_to be_nil + expect(issue['merge_requests_count']).to eq(1) + end + + it 'returns reporter accessible merge requests count' do + get api(api_url, user), params: { scope: 'all' } + + issue = get_issue + expect(issue).not_to be_nil + expect(issue['merge_requests_count']).to eq(2) + end + + it 'returns admin accessible merge requests count' do + get api(api_url, admin), params: { scope: 'all' } + + issue = get_issue + expect(issue).not_to be_nil + expect(issue['merge_requests_count']).to eq(2) + end +end diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb index 57eefd5ef01..354ae7288b1 100644 --- a/spec/support/shared_examples/requests/api/notes.rb +++ b/spec/support/shared_examples/requests/api/notes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'noteable API' do |parent_type, noteable_type, id_name| describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do context 'sorting' do diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions.rb b/spec/support/shared_examples/requests/api/resolvable_discussions.rb index 7e2416b23f3..42054a273f3 100644 --- a/spec/support/shared_examples/requests/api/resolvable_discussions.rb +++ b/spec/support/shared_examples/requests/api/resolvable_discussions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_name| describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do it "resolves discussion if resolved is true" do diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb index ebfc5fed3bb..eebed7e42c1 100644 --- a/spec/support/shared_examples/requests/api/status_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specs for status checking. # # Requires an API request: diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb index 04140cad3f0..2a38d56141a 100644 --- a/spec/support/shared_examples/requests/graphql_shared_examples.rb +++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'a working graphql query' do diff --git a/spec/support/shared_examples/resource_label_events_api.rb b/spec/support/shared_examples/resource_label_events_api.rb new file mode 100644 index 00000000000..945cb8d9f2c --- /dev/null +++ b/spec/support/shared_examples/resource_label_events_api.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +shared_examples 'resource_label_events API' do |parent_type, eventable_type, id_name| + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events" do + it "returns an array of resource label events" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(event.id) + end + + it "returns a 404 error when eventable id not found" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/12345/resource_label_events", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + private_user = create(:user) + + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", private_user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events/:event_id" do + it "returns a resource label event by id" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/#{event.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(event.id) + end + + it "returns a 404 error if resource label event not found" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + end +end diff --git a/spec/support/shared_examples/serializers/note_entity_examples.rb b/spec/support/shared_examples/serializers/note_entity_examples.rb index ec208aba2a9..bfcaa2f1bd5 100644 --- a/spec/support/shared_examples/serializers/note_entity_examples.rb +++ b/spec/support/shared_examples/serializers/note_entity_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'note entity' do subject { entity.as_json } diff --git a/spec/support/shared_examples/services/boards/boards_create_service.rb b/spec/support/shared_examples/services/boards/boards_create_service.rb index 5bdc04f660f..19818a6091b 100644 --- a/spec/support/shared_examples/services/boards/boards_create_service.rb +++ b/spec/support/shared_examples/services/boards/boards_create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'boards create service' do context 'when parent does not have a board' do it 'creates a new board' do diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb index e0d5a7c61f2..566e5050f8e 100644 --- a/spec/support/shared_examples/services/boards/boards_list_service.rb +++ b/spec/support/shared_examples/services/boards/boards_list_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'boards list service' do context 'when parent does not have a board' do it 'creates a new parent board' do diff --git a/spec/support/shared_examples/services/boards/issues_list_service.rb b/spec/support/shared_examples/services/boards/issues_list_service.rb index 8b879cef084..75733c774ef 100644 --- a/spec/support/shared_examples/services/boards/issues_list_service.rb +++ b/spec/support/shared_examples/services/boards/issues_list_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issues list service' do it 'delegates search to IssuesFinder' do params = { board_id: board.id, id: list1.id } diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb index 9dbd1d8e867..d3fa8084185 100644 --- a/spec/support/shared_examples/services/boards/issues_move_service.rb +++ b/spec/support/shared_examples/services/boards/issues_move_service.rb @@ -1,8 +1,19 @@ +# frozen_string_literal: true + shared_examples 'issues move service' do |group| + shared_examples 'updating timestamps' do + it 'updates updated_at' do + expect {described_class.new(parent, user, params).execute(issue)} + .to change {issue.reload.updated_at} + end + end + context 'when moving an issue between lists' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } + it_behaves_like 'updating timestamps' + it 'delegates the label changes to Issues::UpdateService' do service = double(:service) expect(Issues::UpdateService).to receive(:new).and_return(service) @@ -24,6 +35,8 @@ shared_examples 'issues move service' do |group| let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } } + it_behaves_like 'updating timestamps' + it 'delegates the close proceedings to Issues::CloseService' do expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once @@ -46,6 +59,8 @@ shared_examples 'issues move service' do |group| let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression], milestone: milestone) } let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: backlog.id } } + it_behaves_like 'updating timestamps' + it 'keeps labels and milestone' do described_class.new(parent, user, params).execute(issue) issue.reload @@ -59,6 +74,8 @@ shared_examples 'issues move service' do |group| let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } } + it_behaves_like 'updating timestamps' + it 'delegates the re-open proceedings to Issues::ReopenService' do expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once @@ -75,10 +92,13 @@ shared_examples 'issues move service' do |group| end context 'when moving to same list' do - let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } + let(:assignee) { create(:user) } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } + let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:issue) do + create(:labeled_issue, project: project, labels: [bug, development], assignees: [assignee]) + end it 'returns false' do expect(described_class.new(parent, user, params).execute(issue)).to eq false @@ -90,18 +110,36 @@ shared_examples 'issues move service' do |group| expect(issue.reload.labels).to contain_exactly(bug, development) end - it 'sorts issues' do - [issue, issue1, issue2].each do |issue| - issue.move_to_end && issue.save! - end + it 'keeps issues assignees' do + described_class.new(parent, user, params).execute(issue) - params.merge!(move_after_id: issue1.id, move_before_id: issue2.id) + expect(issue.reload.assignees).to contain_exactly(assignee) + end + + it 'sorts issues' do + reorder_issues(params, issues: [issue, issue1, issue2]) described_class.new(parent, user, params).execute(issue) expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) end + it 'does not update updated_at' do + reorder_issues(params, issues: [issue, issue1, issue2]) + + updated_at = issue.updated_at + updated_at1 = issue1.updated_at + updated_at2 = issue2.updated_at + + Timecop.travel(1.minute.from_now) do + described_class.new(parent, user, params).execute(issue) + end + + expect(issue.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0) + expect(issue1.reload.updated_at.change(usec: 0)).to eq updated_at1.change(usec: 0) + expect(issue2.reload.updated_at.change(usec: 0)).to eq updated_at2.change(usec: 0) + end + if group context 'when on a group board' do it 'sends the board_group_id parameter' do @@ -114,5 +152,13 @@ shared_examples 'issues move service' do |group| end end end + + def reorder_issues(params, issues: []) + issues.each do |issue| + issue.move_to_end && issue.save! + end + + params.merge!(move_after_id: issues[1].id, move_before_id: issues[2].id) + end end end diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service.rb b/spec/support/shared_examples/services/boards/lists_destroy_service.rb index 62b6ffe1836..95725078f9d 100644 --- a/spec/support/shared_examples/services/boards/lists_destroy_service.rb +++ b/spec/support/shared_examples/services/boards/lists_destroy_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'lists destroy service' do context 'when list type is label' do it 'removes list from board' do diff --git a/spec/support/shared_examples/services/boards/lists_list_service.rb b/spec/support/shared_examples/services/boards/lists_list_service.rb index 0a8220111ab..29784f6da08 100644 --- a/spec/support/shared_examples/services/boards/lists_list_service.rb +++ b/spec/support/shared_examples/services/boards/lists_list_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'lists list service' do context 'when the board has a backlog list' do let!(:backlog_list) { create(:backlog_list, board: board) } diff --git a/spec/support/shared_examples/services/boards/lists_move_service.rb b/spec/support/shared_examples/services/boards/lists_move_service.rb index 2cdb968a45d..0b3bfd8e2a8 100644 --- a/spec/support/shared_examples/services/boards/lists_move_service.rb +++ b/spec/support/shared_examples/services/boards/lists_move_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'lists move service' do let!(:planning) { create(:list, board: board, position: 0) } let!(:development) { create(:list, board: board, position: 1) } diff --git a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb index 02de47a96dd..1e0ac8b7615 100644 --- a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb +++ b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'check ingress ip executions' do |app_name| describe '#execute' do let(:application) { create(app_name, :installed) } diff --git a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb index b8db35a6ef9..1c3fa5644d3 100644 --- a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb +++ b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'gitlab projects import validations' do context 'with an invalid path' do let(:path) { '/invalid-path/' } diff --git a/spec/support/shared_examples/services/notification_service_shared_examples.rb b/spec/support/shared_examples/services/notification_service_shared_examples.rb new file mode 100644 index 00000000000..dd338ea47c7 --- /dev/null +++ b/spec/support/shared_examples/services/notification_service_shared_examples.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# Note that we actually update the attribute on the target_project/group, rather than +# using `allow`. This is because there are some specs where, based on how the notification +# is done, using an `allow` doesn't change the correct object. +shared_examples 'project emails are disabled' do + let(:target_project) { notification_target.is_a?(Project) ? notification_target : notification_target.project } + + before do + reset_delivered_emails! + target_project.clear_memoization(:emails_disabled) + end + + it 'sends no emails with project emails disabled' do + target_project.update_attribute(:emails_disabled, true) + + notification_trigger + + should_not_email_anyone + end + + it 'sends emails to someone' do + target_project.update_attribute(:emails_disabled, false) + + notification_trigger + + should_email_anyone + end +end + +shared_examples 'group emails are disabled' do + let(:target_group) { notification_target.is_a?(Group) ? notification_target : notification_target.project.group } + + before do + reset_delivered_emails! + target_group.clear_memoization(:emails_disabled) + end + + it 'sends no emails with group emails disabled' do + target_group.update_attribute(:emails_disabled, true) + + notification_trigger + + should_not_email_anyone + end + + it 'sends emails to someone' do + target_group.update_attribute(:emails_disabled, false) + + notification_trigger + + should_email_anyone + end +end diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb index 36c486dbdd6..8ce94064dc3 100644 --- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f } RSpec.shared_examples 'slack or mattermost notifications' do @@ -452,7 +454,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do context 'only notify for the default branch' do context 'when enabled' do let(:pipeline) do - create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch') + create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch') end before do @@ -470,7 +472,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do context 'when disabled' do let(:pipeline) do - create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch') + create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch') end before do diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb index 833c31a57cb..b5321c6db34 100644 --- a/spec/support/shared_examples/snippet_visibility_shared_examples.rb +++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'snippet visibility' do using RSpec::Parameterized::TableSyntax diff --git a/spec/support/shared_examples/snippets_shared_examples.rb b/spec/support/shared_examples/snippets_shared_examples.rb index 85f0facd5c3..5c35617bd36 100644 --- a/spec/support/shared_examples/snippets_shared_examples.rb +++ b/spec/support/shared_examples/snippets_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These shared examples expect a `snippets` array of snippets RSpec.shared_examples 'paginated snippets' do |remote: false| it "is limited to #{Snippet.default_per_page} items per page" do diff --git a/spec/support/shared_examples/taskable_shared_examples.rb b/spec/support/shared_examples/taskable_shared_examples.rb index 4056ff06b84..f04f509f3d2 100644 --- a/spec/support/shared_examples/taskable_shared_examples.rb +++ b/spec/support/shared_examples/taskable_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specs for task state functionality for issues and merge requests. # # Requires a context containing: @@ -105,4 +107,25 @@ shared_examples 'a Taskable' do expect(subject.task_status_short).to match('1 task') end end + + describe 'with tasks in blockquotes' do + before do + subject.description = <<-EOT.strip_heredoc + > - [ ] Task a + > > - [x] Task a.1 + + >>> + 1. [ ] Task 1 + 1. [x] Task 2 + >>> + EOT + end + + it 'returns the correct task status' do + expect(subject.task_status).to match('2 of') + expect(subject.task_status).to match('4 tasks completed') + expect(subject.task_status_short).to match('2/') + expect(subject.task_status_short).to match('4 tasks') + end + end end diff --git a/spec/support/shared_examples/throttled_touch.rb b/spec/support/shared_examples/throttled_touch.rb index eba990d4037..aaaa590862d 100644 --- a/spec/support/shared_examples/throttled_touch.rb +++ b/spec/support/shared_examples/throttled_touch.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'throttled touch' do describe '#touch' do it 'updates the updated_at timestamp' do diff --git a/spec/support/shared_examples/unique_ip_check_shared_examples.rb b/spec/support/shared_examples/unique_ip_check_shared_examples.rb index e5c8ac6a004..65d86ddee9e 100644 --- a/spec/support/shared_examples/unique_ip_check_shared_examples.rb +++ b/spec/support/shared_examples/unique_ip_check_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'unique ips sign in limit' do include StubENV before do diff --git a/spec/support/shared_examples/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb index 64568de424e..b7ac08372f9 100644 --- a/spec/support/shared_examples/update_invalid_issuable.rb +++ b/spec/support/shared_examples/update_invalid_issuable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'update invalid issuable' do |klass| let(:params) do { @@ -38,7 +40,7 @@ shared_examples 'update invalid issuable' do |klass| put :update, params: params expect(response.status).to eq(409) - expect(JSON.parse(response.body)).to have_key('errors') + expect(json_response).to have_key('errors') end end diff --git a/spec/support/shared_examples/updating_mentions_shared_examples.rb b/spec/support/shared_examples/updating_mentions_shared_examples.rb index 5e3f19ba19e..ef385f94cc2 100644 --- a/spec/support/shared_examples/updating_mentions_shared_examples.rb +++ b/spec/support/shared_examples/updating_mentions_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'updating mentions' do |service_class| let(:mentioned_user) { create(:user) } let(:service_class) { service_class } diff --git a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb index 1190863d88e..9263aaff89a 100644 --- a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb +++ b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples "matches the method pattern" do |method| let(:target) { subject } let(:args) { nil } diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb index 1bd176280c5..5d605dd811b 100644 --- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb +++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'with storage' do |store, **stub_params| before do subject.object_store = store diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb index 25277ccd9aa..c5a775fefb6 100644 --- a/spec/support/shared_examples/url_validator_examples.rb +++ b/spec/support/shared_examples/url_validator_examples.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + RSpec.shared_examples 'url validator examples' do |schemes| - let(:validator) { described_class.new(attributes: [:link_url], **options) } - let!(:badge) { build(:badge, link_url: 'http://www.example.com') } + describe '#validate' do + let(:validator) { described_class.new(attributes: [:link_url], **options) } + let(:badge) { build(:badge, link_url: 'http://www.example.com') } - subject { validator.validate(badge) } + subject { validator.validate(badge) } - describe '#validate' do context 'with no options' do let(:options) { {} } @@ -40,3 +42,52 @@ RSpec.shared_examples 'url validator examples' do |schemes| end end end + +RSpec.shared_examples 'public url validator examples' do |setting| + let(:validator) { described_class.new(attributes: [:link_url]) } + let(:badge) { build(:badge, link_url: 'http://www.example.com') } + + subject { validator.validate(badge) } + + context 'by default' do + it 'blocks urls pointing to localhost' do + badge.link_url = 'https://127.0.0.1' + + subject + + expect(badge.errors).to be_present + end + + it 'blocks urls pointing to the local network' do + badge.link_url = 'https://192.168.1.1' + + subject + + expect(badge.errors).to be_present + end + end + + context 'when local requests are allowed' do + let!(:settings) { create(:application_setting) } + + before do + stub_application_setting(setting) + end + + it 'does not block urls pointing to localhost' do + badge.link_url = 'https://127.0.0.1' + + subject + + expect(badge.errors).not_to be_present + end + + it 'does not block urls pointing to the local network' do + badge.link_url = 'https://192.168.1.1' + + subject + + expect(badge.errors).not_to be_present + end + end +end diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb index 6c4e11910d3..585c458a64e 100644 --- a/spec/support/sidekiq.rb +++ b/spec/support/sidekiq.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sidekiq/testing/inline' # If Sidekiq::Testing.inline! is used, SQL transactions done inside @@ -30,6 +32,8 @@ RSpec.configure do |config| end config.after(:each, :sidekiq, :redis) do - Sidekiq.redis { |redis| redis.flushdb } + Sidekiq.redis do |connection| + connection.redis.flushdb + end end end diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb index 55212355daa..95f0f971787 100644 --- a/spec/support/stored_repositories.rb +++ b/spec/support/stored_repositories.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.configure do |config| config.before(:each, :broken_storage) do allow(Gitlab::GitalyClient).to receive(:call) do diff --git a/spec/support/test_reports/test_reports_helper.rb b/spec/support/test_reports/test_reports_helper.rb index 45c6e04dbf3..6ba50c83b25 100644 --- a/spec/support/test_reports/test_reports_helper.rb +++ b/spec/support/test_reports/test_reports_helper.rb @@ -1,36 +1,38 @@ +# frozen_string_literal: true + module TestReportsHelper - def create_test_case_rspec_success + def create_test_case_rspec_success(name = 'test_spec') Gitlab::Ci::Reports::TestCase.new( name: 'Test#sum when a is 1 and b is 3 returns summary', - classname: 'spec.test_spec', + classname: "spec.#{name}", file: './spec/test_spec.rb', execution_time: 1.11, status: Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS) end - def create_test_case_rspec_failed + def create_test_case_rspec_failed(name = 'test_spec') Gitlab::Ci::Reports::TestCase.new( - name: 'Test#sum when a is 2 and b is 2 returns summary', - classname: 'spec.test_spec', + name: 'Test#sum when a is 1 and b is 3 returns summary', + classname: "spec.#{name}", file: './spec/test_spec.rb', execution_time: 2.22, system_output: sample_rspec_failed_message, status: Gitlab::Ci::Reports::TestCase::STATUS_FAILED) end - def create_test_case_rspec_skipped + def create_test_case_rspec_skipped(name = 'test_spec') Gitlab::Ci::Reports::TestCase.new( name: 'Test#sum when a is 3 and b is 3 returns summary', - classname: 'spec.test_spec', + classname: "spec.#{name}", file: './spec/test_spec.rb', execution_time: 3.33, status: Gitlab::Ci::Reports::TestCase::STATUS_SKIPPED) end - def create_test_case_rspec_error + def create_test_case_rspec_error(name = 'test_spec') Gitlab::Ci::Reports::TestCase.new( name: 'Test#sum when a is 4 and b is 4 returns summary', - classname: 'spec.test_spec', + classname: "spec.#{name}", file: './spec/test_spec.rb', execution_time: 4.44, status: Gitlab::Ci::Reports::TestCase::STATUS_ERROR) @@ -48,34 +50,34 @@ module TestReportsHelper EOF end - def create_test_case_java_success + def create_test_case_java_success(name = 'addTest') Gitlab::Ci::Reports::TestCase.new( - name: 'addTest', + name: name, classname: 'CalculatorTest', execution_time: 5.55, status: Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS) end - def create_test_case_java_failed + def create_test_case_java_failed(name = 'addTest') Gitlab::Ci::Reports::TestCase.new( - name: 'subtractTest', + name: name, classname: 'CalculatorTest', execution_time: 6.66, system_output: sample_java_failed_message, status: Gitlab::Ci::Reports::TestCase::STATUS_FAILED) end - def create_test_case_java_skipped + def create_test_case_java_skipped(name = 'addTest') Gitlab::Ci::Reports::TestCase.new( - name: 'multiplyTest', + name: name, classname: 'CalculatorTest', execution_time: 7.77, status: Gitlab::Ci::Reports::TestCase::STATUS_SKIPPED) end - def create_test_case_java_error + def create_test_case_java_error(name = 'addTest') Gitlab::Ci::Reports::TestCase.new( - name: 'divideTest', + name: name, classname: 'CalculatorTest', execution_time: 8.88, status: Gitlab::Ci::Reports::TestCase::STATUS_ERROR) diff --git a/spec/support/trace/trace_helpers.rb b/spec/support/trace/trace_helpers.rb index c7802bbcb94..9255715ff71 100644 --- a/spec/support/trace/trace_helpers.rb +++ b/spec/support/trace/trace_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module TraceHelpers def create_legacy_trace(build, content) File.open(legacy_trace_path(build), 'wb') { |stream| stream.write(content) } diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb index 9ac7e7fc515..32b88edc2df 100644 --- a/spec/support/webmock.rb +++ b/spec/support/webmock.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'webmock' require 'webmock/rspec' |
