summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb6
-rw-r--r--spec/support/helpers/admin_mode_helpers.rb2
-rw-r--r--spec/support/helpers/api_helpers.rb1
-rw-r--r--spec/support/helpers/dependency_proxy_helpers.rb33
-rw-r--r--spec/support/helpers/features/members_table_helpers.rb37
-rw-r--r--spec/support/helpers/features/releases_helpers.rb4
-rw-r--r--spec/support/helpers/features/web_ide_spec_helpers.rb41
-rw-r--r--spec/support/helpers/graphql_helpers.rb25
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb21
-rw-r--r--spec/support/helpers/lfs_http_helpers.rb12
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb8
-rw-r--r--spec/support/helpers/require_migration.rb24
-rw-r--r--spec/support/helpers/search_helpers.rb3
-rw-r--r--spec/support/helpers/snowplow_helpers.rb14
-rw-r--r--spec/support/helpers/table_schema_helpers.rb112
-rw-r--r--spec/support/helpers/test_env.rb16
-rw-r--r--spec/support/helpers/usage_data_helpers.rb1
-rw-r--r--spec/support/helpers/user_login_helper.rb23
-rw-r--r--spec/support/helpers/wiki_helpers.rb1
-rw-r--r--spec/support/import_export/common_util.rb2
-rw-r--r--spec/support/matchers/graphql_matchers.rb77
-rw-r--r--spec/support/patches/rspec_mocks_prepended_methods.rb55
-rw-r--r--spec/support/rspec.rb7
-rw-r--r--spec/support/services/issuable_import_csv_service_shared_examples.rb142
-rw-r--r--spec/support/shared_contexts/design_management_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb20
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb3
-rw-r--r--spec/support/shared_contexts/lib/gitlab/middleware/with_a_mocked_gitlab_instance_shared_context.rb32
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb2
-rw-r--r--spec/support/shared_contexts/policies/project_policy_table_shared_context.rb903
-rw-r--r--spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb1
-rw-r--r--spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb22
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/cached_response_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/controllers/trackable_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb12
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb6
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb430
-rw-r--r--spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/finders/security/jobs_finder_shared_examples.rb87
-rw-r--r--spec/support/shared_examples/graphql/label_fields.rb4
-rw-r--r--spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb26
-rw-r--r--spec/support/shared_examples/graphql/mutations/issues/permission_check_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/graphql/mutations/merge_requests/permission_check_shared_examples.rb73
-rw-r--r--spec/support/shared_examples/helm_commands_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/postgres_model_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb189
-rw-r--r--spec/support/shared_examples/lib/gitlab/repository_size_checker_shared_examples.rb59
-rw-r--r--spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb57
-rw-r--r--spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb139
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/issue_activity_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/mailers/notify_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/models/application_setting_shared_examples.rb56
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/models/cluster_application_core_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/read_only_message_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/discussions_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/requests/api/graphql/read_only_instance_shared_examples.rb47
-rw-r--r--spec/support/shared_examples/requests/api/labels_api_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb94
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb270
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/tracking_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/lfs_http_shared_examples.rb224
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/serializers/note_entity_shared_examples.rb50
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/services/common_system_notes_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/pages_size_limit_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/uploaders/workers/object_storage/migrate_uploads_shared_examples.rb120
-rw-r--r--spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb38
-rw-r--r--spec/support/snowplow.rb8
87 files changed, 3011 insertions, 1085 deletions
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index c577e5cc665..f866220b919 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -6,7 +6,7 @@
# multiple nested contexts. This shouldn't count as a violation.
module CycleAnalyticsHelpers
module TestGeneration
- # Generate the most common set of specs that all cycle analytics phases need to have.
+ # Generate the most common set of specs that all value stream analytics phases need to have.
#
# Arguments:
#
@@ -14,10 +14,10 @@ module CycleAnalyticsHelpers
# data_fn: A function that returns a hash, constituting initial data for the test case
# start_time_conditions: An array of `conditions`. Each condition is an tuple of `condition_name` and `condition_fn`. `condition_fn` is called with
# `context` (no lexical scope, so need to do `context.create` for factories, for example) and `data` (from the `data_fn`).
- # Each `condition_fn` is expected to implement a case which consitutes the start of the given cycle analytics phase.
+ # Each `condition_fn` is expected to implement a case which consitutes the start of the given value stream analytics phase.
# end_time_conditions: An array of `conditions`. Each condition is an tuple of `condition_name` and `condition_fn`. `condition_fn` is called with
# `context` (no lexical scope, so need to do `context.create` for factories, for example) and `data` (from the `data_fn`).
- # Each `condition_fn` is expected to implement a case which consitutes the end of the given cycle analytics phase.
+ # Each `condition_fn` is expected to implement a case which consitutes the end of the given value stream analytics phase.
# before_end_fn: This function is run before calling the end time conditions. Used for setup that needs to be run between the start and end conditions.
# post_fn: Code that needs to be run after running the end time conditions.
diff --git a/spec/support/helpers/admin_mode_helpers.rb b/spec/support/helpers/admin_mode_helpers.rb
index 36ed262f8ae..a6e31791127 100644
--- a/spec/support/helpers/admin_mode_helpers.rb
+++ b/spec/support/helpers/admin_mode_helpers.rb
@@ -13,6 +13,8 @@ module AdminModeHelper
def enable_admin_mode!(user)
fake_user_mode = instance_double(Gitlab::Auth::CurrentUserMode)
+ allow(Gitlab::Auth::CurrentUserMode).to receive(:new).and_call_original
+
allow(Gitlab::Auth::CurrentUserMode).to receive(:new).with(user).and_return(fake_user_mode)
allow(fake_user_mode).to receive(:admin_mode?).and_return(user&.admin?)
end
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index b1e6078c4f2..d3cc7367b6e 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -61,7 +61,6 @@ module ApiHelpers
def expect_response_contain_exactly(*items)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
- expect(json_response.length).to eq(items.size)
expect(json_response.map { |item| item['id'] }).to contain_exactly(*items)
end
diff --git a/spec/support/helpers/dependency_proxy_helpers.rb b/spec/support/helpers/dependency_proxy_helpers.rb
new file mode 100644
index 00000000000..545b9d1f4d0
--- /dev/null
+++ b/spec/support/helpers/dependency_proxy_helpers.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module DependencyProxyHelpers
+ include StubRequests
+
+ def stub_registry_auth(image, token, status = 200, body = nil)
+ auth_body = { 'token' => token }.to_json
+ auth_link = registry.auth_url(image)
+
+ stub_full_request(auth_link)
+ .to_return(status: status, body: body || auth_body)
+ end
+
+ def stub_manifest_download(image, tag, status = 200, body = nil)
+ manifest_url = registry.manifest_url(image, tag)
+
+ stub_full_request(manifest_url)
+ .to_return(status: status, body: body || manifest)
+ end
+
+ def stub_blob_download(image, blob_sha, status = 200, body = '123456')
+ download_link = registry.blob_url(image, blob_sha)
+
+ stub_full_request(download_link)
+ .to_return(status: status, body: body)
+ end
+
+ private
+
+ def registry
+ @registry ||= DependencyProxy::Registry
+ end
+end
diff --git a/spec/support/helpers/features/members_table_helpers.rb b/spec/support/helpers/features/members_table_helpers.rb
new file mode 100644
index 00000000000..5394e370900
--- /dev/null
+++ b/spec/support/helpers/features/members_table_helpers.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module MembersHelpers
+ def members_table
+ page.find('[data-testid="members-table"]')
+ end
+
+ def all_rows
+ page.within(members_table) do
+ page.all('tbody > tr')
+ end
+ end
+
+ def first_row
+ all_rows[0]
+ end
+
+ def second_row
+ all_rows[1]
+ end
+
+ def third_row
+ all_rows[2]
+ end
+
+ def invite_users_form
+ page.find('[data-testid="invite-users-form"]')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/features/releases_helpers.rb b/spec/support/helpers/features/releases_helpers.rb
index 0d46918b05c..44087f71cfa 100644
--- a/spec/support/helpers/features/releases_helpers.rb
+++ b/spec/support/helpers/features/releases_helpers.rb
@@ -66,7 +66,7 @@ module Spec
focused_element.send_keys(:enter)
# Wait for the dropdown to be rendered
- page.find('.project-milestone-combobox .dropdown-menu')
+ page.find('.milestone-combobox .dropdown-menu')
# Clear any existing input
focused_element.attribute('value').length.times { focused_element.send_keys(:backspace) }
@@ -75,7 +75,7 @@ module Spec
focused_element.send_keys(milestone_title, :enter)
# Wait for the search to return
- page.find('.project-milestone-combobox .dropdown-item', text: milestone_title, match: :first)
+ page.find('.milestone-combobox .dropdown-item', text: milestone_title, match: :first)
focused_element.send_keys(:arrow_down, :arrow_down, :enter)
diff --git a/spec/support/helpers/features/web_ide_spec_helpers.rb b/spec/support/helpers/features/web_ide_spec_helpers.rb
index 123bd9b5070..12d3cecd052 100644
--- a/spec/support/helpers/features/web_ide_spec_helpers.rb
+++ b/spec/support/helpers/features/web_ide_spec_helpers.rb
@@ -22,6 +22,8 @@ module WebIdeSpecHelpers
click_link('Web IDE')
wait_for_requests
+
+ save_monaco_editor_reference
end
def ide_tree_body
@@ -36,8 +38,8 @@ module WebIdeSpecHelpers
".js-ide-#{mode}-mode"
end
- def ide_file_row_open?(row)
- row.matches_css?('.is-open')
+ def ide_folder_row_open?(row)
+ row.matches_css?('.folder.is-open')
end
# Creates a file in the IDE by expanding directories
@@ -63,6 +65,17 @@ module WebIdeSpecHelpers
ide_set_editor_value(content)
end
+ def ide_rename_file(path, new_path)
+ container = ide_traverse_to_file(path)
+
+ click_file_action(container, 'Rename/Move')
+
+ within '#ide-new-entry' do
+ find('input').fill_in(with: new_path)
+ click_button('Rename file')
+ end
+ end
+
# Deletes a file by traversing to `path`
# then clicking the 'Delete' action.
#
@@ -90,8 +103,22 @@ module WebIdeSpecHelpers
container
end
+ def ide_close_file(name)
+ within page.find('.multi-file-tabs') do
+ click_button("Close #{name}")
+ end
+ end
+
+ def ide_open_file(path)
+ row = ide_traverse_to_file(path)
+
+ ide_open_file_row(row)
+
+ wait_for_requests
+ end
+
def ide_open_file_row(row)
- return if ide_file_row_open?(row)
+ return if ide_folder_row_open?(row)
row.click
end
@@ -103,6 +130,10 @@ module WebIdeSpecHelpers
execute_script("monaco.editor.getModel('#{uri}').setValue('#{escape_javascript(value)}')")
end
+ def ide_set_editor_position(line, col)
+ execute_script("TEST_EDITOR.setPosition(#{{ lineNumber: line, column: col }.to_json})")
+ end
+
def ide_editor_value
editor = find('.monaco-editor')
uri = editor['data-uri']
@@ -149,4 +180,8 @@ module WebIdeSpecHelpers
wait_for_requests
end
end
+
+ def save_monaco_editor_reference
+ evaluate_script("monaco.editor.onDidCreateEditor(editor => { window.TEST_EDITOR = editor; })")
+ end
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index db769041f1e..a1b4e6eee92 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -17,8 +17,8 @@ module GraphqlHelpers
# ready, then the early return is returned instead.
#
# Then the resolve method is called.
- def resolve(resolver_class, obj: nil, args: {}, ctx: {}, field: nil)
- resolver = resolver_class.new(object: obj, context: ctx, field: field)
+ def resolve(resolver_class, args: {}, **resolver_args)
+ resolver = resolver_instance(resolver_class, **resolver_args)
ready, early_return = sync_all { resolver.ready?(**args) }
return early_return unless ready
@@ -26,6 +26,15 @@ module GraphqlHelpers
resolver.resolve(**args)
end
+ def resolver_instance(resolver_class, obj: nil, ctx: {}, field: nil, schema: GitlabSchema)
+ if ctx.is_a?(Hash)
+ q = double('Query', schema: schema)
+ ctx = GraphQL::Query::Context.new(query: q, object: obj, values: ctx)
+ end
+
+ resolver_class.new(object: obj, context: ctx, field: field)
+ end
+
# Eagerly run a loader's named resolver
# (syncs any lazy values returned by resolve)
def eager_resolve(resolver_class, **opts)
@@ -112,6 +121,16 @@ module GraphqlHelpers
end
end
+ def resolve_field(name, object, args = {})
+ context = double("Context",
+ schema: GitlabSchema,
+ query: GraphQL::Query.new(GitlabSchema),
+ parent: nil)
+ field = described_class.fields[name]
+ instance = described_class.authorized_new(object, context)
+ field.resolve_field(instance, {}, context)
+ end
+
# Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys
#
# prepare_input_for_mutation({ 'my_key' => 1 })
@@ -468,6 +487,8 @@ module GraphqlHelpers
use Gitlab::Graphql::Authorize
use Gitlab::Graphql::Pagination::Connections
+ lazy_resolve ::Gitlab::Graphql::Lazy, :force
+
query(query_type)
end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 113bb31e4be..ff61cceba06 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -33,8 +33,8 @@ module KubernetesHelpers
kube_response(kube_deployments_body)
end
- def kube_ingresses_response
- kube_response(kube_ingresses_body)
+ def kube_ingresses_response(with_canary: false)
+ kube_response(kube_ingresses_body(with_canary: with_canary))
end
def stub_kubeclient_discover_base(api_url)
@@ -155,12 +155,12 @@ module KubernetesHelpers
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
- def stub_kubeclient_ingresses(namespace, status: nil)
+ def stub_kubeclient_ingresses(namespace, status: nil, method: :get, resource_path: "", response: kube_ingresses_response)
stub_kubeclient_discover(service.api_url)
- ingresses_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/ingresses"
+ ingresses_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/ingresses#{resource_path}"
response = { status: status } if status
- WebMock.stub_request(:get, ingresses_url).to_return(response || kube_ingresses_response)
+ WebMock.stub_request(method, ingresses_url).to_return(response)
end
def stub_kubeclient_knative_services(options = {})
@@ -250,6 +250,11 @@ module KubernetesHelpers
.to_return(kube_response({}))
end
+ def stub_kubeclient_delete_role_binding(api_url, name, namespace: 'default')
+ WebMock.stub_request(:delete, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
+ .to_return(kube_response({}))
+ end
+
def stub_kubeclient_put_role_binding(api_url, name, namespace: 'default')
WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
.to_return(kube_response({}))
@@ -541,10 +546,12 @@ module KubernetesHelpers
}
end
- def kube_ingresses_body
+ def kube_ingresses_body(with_canary: false)
+ items = with_canary ? [kube_ingress, kube_ingress(track: :canary)] : [kube_ingress]
+
{
"kind" => "List",
- "items" => [kube_ingress]
+ "items" => items
}
end
diff --git a/spec/support/helpers/lfs_http_helpers.rb b/spec/support/helpers/lfs_http_helpers.rb
index 0537b122040..199d5e70e32 100644
--- a/spec/support/helpers/lfs_http_helpers.rb
+++ b/spec/support/helpers/lfs_http_helpers.rb
@@ -31,16 +31,16 @@ module LfsHttpHelpers
post(url, params: params, headers: headers)
end
- def batch_url(project)
- "#{project.http_url_to_repo}/info/lfs/objects/batch"
+ def batch_url(container)
+ "#{container.http_url_to_repo}/info/lfs/objects/batch"
end
- def objects_url(project, oid = nil, size = nil)
- File.join(["#{project.http_url_to_repo}/gitlab-lfs/objects", oid, size].compact.map(&:to_s))
+ def objects_url(container, oid = nil, size = nil)
+ File.join(["#{container.http_url_to_repo}/gitlab-lfs/objects", oid, size].compact.map(&:to_s))
end
- def authorize_url(project, oid, size)
- File.join(objects_url(project, oid, size), 'authorize')
+ def authorize_url(container, oid, size)
+ File.join(objects_url(container, oid, size), 'authorize')
end
def download_body(objects)
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index 11e67ba394c..e18a708e41c 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -36,4 +36,12 @@ module NavbarStructureHelper
new_sub_nav_item_name: _('Container Registry')
)
end
+
+ def insert_dependency_proxy_nav(within)
+ insert_after_sub_nav_item(
+ _('Package Registry'),
+ within: _('Packages & Registries'),
+ new_sub_nav_item_name: _('Dependency Proxy')
+ )
+ end
end
diff --git a/spec/support/helpers/require_migration.rb b/spec/support/helpers/require_migration.rb
index d3f192a4142..c2902aa4ec7 100644
--- a/spec/support/helpers/require_migration.rb
+++ b/spec/support/helpers/require_migration.rb
@@ -3,26 +3,46 @@
require 'find'
class RequireMigration
- MIGRATION_FOLDERS = %w(db/migrate db/post_migrate ee/db/geo/migrate ee/db/geo/post_migrate).freeze
+ class AutoLoadError < RuntimeError
+ MESSAGE = "Can not find any migration file for `%{file_name}`!\n" \
+ "You can try to provide the migration file name manually."
+
+ def initialize(file_name)
+ message = format(MESSAGE, file_name: file_name)
+
+ super(message)
+ end
+ end
+
+ MIGRATION_FOLDERS = %w[db/migrate db/post_migrate].freeze
SPEC_FILE_PATTERN = /.+\/(?<file_name>.+)_spec\.rb/.freeze
class << self
def require_migration!(file_name)
file_paths = search_migration_file(file_name)
+ raise AutoLoadError.new(file_name) unless file_paths.first
require file_paths.first
end
def search_migration_file(file_name)
- MIGRATION_FOLDERS.flat_map do |path|
+ migration_folders.flat_map do |path|
migration_path = Rails.root.join(path).to_s
Find.find(migration_path).grep(/\d+_#{file_name}\.rb/)
end
end
+
+ private
+
+ def migration_folders
+ MIGRATION_FOLDERS
+ end
end
end
+RequireMigration.prepend_if_ee('EE::RequireMigration')
+
def require_migration!(file_name = nil)
location_info = caller_locations.first.path.match(RequireMigration::SPEC_FILE_PATTERN)
file_name ||= location_info[:file_name]
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index 328f272724a..3d4ff4801a7 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -3,13 +3,14 @@
module SearchHelpers
def fill_in_search(text)
page.within('.search-input-wrap') do
+ find('#search').click
fill_in('search', with: text)
end
wait_for_all_requests
end
- def submit_search(query, scope: nil)
+ def submit_search(query)
page.within('.search-form, .search-page-form') do
field = find_field('search')
field.fill_in(with: query)
diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb
index 3bde01c6fbf..15eac1b24fc 100644
--- a/spec/support/helpers/snowplow_helpers.rb
+++ b/spec/support/helpers/snowplow_helpers.rb
@@ -32,16 +32,8 @@ module SnowplowHelpers
# end
# end
def expect_snowplow_event(category:, action:, **kwargs)
- # This check will no longer be needed with Ruby 2.7 which
- # would not pass any arguments when using **kwargs.
- # https://gitlab.com/gitlab-org/gitlab/-/issues/263430
- if kwargs.present?
- expect(Gitlab::Tracking).to have_received(:event)
- .with(category, action, **kwargs).at_least(:once)
- else
- expect(Gitlab::Tracking).to have_received(:event)
- .with(category, action).at_least(:once)
- end
+ expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
+ .with(category, action, **kwargs).at_least(:once)
end
# Asserts that no call to `Gitlab::Tracking#event` was made.
@@ -56,6 +48,6 @@ module SnowplowHelpers
# end
# end
def expect_no_snowplow_event
- expect(Gitlab::Tracking).not_to have_received(:event)
+ expect(Gitlab::Tracking).not_to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
end
end
diff --git a/spec/support/helpers/table_schema_helpers.rb b/spec/support/helpers/table_schema_helpers.rb
new file mode 100644
index 00000000000..28794211190
--- /dev/null
+++ b/spec/support/helpers/table_schema_helpers.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+module TableSchemaHelpers
+ def connection
+ ActiveRecord::Base.connection
+ end
+
+ def expect_table_to_be_replaced(original_table:, replacement_table:, archived_table:)
+ original_oid = table_oid(original_table)
+ replacement_oid = table_oid(replacement_table)
+
+ yield
+
+ expect(table_oid(original_table)).to eq(replacement_oid)
+ expect(table_oid(archived_table)).to eq(original_oid)
+ expect(table_oid(replacement_table)).to be_nil
+ end
+
+ def expect_index_to_exist(name, schema: nil)
+ expect(index_exists_by_name(name, schema: schema)).to eq(true)
+ end
+
+ def expect_index_not_to_exist(name, schema: nil)
+ expect(index_exists_by_name(name, schema: schema)).to be_nil
+ end
+
+ def expect_primary_keys_after_tables(tables, schema: nil)
+ tables.each do |table|
+ primary_key = primary_key_constraint_name(table, schema: schema)
+
+ expect(primary_key).to eq("#{table}_pkey")
+ end
+ end
+
+ def table_oid(name)
+ connection.select_value(<<~SQL)
+ SELECT oid
+ FROM pg_catalog.pg_class
+ WHERE relname = '#{name}'
+ SQL
+ end
+
+ def table_type(name)
+ connection.select_value(<<~SQL)
+ SELECT
+ CASE class.relkind
+ WHEN 'r' THEN 'normal'
+ WHEN 'p' THEN 'partitioned'
+ ELSE 'other'
+ END as table_type
+ FROM pg_catalog.pg_class class
+ WHERE class.relname = '#{name}'
+ SQL
+ end
+
+ def sequence_owned_by(table_name, column_name)
+ connection.select_value(<<~SQL)
+ SELECT
+ sequence.relname as name
+ FROM pg_catalog.pg_class as sequence
+ INNER JOIN pg_catalog.pg_depend depend
+ ON depend.objid = sequence.oid
+ INNER JOIN pg_catalog.pg_class class
+ ON class.oid = depend.refobjid
+ INNER JOIN pg_catalog.pg_attribute attribute
+ ON attribute.attnum = depend.refobjsubid
+ AND attribute.attrelid = depend.refobjid
+ WHERE class.relname = '#{table_name}'
+ AND attribute.attname = '#{column_name}'
+ SQL
+ end
+
+ def default_expression_for(table_name, column_name)
+ connection.select_value(<<~SQL)
+ SELECT
+ pg_get_expr(attrdef.adbin, attrdef.adrelid) AS default_value
+ FROM pg_catalog.pg_attribute attribute
+ INNER JOIN pg_catalog.pg_attrdef attrdef
+ ON attribute.attrelid = attrdef.adrelid
+ AND attribute.attnum = attrdef.adnum
+ WHERE attribute.attrelid = '#{table_name}'::regclass
+ AND attribute.attname = '#{column_name}'
+ SQL
+ end
+
+ def primary_key_constraint_name(table_name, schema: nil)
+ table_name = schema ? "#{schema}.#{table_name}" : table_name
+
+ connection.select_value(<<~SQL)
+ SELECT
+ conname AS constraint_name
+ FROM pg_catalog.pg_constraint
+ WHERE pg_constraint.conrelid = '#{table_name}'::regclass
+ AND pg_constraint.contype = 'p'
+ SQL
+ end
+
+ def index_exists_by_name(index, schema: nil)
+ schema = schema ? "'#{schema}'" : 'current_schema'
+
+ connection.select_value(<<~SQL)
+ SELECT true
+ FROM pg_catalog.pg_index i
+ INNER JOIN pg_catalog.pg_class c
+ ON c.oid = i.indexrelid
+ INNER JOIN pg_catalog.pg_namespace n
+ ON c.relnamespace = n.oid
+ WHERE c.relname = '#{index}'
+ AND n.nspname = #{schema}
+ SQL
+ end
+end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 641ed24207e..4c78ca0117c 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -517,6 +517,8 @@ module TestEnv
return false if component_matches_git_sha?(component_folder, expected_version)
+ return false if component_ahead_of_target?(component_folder, expected_version)
+
version = File.read(File.join(component_folder, 'VERSION')).strip
# Notice that this will always yield true when using branch versions
@@ -527,6 +529,20 @@ module TestEnv
true
end
+ def component_ahead_of_target?(component_folder, expected_version)
+ # The HEAD of the component_folder will be used as heuristic for the version
+ # of the binaries, allowing to use Git to determine if HEAD is later than
+ # the expected version. Note: Git considers HEAD to be an anchestor of HEAD.
+ _out, exit_status = Gitlab::Popen.popen(%W[
+ #{Gitlab.config.git.bin_path}
+ -C #{component_folder}
+ merge-base --is-ancestor
+ #{expected_version} HEAD
+])
+
+ exit_status == 0
+ end
+
def component_matches_git_sha?(component_folder, expected_version)
# Not a git SHA, so return early
return false unless expected_version =~ ::Gitlab::Git::COMMIT_ID
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index 2592d9f8b42..8e8aeea2ea1 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -98,6 +98,7 @@ module UsageDataHelpers
projects_with_repositories_enabled
projects_with_error_tracking_enabled
projects_with_alerts_service_enabled
+ projects_with_enabled_alert_integrations
projects_with_prometheus_alerts
projects_with_tracing_enabled
projects_with_expiration_policy_enabled
diff --git a/spec/support/helpers/user_login_helper.rb b/spec/support/helpers/user_login_helper.rb
index 66606832883..47e858cb68c 100644
--- a/spec/support/helpers/user_login_helper.rb
+++ b/spec/support/helpers/user_login_helper.rb
@@ -1,18 +1,25 @@
# frozen_string_literal: true
module UserLoginHelper
- def ensure_tab_pane_correctness(visit_path = true)
- if visit_path
- visit new_user_session_path
- end
-
- ensure_tab_pane_counts
+ def ensure_tab_pane_correctness(tab_names)
+ ensure_tab_pane_counts(tab_names.size)
+ ensure_tab_labels(tab_names)
ensure_one_active_tab
ensure_one_active_pane
end
- def ensure_tab_pane_counts
- tabs_count = page.all('[role="tab"]').size
+ def ensure_no_tabs
+ expect(page.all('[role="tab"]').size).to eq(0)
+ end
+
+ def ensure_tab_labels(tab_names)
+ tab_labels = page.all('[role="tab"]').map(&:text)
+
+ expect(tab_names).to match_array(tab_labels)
+ end
+
+ def ensure_tab_pane_counts(tabs_count)
+ expect(page.all('[role="tab"]').size).to eq(tabs_count)
expect(page).to have_selector('[role="tabpanel"]', visible: :all, count: tabs_count)
end
diff --git a/spec/support/helpers/wiki_helpers.rb b/spec/support/helpers/wiki_helpers.rb
index 8873a90579d..e276c896da2 100644
--- a/spec/support/helpers/wiki_helpers.rb
+++ b/spec/support/helpers/wiki_helpers.rb
@@ -4,7 +4,6 @@ module WikiHelpers
extend self
def stub_group_wikis(enabled)
- stub_feature_flags(group_wikis: enabled)
stub_licensed_features(group_wikis: enabled)
end
diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb
index c0c3559cca0..ae951ea35af 100644
--- a/spec/support/import_export/common_util.rb
+++ b/spec/support/import_export/common_util.rb
@@ -15,7 +15,7 @@ module ImportExport
export_path = [prefix, 'spec', 'fixtures', 'lib', 'gitlab', 'import_export', name].compact
export_path = File.join(*export_path)
- allow_any_instance_of(Gitlab::ImportExport).to receive(:export_path) { export_path }
+ allow(Gitlab::ImportExport).to receive(:export_path) { export_path }
end
def setup_reader(reader)
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index 7fa06e25405..8c4ba387a74 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -109,15 +109,82 @@ RSpec::Matchers.define :have_graphql_arguments do |*expected|
end
end
-RSpec::Matchers.define :have_graphql_type do |expected|
- match do |field|
- expect(field.type).to eq(expected)
+module GraphQLTypeHelpers
+ def message(object, expected, **opts)
+ non_null = expected.non_null? || (opts.key?(:null) && !opts[:null])
+
+ actual = object.type
+ actual_type = actual.unwrap.graphql_name
+ actual_type += '!' if actual.non_null?
+
+ expected_type = expected.unwrap.graphql_name
+ expected_type += '!' if non_null
+
+ "expected #{describe_object(object)} to have GraphQL type #{expected_type}, but got #{actual_type}"
+ end
+
+ def describe_object(object)
+ case object
+ when Types::BaseField
+ "#{describe_object(object.owner_type)}.#{object.graphql_name}"
+ when Types::BaseArgument
+ "#{describe_object(object.owner)}.#{object.graphql_name}"
+ when Class
+ object.try(:graphql_name) || object.name
+ else
+ object.to_s
+ end
+ end
+
+ def nullified(type, can_be_nil)
+ return type if can_be_nil.nil? # unknown!
+ return type if can_be_nil
+
+ type.to_non_null_type
+ end
+end
+
+RSpec::Matchers.define :have_graphql_type do |expected, opts = {}|
+ include GraphQLTypeHelpers
+
+ match do |object|
+ expect(object.type).to eq(nullified(expected, opts[:null]))
+ end
+
+ failure_message do |object|
+ message(object, expected, **opts)
+ end
+end
+
+RSpec::Matchers.define :have_nullable_graphql_type do |expected|
+ include GraphQLTypeHelpers
+
+ match do |object|
+ expect(object).to have_graphql_type(expected.unwrap, { null: true })
+ end
+
+ description do
+ "have nullable GraphQL type #{expected.graphql_name}"
+ end
+
+ failure_message do |object|
+ message(object, expected, null: true)
end
end
RSpec::Matchers.define :have_non_null_graphql_type do |expected|
- match do |field|
- expect(field.type.to_graphql).to eq(!expected.to_graphql)
+ include GraphQLTypeHelpers
+
+ match do |object|
+ expect(object).to have_graphql_type(expected, { null: false })
+ end
+
+ description do
+ "have non-null GraphQL type #{expected.graphql_name}"
+ end
+
+ failure_message do |object|
+ message(object, expected, null: false)
end
end
diff --git a/spec/support/patches/rspec_mocks_prepended_methods.rb b/spec/support/patches/rspec_mocks_prepended_methods.rb
new file mode 100644
index 00000000000..fa3a74c670c
--- /dev/null
+++ b/spec/support/patches/rspec_mocks_prepended_methods.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+# This patch allows stubbing of prepended methods
+# Based on https://github.com/rspec/rspec-mocks/pull/1218
+
+module RSpec
+ module Mocks
+ module InstanceMethodStasherForPrependedMethods
+ private
+
+ def method_owned_by_klass?
+ owner = @klass.instance_method(@method).owner
+ owner = owner.class unless Module === owner
+
+ owner == @klass ||
+ # When `extend self` is used, and not under any instance of
+ (owner.singleton_class == @klass && !Mocks.space.any_instance_recorder_for(owner, true)) ||
+ !method_defined_on_klass?(owner)
+ end
+ end
+ end
+end
+
+module RSpec
+ module Mocks
+ module MethodDoubleForPrependedMethods
+ def restore_original_method
+ return show_frozen_warning if object_singleton_class.frozen?
+ return unless @method_is_proxied
+
+ remove_method_from_definition_target
+
+ if @method_stasher.method_is_stashed?
+ @method_stasher.restore
+ restore_original_visibility
+ end
+
+ @method_is_proxied = false
+ end
+
+ def restore_original_visibility
+ method_owner.__send__(@original_visibility, @method_name)
+ end
+
+ private
+
+ def method_owner
+ @method_owner ||= Object.instance_method(:method).bind(object).call(@method_name).owner
+ end
+ end
+ end
+end
+
+RSpec::Mocks::InstanceMethodStasher.prepend(RSpec::Mocks::InstanceMethodStasherForPrependedMethods)
+RSpec::Mocks::MethodDouble.prepend(RSpec::Mocks::MethodDoubleForPrependedMethods)
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index 861b57c9efa..32f738faa9b 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -5,6 +5,13 @@ require_relative "helpers/stub_metrics"
require_relative "helpers/stub_object_storage"
require_relative "helpers/stub_env"
require_relative "helpers/fast_rails_root"
+
+# so we need to load rubocop here due to the rubocop support file loading cop_helper
+# which monkey patches class Cop
+# if cop helper is loaded before rubocop (where class Cop is defined as class Cop < Base)
+# we get a `superclass mismatch for class Cop` error when running a rspec for a locally defined
+# rubocop cop - therefore we need rubocop required first since it had an inheritance added to the Cop class
+require 'rubocop'
require 'rubocop/rspec/support'
RSpec.configure do |config|
diff --git a/spec/support/services/issuable_import_csv_service_shared_examples.rb b/spec/support/services/issuable_import_csv_service_shared_examples.rb
new file mode 100644
index 00000000000..20ac2ff5c7c
--- /dev/null
+++ b/spec/support/services/issuable_import_csv_service_shared_examples.rb
@@ -0,0 +1,142 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'issuable import csv service' do |issuable_type|
+ let_it_be_with_refind(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ subject { service.execute }
+
+ shared_examples_for 'an issuable importer' do
+ if issuable_type == 'issue'
+ it 'records the import attempt if resource is an issue' do
+ expect { subject }
+ .to change { Issues::CsvImport.where(project: project, user: user).count }
+ .by 1
+ end
+ end
+ end
+
+ shared_examples_for 'importer with email notification' do
+ it 'notifies user of import result' do
+ expect(Notify).to receive_message_chain(email_method, :deliver_later)
+
+ subject
+ end
+ end
+
+ describe '#execute' do
+ context 'invalid file' do
+ let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }
+
+ it 'returns invalid file error' do
+ expect(subject[:success]).to eq(0)
+ expect(subject[:parse_error]).to eq(true)
+ end
+
+ it_behaves_like 'importer with email notification'
+ it_behaves_like 'an issuable importer'
+ end
+
+ context 'file without headers' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_no_headers.csv') }
+
+ it 'returns invalid file error' do
+ expect(subject[:success]).to eq(0)
+ expect(subject[:parse_error]).to eq(true)
+ end
+
+ it_behaves_like 'importer with email notification'
+ it_behaves_like 'an issuable importer'
+ end
+
+ context 'with a file generated by Gitlab CSV export' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_gitlab_export.csv') }
+
+ it 'imports the CSV without errors' do
+ expect(subject[:success]).to eq(4)
+ expect(subject[:error_lines]).to eq([])
+ expect(subject[:parse_error]).to eq(false)
+ end
+
+ it 'correctly sets the issuable attributes' do
+ expect { subject }.to change { issuables.count }.by 4
+
+ expect(issuables.reload.last).to have_attributes(
+ title: 'Test Title',
+ description: 'Test Description'
+ )
+ end
+
+ it_behaves_like 'importer with email notification'
+ it_behaves_like 'an issuable importer'
+ end
+
+ context 'comma delimited file' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') }
+
+ it 'imports CSV without errors' do
+ expect(subject[:success]).to eq(3)
+ expect(subject[:error_lines]).to eq([])
+ expect(subject[:parse_error]).to eq(false)
+ end
+
+ it 'correctly sets the issuable attributes' do
+ expect { subject }.to change { issuables.count }.by 3
+
+ expect(issuables.reload.last).to have_attributes(
+ title: 'Title with quote"',
+ description: 'Description'
+ )
+ end
+
+ it_behaves_like 'importer with email notification'
+ it_behaves_like 'an issuable importer'
+ end
+
+ context 'tab delimited file with error row' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_tab.csv') }
+
+ it 'imports CSV with some error rows' do
+ expect(subject[:success]).to eq(2)
+ expect(subject[:error_lines]).to eq([3])
+ expect(subject[:parse_error]).to eq(false)
+ end
+
+ it 'correctly sets the issuable attributes' do
+ expect { subject }.to change { issuables.count }.by 2
+
+ expect(issuables.reload.last).to have_attributes(
+ title: 'Hello',
+ description: 'World'
+ )
+ end
+
+ it_behaves_like 'importer with email notification'
+ it_behaves_like 'an issuable importer'
+ end
+
+ context 'semicolon delimited file with CRLF' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_semicolon.csv') }
+
+ it 'imports CSV with a blank row' do
+ expect(subject[:success]).to eq(3)
+ expect(subject[:error_lines]).to eq([4])
+ expect(subject[:parse_error]).to eq(false)
+ end
+
+ it 'correctly sets the issuable attributes' do
+ expect { subject }.to change { issuables.count }.by 3
+
+ expect(issuables.reload.last).to have_attributes(
+ title: 'Hello',
+ description: 'World'
+ )
+ end
+
+ it_behaves_like 'importer with email notification'
+ it_behaves_like 'an issuable importer'
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/design_management_shared_contexts.rb b/spec/support/shared_contexts/design_management_shared_contexts.rb
index 3ff6a521338..e6ae7e03664 100644
--- a/spec/support/shared_contexts/design_management_shared_contexts.rb
+++ b/spec/support/shared_contexts/design_management_shared_contexts.rb
@@ -18,12 +18,14 @@ RSpec.shared_context 'four designs in three versions' do
modified_designs: [],
deleted_designs: [])
end
+
let_it_be(:second_version) do
create(:design_version, issue: issue,
created_designs: [design_b, design_c, design_d],
modified_designs: [design_a],
deleted_designs: [])
end
+
let_it_be(:third_version) do
create(:design_version, issue: issue,
created_designs: [],
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 2b6edb4c07d..68ff16922d8 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,21 +1,21 @@
# frozen_string_literal: true
RSpec.shared_context 'GroupProjectsFinder context' do
- let(:group) { create(:group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let_it_be(:current_user) { create(:user) }
let(:params) { {} }
let(:options) { {} }
let(:finder) { described_class.new(group: group, current_user: current_user, params: params, options: options) }
- let!(:public_project) { create(:project, :public, group: group, path: '1') }
- let!(:private_project) { create(:project, :private, group: group, path: '2') }
- let!(:shared_project_1) { create(:project, :public, path: '3') }
- let!(:shared_project_2) { create(:project, :private, path: '4') }
- let!(:shared_project_3) { create(:project, :internal, path: '5') }
- let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
- let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
+ let_it_be(:public_project) { create(:project, :public, group: group, path: '1') }
+ let_it_be(:private_project) { create(:project, :private, group: group, path: '2') }
+ let_it_be(:shared_project_1) { create(:project, :public, path: '3') }
+ let_it_be(:shared_project_2) { create(:project, :private, path: '4') }
+ let_it_be(:shared_project_3) { create(:project, :internal, path: '5') }
+ let_it_be(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
+ let_it_be(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
before do
shared_project_1.project_group_links.create!(group_access: Gitlab::Access::MAINTAINER, group: group)
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 010c445d8df..88c31bf9cfd 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
@@ -23,6 +23,7 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
# We cannot use `let_it_be` here otherwise we get:
# Failure/Error: allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
# The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported.
+
let!(:project2) do
allow_gitaly_n_plus_1 do
fork_project(project1, user)
@@ -40,9 +41,11 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
let_it_be(:project4, reload: true) do
allow_gitaly_n_plus_1 { create(:project, :repository, group: subgroup) }
end
+
let_it_be(:project5, reload: true) do
allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
end
+
let_it_be(:project6, reload: true) do
allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
end
diff --git a/spec/support/shared_contexts/lib/gitlab/middleware/with_a_mocked_gitlab_instance_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/middleware/with_a_mocked_gitlab_instance_shared_context.rb
new file mode 100644
index 00000000000..3830b89f1ff
--- /dev/null
+++ b/spec/support/shared_contexts/lib/gitlab/middleware/with_a_mocked_gitlab_instance_shared_context.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'with a mocked GitLab instance' do
+ let(:rack_stack) do
+ rack = Rack::Builder.new do
+ use ActionDispatch::Session::CacheStore
+ use ActionDispatch::Flash
+ end
+
+ rack.run(subject)
+ rack.to_app
+ end
+
+ let(:observe_env) do
+ Module.new do
+ attr_reader :env
+
+ def call(env)
+ @env = env
+ super
+ end
+ end
+ end
+
+ let(:request) { Rack::MockRequest.new(rack_stack) }
+
+ subject do
+ described_class.new(fake_app).tap do |app|
+ app.extend(observe_env)
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 9ebfdcb9522..ed74c3f179f 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -56,6 +56,7 @@ RSpec.shared_context 'project navbar structure' do
nav_item: _('CI / CD'),
nav_sub_items: [
_('Pipelines'),
+ s_('Pipelines|Editor'),
_('Jobs'),
_('Artifacts'),
_('Schedules')
@@ -71,6 +72,7 @@ RSpec.shared_context 'project navbar structure' do
_('Alerts'),
_('Incidents'),
_('Serverless'),
+ _('Terraform'),
_('Kubernetes'),
_('Environments'),
_('Feature Flags'),
diff --git a/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
index efd82ecb15a..8c9a60fa703 100644
--- a/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
@@ -3,6 +3,8 @@
RSpec.shared_context 'ProjectPolicyTable context' do
using RSpec::Parameterized::TableSyntax
+ include AdminModeHelper
+
let(:pendings) { {} }
let(:pending?) do
pendings.include?(
@@ -10,106 +12,117 @@ RSpec.shared_context 'ProjectPolicyTable context' do
project_level: project_level,
feature_access_level: feature_access_level,
membership: membership,
+ admin_mode: admin_mode,
expected_count: expected_count
}
)
end
# rubocop:disable Metrics/AbcSize
- # project_level, :feature_access_level, :membership, :expected_count
+ # project_level, :feature_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_reporter_feature_access
- :public | :enabled | :admin | 1
- :public | :enabled | :reporter | 1
- :public | :enabled | :guest | 1
- :public | :enabled | :non_member | 1
- :public | :enabled | :anonymous | 1
-
- :public | :private | :admin | 1
- :public | :private | :reporter | 1
- :public | :private | :guest | 0
- :public | :private | :non_member | 0
- :public | :private | :anonymous | 0
-
- :public | :disabled | :reporter | 0
- :public | :disabled | :guest | 0
- :public | :disabled | :non_member | 0
- :public | :disabled | :anonymous | 0
-
- :internal | :enabled | :admin | 1
- :internal | :enabled | :reporter | 1
- :internal | :enabled | :guest | 1
- :internal | :enabled | :non_member | 1
- :internal | :enabled | :anonymous | 0
-
- :internal | :private | :admin | 1
- :internal | :private | :reporter | 1
- :internal | :private | :guest | 0
- :internal | :private | :non_member | 0
- :internal | :private | :anonymous | 0
-
- :internal | :disabled | :reporter | 0
- :internal | :disabled | :guest | 0
- :internal | :disabled | :non_member | 0
- :internal | :disabled | :anonymous | 0
-
- :private | :private | :admin | 1
- :private | :private | :reporter | 1
- :private | :private | :guest | 0
- :private | :private | :non_member | 0
- :private | :private | :anonymous | 0
-
- :private | :disabled | :reporter | 0
- :private | :disabled | :guest | 0
- :private | :disabled | :non_member | 0
- :private | :disabled | :anonymous | 0
+ :public | :enabled | :admin | true | 1
+ :public | :enabled | :admin | false | 1
+ :public | :enabled | :reporter | nil | 1
+ :public | :enabled | :guest | nil | 1
+ :public | :enabled | :non_member | nil | 1
+ :public | :enabled | :anonymous | nil | 1
+
+ :public | :private | :admin | true | 1
+ :public | :private | :admin | false | 0
+ :public | :private | :reporter | nil | 1
+ :public | :private | :guest | nil | 0
+ :public | :private | :non_member | nil | 0
+ :public | :private | :anonymous | nil | 0
+
+ :public | :disabled | :reporter | nil | 0
+ :public | :disabled | :guest | nil | 0
+ :public | :disabled | :non_member | nil | 0
+ :public | :disabled | :anonymous | nil | 0
+
+ :internal | :enabled | :admin | true | 1
+ :internal | :enabled | :admin | false | 1
+ :internal | :enabled | :reporter | nil | 1
+ :internal | :enabled | :guest | nil | 1
+ :internal | :enabled | :non_member | nil | 1
+ :internal | :enabled | :anonymous | nil | 0
+
+ :internal | :private | :admin | true | 1
+ :internal | :private | :admin | false | 0
+ :internal | :private | :reporter | nil | 1
+ :internal | :private | :guest | nil | 0
+ :internal | :private | :non_member | nil | 0
+ :internal | :private | :anonymous | nil | 0
+
+ :internal | :disabled | :reporter | nil | 0
+ :internal | :disabled | :guest | nil | 0
+ :internal | :disabled | :non_member | nil | 0
+ :internal | :disabled | :anonymous | nil | 0
+
+ :private | :private | :admin | true | 1
+ :private | :private | :admin | false | 0
+ :private | :private | :reporter | nil | 1
+ :private | :private | :guest | nil | 0
+ :private | :private | :non_member | nil | 0
+ :private | :private | :anonymous | nil | 0
+
+ :private | :disabled | :reporter | nil | 0
+ :private | :disabled | :guest | nil | 0
+ :private | :disabled | :non_member | nil | 0
+ :private | :disabled | :anonymous | nil | 0
end
- # project_level, :feature_access_level, :membership, :expected_count
+ # project_level, :feature_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_guest_feature_access
- :public | :enabled | :admin | 1
- :public | :enabled | :reporter | 1
- :public | :enabled | :guest | 1
- :public | :enabled | :non_member | 1
- :public | :enabled | :anonymous | 1
-
- :public | :private | :admin | 1
- :public | :private | :reporter | 1
- :public | :private | :guest | 1
- :public | :private | :non_member | 0
- :public | :private | :anonymous | 0
-
- :public | :disabled | :reporter | 0
- :public | :disabled | :guest | 0
- :public | :disabled | :non_member | 0
- :public | :disabled | :anonymous | 0
-
- :internal | :enabled | :admin | 1
- :internal | :enabled | :reporter | 1
- :internal | :enabled | :guest | 1
- :internal | :enabled | :non_member | 1
- :internal | :enabled | :anonymous | 0
-
- :internal | :private | :admin | 1
- :internal | :private | :reporter | 1
- :internal | :private | :guest | 1
- :internal | :private | :non_member | 0
- :internal | :private | :anonymous | 0
-
- :internal | :disabled | :reporter | 0
- :internal | :disabled | :guest | 0
- :internal | :disabled | :non_member | 0
- :internal | :disabled | :anonymous | 0
-
- :private | :private | :admin | 1
- :private | :private | :reporter | 1
- :private | :private | :guest | 1
- :private | :private | :non_member | 0
- :private | :private | :anonymous | 0
-
- :private | :disabled | :reporter | 0
- :private | :disabled | :guest | 0
- :private | :disabled | :non_member | 0
- :private | :disabled | :anonymous | 0
+ :public | :enabled | :admin | true | 1
+ :public | :enabled | :admin | false | 1
+ :public | :enabled | :reporter | nil | 1
+ :public | :enabled | :guest | nil | 1
+ :public | :enabled | :non_member | nil | 1
+ :public | :enabled | :anonymous | nil | 1
+
+ :public | :private | :admin | true | 1
+ :public | :private | :admin | false | 0
+ :public | :private | :reporter | nil | 1
+ :public | :private | :guest | nil | 1
+ :public | :private | :non_member | nil | 0
+ :public | :private | :anonymous | nil | 0
+
+ :public | :disabled | :reporter | nil | 0
+ :public | :disabled | :guest | nil | 0
+ :public | :disabled | :non_member | nil | 0
+ :public | :disabled | :anonymous | nil | 0
+
+ :internal | :enabled | :admin | true | 1
+ :internal | :enabled | :admin | false | 1
+ :internal | :enabled | :reporter | nil | 1
+ :internal | :enabled | :guest | nil | 1
+ :internal | :enabled | :non_member | nil | 1
+ :internal | :enabled | :anonymous | nil | 0
+
+ :internal | :private | :admin | true | 1
+ :internal | :private | :admin | false | 0
+ :internal | :private | :reporter | nil | 1
+ :internal | :private | :guest | nil | 1
+ :internal | :private | :non_member | nil | 0
+ :internal | :private | :anonymous | nil | 0
+
+ :internal | :disabled | :reporter | nil | 0
+ :internal | :disabled | :guest | nil | 0
+ :internal | :disabled | :non_member | nil | 0
+ :internal | :disabled | :anonymous | nil | 0
+
+ :private | :private | :admin | true | 1
+ :private | :private | :admin | false | 0
+ :private | :private | :reporter | nil | 1
+ :private | :private | :guest | nil | 1
+ :private | :private | :non_member | nil | 0
+ :private | :private | :anonymous | nil | 0
+
+ :private | :disabled | :reporter | nil | 0
+ :private | :disabled | :guest | nil | 0
+ :private | :disabled | :non_member | nil | 0
+ :private | :disabled | :anonymous | nil | 0
end
# This table is based on permission_table_for_guest_feature_access,
@@ -121,184 +134,208 @@ RSpec.shared_context 'ProjectPolicyTable context' do
# e.g. `repository` feature has minimum requirement of GUEST,
# but a GUEST are prohibited from reading code if project is private.
#
- # project_level, :feature_access_level, :membership, :expected_count
+ # project_level, :feature_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_guest_feature_access_and_non_private_project_only
- :public | :enabled | :admin | 1
- :public | :enabled | :reporter | 1
- :public | :enabled | :guest | 1
- :public | :enabled | :non_member | 1
- :public | :enabled | :anonymous | 1
-
- :public | :private | :admin | 1
- :public | :private | :reporter | 1
- :public | :private | :guest | 1
- :public | :private | :non_member | 0
- :public | :private | :anonymous | 0
-
- :public | :disabled | :reporter | 0
- :public | :disabled | :guest | 0
- :public | :disabled | :non_member | 0
- :public | :disabled | :anonymous | 0
-
- :internal | :enabled | :admin | 1
- :internal | :enabled | :reporter | 1
- :internal | :enabled | :guest | 1
- :internal | :enabled | :non_member | 1
- :internal | :enabled | :anonymous | 0
-
- :internal | :private | :admin | 1
- :internal | :private | :reporter | 1
- :internal | :private | :guest | 1
- :internal | :private | :non_member | 0
- :internal | :private | :anonymous | 0
-
- :internal | :disabled | :reporter | 0
- :internal | :disabled | :guest | 0
- :internal | :disabled | :non_member | 0
- :internal | :disabled | :anonymous | 0
-
- :private | :private | :admin | 1
- :private | :private | :reporter | 1
- :private | :private | :guest | 0
- :private | :private | :non_member | 0
- :private | :private | :anonymous | 0
-
- :private | :disabled | :reporter | 0
- :private | :disabled | :guest | 0
- :private | :disabled | :non_member | 0
- :private | :disabled | :anonymous | 0
+ :public | :enabled | :admin | true | 1
+ :public | :enabled | :admin | false | 1
+ :public | :enabled | :reporter | nil | 1
+ :public | :enabled | :guest | nil | 1
+ :public | :enabled | :non_member | nil | 1
+ :public | :enabled | :anonymous | nil | 1
+
+ :public | :private | :admin | true | 1
+ :public | :private | :admin | false | 0
+ :public | :private | :reporter | nil | 1
+ :public | :private | :guest | nil | 1
+ :public | :private | :non_member | nil | 0
+ :public | :private | :anonymous | nil | 0
+
+ :public | :disabled | :reporter | nil | 0
+ :public | :disabled | :guest | nil | 0
+ :public | :disabled | :non_member | nil | 0
+ :public | :disabled | :anonymous | nil | 0
+
+ :internal | :enabled | :admin | true | 1
+ :internal | :enabled | :admin | false | 1
+ :internal | :enabled | :reporter | nil | 1
+ :internal | :enabled | :guest | nil | 1
+ :internal | :enabled | :non_member | nil | 1
+ :internal | :enabled | :anonymous | nil | 0
+
+ :internal | :private | :admin | true | 1
+ :internal | :private | :admin | false | 0
+ :internal | :private | :reporter | nil | 1
+ :internal | :private | :guest | nil | 1
+ :internal | :private | :non_member | nil | 0
+ :internal | :private | :anonymous | nil | 0
+
+ :internal | :disabled | :reporter | nil | 0
+ :internal | :disabled | :guest | nil | 0
+ :internal | :disabled | :non_member | nil | 0
+ :internal | :disabled | :anonymous | nil | 0
+
+ :private | :private | :admin | true | 1
+ :private | :private | :admin | false | 0
+ :private | :private | :reporter | nil | 1
+ :private | :private | :guest | nil | 0
+ :private | :private | :non_member | nil | 0
+ :private | :private | :anonymous | nil | 0
+
+ :private | :disabled | :reporter | nil | 0
+ :private | :disabled | :guest | nil | 0
+ :private | :disabled | :non_member | nil | 0
+ :private | :disabled | :anonymous | nil | 0
end
- # :project_level, :issues_access_level, :merge_requests_access_level, :membership, :expected_count
+ # :project_level, :issues_access_level, :merge_requests_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_milestone_access
- :public | :enabled | :enabled | :admin | 1
- :public | :enabled | :enabled | :reporter | 1
- :public | :enabled | :enabled | :guest | 1
- :public | :enabled | :enabled | :non_member | 1
- :public | :enabled | :enabled | :anonymous | 1
-
- :public | :enabled | :private | :admin | 1
- :public | :enabled | :private | :reporter | 1
- :public | :enabled | :private | :guest | 1
- :public | :enabled | :private | :non_member | 1
- :public | :enabled | :private | :anonymous | 1
-
- :public | :enabled | :disabled | :admin | 1
- :public | :enabled | :disabled | :reporter | 1
- :public | :enabled | :disabled | :guest | 1
- :public | :enabled | :disabled | :non_member | 1
- :public | :enabled | :disabled | :anonymous | 1
-
- :public | :private | :enabled | :admin | 1
- :public | :private | :enabled | :reporter | 1
- :public | :private | :enabled | :guest | 1
- :public | :private | :enabled | :non_member | 1
- :public | :private | :enabled | :anonymous | 1
-
- :public | :private | :private | :admin | 1
- :public | :private | :private | :reporter | 1
- :public | :private | :private | :guest | 1
- :public | :private | :private | :non_member | 0
- :public | :private | :private | :anonymous | 0
-
- :public | :private | :disabled | :admin | 1
- :public | :private | :disabled | :reporter | 1
- :public | :private | :disabled | :guest | 1
- :public | :private | :disabled | :non_member | 0
- :public | :private | :disabled | :anonymous | 0
-
- :public | :disabled | :enabled | :admin | 1
- :public | :disabled | :enabled | :reporter | 1
- :public | :disabled | :enabled | :guest | 1
- :public | :disabled | :enabled | :non_member | 1
- :public | :disabled | :enabled | :anonymous | 1
-
- :public | :disabled | :private | :admin | 1
- :public | :disabled | :private | :reporter | 1
- :public | :disabled | :private | :guest | 0
- :public | :disabled | :private | :non_member | 0
- :public | :disabled | :private | :anonymous | 0
-
- :public | :disabled | :disabled | :reporter | 0
- :public | :disabled | :disabled | :guest | 0
- :public | :disabled | :disabled | :non_member | 0
- :public | :disabled | :disabled | :anonymous | 0
-
- :internal | :enabled | :enabled | :admin | 1
- :internal | :enabled | :enabled | :reporter | 1
- :internal | :enabled | :enabled | :guest | 1
- :internal | :enabled | :enabled | :non_member | 1
- :internal | :enabled | :enabled | :anonymous | 0
-
- :internal | :enabled | :private | :admin | 1
- :internal | :enabled | :private | :reporter | 1
- :internal | :enabled | :private | :guest | 1
- :internal | :enabled | :private | :non_member | 1
- :internal | :enabled | :private | :anonymous | 0
-
- :internal | :enabled | :disabled | :admin | 1
- :internal | :enabled | :disabled | :reporter | 1
- :internal | :enabled | :disabled | :guest | 1
- :internal | :enabled | :disabled | :non_member | 1
- :internal | :enabled | :disabled | :anonymous | 0
-
- :internal | :private | :enabled | :admin | 1
- :internal | :private | :enabled | :reporter | 1
- :internal | :private | :enabled | :guest | 1
- :internal | :private | :enabled | :non_member | 1
- :internal | :private | :enabled | :anonymous | 0
-
- :internal | :private | :private | :admin | 1
- :internal | :private | :private | :reporter | 1
- :internal | :private | :private | :guest | 1
- :internal | :private | :private | :non_member | 0
- :internal | :private | :private | :anonymous | 0
-
- :internal | :private | :disabled | :admin | 1
- :internal | :private | :disabled | :reporter | 1
- :internal | :private | :disabled | :guest | 1
- :internal | :private | :disabled | :non_member | 0
- :internal | :private | :disabled | :anonymous | 0
-
- :internal | :disabled | :enabled | :admin | 1
- :internal | :disabled | :enabled | :reporter | 1
- :internal | :disabled | :enabled | :guest | 1
- :internal | :disabled | :enabled | :non_member | 1
- :internal | :disabled | :enabled | :anonymous | 0
-
- :internal | :disabled | :private | :admin | 1
- :internal | :disabled | :private | :reporter | 1
- :internal | :disabled | :private | :guest | 0
- :internal | :disabled | :private | :non_member | 0
- :internal | :disabled | :private | :anonymous | 0
-
- :internal | :disabled | :disabled | :reporter | 0
- :internal | :disabled | :disabled | :guest | 0
- :internal | :disabled | :disabled | :non_member | 0
- :internal | :disabled | :disabled | :anonymous | 0
-
- :private | :private | :private | :admin | 1
- :private | :private | :private | :reporter | 1
- :private | :private | :private | :guest | 1
- :private | :private | :private | :non_member | 0
- :private | :private | :private | :anonymous | 0
-
- :private | :private | :disabled | :admin | 1
- :private | :private | :disabled | :reporter | 1
- :private | :private | :disabled | :guest | 1
- :private | :private | :disabled | :non_member | 0
- :private | :private | :disabled | :anonymous | 0
-
- :private | :disabled | :private | :admin | 1
- :private | :disabled | :private | :reporter | 1
- :private | :disabled | :private | :guest | 0
- :private | :disabled | :private | :non_member | 0
- :private | :disabled | :private | :anonymous | 0
-
- :private | :disabled | :disabled | :reporter | 0
- :private | :disabled | :disabled | :guest | 0
- :private | :disabled | :disabled | :non_member | 0
- :private | :disabled | :disabled | :anonymous | 0
+ :public | :enabled | :enabled | :admin | true | 1
+ :public | :enabled | :enabled | :admin | false | 1
+ :public | :enabled | :enabled | :reporter | nil | 1
+ :public | :enabled | :enabled | :guest | nil | 1
+ :public | :enabled | :enabled | :non_member | nil | 1
+ :public | :enabled | :enabled | :anonymous | nil | 1
+
+ :public | :enabled | :private | :admin | true | 1
+ :public | :enabled | :private | :admin | false | 1
+ :public | :enabled | :private | :reporter | nil | 1
+ :public | :enabled | :private | :guest | nil | 1
+ :public | :enabled | :private | :non_member | nil | 1
+ :public | :enabled | :private | :anonymous | nil | 1
+
+ :public | :enabled | :disabled | :admin | true | 1
+ :public | :enabled | :disabled | :admin | false | 1
+ :public | :enabled | :disabled | :reporter | nil | 1
+ :public | :enabled | :disabled | :guest | nil | 1
+ :public | :enabled | :disabled | :non_member | nil | 1
+ :public | :enabled | :disabled | :anonymous | nil | 1
+
+ :public | :private | :enabled | :admin | true | 1
+ :public | :private | :enabled | :admin | false | 1
+ :public | :private | :enabled | :reporter | nil | 1
+ :public | :private | :enabled | :guest | nil | 1
+ :public | :private | :enabled | :non_member | nil | 1
+ :public | :private | :enabled | :anonymous | nil | 1
+
+ :public | :private | :private | :admin | true | 1
+ :public | :private | :private | :admin | false | 0
+ :public | :private | :private | :reporter | nil | 1
+ :public | :private | :private | :guest | nil | 1
+ :public | :private | :private | :non_member | nil | 0
+ :public | :private | :private | :anonymous | nil | 0
+
+ :public | :private | :disabled | :admin | true | 1
+ :public | :private | :disabled | :admin | false | 0
+ :public | :private | :disabled | :reporter | nil | 1
+ :public | :private | :disabled | :guest | nil | 1
+ :public | :private | :disabled | :non_member | nil | 0
+ :public | :private | :disabled | :anonymous | nil | 0
+
+ :public | :disabled | :enabled | :admin | true | 1
+ :public | :disabled | :enabled | :admin | false | 1
+ :public | :disabled | :enabled | :reporter | nil | 1
+ :public | :disabled | :enabled | :guest | nil | 1
+ :public | :disabled | :enabled | :non_member | nil | 1
+ :public | :disabled | :enabled | :anonymous | nil | 1
+
+ :public | :disabled | :private | :admin | true | 1
+ :public | :disabled | :private | :admin | false | 0
+ :public | :disabled | :private | :reporter | nil | 1
+ :public | :disabled | :private | :guest | nil | 0
+ :public | :disabled | :private | :non_member | nil | 0
+ :public | :disabled | :private | :anonymous | nil | 0
+
+ :public | :disabled | :disabled | :reporter | nil | 0
+ :public | :disabled | :disabled | :guest | nil | 0
+ :public | :disabled | :disabled | :non_member | nil | 0
+ :public | :disabled | :disabled | :anonymous | nil | 0
+
+ :internal | :enabled | :enabled | :admin | true | 1
+ :internal | :enabled | :enabled | :admin | false | 1
+ :internal | :enabled | :enabled | :reporter | nil | 1
+ :internal | :enabled | :enabled | :guest | nil | 1
+ :internal | :enabled | :enabled | :non_member | nil | 1
+ :internal | :enabled | :enabled | :anonymous | nil | 0
+
+ :internal | :enabled | :private | :admin | true | 1
+ :internal | :enabled | :private | :admin | false | 1
+ :internal | :enabled | :private | :reporter | nil | 1
+ :internal | :enabled | :private | :guest | nil | 1
+ :internal | :enabled | :private | :non_member | nil | 1
+ :internal | :enabled | :private | :anonymous | nil | 0
+
+ :internal | :enabled | :disabled | :admin | true | 1
+ :internal | :enabled | :disabled | :admin | false | 1
+ :internal | :enabled | :disabled | :reporter | nil | 1
+ :internal | :enabled | :disabled | :guest | nil | 1
+ :internal | :enabled | :disabled | :non_member | nil | 1
+ :internal | :enabled | :disabled | :anonymous | nil | 0
+
+ :internal | :private | :enabled | :admin | true | 1
+ :internal | :private | :enabled | :admin | false | 1
+ :internal | :private | :enabled | :reporter | nil | 1
+ :internal | :private | :enabled | :guest | nil | 1
+ :internal | :private | :enabled | :non_member | nil | 1
+ :internal | :private | :enabled | :anonymous | nil | 0
+
+ :internal | :private | :private | :admin | true | 1
+ :internal | :private | :private | :admin | false | 0
+ :internal | :private | :private | :reporter | nil | 1
+ :internal | :private | :private | :guest | nil | 1
+ :internal | :private | :private | :non_member | nil | 0
+ :internal | :private | :private | :anonymous | nil | 0
+
+ :internal | :private | :disabled | :admin | true | 1
+ :internal | :private | :disabled | :admin | false | 0
+ :internal | :private | :disabled | :reporter | nil | 1
+ :internal | :private | :disabled | :guest | nil | 1
+ :internal | :private | :disabled | :non_member | nil | 0
+ :internal | :private | :disabled | :anonymous | nil | 0
+
+ :internal | :disabled | :enabled | :admin | true | 1
+ :internal | :disabled | :enabled | :admin | false | 1
+ :internal | :disabled | :enabled | :reporter | nil | 1
+ :internal | :disabled | :enabled | :guest | nil | 1
+ :internal | :disabled | :enabled | :non_member | nil | 1
+ :internal | :disabled | :enabled | :anonymous | nil | 0
+
+ :internal | :disabled | :private | :admin | true | 1
+ :internal | :disabled | :private | :admin | false | 0
+ :internal | :disabled | :private | :reporter | nil | 1
+ :internal | :disabled | :private | :guest | nil | 0
+ :internal | :disabled | :private | :non_member | nil | 0
+ :internal | :disabled | :private | :anonymous | nil | 0
+
+ :internal | :disabled | :disabled | :reporter | nil | 0
+ :internal | :disabled | :disabled | :guest | nil | 0
+ :internal | :disabled | :disabled | :non_member | nil | 0
+ :internal | :disabled | :disabled | :anonymous | nil | 0
+
+ :private | :private | :private | :admin | true | 1
+ :private | :private | :private | :admin | false | 0
+ :private | :private | :private | :reporter | nil | 1
+ :private | :private | :private | :guest | nil | 1
+ :private | :private | :private | :non_member | nil | 0
+ :private | :private | :private | :anonymous | nil | 0
+
+ :private | :private | :disabled | :admin | true | 1
+ :private | :private | :disabled | :admin | false | 0
+ :private | :private | :disabled | :reporter | nil | 1
+ :private | :private | :disabled | :guest | nil | 1
+ :private | :private | :disabled | :non_member | nil | 0
+ :private | :private | :disabled | :anonymous | nil | 0
+
+ :private | :disabled | :private | :admin | true | 1
+ :private | :disabled | :private | :admin | false | 0
+ :private | :disabled | :private | :reporter | nil | 1
+ :private | :disabled | :private | :guest | nil | 0
+ :private | :disabled | :private | :non_member | nil | 0
+ :private | :disabled | :private | :anonymous | nil | 0
+
+ :private | :disabled | :disabled | :reporter | nil | 0
+ :private | :disabled | :disabled | :guest | nil | 0
+ :private | :disabled | :disabled | :non_member | nil | 0
+ :private | :disabled | :disabled | :anonymous | nil | 0
end
# :project_level, :membership, :expected_count
@@ -321,166 +358,192 @@ RSpec.shared_context 'ProjectPolicyTable context' do
# :snippet_level, :project_level, :feature_access_level, :membership, :expected_count
def permission_table_for_project_snippet_access
- :public | :public | :enabled | :admin | 1
- :public | :public | :enabled | :reporter | 1
- :public | :public | :enabled | :guest | 1
- :public | :public | :enabled | :non_member | 1
- :public | :public | :enabled | :anonymous | 1
-
- :public | :public | :private | :admin | 1
- :public | :public | :private | :reporter | 1
- :public | :public | :private | :guest | 1
- :public | :public | :private | :non_member | 0
- :public | :public | :private | :anonymous | 0
-
- :public | :public | :disabled | :admin | 1
- :public | :public | :disabled | :reporter | 0
- :public | :public | :disabled | :guest | 0
- :public | :public | :disabled | :non_member | 0
- :public | :public | :disabled | :anonymous | 0
-
- :public | :internal | :enabled | :admin | 1
- :public | :internal | :enabled | :reporter | 1
- :public | :internal | :enabled | :guest | 1
- :public | :internal | :enabled | :non_member | 1
- :public | :internal | :enabled | :anonymous | 0
-
- :public | :internal | :private | :admin | 1
- :public | :internal | :private | :reporter | 1
- :public | :internal | :private | :guest | 1
- :public | :internal | :private | :non_member | 0
- :public | :internal | :private | :anonymous | 0
-
- :public | :internal | :disabled | :admin | 1
- :public | :internal | :disabled | :reporter | 0
- :public | :internal | :disabled | :guest | 0
- :public | :internal | :disabled | :non_member | 0
- :public | :internal | :disabled | :anonymous | 0
-
- :public | :private | :private | :admin | 1
- :public | :private | :private | :reporter | 1
- :public | :private | :private | :guest | 1
- :public | :private | :private | :non_member | 0
- :public | :private | :private | :anonymous | 0
-
- :public | :private | :disabled | :reporter | 0
- :public | :private | :disabled | :guest | 0
- :public | :private | :disabled | :non_member | 0
- :public | :private | :disabled | :anonymous | 0
-
- :internal | :public | :enabled | :admin | 1
- :internal | :public | :enabled | :reporter | 1
- :internal | :public | :enabled | :guest | 1
- :internal | :public | :enabled | :non_member | 1
- :internal | :public | :enabled | :anonymous | 0
-
- :internal | :public | :private | :admin | 1
- :internal | :public | :private | :reporter | 1
- :internal | :public | :private | :guest | 1
- :internal | :public | :private | :non_member | 0
- :internal | :public | :private | :anonymous | 0
-
- :internal | :public | :disabled | :admin | 1
- :internal | :public | :disabled | :reporter | 0
- :internal | :public | :disabled | :guest | 0
- :internal | :public | :disabled | :non_member | 0
- :internal | :public | :disabled | :anonymous | 0
-
- :internal | :internal | :enabled | :admin | 1
- :internal | :internal | :enabled | :reporter | 1
- :internal | :internal | :enabled | :guest | 1
- :internal | :internal | :enabled | :non_member | 1
- :internal | :internal | :enabled | :anonymous | 0
-
- :internal | :internal | :private | :admin | 1
- :internal | :internal | :private | :reporter | 1
- :internal | :internal | :private | :guest | 1
- :internal | :internal | :private | :non_member | 0
- :internal | :internal | :private | :anonymous | 0
-
- :internal | :internal | :disabled | :admin | 1
- :internal | :internal | :disabled | :reporter | 0
- :internal | :internal | :disabled | :guest | 0
- :internal | :internal | :disabled | :non_member | 0
- :internal | :internal | :disabled | :anonymous | 0
-
- :internal | :private | :private | :admin | 1
- :internal | :private | :private | :reporter | 1
- :internal | :private | :private | :guest | 1
- :internal | :private | :private | :non_member | 0
- :internal | :private | :private | :anonymous | 0
-
- :internal | :private | :disabled | :admin | 1
- :internal | :private | :disabled | :reporter | 0
- :internal | :private | :disabled | :guest | 0
- :internal | :private | :disabled | :non_member | 0
- :internal | :private | :disabled | :anonymous | 0
-
- :private | :public | :enabled | :admin | 1
- :private | :public | :enabled | :reporter | 1
- :private | :public | :enabled | :guest | 1
- :private | :public | :enabled | :non_member | 0
- :private | :public | :enabled | :anonymous | 0
-
- :private | :public | :private | :admin | 1
- :private | :public | :private | :reporter | 1
- :private | :public | :private | :guest | 1
- :private | :public | :private | :non_member | 0
- :private | :public | :private | :anonymous | 0
-
- :private | :public | :disabled | :admin | 1
- :private | :public | :disabled | :reporter | 0
- :private | :public | :disabled | :guest | 0
- :private | :public | :disabled | :non_member | 0
- :private | :public | :disabled | :anonymous | 0
-
- :private | :internal | :enabled | :admin | 1
- :private | :internal | :enabled | :reporter | 1
- :private | :internal | :enabled | :guest | 1
- :private | :internal | :enabled | :non_member | 0
- :private | :internal | :enabled | :anonymous | 0
-
- :private | :internal | :private | :admin | 1
- :private | :internal | :private | :reporter | 1
- :private | :internal | :private | :guest | 1
- :private | :internal | :private | :non_member | 0
- :private | :internal | :private | :anonymous | 0
-
- :private | :internal | :disabled | :admin | 1
- :private | :internal | :disabled | :reporter | 0
- :private | :internal | :disabled | :guest | 0
- :private | :internal | :disabled | :non_member | 0
- :private | :internal | :disabled | :anonymous | 0
-
- :private | :private | :private | :admin | 1
- :private | :private | :private | :reporter | 1
- :private | :private | :private | :guest | 1
- :private | :private | :private | :non_member | 0
- :private | :private | :private | :anonymous | 0
-
- :private | :private | :disabled | :admin | 1
- :private | :private | :disabled | :reporter | 0
- :private | :private | :disabled | :guest | 0
- :private | :private | :disabled | :non_member | 0
- :private | :private | :disabled | :anonymous | 0
+ :public | :public | :enabled | :admin | true | 1
+ :public | :public | :enabled | :admin | false | 1
+ :public | :public | :enabled | :reporter | nil | 1
+ :public | :public | :enabled | :guest | nil | 1
+ :public | :public | :enabled | :non_member | nil | 1
+ :public | :public | :enabled | :anonymous | nil | 1
+
+ :public | :public | :private | :admin | true | 1
+ :public | :public | :private | :admin | false | 0
+ :public | :public | :private | :reporter | nil | 1
+ :public | :public | :private | :guest | nil | 1
+ :public | :public | :private | :non_member | nil | 0
+ :public | :public | :private | :anonymous | nil | 0
+
+ :public | :public | :disabled | :admin | true | 1
+ :public | :public | :disabled | :admin | false | 0
+ :public | :public | :disabled | :reporter | nil | 0
+ :public | :public | :disabled | :guest | nil | 0
+ :public | :public | :disabled | :non_member | nil | 0
+ :public | :public | :disabled | :anonymous | nil | 0
+
+ :public | :internal | :enabled | :admin | true | 1
+ :public | :internal | :enabled | :admin | false | 1
+ :public | :internal | :enabled | :reporter | nil | 1
+ :public | :internal | :enabled | :guest | nil | 1
+ :public | :internal | :enabled | :non_member | nil | 1
+ :public | :internal | :enabled | :anonymous | nil | 0
+
+ :public | :internal | :private | :admin | true | 1
+ :public | :internal | :private | :admin | false | 0
+ :public | :internal | :private | :reporter | nil | 1
+ :public | :internal | :private | :guest | nil | 1
+ :public | :internal | :private | :non_member | nil | 0
+ :public | :internal | :private | :anonymous | nil | 0
+
+ :public | :internal | :disabled | :admin | true | 1
+ :public | :internal | :disabled | :admin | false | 0
+ :public | :internal | :disabled | :reporter | nil | 0
+ :public | :internal | :disabled | :guest | nil | 0
+ :public | :internal | :disabled | :non_member | nil | 0
+ :public | :internal | :disabled | :anonymous | nil | 0
+
+ :public | :private | :private | :admin | true | 1
+ :public | :private | :private | :admin | false | 0
+ :public | :private | :private | :reporter | nil | 1
+ :public | :private | :private | :guest | nil | 1
+ :public | :private | :private | :non_member | nil | 0
+ :public | :private | :private | :anonymous | nil | 0
+
+ :public | :private | :disabled | :reporter | nil | 0
+ :public | :private | :disabled | :guest | nil | 0
+ :public | :private | :disabled | :non_member | nil | 0
+ :public | :private | :disabled | :anonymous | nil | 0
+
+ :internal | :public | :enabled | :admin | true | 1
+ :internal | :public | :enabled | :admin | false | 1
+ :internal | :public | :enabled | :reporter | nil | 1
+ :internal | :public | :enabled | :guest | nil | 1
+ :internal | :public | :enabled | :non_member | nil | 1
+ :internal | :public | :enabled | :anonymous | nil | 0
+
+ :internal | :public | :private | :admin | true | 1
+ :internal | :public | :private | :admin | false | 0
+ :internal | :public | :private | :reporter | nil | 1
+ :internal | :public | :private | :guest | nil | 1
+ :internal | :public | :private | :non_member | nil | 0
+ :internal | :public | :private | :anonymous | nil | 0
+
+ :internal | :public | :disabled | :admin | true | 1
+ :internal | :public | :disabled | :admin | false | 0
+ :internal | :public | :disabled | :reporter | nil | 0
+ :internal | :public | :disabled | :guest | nil | 0
+ :internal | :public | :disabled | :non_member | nil | 0
+ :internal | :public | :disabled | :anonymous | nil | 0
+
+ :internal | :internal | :enabled | :admin | true | 1
+ :internal | :internal | :enabled | :admin | false | 1
+ :internal | :internal | :enabled | :reporter | nil | 1
+ :internal | :internal | :enabled | :guest | nil | 1
+ :internal | :internal | :enabled | :non_member | nil | 1
+ :internal | :internal | :enabled | :anonymous | nil | 0
+
+ :internal | :internal | :private | :admin | true | 1
+ :internal | :internal | :private | :admin | false | 0
+ :internal | :internal | :private | :reporter | nil | 1
+ :internal | :internal | :private | :guest | nil | 1
+ :internal | :internal | :private | :non_member | nil | 0
+ :internal | :internal | :private | :anonymous | nil | 0
+
+ :internal | :internal | :disabled | :admin | true | 1
+ :internal | :internal | :disabled | :admin | false | 0
+ :internal | :internal | :disabled | :reporter | nil | 0
+ :internal | :internal | :disabled | :guest | nil | 0
+ :internal | :internal | :disabled | :non_member | nil | 0
+ :internal | :internal | :disabled | :anonymous | nil | 0
+
+ :internal | :private | :private | :admin | true | 1
+ :internal | :private | :private | :admin | false | 0
+ :internal | :private | :private | :reporter | nil | 1
+ :internal | :private | :private | :guest | nil | 1
+ :internal | :private | :private | :non_member | nil | 0
+ :internal | :private | :private | :anonymous | nil | 0
+
+ :internal | :private | :disabled | :admin | true | 1
+ :internal | :private | :disabled | :admin | false | 0
+ :internal | :private | :disabled | :reporter | nil | 0
+ :internal | :private | :disabled | :guest | nil | 0
+ :internal | :private | :disabled | :non_member | nil | 0
+ :internal | :private | :disabled | :anonymous | nil | 0
+
+ :private | :public | :enabled | :admin | true | 1
+ :private | :public | :enabled | :admin | false | 0
+ :private | :public | :enabled | :reporter | nil | 1
+ :private | :public | :enabled | :guest | nil | 1
+ :private | :public | :enabled | :non_member | nil | 0
+ :private | :public | :enabled | :anonymous | nil | 0
+
+ :private | :public | :private | :admin | true | 1
+ :private | :public | :private | :admin | false | 0
+ :private | :public | :private | :reporter | nil | 1
+ :private | :public | :private | :guest | nil | 1
+ :private | :public | :private | :non_member | nil | 0
+ :private | :public | :private | :anonymous | nil | 0
+
+ :private | :public | :disabled | :admin | true | 1
+ :private | :public | :disabled | :admin | false | 0
+ :private | :public | :disabled | :reporter | nil | 0
+ :private | :public | :disabled | :guest | nil | 0
+ :private | :public | :disabled | :non_member | nil | 0
+ :private | :public | :disabled | :anonymous | nil | 0
+
+ :private | :internal | :enabled | :admin | true | 1
+ :private | :internal | :enabled | :admin | false | 0
+ :private | :internal | :enabled | :reporter | nil | 1
+ :private | :internal | :enabled | :guest | nil | 1
+ :private | :internal | :enabled | :non_member | nil | 0
+ :private | :internal | :enabled | :anonymous | nil | 0
+
+ :private | :internal | :private | :admin | true | 1
+ :private | :internal | :private | :admin | false | 0
+ :private | :internal | :private | :reporter | nil | 1
+ :private | :internal | :private | :guest | nil | 1
+ :private | :internal | :private | :non_member | nil | 0
+ :private | :internal | :private | :anonymous | nil | 0
+
+ :private | :internal | :disabled | :admin | true | 1
+ :private | :internal | :disabled | :admin | false | 0
+ :private | :internal | :disabled | :reporter | nil | 0
+ :private | :internal | :disabled | :guest | nil | 0
+ :private | :internal | :disabled | :non_member | nil | 0
+ :private | :internal | :disabled | :anonymous | nil | 0
+
+ :private | :private | :private | :admin | true | 1
+ :private | :private | :private | :admin | false | 0
+ :private | :private | :private | :reporter | nil | 1
+ :private | :private | :private | :guest | nil | 1
+ :private | :private | :private | :non_member | nil | 0
+ :private | :private | :private | :anonymous | nil | 0
+
+ :private | :private | :disabled | :admin | true | 1
+ :private | :private | :disabled | :admin | false | 0
+ :private | :private | :disabled | :reporter | nil | 0
+ :private | :private | :disabled | :guest | nil | 0
+ :private | :private | :disabled | :non_member | nil | 0
+ :private | :private | :disabled | :anonymous | nil | 0
end
# :snippet_level, :membership, :expected_count
def permission_table_for_personal_snippet_access
- :public | :admin | 1
- :public | :author | 1
- :public | :non_member | 1
- :public | :anonymous | 1
-
- :internal | :admin | 1
- :internal | :author | 1
- :internal | :non_member | 1
- :internal | :anonymous | 0
-
- :private | :admin | 1
- :private | :author | 1
- :private | :non_member | 0
- :private | :anonymous | 0
+ :public | :admin | true | 1
+ :public | :admin | false | 1
+ :public | :author | nil | 1
+ :public | :non_member | nil | 1
+ :public | :anonymous | nil | 1
+
+ :internal | :admin | true | 1
+ :internal | :admin | false | 1
+ :internal | :author | nil | 1
+ :internal | :non_member | nil | 1
+ :internal | :anonymous | nil | 0
+
+ :private | :admin | true | 1
+ :private | :admin | false | 0
+ :private | :author | nil | 1
+ :private | :non_member | nil | 0
+ :private | :anonymous | nil | 0
end
# rubocop:enable Metrics/AbcSize
end
diff --git a/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb b/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
index edc5b313220..de40b926a1c 100644
--- a/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
+++ b/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
@@ -116,6 +116,7 @@ RSpec.shared_context 'Jira projects request context' do
"uuid": "14935009-f8aa-481e-94bc-f7251f320b0e"
}]'
end
+
let_it_be(:empty_jira_projects_json) do
'{
"self": "https://your-domain.atlassian.net/rest/api/2/project/search?startAt=0&maxResults=2",
diff --git a/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb
new file mode 100644
index 00000000000..7c23ec33cf8
--- /dev/null
+++ b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'npm api setup' do
+ include PackagesManagerApiSpecHelpers
+ include HttpBasicAuthHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
+ let_it_be(:package, reload: true) { create(:npm_package, project: project) }
+ let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ let(:package_name) { package.name }
+
+ before do
+ project.add_developer(user)
+ 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
index 84910d0dfe4..38a5ed244c4 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -48,7 +48,7 @@ RSpec.shared_examples 'multiple issue boards' do
expect(page).to have_button('This is a new board')
end
- it 'deletes board' do
+ it 'deletes board', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/280554' do
in_boards_switcher_dropdown do
click_button 'Delete board'
end
diff --git a/spec/support/shared_examples/cached_response_shared_examples.rb b/spec/support/shared_examples/cached_response_shared_examples.rb
deleted file mode 100644
index 34e5f741b4e..00000000000
--- a/spec/support/shared_examples/cached_response_shared_examples.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-#
-# Negates lib/gitlab/no_cache_headers.rb
-#
-
-RSpec.shared_examples 'cached response' do
- it 'defines a cached header response' do
- expect(response.headers["Cache-Control"]).not_to include("no-store", "no-cache")
- expect(response.headers["Pragma"]).not_to eq("no-cache")
- expect(response.headers["Expires"]).not_to eq("Fri, 01 Jan 1990 00:00:00 GMT")
- end
-end
diff --git a/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb
index 54d41f9a68c..dd71107455f 100644
--- a/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb
@@ -73,7 +73,23 @@ RSpec.shared_examples 'project access tokens available #create' do
end
end
- it { expect(subject).to render_template(:index) }
+ it 'does not create the token' do
+ expect { subject }.not_to change { PersonalAccessToken.count }
+ end
+
+ it 'does not add the project bot as a member' do
+ expect { subject }.not_to change { Member.count }
+ end
+
+ it 'does not create the project bot user' do
+ expect { subject }.not_to change { User.count }
+ end
+
+ it 'shows a failure alert' do
+ subject
+
+ expect(response.flash[:alert]).to match("Failed to create new project access token: Failed!")
+ end
end
end
diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
index 2fcc88ef36a..5a4322f73b6 100644
--- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
@@ -145,6 +145,8 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
group.add_owner(user)
client = stub_client(repos: repos, orgs: [org], org_repos: [org_repo])
allow(client).to receive(:each_page).and_return([OpenStruct.new(objects: repos)].to_enum)
+ # GitHub controller has filtering done using GitHub Search API
+ stub_feature_flags(remove_legacy_github_client: false)
end
it 'filters list of repositories by name' do
@@ -157,6 +159,16 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
end
+ it 'filters the list, ignoring the case of the name' do
+ get :status, params: { filter: 'EMACS' }, format: :json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.dig("imported_projects").count).to eq(0)
+ expect(json_response.dig("provider_repos").count).to eq(1)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_2.id)
+ expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
+ end
+
context 'when user input contains html' do
let(:expected_filter) { 'test' }
let(:filter) { "<html>#{expected_filter}</html>" }
@@ -167,6 +179,23 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
expect(assigns(:filter)).to eq(expected_filter)
end
end
+
+ context 'when the client returns a non-string name' do
+ before do
+ repos = [build(:project, name: 2, path: 'test')]
+
+ client = stub_client(repos: repos)
+ allow(client).to receive(:each_page).and_return([OpenStruct.new(objects: repos)].to_enum)
+ end
+
+ it 'does not raise an error' do
+ get :status, params: { filter: '2' }, format: :json
+
+ expect(response).to have_gitlab_http_status :ok
+
+ expect(json_response.dig("provider_repos").count).to eq(1)
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/controllers/trackable_shared_examples.rb b/spec/support/shared_examples/controllers/trackable_shared_examples.rb
index e82c27c43f5..dac7d8c94ff 100644
--- a/spec/support/shared_examples/controllers/trackable_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/trackable_shared_examples.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.shared_examples 'a Trackable Controller' do
- describe '#track_event' do
+ describe '#track_event', :snowplow do
before do
sign_in user
end
@@ -14,9 +14,10 @@ RSpec.shared_examples 'a Trackable Controller' do
end
end
- it 'tracks the action name' do
- expect(Gitlab::Tracking).to receive(:event).with('AnonymousController', 'index', {})
+ it 'tracks the action name', :snowplow do
get :index
+
+ expect_snowplow_event(category: 'AnonymousController', action: 'index')
end
end
@@ -29,8 +30,9 @@ RSpec.shared_examples 'a Trackable Controller' do
end
it 'tracks with the specified param' do
- expect(Gitlab::Tracking).to receive(:event).with('SomeCategory', 'some_event', label: 'errorlabel')
get :index
+
+ expect_snowplow_event(category: 'SomeCategory', action: 'some_event', label: 'errorlabel')
end
end
end
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 9fc5d8933e5..560cfbfb117 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -56,12 +56,12 @@ RSpec.shared_examples 'thread comments' do |resource_name|
expect(items.first).to have_content 'Comment'
expect(items.first).to have_content "Add a general comment to this #{resource_name}."
- expect(items.first).to have_selector '.fa-check'
+ expect(items.first).to have_selector '[data-testid="check-icon"]'
expect(items.first['class']).to match 'droplab-item-selected'
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).not_to have_selector '[data-testid="check-icon"]'
expect(items.last['class']).not_to match 'droplab-item-selected'
end
@@ -228,11 +228,11 @@ RSpec.shared_examples 'thread comments' do |resource_name|
items = all("#{menu_selector} li")
expect(items.first).to have_content 'Comment'
- expect(items.first).not_to have_selector '.fa-check'
+ expect(items.first).not_to have_selector '[data-testid="check-icon"]'
expect(items.first['class']).not_to match 'droplab-item-selected'
expect(items.last).to have_content 'Start thread'
- expect(items.last).to have_selector '.fa-check'
+ expect(items.last).to have_selector '[data-testid="check-icon"]'
expect(items.last['class']).to match 'droplab-item-selected'
end
@@ -274,11 +274,11 @@ RSpec.shared_examples 'thread comments' do |resource_name|
aggregate_failures do
expect(items.first).to have_content 'Comment'
- expect(items.first).to have_selector '.fa-check'
+ expect(items.first).to have_selector '[data-testid="check-icon"]'
expect(items.first['class']).to match 'droplab-item-selected'
expect(items.last).to have_content 'Start thread'
- expect(items.last).not_to have_selector '.fa-check'
+ expect(items.last).not_to have_selector '[data-testid="check-icon"]'
expect(items.last['class']).not_to match 'droplab-item-selected'
end
end
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 ffe4fb83283..724d6db2705 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,6 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'Maintainer manages access requests' do
+ include Spec::Support::Helpers::Features::MembersHelpers
+
let(:user) { create(:user) }
let(:maintainer) { create(:user) }
@@ -26,7 +28,7 @@ RSpec.shared_examples 'Maintainer manages access requests' do
expect_no_visible_access_request(entity, user)
- page.within('[data-qa-selector="members_list"]') do
+ page.within(members_table) do
expect(page).to have_content user.name
end
end
@@ -35,7 +37,7 @@ RSpec.shared_examples 'Maintainer manages access requests' do
expect_visible_access_request(entity, user)
# Open modal
- click_on 'Deny access request'
+ click_on 'Deny access'
expect(page).not_to have_field "Also unassign this user from related issues and merge requests"
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index 218ef070221..e0d169c6868 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -1,387 +1,295 @@
# frozen_string_literal: true
RSpec.shared_examples 'variable list' do
- it 'shows list of variables' do
- page.within('.js-ci-variable-list-section') do
- expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
+ it 'shows a list of variables' do
+ page.within('.ci-variable-table') do
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
end
end
- it 'adds new CI variable' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('key')
- find('.js-ci-variable-input-value').set('key_value')
+ it 'adds a new CI variable' do
+ click_button('Add Variable')
+
+ fill_variable('key', 'key_value') do
+ click_button('Add variable')
end
- click_button('Save variables')
wait_for_requests
- visit page_path
-
- # We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-key').value).to eq('key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
+ page.within('.ci-variable-table') do
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
end
end
it 'adds a new protected variable' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('key')
- find('.js-ci-variable-input-value').set('key_value')
+ click_button('Add Variable')
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
+ fill_variable('key', 'key_value') do
+ click_button('Add variable')
end
- click_button('Save variables')
wait_for_requests
- visit page_path
-
- # We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-key').value).to eq('key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
+ page.within('.ci-variable-table') do
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Protected"] svg[data-testid="mobile-issue-close-icon"]')).to be_present
end
end
it 'defaults to unmasked' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('key')
- find('.js-ci-variable-input-value').set('key_value')
+ click_button('Add Variable')
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ fill_variable('key', 'key_value') do
+ click_button('Add variable')
end
- click_button('Save variables')
wait_for_requests
- visit page_path
-
- # We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-key').value).to eq('key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
- end
- end
-
- context 'defaults to the application setting' do
- context 'application setting is true' do
- before do
- stub_application_setting(protected_ci_variables: true)
-
- visit page_path
- end
-
- it 'defaults to protected' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('key')
- end
-
- values = all('.js-ci-variable-input-protected', visible: false).map(&:value)
-
- expect(values).to eq %w(false true true)
- end
-
- it 'shows a message regarding the changed default' do
- expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default'
- end
- end
-
- context 'application setting is false' do
- before do
- stub_application_setting(protected_ci_variables: false)
-
- visit page_path
- end
-
- it 'defaults to unprotected' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('key')
- end
-
- values = all('.js-ci-variable-input-protected', visible: false).map(&:value)
-
- expect(values).to eq %w(false false false)
- end
-
- it 'does not show a message regarding the default' do
- expect(page).not_to have_content 'Environment variables are configured by your administrator to be protected by default'
- end
+ page.within('.ci-variable-table') do
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="close-icon"]')).to be_present
end
end
it 'reveals and hides variables' do
- page.within('.js-ci-variable-list-section') do
- expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
- expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value)
+ page.within('.ci-variable-table') do
+ expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
expect(page).to have_content('*' * 17)
click_button('Reveal value')
- expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
- expect(first('.js-ci-variable-input-value').value).to eq(variable.value)
+ expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
+ expect(first('.js-ci-variable-row td[data-label="Value"]').text).to eq(variable.value)
expect(page).not_to have_content('*' * 17)
click_button('Hide value')
- expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
- expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value)
+ expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
expect(page).to have_content('*' * 17)
end
end
- it 'deletes variable' do
- page.within('.js-ci-variable-list-section') do
- expect(page).to have_selector('.js-row', count: 2)
+ it 'deletes a variable' do
+ expect(page).to have_selector('.js-ci-variable-row', count: 1)
- first('.js-row-remove-button').click
-
- click_button('Save variables')
- wait_for_requests
-
- expect(page).to have_selector('.js-row', count: 1)
+ page.within('.ci-variable-table') do
+ click_button('Edit')
end
- end
- it 'edits variable' do
- page.within('.js-ci-variable-list-section') do
- click_button('Reveal value')
-
- page.within('.js-row:nth-child(2)') do
- find('.js-ci-variable-input-key').set('new_key')
- find('.js-ci-variable-input-value').set('new_value')
- end
+ page.within('#add-ci-variable') do
+ click_button('Delete variable')
+ end
- click_button('Save variables')
- wait_for_requests
+ wait_for_requests
- visit page_path
+ expect(first('.js-ci-variable-row').text).to eq('There are no variables yet.')
+ end
- page.within('.js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-key').value).to eq('new_key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('new_value')
- end
+ it 'edits a variable' do
+ page.within('.ci-variable-table') do
+ click_button('Edit')
end
- end
- it 'edits variable to be protected' do
- # Create the unprotected variable
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('unprotected_key')
- find('.js-ci-variable-input-value').set('unprotected_value')
- find('.ci-variable-protected-item .js-project-feature-toggle').click
+ page.within('#add-ci-variable') do
+ find('[data-qa-selector="ci_variable_key_field"] input').set('new_key')
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
+ click_button('Update variable')
end
- click_button('Save variables')
wait_for_requests
- visit page_path
+ expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq('new_key')
+ end
+
+ it 'edits a variable to be unmasked' do
+ page.within('.ci-variable-table') do
+ click_button('Edit')
+ end
- # We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do
- find('.ci-variable-protected-item .js-project-feature-toggle').click
+ page.within('#add-ci-variable') do
+ find('[data-testid="ci-variable-protected-checkbox"]').click
+ find('[data-testid="ci-variable-masked-checkbox"]').click
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
+ click_button('Update variable')
end
- click_button('Save variables')
wait_for_requests
- visit page_path
-
- # We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do
- expect(find('.js-ci-variable-input-key').value).to eq('unprotected_key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('unprotected_value')
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
+ page.within('.ci-variable-table') do
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="close-icon"]')).to be_present
end
end
- it 'edits variable to be unprotected' do
- # Create the protected variable
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('protected_key')
- find('.js-ci-variable-input-value').set('protected_value')
+ it 'edits a variable to be masked' do
+ page.within('.ci-variable-table') do
+ click_button('Edit')
+ end
+
+ page.within('#add-ci-variable') do
+ find('[data-testid="ci-variable-masked-checkbox"]').click
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
+ click_button('Update variable')
end
- click_button('Save variables')
wait_for_requests
- visit page_path
+ page.within('.ci-variable-table') do
+ click_button('Edit')
+ end
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- find('.ci-variable-protected-item .js-project-feature-toggle').click
+ page.within('#add-ci-variable') do
+ find('[data-testid="ci-variable-masked-checkbox"]').click
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
+ click_button('Update variable')
end
- click_button('Save variables')
- wait_for_requests
+ page.within('.ci-variable-table') do
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="mobile-issue-close-icon"]')).to be_present
+ end
+ end
- visit page_path
+ it 'shows a validation error box about duplicate keys' do
+ click_button('Add Variable')
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-key').value).to eq('protected_key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('protected_value')
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
+ fill_variable('key', 'key_value') do
+ click_button('Add variable')
end
- end
- it 'edits variable to be unmasked' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('unmasked_key')
- find('.js-ci-variable-input-value').set('unmasked_value')
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ wait_for_requests
- find('.ci-variable-masked-item .js-project-feature-toggle').click
+ click_button('Add Variable')
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ fill_variable('key', 'key_value') do
+ click_button('Add variable')
end
- click_button('Save variables')
wait_for_requests
- visit page_path
+ expect(find('.flash-container')).to be_present
+ expect(find('.flash-text').text).to have_content('Variables key (key) has already been taken')
+ end
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ it 'prevents a variable to be added if no values are provided when a variable is set to masked' do
+ click_button('Add Variable')
- find('.ci-variable-masked-item .js-project-feature-toggle').click
+ page.within('#add-ci-variable') do
+ find('[data-qa-selector="ci_variable_key_field"] input').set('empty_mask_key')
+ find('[data-testid="ci-variable-protected-checkbox"]').click
+ find('[data-testid="ci-variable-masked-checkbox"]').click
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ expect(find_button('Add variable', disabled: true)).to be_present
end
+ end
- click_button('Save variables')
- wait_for_requests
-
- visit page_path
+ it 'shows validation error box about unmaskable values' do
+ click_button('Add Variable')
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ fill_variable('empty_mask_key', '???', protected: true, masked: true) do
+ expect(page).to have_content('This variable can not be masked')
+ expect(find_button('Add variable', disabled: true)).to be_present
end
end
- it 'edits variable to be masked' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('masked_key')
- find('.js-ci-variable-input-value').set('masked_value')
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ it 'handles multiple edits and a deletion' do
+ # Create two variables
+ click_button('Add Variable')
- find('.ci-variable-masked-item .js-project-feature-toggle').click
-
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ fill_variable('akey', 'akeyvalue') do
+ click_button('Add variable')
end
- click_button('Save variables')
wait_for_requests
- visit page_path
+ click_button('Add Variable')
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ fill_variable('zkey', 'zkeyvalue') do
+ click_button('Add variable')
end
- end
-
- it 'handles multiple edits and deletion in the middle' do
- page.within('.js-ci-variable-list-section') do
- # Create 2 variables
- page.within('.js-row:last-child') do
- find('.js-ci-variable-input-key').set('akey')
- find('.js-ci-variable-input-value').set('akeyvalue')
- end
- page.within('.js-row:last-child') do
- find('.js-ci-variable-input-key').set('zkey')
- find('.js-ci-variable-input-value').set('zkeyvalue')
- end
- click_button('Save variables')
- wait_for_requests
+ wait_for_requests
- expect(page).to have_selector('.js-row', count: 4)
+ expect(page).to have_selector('.js-ci-variable-row', count: 3)
- # Remove the `akey` variable
- page.within('.js-row:nth-child(3)') do
- first('.js-row-remove-button').click
+ # Remove the `akey` variable
+ page.within('.ci-variable-table') do
+ page.within('.js-ci-variable-row:first-child') do
+ click_button('Edit')
end
+ end
- # Add another variable
- page.within('.js-row:last-child') do
- find('.js-ci-variable-input-key').set('ckey')
- find('.js-ci-variable-input-value').set('ckeyvalue')
- end
+ page.within('#add-ci-variable') do
+ click_button('Delete variable')
+ end
- click_button('Save variables')
- wait_for_requests
+ wait_for_requests
- visit page_path
+ # Add another variable
+ click_button('Add Variable')
- # Expect to find 3 variables(4 rows) in alphbetical order
- expect(page).to have_selector('.js-row', count: 4)
- row_keys = all('.js-ci-variable-input-key')
- expect(row_keys[0].value).to eq('ckey')
- expect(row_keys[1].value).to eq('test_key')
- expect(row_keys[2].value).to eq('zkey')
- expect(row_keys[3].value).to eq('')
+ fill_variable('ckey', 'ckeyvalue') do
+ click_button('Add variable')
end
+
+ wait_for_requests
+
+ # expect to find 3 rows of variables in alphabetical order
+ expect(page).to have_selector('.js-ci-variable-row', count: 3)
+ rows = all('.js-ci-variable-row')
+ expect(rows[0].find('td[data-label="Key"]').text).to eq('ckey')
+ expect(rows[1].find('td[data-label="Key"]').text).to eq('test_key')
+ expect(rows[2].find('td[data-label="Key"]').text).to eq('zkey')
end
- it 'shows validation error box about duplicate keys' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('samekey')
- find('.js-ci-variable-input-value').set('value123')
- end
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('samekey')
- find('.js-ci-variable-input-value').set('value456')
- end
+ context 'defaults to the application setting' do
+ context 'application setting is true' do
+ before do
+ stub_application_setting(protected_ci_variables: true)
- click_button('Save variables')
- wait_for_requests
+ visit page_path
+ end
- expect(all('.js-ci-variable-list-section .js-ci-variable-error-box ul li').count).to eq(1)
+ it 'defaults to protected' do
+ click_button('Add Variable')
- # We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section') do
- expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables have duplicate values \(.+\)/)
- end
- end
+ page.within('#add-ci-variable') do
+ expect(find('[data-testid="ci-variable-protected-checkbox"]')).to be_checked
+ end
+ end
- it 'shows validation error box about masking empty values' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('empty_value')
- find('.js-ci-variable-input-value').set('')
- find('.ci-variable-masked-item .js-project-feature-toggle').click
+ it 'shows a message regarding the changed default' do
+ expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default'
+ end
end
- click_button('Save variables')
- wait_for_requests
+ context 'application setting is false' do
+ before do
+ stub_application_setting(protected_ci_variables: false)
- page.within('.js-ci-variable-list-section') do
- expect(all('.js-ci-variable-error-box ul li').count).to eq(1)
- expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/)
- end
- end
+ visit page_path
+ end
- it 'shows validation error box about unmaskable values' do
- page.within('.js-ci-variable-list-section .js-row:last-child') do
- find('.js-ci-variable-input-key').set('unmaskable_value')
- find('.js-ci-variable-input-value').set('???')
- find('.ci-variable-masked-item .js-project-feature-toggle').click
+ it 'defaults to unprotected' do
+ click_button('Add Variable')
+
+ page.within('#add-ci-variable') do
+ expect(find('[data-testid="ci-variable-protected-checkbox"]')).not_to be_checked
+ end
+ end
+
+ it 'does not show a message regarding the default' do
+ expect(page).not_to have_content 'Environment variables are configured by your administrator to be protected by default'
+ end
end
+ end
- click_button('Save variables')
- wait_for_requests
+ def fill_variable(key, value, protected: false, masked: false)
+ page.within('#add-ci-variable') do
+ find('[data-qa-selector="ci_variable_key_field"] input').set(key)
+ find('[data-qa-selector="ci_variable_value_field"]').set(value) if value.present?
+ find('[data-testid="ci-variable-protected-checkbox"]').click if protected
+ find('[data-testid="ci-variable-masked-checkbox"]').click if masked
- page.within('.js-ci-variable-list-section') do
- expect(all('.js-ci-variable-error-box ul li').count).to eq(1)
- expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/)
+ yield
end
end
end
diff --git a/spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb
index e1fd9c8dbec..ee0261771f9 100644
--- a/spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb
@@ -17,8 +17,8 @@ RSpec.shared_examples 'User deletes wiki page' do
it 'deletes a page', :js do
click_on('Edit')
click_on('Delete')
- find('.modal-footer .btn-danger').click
+ find('[data-testid="confirm_deletion_button"]').click
- expect(page).to have_content('Page was successfully deleted')
+ expect(page).to have_content('Wiki page was successfully deleted.')
end
end
diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
index 1a5f8d7d8df..3350e54a8a7 100644
--- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
@@ -213,11 +213,11 @@ RSpec.shared_examples 'User updates wiki page' do
visit wiki_page_path(wiki_page.wiki, wiki_page, action: :edit)
end
- it 'allows changing the title if the content does not change' do
+ it 'allows changing the title if the content does not change', :js do
fill_in 'Title', with: 'new title'
click_on 'Save changes'
- expect(page).to have_content('Wiki was successfully updated.')
+ expect(page).to have_content('Wiki page was successfully updated.')
end
it 'shows a validation error when trying to change the content' do
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
index 85eedbf4cc5..af769be6d4b 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
@@ -33,7 +33,7 @@ RSpec.shared_examples 'User views a wiki page' do
click_on('Create page')
end
- expect(page).to have_content('Wiki was successfully updated.')
+ expect(page).to have_content('Wiki page was successfully created.')
end
it 'shows the history of a page that has a path' do
@@ -49,7 +49,7 @@ RSpec.shared_examples 'User views a wiki page' do
end
end
- it 'shows an old version of a page' do
+ it 'shows an old version of a page', :js do
expect(current_path).to include('one/two/three-test')
expect(find('.wiki-pages')).to have_content('three')
@@ -65,7 +65,7 @@ RSpec.shared_examples 'User views a wiki page' do
fill_in('Content', with: 'Updated Wiki Content')
click_on('Save changes')
- expect(page).to have_content('Wiki was successfully updated.')
+ expect(page).to have_content('Wiki page was successfully updated.')
click_on('Page history')
diff --git a/spec/support/shared_examples/finders/security/jobs_finder_shared_examples.rb b/spec/support/shared_examples/finders/security/jobs_finder_shared_examples.rb
new file mode 100644
index 00000000000..a332b213866
--- /dev/null
+++ b/spec/support/shared_examples/finders/security/jobs_finder_shared_examples.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples ::Security::JobsFinder do |default_job_types|
+ let(:pipeline) { create(:ci_pipeline) }
+
+ describe '#new' do
+ it "does not get initialized for unsupported job types" do
+ expect { described_class.new(pipeline: pipeline, job_types: [:abcd]) }.to raise_error(
+ ArgumentError,
+ "job_types must be from the following: #{default_job_types}"
+ )
+ end
+ end
+
+ describe '#execute' do
+ let(:finder) { described_class.new(pipeline: pipeline) }
+
+ subject { finder.execute }
+
+ shared_examples 'JobsFinder core functionality' do
+ context 'when the pipeline has no jobs' do
+ it { is_expected.to be_empty }
+ end
+
+ context 'when the pipeline has no Secure jobs' do
+ before do
+ create(:ci_build, pipeline: pipeline)
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when the pipeline only has jobs without report artifacts' do
+ before do
+ create(:ci_build, pipeline: pipeline, options: { artifacts: { file: 'test.file' } })
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when the pipeline only has jobs with reports unrelated to Secure products' do
+ before do
+ create(:ci_build, pipeline: pipeline, options: { artifacts: { reports: { file: 'test.file' } } })
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when the pipeline only has jobs with reports with paths similar but not identical to Secure reports' do
+ before do
+ create(:ci_build, pipeline: pipeline, options: { artifacts: { reports: { file: 'report:sast:result.file' } } })
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when there is more than one pipeline' do
+ let(:job_type) { default_job_types.first }
+ let!(:build) { create(:ci_build, job_type, pipeline: pipeline) }
+
+ before do
+ create(:ci_build, job_type, pipeline: create(:ci_pipeline))
+ end
+
+ it 'returns jobs associated with provided pipeline' do
+ is_expected.to eq([build])
+ end
+ end
+ end
+
+ context 'when using legacy CI build metadata config storage' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: false)
+ end
+
+ it_behaves_like 'JobsFinder core functionality'
+ end
+
+ context 'when using the new CI build metadata config storage' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: true)
+ end
+
+ it_behaves_like 'JobsFinder core functionality'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/label_fields.rb b/spec/support/shared_examples/graphql/label_fields.rb
index b1bfb395bc6..caf5dae409a 100644
--- a/spec/support/shared_examples/graphql/label_fields.rb
+++ b/spec/support/shared_examples/graphql/label_fields.rb
@@ -106,13 +106,11 @@ RSpec.shared_examples 'querying a GraphQL type with labels' do
end
it 'batches queries for labels by title' do
- pending('See: https://gitlab.com/gitlab-org/gitlab/-/issues/217767')
-
multi_selection = query_for(label_b, label_c)
single_selection = query_for(label_d)
expect { run_query(multi_selection) }
- .to issue_same_number_of_queries_as { run_query(single_selection) }
+ .to issue_same_number_of_queries_as { run_query(single_selection) }.ignoring_cached_queries
end
end
diff --git a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
index ec64519cd9c..9c0b398a5c1 100644
--- a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
@@ -65,7 +65,7 @@ RSpec.shared_examples 'boards create mutation' do
let(:params) { { name: name } }
it_behaves_like 'a mutation that returns top-level errors',
- errors: ['group_path or project_path arguments are required']
+ errors: ['Exactly one of group_path or project_path arguments is required']
it 'does not create the board' do
expect { subject }.not_to change { Board.count }
diff --git a/spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb
new file mode 100644
index 00000000000..fbef8be9e88
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'create todo mutation' do
+ let_it_be(:current_user) { create(:user) }
+
+ let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+
+ context 'when user does not have permission to create todo' do
+ it 'raises error' do
+ expect { mutation.resolve(target_id: global_id_of(target)) }
+ .to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when user has permission to create todo' do
+ it 'creates a todo' do
+ target.resource_parent.add_reporter(current_user)
+
+ result = mutation.resolve(target_id: global_id_of(target))
+
+ expect(result[:todo]).to be_valid
+ expect(result[:todo].target).to eq(target)
+ expect(result[:todo].state).to eq('pending')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/mutations/issues/permission_check_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/issues/permission_check_shared_examples.rb
new file mode 100644
index 00000000000..34c58f524cd
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/issues/permission_check_shared_examples.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'permission level for issue mutation is correctly verified' do |raises_for_all_errors = false|
+ before do
+ issue.assignees = []
+ issue.author = user
+ end
+
+ shared_examples_for 'when the user does not have access to the resource' do |raise_for_assigned|
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'even if assigned to the issue' do
+ before do
+ issue.assignees.push(user)
+ end
+
+ it 'does not modify issue' do
+ if raises_for_all_errors || raise_for_assigned
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ else
+ expect(subject[:issue]).to eq issue
+ end
+ end
+ end
+
+ context 'even if author of the issue' do
+ before do
+ issue.author = user
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+ end
+
+ context 'when the user is not a project member' do
+ it_behaves_like 'when the user does not have access to the resource', true
+ end
+
+ context 'when the user is a project member' do
+ context 'with guest role' do
+ before do
+ issue.project.add_guest(user)
+ end
+
+ it_behaves_like 'when the user does not have access to the resource', false
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/mutations/merge_requests/permission_check_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/merge_requests/permission_check_shared_examples.rb
new file mode 100644
index 00000000000..1ddbad1cea7
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/merge_requests/permission_check_shared_examples.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'permission level for merge request mutation is correctly verified' do
+ before do
+ merge_request.assignees = []
+ merge_request.reviewers = []
+ merge_request.author = nil
+ end
+
+ shared_examples_for 'when the user does not have access to the resource' do |raise_for_assigned|
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'even if assigned to the merge request' do
+ before do
+ merge_request.assignees.push(user)
+ end
+
+ it 'does not modify merge request' do
+ if raise_for_assigned
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ else
+ # In some cases we simply do nothing instead of raising
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/196241
+ expect(subject[:merge_request]).to eq merge_request
+ end
+ end
+ end
+
+ context 'even if reviewer of the merge request' do
+ before do
+ merge_request.reviewers.push(user)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'even if author of the merge request' do
+ before do
+ merge_request.author = user
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+ end
+
+ context 'when the user is not a project member' do
+ it_behaves_like 'when the user does not have access to the resource', true
+ end
+
+ context 'when the user is a project member' do
+ context 'with guest role' do
+ before do
+ merge_request.project.add_guest(user)
+ end
+
+ it_behaves_like 'when the user does not have access to the resource', true
+ end
+
+ context 'with reporter role' do
+ before do
+ merge_request.project.add_reporter(user)
+ end
+
+ it_behaves_like 'when the user does not have access to the resource', false
+ end
+ end
+end
diff --git a/spec/support/shared_examples/helm_commands_shared_examples.rb b/spec/support/shared_examples/helm_commands_shared_examples.rb
index 0a94c6648cc..64f176c5ae9 100644
--- a/spec/support/shared_examples/helm_commands_shared_examples.rb
+++ b/spec/support/shared_examples/helm_commands_shared_examples.rb
@@ -15,6 +15,18 @@ RSpec.shared_examples 'helm command generator' do
end
RSpec.shared_examples 'helm command' do
+ describe 'HELM_VERSION' do
+ subject { command.class::HELM_VERSION }
+
+ it { is_expected.to match(/\d+\.\d+\.\d+/) }
+ end
+
+ describe '#env' do
+ subject { command.env }
+
+ it { is_expected.to be_a Hash }
+ end
+
describe '#rbac?' do
subject { command.rbac? }
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
index d0e41605e00..145a7290ac8 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples_for 'cycle analytics event' do
+RSpec.shared_examples_for 'value stream analytics event' do
let(:params) { {} }
let(:instance) { described_class.new(params) }
diff --git a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
new file mode 100644
index 00000000000..20f3270526e
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'marks background migration job records' do
+ it 'marks each job record as succeeded after processing' do
+ create(:background_migration_job, class_name: "::#{described_class.name}",
+ arguments: arguments)
+
+ expect(::Gitlab::Database::BackgroundMigrationJob).to receive(:mark_all_as_succeeded).and_call_original
+
+ expect do
+ subject.perform(*arguments)
+ end.to change { ::Gitlab::Database::BackgroundMigrationJob.succeeded.count }.from(0).to(1)
+ end
+
+ it 'returns the number of job records marked as succeeded' do
+ create(:background_migration_job, class_name: "::#{described_class.name}",
+ arguments: arguments)
+
+ jobs_updated = subject.perform(*arguments)
+
+ expect(jobs_updated).to eq(1)
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/database/postgres_model_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/postgres_model_shared_examples.rb
new file mode 100644
index 00000000000..ffebbabca58
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/database/postgres_model_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a postgres model' do
+ describe '.by_identifier' do
+ it "finds the #{described_class}" do
+ expect(find(identifier)).to be_a(described_class)
+ end
+
+ it 'raises an error if not found' do
+ expect { find('public.idontexist') }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'raises ArgumentError if given a non-fully qualified identifier' do
+ expect { find('foo') }.to raise_error(ArgumentError, /not fully qualified/)
+ end
+ end
+
+ describe '#to_s' do
+ it 'returns the name' do
+ expect(find(identifier).to_s).to eq(name)
+ end
+ end
+
+ describe '#schema' do
+ it 'returns the schema' do
+ expect(find(identifier).schema).to eq(schema)
+ end
+ end
+
+ describe '#name' do
+ it 'returns the name' do
+ expect(find(identifier).name).to eq(name)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb
new file mode 100644
index 00000000000..e07d3e2dec9
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb
@@ -0,0 +1,189 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'write access for a read-only GitLab instance' do
+ include Rack::Test::Methods
+ using RSpec::Parameterized::TableSyntax
+
+ include_context 'with a mocked GitLab instance'
+
+ context 'normal requests to a read-only GitLab instance' do
+ let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] } }
+
+ it 'expects PATCH requests to be disallowed' do
+ response = request.patch('/test_request')
+
+ expect(response).to be_redirect
+ expect(subject).to disallow_request
+ end
+
+ it 'expects PUT requests to be disallowed' do
+ response = request.put('/test_request')
+
+ expect(response).to be_redirect
+ expect(subject).to disallow_request
+ end
+
+ it 'expects POST requests to be disallowed' do
+ response = request.post('/test_request')
+
+ expect(response).to be_redirect
+ expect(subject).to disallow_request
+ end
+
+ it 'expects a internal POST request to be allowed after a disallowed request' do
+ response = request.post('/test_request')
+
+ expect(response).to be_redirect
+
+ response = request.post("/api/#{API::API.version}/internal")
+
+ expect(response).not_to be_redirect
+ end
+
+ it 'expects DELETE requests to be disallowed' do
+ response = request.delete('/test_request')
+
+ expect(response).to be_redirect
+ expect(subject).to disallow_request
+ end
+
+ it 'expects POST of new file that looks like an LFS batch url to be disallowed' do
+ expect(Rails.application.routes).to receive(:recognize_path).and_call_original
+ response = request.post('/root/gitlab-ce/new/master/app/info/lfs/objects/batch')
+
+ expect(response).to be_redirect
+ expect(subject).to disallow_request
+ end
+
+ it 'returns last_vistited_url for disallowed request' do
+ response = request.post('/test_request')
+
+ expect(response.location).to eq 'http://localhost/'
+ end
+
+ context 'allowlisted requests' do
+ it 'expects a POST internal request to be allowed' do
+ expect(Rails.application.routes).not_to receive(:recognize_path)
+ response = request.post("/api/#{API::API.version}/internal")
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+ end
+
+ it 'expects a graphql request to be allowed' do
+ response = request.post("/api/graphql")
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+ end
+
+ context 'relative URL is configured' do
+ before do
+ stub_config_setting(relative_url_root: '/gitlab')
+ end
+
+ it 'expects a graphql request to be allowed' do
+ response = request.post("/gitlab/api/graphql")
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+ end
+ end
+
+ context 'sidekiq admin requests' do
+ where(:mounted_at) do
+ [
+ '',
+ '/',
+ '/gitlab',
+ '/gitlab/',
+ '/gitlab/gitlab',
+ '/gitlab/gitlab/'
+ ]
+ end
+
+ with_them do
+ before do
+ stub_config_setting(relative_url_root: mounted_at)
+ end
+
+ it 'allows requests' do
+ path = File.join(mounted_at, 'admin/sidekiq')
+ response = request.post(path)
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+
+ response = request.get(path)
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+ end
+ end
+ end
+
+ where(:description, :path) do
+ 'LFS request to batch' | '/root/rouge.git/info/lfs/objects/batch'
+ 'request to git-upload-pack' | '/root/rouge.git/git-upload-pack'
+ end
+
+ with_them do
+ it "expects a POST #{description} URL to be allowed" do
+ expect(Rails.application.routes).to receive(:recognize_path).and_call_original
+ response = request.post(path)
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+ end
+ end
+
+ where(:description, :path) do
+ 'LFS request to locks verify' | '/root/rouge.git/info/lfs/locks/verify'
+ 'LFS request to locks create' | '/root/rouge.git/info/lfs/locks'
+ 'LFS request to locks unlock' | '/root/rouge.git/info/lfs/locks/1/unlock'
+ end
+
+ with_them do
+ it "expects a POST #{description} URL not to be allowed" do
+ response = request.post(path)
+
+ expect(response).to be_redirect
+ expect(subject).to disallow_request
+ end
+ end
+ end
+ end
+
+ context 'json requests to a read-only GitLab instance' do
+ let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'application/json' }, ['OK']] } }
+ let(:content_json) { { 'CONTENT_TYPE' => 'application/json' } }
+
+ before do
+ allow(Gitlab::Database).to receive(:read_only?) { true }
+ end
+
+ it 'expects PATCH requests to be disallowed' do
+ response = request.patch('/test_request', content_json)
+
+ expect(response).to disallow_request_in_json
+ end
+
+ it 'expects PUT requests to be disallowed' do
+ response = request.put('/test_request', content_json)
+
+ expect(response).to disallow_request_in_json
+ end
+
+ it 'expects POST requests to be disallowed' do
+ response = request.post('/test_request', content_json)
+
+ expect(response).to disallow_request_in_json
+ end
+
+ it 'expects DELETE requests to be disallowed' do
+ response = request.delete('/test_request', content_json)
+
+ expect(response).to disallow_request_in_json
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/repository_size_checker_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/repository_size_checker_shared_examples.rb
index bb909ffe82a..30413f206f8 100644
--- a/spec/support/shared_examples/lib/gitlab/repository_size_checker_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/repository_size_checker_shared_examples.rb
@@ -17,35 +17,58 @@ RSpec.shared_examples 'checker size not over limit' do
end
RSpec.shared_examples 'checker size exceeded' do
- context 'when current size is below or equal to the limit' do
- let(:current_size) { 50 }
+ context 'when no change size provided' do
+ context 'when current size is below the limit' do
+ let(:current_size) { limit - 1 }
- it 'returns zero' do
- expect(subject.exceeded_size).to eq(0)
+ it 'returns zero' do
+ expect(subject.exceeded_size).to eq(0)
+ end
end
- end
- context 'when current size is over the limit' do
- let(:current_size) { 51 }
+ context 'when current size is equal to the limit' do
+ let(:current_size) { limit }
- it 'returns zero' do
- expect(subject.exceeded_size).to eq(1.megabytes)
+ it 'returns zero' do
+ expect(subject.exceeded_size).to eq(0)
+ end
end
- end
- context 'when change size will be over the limit' do
- let(:current_size) { 50 }
+ context 'when current size is over the limit' do
+ let(:current_size) { limit + 1 }
+ let(:total_repository_size_excess) { 1 }
- it 'returns zero' do
- expect(subject.exceeded_size(1.megabytes)).to eq(1.megabytes)
+ it 'returns a positive number' do
+ expect(subject.exceeded_size).to eq(1.megabyte)
+ end
end
end
- context 'when change size will not be over the limit' do
- let(:current_size) { 49 }
+ context 'when a change size is provided' do
+ let(:change_size) { 1.megabyte }
+
+ context 'when change size will be over the limit' do
+ let(:current_size) { limit }
+
+ it 'returns a positive number' do
+ expect(subject.exceeded_size(change_size)).to eq(1.megabyte)
+ end
+ end
+
+ context 'when change size will be at the limit' do
+ let(:current_size) { limit - 1 }
+
+ it 'returns zero' do
+ expect(subject.exceeded_size(change_size)).to eq(0)
+ end
+ end
+
+ context 'when change size will be under the limit' do
+ let(:current_size) { limit - 2 }
- it 'returns zero' do
- expect(subject.exceeded_size(1.megabytes)).to eq(0)
+ it 'returns zero' do
+ expect(subject.exceeded_size(change_size)).to eq(0)
+ end
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb
index d0bef2ad730..e70dfec80b1 100644
--- a/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb
@@ -4,66 +4,27 @@ RSpec.shared_examples 'search results filtered by confidential' do
context 'filter not provided (all behavior)' do
let(:filters) { {} }
- context 'when Feature search_filter_by_confidential enabled' do
- it 'returns confidential and not confidential results', :aggregate_failures do
- expect(results.objects('issues')).to include confidential_result
- expect(results.objects('issues')).to include opened_result
- end
- end
-
- context 'when Feature search_filter_by_confidential not enabled' do
- before do
- stub_feature_flags(search_filter_by_confidential: false)
- end
-
- it 'returns confidential and not confidential results', :aggregate_failures do
- expect(results.objects('issues')).to include confidential_result
- expect(results.objects('issues')).to include opened_result
- end
+ it 'returns confidential and not confidential results', :aggregate_failures do
+ expect(results.objects('issues')).to include confidential_result
+ expect(results.objects('issues')).to include opened_result
end
end
context 'confidential filter' do
let(:filters) { { confidential: true } }
- context 'when Feature search_filter_by_confidential enabled' do
- it 'returns only confidential results', :aggregate_failures do
- expect(results.objects('issues')).to include confidential_result
- expect(results.objects('issues')).not_to include opened_result
- end
- end
-
- context 'when Feature search_filter_by_confidential not enabled' do
- before do
- stub_feature_flags(search_filter_by_confidential: false)
- end
-
- it 'returns confidential and not confidential results', :aggregate_failures do
- expect(results.objects('issues')).to include confidential_result
- expect(results.objects('issues')).to include opened_result
- end
+ it 'returns only confidential results', :aggregate_failures do
+ expect(results.objects('issues')).to include confidential_result
+ expect(results.objects('issues')).not_to include opened_result
end
end
context 'not confidential filter' do
let(:filters) { { confidential: false } }
- context 'when Feature search_filter_by_confidential enabled' do
- it 'returns not confidential results', :aggregate_failures do
- expect(results.objects('issues')).not_to include confidential_result
- expect(results.objects('issues')).to include opened_result
- end
- end
-
- context 'when Feature search_filter_by_confidential not enabled' do
- before do
- stub_feature_flags(search_filter_by_confidential: false)
- end
-
- it 'returns confidential and not confidential results', :aggregate_failures do
- expect(results.objects('issues')).to include confidential_result
- expect(results.objects('issues')).to include opened_result
- end
+ it 'returns not confidential results', :aggregate_failures do
+ expect(results.objects('issues')).not_to include confidential_result
+ expect(results.objects('issues')).to include opened_result
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb
index 765279a78fe..07d01d5c50e 100644
--- a/spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'search results sorted' do
context 'sort: newest' do
- let(:sort) { 'newest' }
+ let(:sort) { 'created_desc' }
it 'sorts results by created_at' do
expect(results.objects(scope).map(&:id)).to eq([new_result.id, old_result.id, very_old_result.id])
@@ -10,7 +10,7 @@ RSpec.shared_examples 'search results sorted' do
end
context 'sort: oldest' do
- let(:sort) { 'oldest' }
+ let(:sort) { 'created_asc' }
it 'sorts results by created_at' do
expect(results.objects(scope).map(&:id)).to eq([very_old_result.id, old_result.id, new_result.id])
diff --git a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
new file mode 100644
index 00000000000..2936bb354cf
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
+ let(:fake_duplicate_job) do
+ instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob)
+ end
+
+ let(:expected_message) { "dropped #{strategy_name.to_s.humanize.downcase}" }
+
+ subject(:strategy) { Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies.for(strategy_name).new(fake_duplicate_job) }
+
+ describe '#schedule' do
+ before do
+ allow(Gitlab::SidekiqLogging::DeduplicationLogger.instance).to receive(:log)
+ end
+
+ it 'checks for duplicates before yielding' do
+ expect(fake_duplicate_job).to receive(:scheduled?).twice.ordered.and_return(false)
+ expect(fake_duplicate_job).to(
+ receive(:check!)
+ .with(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DUPLICATE_KEY_TTL)
+ .ordered
+ .and_return('a jid'))
+ expect(fake_duplicate_job).to receive(:duplicate?).ordered.and_return(false)
+
+ expect { |b| strategy.schedule({}, &b) }.to yield_control
+ end
+
+ it 'checks worker options for scheduled jobs' do
+ expect(fake_duplicate_job).to receive(:scheduled?).ordered.and_return(true)
+ expect(fake_duplicate_job).to receive(:options).ordered.and_return({})
+ expect(fake_duplicate_job).not_to receive(:check!)
+
+ expect { |b| strategy.schedule({}, &b) }.to yield_control
+ end
+
+ context 'job marking' do
+ it 'adds the jid of the existing job to the job hash' do
+ allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
+ allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
+ allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ allow(fake_duplicate_job).to receive(:options).and_return({})
+ job_hash = {}
+
+ expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
+ expect(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
+
+ strategy.schedule(job_hash) {}
+
+ expect(job_hash).to include('duplicate-of' => 'the jid')
+ end
+
+ context 'scheduled jobs' do
+ let(:time_diff) { 1.minute }
+
+ context 'scheduled in the past' do
+ it 'adds the jid of the existing job to the job hash' do
+ allow(fake_duplicate_job).to receive(:scheduled?).twice.and_return(true)
+ allow(fake_duplicate_job).to receive(:scheduled_at).and_return(Time.now - time_diff)
+ allow(fake_duplicate_job).to receive(:options).and_return({ including_scheduled: true })
+ allow(fake_duplicate_job).to(
+ receive(:check!)
+ .with(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DUPLICATE_KEY_TTL)
+ .and_return('the jid'))
+ allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ job_hash = {}
+
+ expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
+ expect(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
+
+ strategy.schedule(job_hash) {}
+
+ expect(job_hash).to include('duplicate-of' => 'the jid')
+ end
+ end
+
+ context 'scheduled in the future' do
+ it 'adds the jid of the existing job to the job hash' do
+ freeze_time do
+ allow(fake_duplicate_job).to receive(:scheduled?).twice.and_return(true)
+ allow(fake_duplicate_job).to receive(:scheduled_at).and_return(Time.now + time_diff)
+ allow(fake_duplicate_job).to receive(:options).and_return({ including_scheduled: true })
+ allow(fake_duplicate_job).to(
+ receive(:check!).with(time_diff.to_i).and_return('the jid'))
+ allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ job_hash = {}
+
+ expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
+ expect(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
+
+ strategy.schedule(job_hash) {}
+
+ expect(job_hash).to include('duplicate-of' => 'the jid')
+ end
+ end
+ end
+ end
+ end
+
+ context "when the job is droppable" do
+ before do
+ allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
+ allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
+ allow(fake_duplicate_job).to receive(:duplicate?).and_return(true)
+ allow(fake_duplicate_job).to receive(:options).and_return({})
+ allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
+ allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ end
+
+ it 'drops the job' do
+ schedule_result = nil
+
+ expect(fake_duplicate_job).to receive(:droppable?).and_return(true)
+
+ expect { |b| schedule_result = strategy.schedule({}, &b) }.not_to yield_control
+ expect(schedule_result).to be(false)
+ end
+
+ it 'logs that the job was dropped' do
+ fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
+
+ expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
+ expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, {})
+
+ strategy.schedule({ 'jid' => 'new jid' }) {}
+ end
+
+ it 'logs the deduplication options of the worker' do
+ fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
+
+ expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
+ allow(fake_duplicate_job).to receive(:options).and_return({ foo: :bar })
+ expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, { foo: :bar })
+
+ strategy.schedule({ 'jid' => 'new jid' }) {}
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issue_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issue_activity_shared_examples.rb
new file mode 100644
index 00000000000..286305f2506
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issue_activity_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a tracked issue edit event' do |event|
+ before do
+ stub_application_setting(usage_ping_enabled: true)
+ end
+
+ def count_unique(date_from:, date_to:)
+ Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
+ end
+
+ specify do
+ aggregate_failures do
+ expect(track_action(author: user1)).to be_truthy
+ expect(track_action(author: user1)).to be_truthy
+ expect(track_action(author: user2)).to be_truthy
+ expect(track_action(author: user3, time: time - 3.days)).to be_truthy
+
+ expect(count_unique(date_from: time, date_to: time)).to eq(2)
+ expect(count_unique(date_from: time - 5.days, date_to: 1.day.since(time))).to eq(3)
+ end
+ end
+
+ it 'does not track edit actions if author is not present' do
+ expect(track_action(author: nil)).to be_nil
+ end
+
+ context 'when feature flag track_issue_activity_actions is disabled' do
+ it 'does not track edit actions' do
+ stub_feature_flags(track_issue_activity_actions: false)
+
+ expect(track_action(author: user1)).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb
index 7ce7b2161f6..0143bf693c7 100644
--- a/spec/support/shared_examples/mailers/notify_shared_examples.rb
+++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb
@@ -273,3 +273,12 @@ RSpec.shared_examples 'no email is sent' do
expect(subject.message).to be_a_kind_of(ActionMailer::Base::NullMail)
end
end
+
+RSpec.shared_examples 'does not render a manage notifications link' do
+ it do
+ aggregate_failures do
+ expect(subject).not_to have_body_text("Manage all notifications")
+ expect(subject).not_to have_body_text(profile_notifications_url)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/application_setting_shared_examples.rb b/spec/support/shared_examples/models/application_setting_shared_examples.rb
index 01513161d24..92fd4363134 100644
--- a/spec/support/shared_examples/models/application_setting_shared_examples.rb
+++ b/spec/support/shared_examples/models/application_setting_shared_examples.rb
@@ -1,84 +1,84 @@
# frozen_string_literal: true
-RSpec.shared_examples 'string of domains' do |attribute|
+RSpec.shared_examples 'string of domains' do |mapped_name, attribute|
it 'sets single domain' do
- setting.method("#{attribute}_raw=").call('example.com')
+ setting.method("#{mapped_name}_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')
+ setting.method("#{mapped_name}_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")
+ setting.method("#{mapped_name}_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")
+ setting.method("#{mapped_name}_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")
+ setting.method("#{mapped_name}_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")
+ setting.method("#{mapped_name}_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")
+ setting.method("#{mapped_name}_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;")
+ setting.method("#{mapped_name}_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)
+ setting.method("#{mapped_name}_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_behaves_like 'string of domains', :domain_whitelist
+ it_behaves_like 'string of domains', :domain_allowlist, :domain_allowlist
end
- context 'blacklisted signup domains' do
- it_behaves_like 'string of domains', :domain_blacklist
+ context 'denied signup domains' do
+ it_behaves_like 'string of domains', :domain_denylist, :domain_denylist
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')
+ setting.domain_denylist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_denylist.txt'))
+ expect(setting.domain_denylist).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_behaves_like 'string of domains', :outbound_local_requests_allowlist, :outbound_local_requests_whitelist
- it 'clears outbound_local_requests_whitelist_arrays memoization' do
- setting.outbound_local_requests_whitelist_raw = 'example.com'
+ it 'clears outbound_local_requests_allowlist_arrays memoization' do
+ setting.outbound_local_requests_allowlist_raw = 'example.com'
- expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ expect(setting.outbound_local_requests_allowlist_arrays).to contain_exactly(
[], [an_object_having_attributes(domain: 'example.com')]
)
- setting.outbound_local_requests_whitelist_raw = 'gitlab.com'
- expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ setting.outbound_local_requests_allowlist_raw = 'gitlab.com'
+ expect(setting.outbound_local_requests_allowlist_arrays).to contain_exactly(
[], [an_object_having_attributes(domain: 'gitlab.com')]
)
end
end
- context 'outbound_local_requests_whitelist_arrays' do
+ context 'outbound_local_requests_allowlist_arrays' do
it 'separates the IPs and domains' do
setting.outbound_local_requests_whitelist = [
'192.168.1.1',
@@ -118,7 +118,7 @@ RSpec.shared_examples 'application settings examples' do
an_object_having_attributes(domain: 'example.com', port: 8080)
]
- expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ expect(setting.outbound_local_requests_allowlist_arrays).to contain_exactly(
ip_whitelist, domain_whitelist
)
end
@@ -139,10 +139,10 @@ RSpec.shared_examples 'application settings examples' do
)
end
- it 'clears outbound_local_requests_whitelist_arrays memoization' do
+ it 'clears outbound_local_requests_allowlist_arrays memoization' do
setting.outbound_local_requests_whitelist = ['example.com']
- expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ expect(setting.outbound_local_requests_allowlist_arrays).to contain_exactly(
[],
[an_object_having_attributes(domain: 'example.com')]
)
@@ -151,7 +151,7 @@ RSpec.shared_examples 'application settings examples' do
['example.com', 'gitlab.com']
)
- expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ expect(setting.outbound_local_requests_allowlist_arrays).to contain_exactly(
[],
[an_object_having_attributes(domain: 'example.com'), an_object_having_attributes(domain: 'gitlab.com')]
)
@@ -163,7 +163,7 @@ RSpec.shared_examples 'application settings examples' do
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(
+ expect(setting.outbound_local_requests_allowlist_arrays).to contain_exactly(
[], [an_object_having_attributes(domain: 'gitlab.com')]
)
end
@@ -171,7 +171,7 @@ RSpec.shared_examples 'application settings examples' do
it 'does not raise error with nil' do
setting.outbound_local_requests_whitelist = nil
- expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly([], [])
+ expect(setting.outbound_local_requests_allowlist_arrays).to contain_exactly([], [])
end
end
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 62d56f2e86e..fe99b1cacd9 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
@@ -76,6 +76,26 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
end
end
+ describe 'supply of internal ids' do
+ let(:scope_value) { scope_attrs.each_value.first }
+ let(:method_name) { :"with_#{scope}_#{internal_id_attribute}_supply" }
+
+ it 'provides a persistent supply of IID values, sensitive to the current state' do
+ iid = rand(1..1000)
+ write_internal_id(iid)
+ instance.public_send(:"track_#{scope}_#{internal_id_attribute}!")
+
+ # Allocate 3 IID values
+ described_class.public_send(method_name, scope_value) do |supply|
+ 3.times { supply.next_value }
+ end
+
+ current_value = described_class.public_send(method_name, scope_value, &:current_value)
+
+ expect(current_value).to eq(iid + 3)
+ end
+ end
+
describe "#reset_scope_internal_id_attribute" do
it 'rewinds the allocated IID' do
expect { ensure_scope_attribute! }.not_to raise_error
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 85a7c90ee42..51071ae47c3 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
@@ -25,4 +25,21 @@ RSpec.shared_examples 'cluster application core specs' do |application_name|
describe '.association_name' do
it { expect(described_class.association_name).to eq(:"application_#{subject.name}") }
end
+
+ describe '#helm_command_module' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:helm_major_version, :expected_helm_command_module) do
+ 2 | Gitlab::Kubernetes::Helm::V2
+ 3 | Gitlab::Kubernetes::Helm::V3
+ end
+
+ with_them do
+ subject { described_class.new(cluster: cluster).helm_command_module }
+
+ let(:cluster) { build(:cluster, helm_major_version: helm_major_version)}
+
+ it { is_expected.to eq(expected_helm_command_module) }
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb
index ac8022a4726..187a44ec3cd 100644
--- a/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb
@@ -6,7 +6,7 @@ RSpec.shared_examples 'cluster application helm specs' do |application_name|
describe '#uninstall_command' do
subject { application.uninstall_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it 'has files' do
expect(subject.files).to eq(application.files)
diff --git a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
index 8092f87383d..17948d648cb 100644
--- a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
+++ b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'cycle analytics stage' do
+RSpec.shared_examples 'value stream analytics stage' do
let(:valid_params) do
{
name: 'My Stage',
@@ -111,7 +111,7 @@ RSpec.shared_examples 'cycle analytics stage' do
end
end
-RSpec.shared_examples 'cycle analytics label based stage' do
+RSpec.shared_examples 'value stream analytics label based stage' do
context 'when creating label based event' do
context 'when the label id is not passed' do
it 'returns validation error when `start_event_label_id` is missing' do
diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
index 37ee2548dfe..17fd2b836d3 100644
--- a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
@@ -13,7 +13,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type|
end
it 'renders the sidebar component empty state' do
- page.within '.time-tracking-no-tracking-pane' do
+ page.within '[data-testid="noTrackingPane"]' do
expect(page).to have_content 'No estimate or time spent'
end
end
@@ -22,7 +22,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type|
submit_time('/estimate 3w 1d 1h')
wait_for_requests
- page.within '.time-tracking-estimate-only-pane' do
+ page.within '[data-testid="estimateOnlyPane"]' do
expect(page).to have_content '3w 1d 1h'
end
end
@@ -31,7 +31,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type|
submit_time('/spend 3w 1d 1h')
wait_for_requests
- page.within '.time-tracking-spend-only-pane' do
+ page.within '[data-testid="spentOnlyPane"]' do
expect(page).to have_content '3w 1d 1h'
end
end
@@ -41,7 +41,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type|
submit_time('/spend 3w 1d 1h')
wait_for_requests
- page.within '.time-tracking-comparison-pane' do
+ page.within '[data-testid="timeTrackingComparisonPane"]' do
expect(page).to have_content '3w 1d 1h'
end
end
diff --git a/spec/support/shared_examples/read_only_message_shared_examples.rb b/spec/support/shared_examples/read_only_message_shared_examples.rb
new file mode 100644
index 00000000000..4ae97ea7748
--- /dev/null
+++ b/spec/support/shared_examples/read_only_message_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'Read-only instance' do |message|
+ it 'shows read-only banner' do
+ visit root_dashboard_path
+
+ expect(page).to have_content(message)
+ end
+end
+
+RSpec.shared_examples 'Read-write instance' do |message|
+ it 'does not show read-only banner' do
+ visit root_dashboard_path
+
+ expect(page).not_to have_content(message)
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
index ec32cb4b2ff..f55043fe64f 100644
--- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
@@ -20,7 +20,7 @@ RSpec.shared_context 'Debian repository shared context' do |object_type|
let(:source_package) { 'sample' }
let(:letter) { source_package[0..2] == 'lib' ? source_package[0..3] : source_package[0] }
let(:package_name) { 'libsample0' }
- let(:package_version) { '1.2.3~alpha2-1' }
+ let(:package_version) { '1.2.3~alpha2' }
let(:file_name) { "#{package_name}_#{package_version}_#{architecture}.deb" }
let(:method) { :get }
diff --git a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
index 6315c10b0c4..a12cb24a513 100644
--- a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
@@ -117,15 +117,10 @@ RSpec.shared_examples 'discussions API' do |parent_type, noteable_type, id_name,
expect(response).to have_gitlab_http_status(:unauthorized)
end
- it 'tracks a Notes::CreateService event' do
- expect(Gitlab::Tracking).to receive(:event) do |category, action, data|
- expect(category).to eq('Notes::CreateService')
- expect(action).to eq('execute')
- expect(data[:label]).to eq('note')
- expect(data[:value]).to be_an(Integer)
- end
-
+ it 'tracks a Notes::CreateService event', :snowplow do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), params: { body: 'hi!' }
+
+ expect_snowplow_event(category: 'Notes::CreateService', action: 'execute', label: 'note', value: anything)
end
context 'with notes_create_service_tracking feature flag disabled' do
@@ -133,10 +128,10 @@ RSpec.shared_examples 'discussions API' do |parent_type, noteable_type, id_name,
stub_feature_flags(notes_create_service_tracking: false)
end
- it 'does not track any events' do
- expect(Gitlab::Tracking).not_to receive(:event)
-
+ it 'does not track any events', :snowplow do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), params: { body: 'hi!' }
+
+ expect_no_snowplow_event
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/read_only_instance_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/read_only_instance_shared_examples.rb
new file mode 100644
index 00000000000..be163d6aa0e
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/read_only_instance_shared_examples.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'graphql on a read-only GitLab instance' do
+ include GraphqlHelpers
+
+ context 'mutations' do
+ let(:current_user) { note.author }
+ let!(:note) { create(:note) }
+
+ let(:mutation) do
+ variables = {
+ id: GitlabSchema.id_from_object(note).to_s
+ }
+
+ graphql_mutation(:destroy_note, variables)
+ end
+
+ it 'disallows the query' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(json_response['errors'].first['message']).to eq(Mutations::BaseMutation::ERROR_MESSAGE)
+ end
+
+ it 'does not destroy the Note' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Note.count }
+ end
+ end
+
+ context 'read-only queries' do
+ let(:current_user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'allows the query' do
+ query = graphql_query_for('project', 'fullPath' => project.full_path)
+
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']).not_to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb
new file mode 100644
index 00000000000..02e50b789cc
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'fetches labels' do
+ it 'returns correct labels' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response).to all(match_schema('public_api/v4/labels/label'))
+ expect(json_response.size).to eq(expected_labels.size)
+ expect(json_response.map {|r| r['name'] }).to match_array(expected_labels)
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
new file mode 100644
index 00000000000..54aa9d47dd8
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'multiple and scoped issue boards' do |route_definition|
+ let(:root_url) { route_definition.gsub(":id", board_parent.id.to_s) }
+
+ context 'multiple issue boards' do
+ before do
+ board_parent.add_reporter(user)
+ stub_licensed_features(multiple_group_issue_boards: true)
+ end
+
+ describe "POST #{route_definition}" do
+ it 'creates a board' do
+ post api(root_url, user), params: { name: "new board" }
+
+ expect(response).to have_gitlab_http_status(:created)
+
+ expect(response).to match_response_schema('public_api/v4/board', dir: "ee")
+ end
+ end
+
+ describe "PUT #{route_definition}/:board_id" do
+ let(:url) { "#{root_url}/#{board.id}" }
+
+ it 'updates a board' do
+ put api(url, user), params: { name: 'new name', weight: 4, labels: 'foo, bar' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ expect(response).to match_response_schema('public_api/v4/board', dir: "ee")
+
+ board.reload
+
+ expect(board.name).to eq('new name')
+ expect(board.weight).to eq(4)
+ expect(board.labels.map(&:title)).to contain_exactly('foo', 'bar')
+ end
+
+ it 'does not remove missing attributes from the board' do
+ expect { put api(url, user), params: { name: 'new name' } }
+ .to not_change { board.reload.assignee }
+ .and not_change { board.reload.milestone }
+ .and not_change { board.reload.weight }
+ .and not_change { board.reload.labels.map(&:title).sort }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/board', dir: "ee")
+ end
+
+ it 'allows removing optional attributes' do
+ put api(url, user), params: { name: 'new name', assignee_id: nil, milestone_id: nil, weight: nil, labels: nil }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/board', dir: "ee")
+
+ board.reload
+
+ expect(board.name).to eq('new name')
+ expect(board.assignee).to be_nil
+ expect(board.milestone).to be_nil
+ expect(board.weight).to be_nil
+ expect(board.labels).to be_empty
+ end
+ end
+
+ describe "DELETE #{route_definition}/:board_id" do
+ let(:url) { "#{root_url}/#{board.id}" }
+
+ it 'deletes a board' do
+ delete api(url, user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+ end
+
+ context 'with the scoped_issue_board-feature available' do
+ it 'returns the milestone when the `scoped_issue_board` feature is enabled' do
+ stub_licensed_features(scoped_issue_board: true)
+
+ get api(root_url, user)
+
+ expect(json_response.first["milestone"]).not_to be_nil
+ end
+
+ it 'hides the milestone when the `scoped_issue_board` feature is disabled' do
+ stub_licensed_features(scoped_issue_board: false)
+
+ get api(root_url, user)
+
+ expect(json_response.first["milestone"]).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
new file mode 100644
index 00000000000..d3ad7aa0595
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -0,0 +1,270 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'handling get metadata requests' do
+ let_it_be(:package_dependency_link1) { create(:packages_dependency_link, package: package, dependency_type: :dependencies) }
+ let_it_be(:package_dependency_link2) { create(:packages_dependency_link, package: package, dependency_type: :devDependencies) }
+ let_it_be(:package_dependency_link3) { create(:packages_dependency_link, package: package, dependency_type: :bundleDependencies) }
+ let_it_be(:package_dependency_link4) { create(:packages_dependency_link, package: package, dependency_type: :peerDependencies) }
+
+ let(:params) { {} }
+ let(:headers) { {} }
+
+ subject { get(url, params: params, headers: headers) }
+
+ shared_examples 'returning the npm package info' do
+ it 'returns the package info' do
+ subject
+
+ expect_a_valid_package_response
+ end
+ end
+
+ shared_examples 'a package that requires auth' do
+ it 'denies request without oauth token' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'with oauth token' do
+ let(:params) { { access_token: token.token } }
+
+ it 'returns the package info with oauth token' do
+ subject
+
+ expect_a_valid_package_response
+ end
+ end
+
+ context 'with job token' do
+ let(:params) { { job_token: job.token } }
+
+ it 'returns the package info with running job token' do
+ subject
+
+ expect_a_valid_package_response
+ end
+
+ it 'denies request without running job token' do
+ job.update!(status: :success)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'with deploy token' do
+ let(:headers) { build_token_auth_header(deploy_token.token) }
+
+ it 'returns the package info with deploy token' do
+ subject
+
+ expect_a_valid_package_response
+ end
+ end
+ end
+
+ context 'a public project' do
+ it_behaves_like 'returning the npm package info'
+
+ context 'project path with a dot' do
+ before do
+ project.update!(path: 'foo.bar')
+ end
+
+ it_behaves_like 'returning the npm package info'
+ end
+
+ context 'with request forward disabled' do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: false)
+ end
+
+ it_behaves_like 'returning the npm package info'
+
+ context 'with unknown package' do
+ let(:package_name) { 'unknown' }
+
+ it 'returns the proper response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'with request forward enabled' do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: true)
+ end
+
+ it_behaves_like 'returning the npm package info'
+
+ context 'with unknown package' do
+ let(:package_name) { 'unknown' }
+
+ it 'returns a redirect' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response.headers['Location']).to eq('https://registry.npmjs.org/unknown')
+ end
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'npm_request_forward'
+ end
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'a package that requires auth'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'a package that requires auth'
+
+ context 'with guest' do
+ let(:params) { { access_token: token.token } }
+
+ it 'denies request when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ def expect_a_valid_package_response
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/json')
+ expect(response).to match_response_schema('public_api/v4/packages/npm_package')
+ expect(json_response['name']).to eq(package.name)
+ expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version')
+ ::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any
+ end
+ expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags')
+ end
+end
+
+RSpec.shared_examples 'handling get dist tags requests' do
+ let_it_be(:package_tag1) { create(:packages_tag, package: package) }
+ let_it_be(:package_tag2) { create(:packages_tag, package: package) }
+
+ let(:params) { {} }
+
+ subject { get(url, params: params) }
+
+ context 'with public project' do
+ context 'with authenticated user' do
+ let(:params) { { private_token: personal_access_token.token } }
+
+ it_behaves_like 'returns package tags', :maintainer
+ it_behaves_like 'returns package tags', :developer
+ it_behaves_like 'returns package tags', :reporter
+ it_behaves_like 'returns package tags', :guest
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'returns package tags', :no_type
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ let(:params) { { private_token: personal_access_token.token } }
+
+ it_behaves_like 'returns package tags', :maintainer
+ it_behaves_like 'returns package tags', :developer
+ it_behaves_like 'returns package tags', :reporter
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :not_found
+ end
+ end
+end
+
+RSpec.shared_examples 'handling create dist tag requests' do
+ let_it_be(:tag_name) { 'test' }
+
+ let(:params) { {} }
+ let(:env) { {} }
+ let(:version) { package.version }
+
+ subject { put(url, env: env, params: params) }
+
+ context 'with public project' do
+ context 'with authenticated user' do
+ let(:params) { { private_token: personal_access_token.token } }
+ let(:env) { { 'api.request.body': version } }
+
+ it_behaves_like 'create package tag', :maintainer
+ it_behaves_like 'create package tag', :developer
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+end
+
+RSpec.shared_examples 'handling delete dist tag requests' do
+ let_it_be(:package_tag) { create(:packages_tag, package: package) }
+
+ let(:params) { {} }
+ let(:tag_name) { package_tag.name }
+
+ subject { delete(url, params: params) }
+
+ context 'with public project' do
+ context 'with authenticated user' do
+ let(:params) { { private_token: personal_access_token.token } }
+
+ it_behaves_like 'delete package tag', :maintainer
+ it_behaves_like 'rejects package tags access', :developer, :forbidden
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ let(:params) { { private_token: personal_access_token.token } }
+
+ it_behaves_like 'delete package tag', :maintainer
+ it_behaves_like 'rejects package tags access', :developer, :forbidden
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
index d730ed53109..3833604e304 100644
--- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -128,9 +128,13 @@ RSpec.shared_examples 'job token for package uploads' do
end
RSpec.shared_examples 'a package tracking event' do |category, action|
- it "creates a gitlab tracking event #{action}" do
- expect(Gitlab::Tracking).to receive(:event).with(category, action, {})
+ before do
+ stub_feature_flags(collect_package_events: true)
+ end
+ it "creates a gitlab tracking event #{action}", :snowplow do
expect { subject }.to change { Packages::Event.count }.by(1)
+
+ expect_snowplow_event(category: category, action: action)
end
end
diff --git a/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
index a371d380f47..2c203dc096e 100644
--- a/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
@@ -40,7 +40,7 @@ RSpec.shared_examples 'returns package tags' do |user_type|
context 'with invalid package name' do
where(:package_name, :status) do
'%20' | :bad_request
- nil | :forbidden
+ nil | :not_found
end
with_them do
@@ -95,7 +95,7 @@ RSpec.shared_examples 'create package tag' do |user_type|
context 'with invalid package name' do
where(:package_name, :status) do
- 'unknown' | :forbidden
+ 'unknown' | :not_found
'' | :not_found
'%20' | :bad_request
end
@@ -160,7 +160,7 @@ RSpec.shared_examples 'delete package tag' do |user_type|
context 'with invalid package name' do
where(:package_name, :status) do
- 'unknown' | :forbidden
+ 'unknown' | :not_found
'' | :not_found
'%20' | :bad_request
end
diff --git a/spec/support/shared_examples/requests/api/tracking_shared_examples.rb b/spec/support/shared_examples/requests/api/tracking_shared_examples.rb
index 2e6feae3f98..826139635ed 100644
--- a/spec/support/shared_examples/requests/api/tracking_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/tracking_shared_examples.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
RSpec.shared_examples 'a gitlab tracking event' do |category, action|
- it "creates a gitlab tracking event #{action}" do
- expect(Gitlab::Tracking).to receive(:event).with(category, action, {})
-
+ it "creates a gitlab tracking event #{action}", :snowplow do
subject
+
+ expect_snowplow_event(category: category, action: action)
end
end
diff --git a/spec/support/shared_examples/requests/lfs_http_shared_examples.rb b/spec/support/shared_examples/requests/lfs_http_shared_examples.rb
index 48c5a5933e6..4ae77179527 100644
--- a/spec/support/shared_examples/requests/lfs_http_shared_examples.rb
+++ b/spec/support/shared_examples/requests/lfs_http_shared_examples.rb
@@ -2,42 +2,252 @@
RSpec.shared_examples 'LFS http 200 response' do
it_behaves_like 'LFS http expected response code and message' do
- let(:response_code) { 200 }
+ let(:response_code) { :ok }
+ end
+end
+
+RSpec.shared_examples 'LFS http 200 blob response' do
+ it_behaves_like 'LFS http expected response code and message' do
+ let(:response_code) { :ok }
+ let(:content_type) { Repositories::LfsApiController::LFS_TRANSFER_CONTENT_TYPE }
+ let(:response_headers) { { 'X-Sendfile' => lfs_object.file.path } }
+ end
+end
+
+RSpec.shared_examples 'LFS http 200 workhorse response' do
+ it_behaves_like 'LFS http expected response code and message' do
+ let(:response_code) { :ok }
+ let(:content_type) { Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE }
end
end
RSpec.shared_examples 'LFS http 401 response' do
it_behaves_like 'LFS http expected response code and message' do
- let(:response_code) { 401 }
+ let(:response_code) { :unauthorized }
+ let(:content_type) { 'text/plain' }
end
end
RSpec.shared_examples 'LFS http 403 response' do
it_behaves_like 'LFS http expected response code and message' do
- let(:response_code) { 403 }
+ let(:response_code) { :forbidden }
let(:message) { 'Access forbidden. Check your access level.' }
end
end
RSpec.shared_examples 'LFS http 501 response' do
it_behaves_like 'LFS http expected response code and message' do
- let(:response_code) { 501 }
+ let(:response_code) { :not_implemented }
let(:message) { 'Git LFS is not enabled on this GitLab server, contact your admin.' }
end
end
RSpec.shared_examples 'LFS http 404 response' do
it_behaves_like 'LFS http expected response code and message' do
- let(:response_code) { 404 }
+ let(:response_code) { :not_found }
end
end
RSpec.shared_examples 'LFS http expected response code and message' do
let(:response_code) { }
- let(:message) { }
+ let(:response_headers) { {} }
+ let(:content_type) { LfsRequest::CONTENT_TYPE }
+ let(:message) {}
- it 'responds with the expected response code and message' do
+ specify do
expect(response).to have_gitlab_http_status(response_code)
+ expect(response.headers.to_hash).to include(response_headers)
+ expect(response.media_type).to match(content_type)
expect(json_response['message']).to eq(message) if message
end
end
+
+RSpec.shared_examples 'LFS http requests' do
+ include LfsHttpHelpers
+
+ let(:authorize_guest) {}
+ let(:authorize_download) {}
+ let(:authorize_upload) {}
+
+ let(:lfs_object) { create(:lfs_object, :with_file) }
+ let(:sample_oid) { lfs_object.oid }
+
+ let(:authorization) { authorize_user }
+ let(:headers) do
+ {
+ 'Authorization' => authorization,
+ 'X-Sendfile-Type' => 'X-Sendfile'
+ }
+ end
+
+ let(:request_download) do
+ get objects_url(container, sample_oid), params: {}, headers: headers
+ end
+
+ let(:request_upload) do
+ post_lfs_json batch_url(container), upload_body(multiple_objects), headers
+ end
+
+ before do
+ stub_lfs_setting(enabled: true)
+ end
+
+ context 'when LFS is disabled globally' do
+ before do
+ stub_lfs_setting(enabled: false)
+ end
+
+ describe 'download request' do
+ before do
+ request_download
+ end
+
+ it_behaves_like 'LFS http 501 response'
+ end
+
+ describe 'upload request' do
+ before do
+ request_upload
+ end
+
+ it_behaves_like 'LFS http 501 response'
+ end
+ end
+
+ context 'unauthenticated' do
+ let(:headers) { {} }
+
+ describe 'download request' do
+ before do
+ request_download
+ end
+
+ it_behaves_like 'LFS http 401 response'
+ end
+
+ describe 'upload request' do
+ before do
+ request_upload
+ end
+
+ it_behaves_like 'LFS http 401 response'
+ end
+ end
+
+ context 'without access' do
+ describe 'download request' do
+ before do
+ request_download
+ end
+
+ it_behaves_like 'LFS http 404 response'
+ end
+
+ describe 'upload request' do
+ before do
+ request_upload
+ end
+
+ it_behaves_like 'LFS http 404 response'
+ end
+ end
+
+ context 'with guest access' do
+ before do
+ authorize_guest
+ end
+
+ describe 'download request' do
+ before do
+ request_download
+ end
+
+ it_behaves_like 'LFS http 404 response'
+ end
+
+ describe 'upload request' do
+ before do
+ request_upload
+ end
+
+ it_behaves_like 'LFS http 404 response'
+ end
+ end
+
+ context 'with download permission' do
+ before do
+ authorize_download
+ end
+
+ describe 'download request' do
+ before do
+ request_download
+ end
+
+ it_behaves_like 'LFS http 200 blob response'
+
+ context 'when container does not exist' do
+ def objects_url(*args)
+ super.sub(container.full_path, 'missing/path')
+ end
+
+ it_behaves_like 'LFS http 404 response'
+ end
+ end
+
+ describe 'upload request' do
+ before do
+ request_upload
+ end
+
+ it_behaves_like 'LFS http 403 response'
+ end
+ end
+
+ context 'with upload permission' do
+ before do
+ authorize_upload
+ end
+
+ describe 'upload request' do
+ before do
+ request_upload
+ end
+
+ it_behaves_like 'LFS http 200 response'
+ end
+ end
+
+ describe 'deprecated API' do
+ shared_examples 'deprecated request' do
+ before do
+ request
+ end
+
+ it_behaves_like 'LFS http expected response code and message' do
+ let(:response_code) { 501 }
+ let(:message) { 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.' }
+ end
+ end
+
+ context 'when fetching LFS object using deprecated API' do
+ subject(:request) do
+ get deprecated_objects_url(container, sample_oid), params: {}, headers: headers
+ end
+
+ it_behaves_like 'deprecated request'
+ end
+
+ context 'when handling LFS request using deprecated API' do
+ subject(:request) do
+ post_lfs_json deprecated_objects_url(container), nil, headers
+ end
+
+ it_behaves_like 'deprecated request'
+ end
+
+ def deprecated_objects_url(container, oid = nil)
+ File.join(["#{container.http_url_to_repo}/info/lfs/objects/", oid].compact)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
index 730df4dc5ab..d4ee68309ff 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -81,8 +81,15 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
end
it 'logs RackAttack info into structured logs' do
- requests_per_period.times do
- make_request(request_args)
+ control_count = 0
+
+ requests_per_period.times do |i|
+ if i == 0
+ control_count = ActiveRecord::QueryRecorder.new { make_request(request_args) }.count
+ else
+ make_request(request_args)
+ end
+
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
@@ -93,13 +100,15 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
request_method: request_method,
path: request_args.first,
user_id: user.id,
- username: user.username,
- throttle_type: throttle_types[throttle_setting_prefix]
+ 'meta.user' => user.username,
+ matched: throttle_types[throttle_setting_prefix]
}
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
- expect_rejection { make_request(request_args) }
+ expect_rejection do
+ expect { make_request(request_args) }.not_to exceed_query_limit(control_count)
+ end
end
end
@@ -210,8 +219,15 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
end
it 'logs RackAttack info into structured logs' do
- requests_per_period.times do
- request_authenticated_web_url
+ control_count = 0
+
+ requests_per_period.times do |i|
+ if i == 0
+ control_count = ActiveRecord::QueryRecorder.new { request_authenticated_web_url }.count
+ else
+ request_authenticated_web_url
+ end
+
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
@@ -222,13 +238,12 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
request_method: request_method,
path: url_that_requires_authentication,
user_id: user.id,
- username: user.username,
- throttle_type: throttle_types[throttle_setting_prefix]
+ 'meta.user' => user.username,
+ matched: throttle_types[throttle_setting_prefix]
}
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
-
- request_authenticated_web_url
+ expect { request_authenticated_web_url }.not_to exceed_query_limit(control_count)
end
end
diff --git a/spec/support/shared_examples/serializers/note_entity_shared_examples.rb b/spec/support/shared_examples/serializers/note_entity_shared_examples.rb
index a90a2dc3667..9af6ec45e49 100644
--- a/spec/support/shared_examples/serializers/note_entity_shared_examples.rb
+++ b/spec/support/shared_examples/serializers/note_entity_shared_examples.rb
@@ -5,8 +5,21 @@ RSpec.shared_examples 'note entity' do
context 'basic note' do
it 'exposes correct elements' do
- expect(subject).to include(:type, :author, :note, :note_html, :current_user, :discussion_id,
- :emoji_awardable, :award_emoji, :report_abuse_path, :attachment, :noteable_note_url, :resolvable)
+ expect(subject).to include(
+ :attachment,
+ :author,
+ :award_emoji,
+ :base_discussion,
+ :current_user,
+ :discussion_id,
+ :emoji_awardable,
+ :note,
+ :note_html,
+ :noteable_note_url,
+ :report_abuse_path,
+ :resolvable,
+ :type
+ )
end
it 'does not expose elements for specific notes cases' do
@@ -20,6 +33,39 @@ RSpec.shared_examples 'note entity' do
it 'does not expose web_url for author' do
expect(subject[:author]).not_to include(:web_url)
end
+
+ it 'exposes permission fields on current_user' do
+ expect(subject[:current_user]).to include(:can_edit, :can_award_emoji, :can_resolve, :can_resolve_discussion)
+ end
+
+ describe ':can_resolve_discussion' do
+ context 'discussion is resolvable' do
+ before do
+ expect(note.discussion).to receive(:resolvable?).and_return(true)
+ end
+
+ context 'user can resolve' do
+ it 'is true' do
+ expect(note.discussion).to receive(:can_resolve?).with(user).and_return(true)
+ expect(subject[:current_user][:can_resolve_discussion]).to be_truthy
+ end
+ end
+
+ context 'user cannot resolve' do
+ it 'is false' do
+ expect(note.discussion).to receive(:can_resolve?).with(user).and_return(false)
+ expect(subject[:current_user][:can_resolve_discussion]).to be_falsey
+ end
+ end
+ end
+
+ context 'discussion is not resolvable' do
+ it 'is false' do
+ expect(note.discussion).to receive(:resolvable?).and_return(false)
+ expect(subject[:current_user][:can_resolve_discussion]).to be_falsey
+ end
+ end
+ end
end
context 'when note was edited' do
diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb
index 1ae74979b7a..003705ca21c 100644
--- a/spec/support/shared_examples/services/alert_management_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -8,11 +8,11 @@ RSpec.shared_examples 'creates an alert management alert' do
end
it 'executes the alert service hooks' do
- slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
+ expect_next_instance_of(AlertManagement::Alert) do |alert|
+ expect(alert).to receive(:execute_services)
+ end
subject
-
- expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
end
end
diff --git a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
index 5b95a5753a1..7b277d4bede 100644
--- a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
+++ b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
@@ -17,13 +17,13 @@ RSpec.shared_examples 'system note creation' do |update_params, note_text|
end
end
-RSpec.shared_examples 'draft notes creation' do |wip_action|
+RSpec.shared_examples 'draft notes creation' do |action|
subject { described_class.new(project, user).execute(issuable, old_labels: []) }
it 'creates Draft toggle and title change notes' do
expect { subject }.to change { Note.count }.from(0).to(2)
- expect(Note.first.note).to match("#{wip_action} as a **Work In Progress**")
+ expect(Note.first.note).to match("marked this merge request as **#{action}**")
expect(Note.second.note).to match('changed title')
end
end
diff --git a/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb b/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb
index 7fc7ff8a8de..cbe5c7d89db 100644
--- a/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb
+++ b/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb
@@ -3,7 +3,6 @@
RSpec.shared_examples 'mapping jira users' do
let(:client) { double }
- let_it_be(:project) { create(:project) }
let_it_be(:jira_service) { create(:jira_service, project: project, active: true) }
before do
@@ -11,7 +10,7 @@ RSpec.shared_examples 'mapping jira users' do
allow(client).to receive(:get).with(url).and_return(jira_users)
end
- subject { described_class.new(jira_service, start_at) }
+ subject { described_class.new(current_user, project, start_at) }
context 'jira_users is nil' do
let(:jira_users) { nil }
@@ -22,18 +21,27 @@ RSpec.shared_examples 'mapping jira users' do
end
context 'when jira_users is present' do
- # TODO: now we only create an array in a proper format
- # mapping is tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/219023
let(:mapped_users) do
[
- { jira_account_id: 'abcd', jira_display_name: 'user1', jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
- { jira_account_id: 'efg', jira_display_name: nil, jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
- { jira_account_id: 'hij', jira_display_name: 'user3', jira_email: 'user3@example.com', gitlab_id: nil, gitlab_username: nil, gitlab_name: nil }
+ { jira_account_id: 'abcd', jira_display_name: 'User-Name1', jira_email: nil, gitlab_id: user_1.id },
+ { jira_account_id: 'efg', jira_display_name: 'username-2', jira_email: nil, gitlab_id: user_2.id },
+ { jira_account_id: 'hij', jira_display_name: nil, jira_email: nil, gitlab_id: nil },
+ { jira_account_id: '123', jira_display_name: 'user-4', jira_email: 'user-4@example.com', gitlab_id: user_4.id },
+ { jira_account_id: '456', jira_display_name: 'username5foo', jira_email: 'user-5@example.com', gitlab_id: nil },
+ { jira_account_id: '789', jira_display_name: 'user-6', jira_email: 'user-6@example.com', gitlab_id: nil },
+ { jira_account_id: 'xyz', jira_display_name: 'username-7', jira_email: 'user-7@example.com', gitlab_id: nil },
+ { jira_account_id: 'vhk', jira_display_name: 'user-8', jira_email: 'user8_email@example.com', gitlab_id: user_8.id },
+ { jira_account_id: 'uji', jira_display_name: 'user-9', jira_email: 'uji@example.com', gitlab_id: user_1.id }
]
end
it 'returns users mapped to Gitlab' do
expect(subject.execute).to eq(mapped_users)
end
+
+ # 1 query for getting matched users, 3 queries for MembersFinder
+ it 'runs only 4 queries' do
+ expect { subject }.not_to exceed_query_limit(4)
+ end
end
end
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
index 65f4b3b5513..7987f2c296b 100644
--- a/spec/support/shared_examples/services/packages_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -8,8 +8,8 @@ RSpec.shared_examples 'assigns build to package' do
it 'assigns the pipeline to the package' do
package = subject
- expect(package.build_info).to be_present
- expect(package.build_info.pipeline).to eq job.pipeline
+ expect(package.original_build_info).to be_present
+ expect(package.original_build_info.pipeline).to eq job.pipeline
end
end
end
diff --git a/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb b/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb
index 15bf0d3698a..d9e906ebb75 100644
--- a/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb
+++ b/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb
@@ -4,6 +4,7 @@ RSpec.shared_examples 'pages size limit is' do |size_limit|
context "when size is below the limit" do
before do
allow(metadata).to receive(:total_size).and_return(size_limit - 1.megabyte)
+ allow(metadata).to receive(:entries).and_return([])
end
it 'updates pages correctly' do
@@ -17,6 +18,7 @@ RSpec.shared_examples 'pages size limit is' do |size_limit|
context "when size is above the limit" do
before do
allow(metadata).to receive(:total_size).and_return(size_limit + 1.megabyte)
+ allow(metadata).to receive(:entries).and_return([])
end
it 'limits the maximum size of gitlab pages' do
diff --git a/spec/support/shared_examples/uploaders/workers/object_storage/migrate_uploads_shared_examples.rb b/spec/support/shared_examples/uploaders/workers/object_storage/migrate_uploads_shared_examples.rb
deleted file mode 100644
index 5a9a3dfc2d2..00000000000
--- a/spec/support/shared_examples/uploaders/workers/object_storage/migrate_uploads_shared_examples.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-# frozen_string_literal: true
-
-# Expects the calling spec to define:
-# - model_class
-# - mounted_as
-# - to_store
-RSpec.shared_examples 'uploads migration worker' do
- def perform(uploads, store = nil)
- described_class.new.perform(uploads.ids, model_class.to_s, mounted_as, store || to_store)
- rescue ObjectStorage::MigrateUploadsWorker::Report::MigrationFailures
- # swallow
- end
-
- describe '.enqueue!' do
- def enqueue!
- described_class.enqueue!(uploads, model_class, mounted_as, to_store)
- end
-
- it 'is guarded by .sanity_check!' do
- expect(described_class).to receive(:perform_async)
- expect(described_class).to receive(:sanity_check!)
-
- enqueue!
- end
-
- context 'sanity_check! fails' do
- include_context 'sanity_check! fails'
-
- it 'does not enqueue a job' do
- expect(described_class).not_to receive(:perform_async)
-
- expect { enqueue! }.to raise_error(described_class::SanityCheckError)
- end
- end
- end
-
- describe '.sanity_check!' do
- shared_examples 'raises a SanityCheckError' do |expected_message|
- let(:mount_point) { nil }
-
- it do
- expect { described_class.sanity_check!(uploads, model_class, mount_point) }
- .to raise_error(described_class::SanityCheckError).with_message(expected_message)
- end
- end
-
- context 'uploader types mismatch' do
- let!(:outlier) { create(:upload, uploader: 'GitlabUploader') }
-
- include_examples 'raises a SanityCheckError', /Multiple uploaders found/
- end
-
- context 'mount point not found' do
- include_examples 'raises a SanityCheckError', /Mount point [a-z:]+ not found in/ do
- let(:mount_point) { :potato }
- end
- end
- end
-
- describe '#perform' do
- shared_examples 'outputs correctly' do |success: 0, failures: 0|
- total = success + failures
-
- if success > 0
- it 'outputs the reports' do
- expect(Gitlab::AppLogger).to receive(:info).with(%r{Migrated #{success}/#{total} files})
-
- perform(uploads)
- end
- end
-
- if failures > 0
- it 'outputs upload failures' do
- expect(Gitlab::AppLogger).to receive(:warn).with(/Error .* I am a teapot/)
-
- perform(uploads)
- end
- end
- end
-
- it_behaves_like 'outputs correctly', success: 10
-
- it 'migrates files to remote storage' do
- perform(uploads)
-
- expect(Upload.where(store: ObjectStorage::Store::LOCAL).count).to eq(0)
- end
-
- context 'reversed' do
- let(:to_store) { ObjectStorage::Store::LOCAL }
-
- before do
- perform(uploads, ObjectStorage::Store::REMOTE)
- end
-
- it 'migrates files to local storage' do
- expect(Upload.where(store: ObjectStorage::Store::REMOTE).count).to eq(10)
-
- perform(uploads)
-
- expect(Upload.where(store: ObjectStorage::Store::LOCAL).count).to eq(10)
- end
- end
-
- context 'migration is unsuccessful' do
- before do
- allow_any_instance_of(ObjectStorage::Concern)
- .to receive(:migrate!).and_raise(CarrierWave::UploadError, 'I am a teapot.')
- end
-
- it_behaves_like 'outputs correctly', failures: 10
- end
- end
-end
-
-RSpec.shared_context 'sanity_check! fails' do
- before do
- expect(described_class).to receive(:sanity_check!).and_raise(described_class::SanityCheckError)
- end
-end
diff --git a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
index 50879969e90..37f44f98cda 100644
--- a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
+++ b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
@@ -1,40 +1,32 @@
# frozen_string_literal: true
-# Expects `worker_class` to be defined
+# Expects `subject` to be a job/worker instance
RSpec.shared_examples 'reenqueuer' do
- subject(:job) { worker_class.new }
-
before do
- allow(job).to receive(:sleep) # faster tests
+ allow(subject).to receive(:sleep) # faster tests
end
it 'implements lease_timeout' do
- expect(job.lease_timeout).to be_a(ActiveSupport::Duration)
+ expect(subject.lease_timeout).to be_a(ActiveSupport::Duration)
end
describe '#perform' do
it 'tries to obtain a lease' do
- expect_to_obtain_exclusive_lease(job.lease_key)
+ expect_to_obtain_exclusive_lease(subject.lease_key)
- job.perform
+ subject.perform
end
end
end
-# Example usage:
-#
-# it_behaves_like 'it is rate limited to 1 call per', 5.seconds do
-# subject { described_class.new }
-# let(:rate_limited_method) { subject.perform }
-# end
-#
-RSpec.shared_examples 'it is rate limited to 1 call per' do |minimum_duration|
+# Expects `subject` to be a job/worker instance
+RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_duration|
before do
# Allow Timecop freeze and travel without the block form
Timecop.safe_mode = false
Timecop.freeze
- time_travel_during_rate_limited_method(actual_duration)
+ time_travel_during_perform(actual_duration)
end
after do
@@ -48,7 +40,7 @@ RSpec.shared_examples 'it is rate limited to 1 call per' do |minimum_duration|
it 'sleeps exactly the minimum duration' do
expect(subject).to receive(:sleep).with(a_value_within(0.01).of(minimum_duration))
- rate_limited_method
+ subject.perform
end
end
@@ -58,7 +50,7 @@ RSpec.shared_examples 'it is rate limited to 1 call per' do |minimum_duration|
it 'sleeps 90% of minimum duration' do
expect(subject).to receive(:sleep).with(a_value_within(0.01).of(0.9 * minimum_duration))
- rate_limited_method
+ subject.perform
end
end
@@ -68,7 +60,7 @@ RSpec.shared_examples 'it is rate limited to 1 call per' do |minimum_duration|
it 'sleeps 10% of minimum duration' do
expect(subject).to receive(:sleep).with(a_value_within(0.01).of(0.1 * minimum_duration))
- rate_limited_method
+ subject.perform
end
end
@@ -78,7 +70,7 @@ RSpec.shared_examples 'it is rate limited to 1 call per' do |minimum_duration|
it 'does not sleep' do
expect(subject).not_to receive(:sleep)
- rate_limited_method
+ subject.perform
end
end
@@ -88,7 +80,7 @@ RSpec.shared_examples 'it is rate limited to 1 call per' do |minimum_duration|
it 'does not sleep' do
expect(subject).not_to receive(:sleep)
- rate_limited_method
+ subject.perform
end
end
@@ -98,11 +90,11 @@ RSpec.shared_examples 'it is rate limited to 1 call per' do |minimum_duration|
it 'does not sleep' do
expect(subject).not_to receive(:sleep)
- rate_limited_method
+ subject.perform
end
end
- def time_travel_during_rate_limited_method(actual_duration)
+ def time_travel_during_perform(actual_duration)
# Save the original implementation of ensure_minimum_duration
original_ensure_minimum_duration = subject.method(:ensure_minimum_duration)
diff --git a/spec/support/snowplow.rb b/spec/support/snowplow.rb
index 58812b8f4e6..b67fa96fab8 100644
--- a/spec/support/snowplow.rb
+++ b/spec/support/snowplow.rb
@@ -1,22 +1,24 @@
# frozen_string_literal: true
RSpec.configure do |config|
+ config.include SnowplowHelpers, :snowplow
+
config.before(:each, :snowplow) do
# Using a high buffer size to not cause early flushes
buffer_size = 100
# WebMock is set up to allow requests to `localhost`
host = 'localhost'
- allow(Gitlab::Tracking)
+ allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
.to receive(:emitter)
.and_return(SnowplowTracker::Emitter.new(host, buffer_size: buffer_size))
stub_application_setting(snowplow_enabled: true)
- allow(Gitlab::Tracking).to receive(:event).and_call_original
+ allow(Gitlab::Tracking).to receive(:event).and_call_original # rubocop:disable RSpec/ExpectGitlabTracking
end
config.after(:each, :snowplow) do
- Gitlab::Tracking.send(:snowplow).flush
+ Gitlab::Tracking.send(:snowplow).send(:tracker).flush
end
end