Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/finder_collection.rb48
-rw-r--r--spec/support/finder_collection_allowlist.yml66
-rw-r--r--spec/support/gitlab_experiment.rb1
-rw-r--r--spec/support/gitlab_stubs/gitlab_ci_dast_includes.yml10
-rw-r--r--spec/support/graphql/arguments.rb4
-rw-r--r--spec/support/helpers/database/database_helpers.rb6
-rw-r--r--spec/support/helpers/detailed_error_helpers.rb19
-rw-r--r--spec/support/helpers/features/invite_members_modal_helper.rb44
-rw-r--r--spec/support/helpers/features/source_editor_spec_helpers.rb7
-rw-r--r--spec/support/helpers/features/web_ide_spec_helpers.rb14
-rw-r--r--spec/support/helpers/harbor_helper.rb27
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb18
-rw-r--r--spec/support/helpers/project_forks_helper.rb9
-rw-r--r--spec/support/helpers/project_helpers.rb2
-rw-r--r--spec/support/helpers/prometheus_helpers.rb7
-rw-r--r--spec/support/helpers/repo_helpers.rb76
-rw-r--r--spec/support/helpers/seed_helper.rb47
-rw-r--r--spec/support/helpers/stub_snowplow.rb (renamed from spec/support/stub_snowplow.rb)2
-rw-r--r--spec/support/helpers/test_env.rb58
-rw-r--r--spec/support/helpers/usage_data_helpers.rb2
-rw-r--r--spec/support/matchers/background_migrations_matchers.rb7
-rw-r--r--spec/support/matchers/event_store.rb11
-rw-r--r--spec/support/matchers/match_file.rb2
-rw-r--r--spec/support/services/issuable_import_csv_service_shared_examples.rb4
-rw-r--r--spec/support/shared_contexts/controllers/ldap_omniauth_callbacks_controller_shared_context.rb3
-rw-r--r--spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb7
-rw-r--r--spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/markdown_snapshot_shared_examples.rb4
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb3
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb1
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb5
-rw-r--r--spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb37
-rw-r--r--spec/support/shared_examples/csp.rb76
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/shared_examples/features/inviting_members_shared_examples.rb70
-rw-r--r--spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/finders/issues_finder_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb162
-rw-r--r--spec/support/shared_examples/harbor/container_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb172
-rw-r--r--spec/support/shared_examples/harbor/tags_controller_shared_examples.rb155
-rw-r--r--spec/support/shared_examples/integrations/integration_settings_form.rb5
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb54
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/merge_request_author_auto_assign_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb82
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/concerns/timebox_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/issuable_participants_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb124
-rw-r--r--spec/support/shared_examples/models/mentionable_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/requests/api/debian_common_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/hooks_shared_examples.rb415
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/services/feature_flags/client_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/usage_data_counters/work_item_activity_unique_counter_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/views/themed_layout_examples.rb35
-rw-r--r--spec/support/shared_examples/work_items/widgetable_service_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb37
-rw-r--r--spec/support/snowplow.rb16
83 files changed, 1999 insertions, 540 deletions
diff --git a/spec/support/finder_collection.rb b/spec/support/finder_collection.rb
new file mode 100644
index 00000000000..494dd4bdca1
--- /dev/null
+++ b/spec/support/finder_collection.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'set'
+
+module Support
+ # Ensure that finders' `execute` method always returns
+ # `ActiveRecord::Relation`.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/298771
+ module FinderCollection
+ def self.install_check(finder_class)
+ return unless check?(finder_class)
+
+ finder_class.prepend CheckResult
+ end
+
+ ALLOWLIST_YAML = File.join(__dir__, 'finder_collection_allowlist.yml')
+
+ def self.check?(finder_class)
+ @allowlist ||= YAML.load_file(ALLOWLIST_YAML).to_set
+
+ @allowlist.exclude?(finder_class.name)
+ end
+
+ module CheckResult
+ def execute(...)
+ result = super
+
+ unless result.is_a?(ActiveRecord::Relation)
+ raise <<~MESSAGE
+ #{self.class}#execute returned `#{result.class}` instead of `ActiveRecord::Relation`.
+ All finder classes are expected to return `ActiveRecord::Relation`.
+
+ Read more at https://docs.gitlab.com/ee/development/reusing_abstractions.html#finders
+ MESSAGE
+ end
+
+ result
+ end
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.before(:all, type: :finder) do
+ Support::FinderCollection.install_check(described_class)
+ end
+end
diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml
new file mode 100644
index 00000000000..8f09153afec
--- /dev/null
+++ b/spec/support/finder_collection_allowlist.yml
@@ -0,0 +1,66 @@
+# Allow list for spec/support/finder_collection.rb
+
+# Permenant excludes
+# For example:
+# FooFinder # Reason: It uses a memory backend
+
+# Temporary excludes (aka TODOs)
+# For example:
+# BarFinder # See <ISSUE_URL>
+- AccessRequestsFinder
+- Admin::PlansFinder
+- Analytics::CycleAnalytics::StageFinder
+- ApplicationsFinder
+- Autocomplete::GroupFinder
+- Autocomplete::ProjectFinder
+- Autocomplete::UsersFinder
+- BilledUsersFinder
+- Boards::BoardsFinder
+- Boards::VisitsFinder
+- BranchesFinder
+- Ci::AuthJobFinder
+- Ci::CommitStatusesFinder
+- Ci::DailyBuildGroupReportResultsFinder
+- ClusterAncestorsFinder
+- Clusters::AgentAuthorizationsFinder
+- Clusters::KubernetesNamespaceFinder
+- ComplianceManagement::MergeRequests::ComplianceViolationsFinder
+- ContainerRepositoriesFinder
+- ContextCommitsFinder
+- Environments::EnvironmentNamesFinder
+- Environments::EnvironmentsByDeploymentsFinder
+- EventsFinder
+- GroupDescendantsFinder
+- Groups::ProjectsRequiringAuthorizationsRefresh::OnDirectMembershipFinder
+- Groups::ProjectsRequiringAuthorizationsRefresh::OnTransferFinder
+- KeysFinder
+- LfsPointersFinder
+- LicenseTemplateFinder
+- MergeRequests::OldestPerCommitFinder
+- NotesFinder
+- Packages::BuildInfosFinder
+- Packages::Conan::PackageFileFinder
+- Packages::Go::ModuleFinder
+- Packages::Go::PackageFinder
+- Packages::Go::VersionFinder
+- Packages::PackageFileFinder
+- Packages::PackageFinder
+- Packages::Pypi::PackageFinder
+- Projects::Integrations::Jira::ByIdsFinder
+- Projects::Integrations::Jira::IssuesFinder
+- Releases::EvidencePipelineFinder
+- Repositories::BranchNamesFinder
+- Repositories::ChangelogTagFinder
+- Repositories::TreeFinder
+- Security::FindingsFinder
+- Security::PipelineVulnerabilitiesFinder
+- Security::ScanExecutionPoliciesFinder
+- Security::TrainingProviders::BaseUrlFinder
+- Security::TrainingUrlsFinder
+- SentryIssueFinder
+- ServerlessDomainFinder
+- TagsFinder
+- TemplateFinder
+- UploaderFinder
+- UserGroupNotificationSettingsFinder
+- UserGroupsCounter
diff --git a/spec/support/gitlab_experiment.rb b/spec/support/gitlab_experiment.rb
index 823aab0436e..4236091ca6c 100644
--- a/spec/support/gitlab_experiment.rb
+++ b/spec/support/gitlab_experiment.rb
@@ -2,7 +2,6 @@
# Require the provided spec helper and matchers.
require 'gitlab/experiment/rspec'
-require_relative 'stub_snowplow'
RSpec.configure do |config|
config.include StubSnowplow, :experiment
diff --git a/spec/support/gitlab_stubs/gitlab_ci_dast_includes.yml b/spec/support/gitlab_stubs/gitlab_ci_dast_includes.yml
new file mode 100644
index 00000000000..583d44c452e
--- /dev/null
+++ b/spec/support/gitlab_stubs/gitlab_ci_dast_includes.yml
@@ -0,0 +1,10 @@
+dast:
+ stage: dast
+ image:
+ name: "$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION"
+ variables:
+ GIT_STRATEGY: none
+ allow_failure: true
+ dast_configuration:
+ site_profile: "site_profile_name_included"
+ scanner_profile: "scanner_profile_name_included" \ No newline at end of file
diff --git a/spec/support/graphql/arguments.rb b/spec/support/graphql/arguments.rb
index a5bb01c31a3..478a460a0f6 100644
--- a/spec/support/graphql/arguments.rb
+++ b/spec/support/graphql/arguments.rb
@@ -5,7 +5,7 @@ module Graphql
delegate :blank?, :empty?, to: :to_h
def initialize(values)
- @values = values.compact
+ @values = values
end
def to_h
@@ -42,7 +42,7 @@ module Graphql
when Integer, Float, Symbol then value.to_s
when String, GlobalID then "\"#{value.to_s.gsub(/"/, '\\"')}\""
when Time, Date then "\"#{value.iso8601}\""
- when nil then 'null'
+ when NilClass then 'null'
when true then 'true'
when false then 'false'
else
diff --git a/spec/support/helpers/database/database_helpers.rb b/spec/support/helpers/database/database_helpers.rb
index db093bcef85..f3b2a2a6147 100644
--- a/spec/support/helpers/database/database_helpers.rb
+++ b/spec/support/helpers/database/database_helpers.rb
@@ -4,8 +4,10 @@ module Database
module DatabaseHelpers
# In order to directly work with views using factories,
# we can swapout the view for a table of identical structure.
- def swapout_view_for_table(view)
- ActiveRecord::Base.connection.execute(<<~SQL.squish)
+ def swapout_view_for_table(view, connection: nil)
+ connection ||= ActiveRecord::Base.connection
+
+ connection.execute(<<~SQL.squish)
CREATE TABLE #{view}_copy (LIKE #{view});
DROP VIEW #{view};
ALTER TABLE #{view}_copy RENAME TO #{view};
diff --git a/spec/support/helpers/detailed_error_helpers.rb b/spec/support/helpers/detailed_error_helpers.rb
new file mode 100644
index 00000000000..2da53a6bffd
--- /dev/null
+++ b/spec/support/helpers/detailed_error_helpers.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'google/rpc/status_pb'
+require 'google/protobuf/well_known_types'
+
+module DetailedErrorHelpers
+ def new_detailed_error(error_code, error_message, details)
+ status_error = Google::Rpc::Status.new(
+ code: error_code,
+ message: error_message,
+ details: [Google::Protobuf::Any.pack(details)]
+ )
+
+ GRPC::BadStatus.new(
+ error_code,
+ error_message,
+ { "grpc-status-details-bin" => Google::Rpc::Status.encode(status_error) })
+ end
+end
diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb
index 7ed64615020..b56ac5b32c6 100644
--- a/spec/support/helpers/features/invite_members_modal_helper.rb
+++ b/spec/support/helpers/features/invite_members_modal_helper.rb
@@ -9,18 +9,28 @@ module Spec
click_on 'Invite members'
page.within invite_modal_selector do
- Array.wrap(names).each do |name|
- find(member_dropdown_selector).set(name)
+ select_members(names)
+ choose_options(role, expires_at)
+ click_button 'Invite'
+ end
- wait_for_requests
- click_button name
- end
+ page.refresh if refresh
+ end
- choose_options(role, expires_at)
+ def input_invites(names)
+ click_on 'Invite members'
- click_button 'Invite'
+ page.within invite_modal_selector do
+ select_members(names)
+ end
+ end
+
+ def select_members(names)
+ Array.wrap(names).each do |name|
+ find(member_dropdown_selector).set(name)
- page.refresh if refresh
+ wait_for_requests
+ click_button name
end
end
@@ -64,6 +74,24 @@ module Spec
'[data-testid="invite-modal"]'
end
+ def member_token_error_selector(id)
+ "[data-testid='error-icon-#{id}']"
+ end
+
+ def member_token_avatar_selector
+ "[data-testid='token-avatar']"
+ end
+
+ def member_token_selector(id)
+ "[data-token-id='#{id}']"
+ end
+
+ def remove_token(id)
+ page.within member_token_selector(id) do
+ find('[data-testid="close-icon"]').click
+ end
+ end
+
def expect_to_have_group(group)
expect(page).to have_selector("[entity-id='#{group.id}']")
end
diff --git a/spec/support/helpers/features/source_editor_spec_helpers.rb b/spec/support/helpers/features/source_editor_spec_helpers.rb
index 57057b47fbb..cdc59f9cbe1 100644
--- a/spec/support/helpers/features/source_editor_spec_helpers.rb
+++ b/spec/support/helpers/features/source_editor_spec_helpers.rb
@@ -15,13 +15,6 @@ module Spec
execute_script("monaco.editor.getModel('#{uri}').setValue('#{escape_javascript(value)}')")
end
-
- def editor_get_value
- editor = find('.monaco-editor')
- uri = editor['data-uri']
-
- evaluate_script("monaco.editor.getModel('#{uri}').getValue()")
- end
end
end
end
diff --git a/spec/support/helpers/features/web_ide_spec_helpers.rb b/spec/support/helpers/features/web_ide_spec_helpers.rb
index 358bfacce05..70dedc3ac50 100644
--- a/spec/support/helpers/features/web_ide_spec_helpers.rb
+++ b/spec/support/helpers/features/web_ide_spec_helpers.rb
@@ -12,7 +12,7 @@
# ide_commit
#
module WebIdeSpecHelpers
- include ActionView::Helpers::JavaScriptHelper
+ include Spec::Support::Helpers::Features::SourceEditorSpecHelpers
def ide_visit(project)
visit project_path(project)
@@ -97,17 +97,7 @@ module WebIdeSpecHelpers
end
def ide_set_editor_value(value)
- editor = find('.monaco-editor')
- uri = editor['data-uri']
-
- execute_script("monaco.editor.getModel('#{uri}').setValue('#{escape_javascript(value)}')")
- end
-
- def ide_editor_value
- editor = find('.monaco-editor')
- uri = editor['data-uri']
-
- evaluate_script("monaco.editor.getModel('#{uri}').getValue()")
+ editor_set_value(value)
end
def ide_commit_tab_selector
diff --git a/spec/support/helpers/harbor_helper.rb b/spec/support/helpers/harbor_helper.rb
new file mode 100644
index 00000000000..3f13710ede6
--- /dev/null
+++ b/spec/support/helpers/harbor_helper.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module HarborHelper
+ def harbor_repository_url(container, *args)
+ if container.is_a?(Project)
+ project_harbor_repositories_path(container, *args)
+ else
+ group_harbor_repositories_path(container, *args)
+ end
+ end
+
+ def harbor_artifact_url(container, *args)
+ if container.is_a?(Project)
+ project_harbor_repository_artifacts_path(container, *args)
+ else
+ group_harbor_repository_artifacts_path(container, *args)
+ end
+ end
+
+ def harbor_tag_url(container, *args)
+ if container.is_a?(Project)
+ project_harbor_repository_artifact_tags_path(container, *args)
+ else
+ group_harbor_repository_artifact_tags_path(container, *args)
+ end
+ end
+end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 29064f01913..dd210f02ae7 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -126,24 +126,6 @@ module KubernetesHelpers
WebMock.stub_request(:get, pod_url).to_return(response || kube_pod_response)
end
- def stub_kubeclient_logs(pod_name, namespace, container: nil, status: nil, message: nil)
- stub_kubeclient_discover(service.api_url)
-
- if container
- container_query_param = "container=#{container}&"
- end
-
- logs_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods/#{pod_name}" \
- "/log?#{container_query_param}tailLines=#{::PodLogs::KubernetesService::LOGS_LIMIT}&timestamps=true"
-
- if status
- response = { status: status }
- response[:body] = { message: message }.to_json if message
- end
-
- WebMock.stub_request(:get, logs_url).to_return(response || kube_logs_response)
- end
-
def stub_kubeclient_deployments(namespace, status: nil)
stub_kubeclient_discover(service.api_url)
deployments_url = service.api_url + "/apis/apps/v1/namespaces/#{namespace}/deployments"
diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb
index 84b5dbc1d23..1504625bb00 100644
--- a/spec/support/helpers/project_forks_helper.rb
+++ b/spec/support/helpers/project_forks_helper.rb
@@ -70,12 +70,9 @@ module ProjectForksHelper
def fork_project_with_submodules(project, user = nil, params = {})
Gitlab::GitalyClient.allow_n_plus_1_calls do
forked_project = fork_project_direct(project, user, params)
- TestEnv.copy_repo(
- forked_project,
- bare_repo: TestEnv.forked_repo_path_bare,
- refs: TestEnv::FORKED_BRANCH_SHA
- )
- forked_project.repository.expire_content_cache
+ repo = Gitlab::GlRepository::PROJECT.repository_for(forked_project)
+ repo.create_from_bundle(TestEnv.forked_repo_bundle_path)
+ repo.expire_content_cache
forked_project
end
diff --git a/spec/support/helpers/project_helpers.rb b/spec/support/helpers/project_helpers.rb
index ef8947ab340..2427ed2bcc9 100644
--- a/spec/support/helpers/project_helpers.rb
+++ b/spec/support/helpers/project_helpers.rb
@@ -13,7 +13,7 @@ module ProjectHelpers
when :admin
create(:user, :admin, name: 'admin')
else
- create(:user, name: membership).tap { |u| target.add_user(u, membership) }
+ create(:user, name: membership).tap { |u| target.add_member(u, membership) }
end
end
diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb
index d49abbf3f19..e1f5e6dee14 100644
--- a/spec/support/helpers/prometheus_helpers.rb
+++ b/spec/support/helpers/prometheus_helpers.rb
@@ -267,6 +267,13 @@ module PrometheusHelpers
}
end
+ def prometheus_alert_payload_fingerprint(prometheus_alert)
+ # timestamp is hard-coded in #prometheus_map_alert_payload
+ fingerprint = "#{prometheus_alert.prometheus_metric_id}/2018-09-24T08:57:31.095725221Z"
+
+ Gitlab::AlertManagement::Fingerprint.generate(fingerprint)
+ end
+
private
def prometheus_map_alert_payload(status, alert)
diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb
index f275be39dc4..e76a1dd5a74 100644
--- a/spec/support/helpers/repo_helpers.rb
+++ b/spec/support/helpers/repo_helpers.rb
@@ -137,80 +137,4 @@ eos
file_content: content
).execute
end
-
- def commit_options(repo, index, target, ref, message)
- options = {}
- options[:tree] = index.write_tree(repo)
- options[:author] = {
- email: "test@example.com",
- name: "Test Author",
- time: Time.gm(2014, "mar", 3, 20, 15, 1)
- }
- options[:committer] = {
- email: "test@example.com",
- name: "Test Author",
- time: Time.gm(2014, "mar", 3, 20, 15, 1)
- }
- options[:message] ||= message
- options[:parents] = repo.empty? ? [] : [target].compact
- options[:update_ref] = ref
-
- options
- end
-
- # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
- # contents of CHANGELOG with a single new line of text.
- def new_commit_edit_old_file(repo)
- oid = repo.write("I replaced the changelog with this text", :blob)
- index = repo.index
- index.read_tree(repo.head.target.tree)
- index.add(path: "CHANGELOG", oid: oid, mode: 0100644)
-
- options = commit_options(
- repo,
- index,
- repo.head.target,
- "HEAD",
- "Edit CHANGELOG in its original location"
- )
-
- sha = Rugged::Commit.create(repo, options)
- repo.lookup(sha)
- end
-
- # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
- # contents of the specified file_path with new text.
- def new_commit_edit_new_file(repo, file_path, commit_message, text, branch = repo.head)
- oid = repo.write(text, :blob)
- index = repo.index
- index.read_tree(branch.target.tree)
- index.add(path: file_path, oid: oid, mode: 0100644)
- options = commit_options(repo, index, branch.target, branch.canonical_name, commit_message)
- sha = Rugged::Commit.create(repo, options)
- repo.lookup(sha)
- end
-
- # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
- # contents of encoding/CHANGELOG with new text.
- def new_commit_edit_new_file_on_branch(repo, file_path, branch_name, commit_message, text)
- branch = repo.branches[branch_name]
- new_commit_edit_new_file(repo, file_path, commit_message, text, branch)
- end
-
- # Writes a new commit to the repo and returns a Rugged::Commit. Moves the
- # CHANGELOG file to the encoding/ directory.
- def new_commit_move_file(repo)
- blob_oid = repo.head.target.tree.detect { |i| i[:name] == "CHANGELOG" }[:oid]
- file_content = repo.lookup(blob_oid).content
- oid = repo.write(file_content, :blob)
- index = repo.index
- index.read_tree(repo.head.target.tree)
- index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644)
- index.remove("CHANGELOG")
-
- options = commit_options(repo, index, repo.head.target, "HEAD", "Move CHANGELOG to encoding/")
-
- sha = Rugged::Commit.create(repo, options)
- repo.lookup(sha)
- end
end
diff --git a/spec/support/helpers/seed_helper.rb b/spec/support/helpers/seed_helper.rb
index f65993efa05..59723583cbc 100644
--- a/spec/support/helpers/seed_helper.rb
+++ b/spec/support/helpers/seed_helper.rb
@@ -9,7 +9,6 @@ TEST_REPO_PATH = 'gitlab-git-test.git'
TEST_NORMAL_REPO_PATH = 'not-bare-repo.git'
TEST_MUTABLE_REPO_PATH = 'mutable-repo.git'
TEST_BROKEN_REPO_PATH = 'broken-repo.git'
-TEST_GITATTRIBUTES_REPO_PATH = 'with-git-attributes.git'
module SeedHelper
GITLAB_GIT_TEST_REPO_URL = File.expand_path('../gitlab-git-test.git', __dir__)
@@ -25,8 +24,6 @@ module SeedHelper
create_normal_seeds
create_mutable_seeds
create_broken_seeds
- create_git_attributes
- create_invalid_git_attributes
end
def create_bare_seeds
@@ -67,48 +64,4 @@ module SeedHelper
FileUtils.rm_r(refs_path)
end
-
- def create_git_attributes
- system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} #{TEST_GITATTRIBUTES_REPO_PATH}),
- chdir: SEED_STORAGE_PATH,
- out: '/dev/null',
- err: '/dev/null')
-
- dir = File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info')
-
- FileUtils.mkdir_p(dir)
-
- File.open(File.join(dir, 'attributes'), 'w') do |handle|
- handle.write <<-EOF.strip
-# This is a comment, it should be ignored.
-
-*.txt text
-*.jpg -text
-*.sh eol=lf gitlab-language=shell
-*.haml.* gitlab-language=haml
-foo/bar.* foo
-*.cgi key=value?p1=v1&p2=v2
-/*.png gitlab-language=png
-*.binary binary
-/custom-highlighting/*.gitlab-custom gitlab-language=ruby
-/custom-highlighting/*.gitlab-cgi gitlab-language=erb?parent=json
-
-# This uses a tab instead of spaces to ensure the parser also supports this.
-*.md\tgitlab-language=markdown
-bla/bla.txt
- EOF
- end
- end
-
- def create_invalid_git_attributes
- dir = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git', 'info')
-
- FileUtils.mkdir_p(dir)
-
- enc = Encoding::UTF_16
-
- File.open(File.join(dir, 'attributes'), 'w', encoding: enc) do |handle|
- handle.write('# hello'.encode(enc))
- end
- end
end
diff --git a/spec/support/stub_snowplow.rb b/spec/support/helpers/stub_snowplow.rb
index c6e3b40972f..85c605efea3 100644
--- a/spec/support/stub_snowplow.rb
+++ b/spec/support/helpers/stub_snowplow.rb
@@ -13,7 +13,7 @@ module StubSnowplow
.and_return(SnowplowTracker::Emitter.new(host, buffer_size: buffer_size))
# rubocop:enable RSpec/AnyInstanceOf
- stub_application_setting(snowplow_enabled: true)
+ stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: host)
allow(SnowplowTracker::SelfDescribingJson).to receive(:new).and_call_original
allow(Gitlab::Tracking).to receive(:event).and_call_original # rubocop:disable RSpec/ExpectGitlabTracking
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 7c865dd7e11..03e9ad1a08e 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -82,7 +82,13 @@ module TestEnv
'trailers' => 'f0a5ed6',
'add_commit_with_5mb_subject' => '8cf8e80',
'blame-on-renamed' => '32c33da',
- 'with-executables' => '6b8dc4a'
+ 'with-executables' => '6b8dc4a',
+ 'spooky-stuff' => 'ba3343b',
+ 'few-commits' => '0031876',
+ 'two-commits' => '304d257',
+ 'utf-16' => 'f05a987',
+ 'gitaly-rename-test' => '94bb47c',
+ 'smime-signed-commits' => 'ed775cc'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
@@ -259,43 +265,35 @@ module TestEnv
# Create repository for FactoryBot.create(:project)
def setup_factory_repo
- setup_repo(factory_repo_path, factory_repo_path_bare, factory_repo_name, BRANCH_SHA)
+ setup_repo(factory_repo_path, factory_repo_bundle_path, factory_repo_name, BRANCH_SHA)
end
# Create repository for FactoryBot.create(:forked_project_with_submodules)
# This repo has a submodule commit that is not present in the main test
# repository.
def setup_forked_repo
- setup_repo(forked_repo_path, forked_repo_path_bare, forked_repo_name, FORKED_BRANCH_SHA)
+ setup_repo(forked_repo_path, forked_repo_bundle_path, forked_repo_name, FORKED_BRANCH_SHA)
end
- def setup_repo(repo_path, repo_path_bare, repo_name, refs)
+ def setup_repo(repo_path, repo_bundle_path, repo_name, refs)
clone_url = "https://gitlab.com/gitlab-org/#{repo_name}.git"
unless File.directory?(repo_path)
start = Time.now
system(*%W(#{Gitlab.config.git.bin_path} clone --quiet -- #{clone_url} #{repo_path}))
+ system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} remote remove origin))
puts "==> #{repo_path} set up in #{Time.now - start} seconds...\n"
end
set_repo_refs(repo_path, refs)
- unless File.directory?(repo_path_bare)
+ unless File.file?(repo_bundle_path)
start = Time.now
- # We must copy bare repositories because we will push to them.
- system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --quiet --bare -- #{repo_path} #{repo_path_bare}))
- puts "==> #{repo_path_bare} set up in #{Time.now - start} seconds...\n"
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} -C #{repo_path} bundle create #{repo_bundle_path} --all))
+ puts "==> #{repo_bundle_path} generated in #{Time.now - start} seconds...\n"
end
end
- def copy_repo(subject, bare_repo:, refs:)
- target_repo_path = File.expand_path(repos_path + "/#{subject.disk_path}.git")
-
- FileUtils.mkdir_p(target_repo_path)
- FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path)
- FileUtils.chmod_R 0755, target_repo_path
- end
-
def rm_storage_dir(storage, dir)
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
target_repo_refs_path = File.join(GitalySetup.repos_path(storage), dir)
@@ -310,14 +308,6 @@ module TestEnv
end
end
- def create_bare_repository(path)
- FileUtils.mkdir_p(path)
-
- system(git_env, *%W(#{Gitlab.config.git.bin_path} -C #{path} init --bare),
- out: '/dev/null',
- err: '/dev/null')
- end
-
def repos_path
@repos_path ||= GitalySetup.repos_path
end
@@ -357,20 +347,12 @@ module TestEnv
Capybara.current_session.visit '/'
end
- def factory_repo_path_bare
- "#{factory_repo_path}_bare"
- end
-
- def forked_repo_path_bare
- "#{forked_repo_path}_bare"
+ def factory_repo_bundle_path
+ "#{factory_repo_path}.bundle"
end
- def with_empty_bare_repository(name = nil)
- path = Rails.root.join('tmp/tests', name || 'empty-bare-repository').to_s
-
- yield(Rugged::Repository.init_at(path, :bare))
- ensure
- FileUtils.rm_rf(path)
+ def forked_repo_bundle_path
+ "#{forked_repo_path}.bundle"
end
def seed_db
@@ -386,9 +368,9 @@ module TestEnv
gitaly
gitlab-shell
gitlab-test
- gitlab-test_bare
+ gitlab-test.bundle
gitlab-test-fork
- gitlab-test-fork_bare
+ gitlab-test-fork.bundle
gitlab-workhorse
gitlab_workhorse_secret
]
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index 50d1b14cf56..2a9144614d0 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -53,7 +53,6 @@ module UsageDataHelpers
clusters_platforms_eks
clusters_platforms_gke
clusters_platforms_user
- clusters_integrations_elastic_stack
clusters_integrations_prometheus
clusters_management_project
in_review_folder
@@ -91,7 +90,6 @@ module UsageDataHelpers
projects_with_repositories_enabled
projects_with_error_tracking_enabled
projects_with_enabled_alert_integrations
- projects_with_tracing_enabled
projects_with_expiration_policy_enabled
projects_with_expiration_policy_disabled
projects_with_expiration_policy_enabled_with_keep_n_unset
diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb
index c5b3e140585..9f39f576b95 100644
--- a/spec/support/matchers/background_migrations_matchers.rb
+++ b/spec/support/matchers/background_migrations_matchers.rb
@@ -74,6 +74,13 @@ RSpec::Matchers.define :have_scheduled_batched_migration do |gitlab_schema: :git
.for_configuration(gitlab_schema, migration, table_name, column_name, job_arguments)
expect(batched_migrations.count).to be(1)
+
+ # the :batch_min_value & :batch_max_value attribute argument values get applied to the
+ # :min_value & :max_value columns on the database. Here we change the attribute names
+ # for the rspec have_attributes matcher used below to pass
+ attributes[:min_value] = attributes.delete :batch_min_value if attributes.include?(:batch_min_value)
+ attributes[:max_value] = attributes.delete :batch_max_value if attributes.include?(:batch_max_value)
+
expect(batched_migrations).to all(have_attributes(attributes)) if attributes.present?
end
diff --git a/spec/support/matchers/event_store.rb b/spec/support/matchers/event_store.rb
index eb5b37f39e5..14f6a42d7f4 100644
--- a/spec/support/matchers/event_store.rb
+++ b/spec/support/matchers/event_store.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec::Matchers.define :publish_event do |expected_event_class|
+ include RSpec::Matchers::Composable
+
supports_block_expectations
match do |proc|
@@ -15,10 +17,17 @@ RSpec::Matchers.define :publish_event do |expected_event_class|
proc.call
@events.any? do |event|
- event.instance_of?(expected_event_class) && event.data == @expected_data
+ event.instance_of?(expected_event_class) && match_data?(event.data, @expected_data)
end
end
+ def match_data?(actual, expected)
+ values_match?(actual.keys, expected.keys) &&
+ actual.keys.each do |key|
+ values_match?(actual[key], expected[key])
+ end
+ end
+
chain :with do |expected_data|
@expected_data = expected_data
end
diff --git a/spec/support/matchers/match_file.rb b/spec/support/matchers/match_file.rb
index 4e522b52912..0c1f95d45f5 100644
--- a/spec/support/matchers/match_file.rb
+++ b/spec/support/matchers/match_file.rb
@@ -2,6 +2,6 @@
RSpec::Matchers.define :match_file do |expected|
match do |actual|
- expect(Digest::MD5.hexdigest(actual)).to eq(Digest::MD5.hexdigest(File.read(expected)))
+ expect(Digest::SHA256.hexdigest(actual)).to eq(Digest::SHA256.hexdigest(File.read(expected)))
end
end
diff --git a/spec/support/services/issuable_import_csv_service_shared_examples.rb b/spec/support/services/issuable_import_csv_service_shared_examples.rb
index 07118198969..0dea6cfb729 100644
--- a/spec/support/services/issuable_import_csv_service_shared_examples.rb
+++ b/spec/support/services/issuable_import_csv_service_shared_examples.rb
@@ -37,6 +37,10 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type|
end
describe '#execute' do
+ before do
+ project.add_developer(user)
+ end
+
context 'invalid file extension' do
let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }
diff --git a/spec/support/shared_contexts/controllers/ldap_omniauth_callbacks_controller_shared_context.rb b/spec/support/shared_contexts/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
index 8635c9a8ff9..b31fe9ee0d1 100644
--- a/spec/support/shared_contexts/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
+++ b/spec/support/shared_contexts/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
@@ -14,6 +14,8 @@ RSpec.shared_context 'Ldap::OmniauthCallbacksController' do
{ main: ldap_config_defaults(:main) }
end
+ let(:multiple_ldap_servers_license_available) { true }
+
def ldap_config_defaults(key, hash = {})
{
provider_name: "ldap#{key}",
@@ -23,6 +25,7 @@ RSpec.shared_context 'Ldap::OmniauthCallbacksController' do
end
before do
+ stub_licensed_features(multiple_ldap_servers: multiple_ldap_servers_license_available)
stub_ldap_setting(ldap_settings)
described_class.define_providers!
Rails.application.reload_routes!
diff --git a/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
index aa8bc6fa79f..a3c688bb69e 100644
--- a/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
+++ b/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
@@ -16,9 +16,6 @@ RSpec.shared_context 'structured_logger' do
"created_at" => created_at.to_f,
"enqueued_at" => created_at.to_f,
"correlation_id" => 'cid',
- "error_message" => "wrong number of arguments (2 for 3)",
- "error_class" => "ArgumentError",
- "error_backtrace" => [],
"exception.message" => "wrong number of arguments (2 for 3)",
"exception.class" => "ArgumentError",
"exception.backtrace" => []
@@ -32,7 +29,6 @@ RSpec.shared_context 'structured_logger' do
let(:clock_thread_cputime_end) { 1.333333799 }
let(:start_payload) do
job.except(
- 'error_message', 'error_class', 'error_backtrace',
'exception.backtrace', 'exception.class', 'exception.message'
).merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start',
@@ -73,9 +69,6 @@ RSpec.shared_context 'structured_logger' do
end_payload.merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec',
'job_status' => 'fail',
- 'error_class' => 'ArgumentError',
- 'error_message' => 'Something went wrong',
- 'error_backtrace' => be_a(Array).and(be_present),
'exception.class' => 'ArgumentError',
'exception.message' => 'Something went wrong',
'exception.backtrace' => be_a(Array).and(be_present)
diff --git a/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb
index 0d992f33c61..449db59e35d 100644
--- a/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb
+++ b/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb
@@ -10,6 +10,7 @@ RSpec.shared_context 'server metrics with mocked prometheus' do
let(:gitaly_seconds_metric) { double('gitaly seconds metric') }
let(:failed_total_metric) { double('failed total metric') }
let(:retried_total_metric) { double('retried total metric') }
+ let(:interrupted_total_metric) { double('interrupted total metric') }
let(:redis_requests_total) { double('redis calls total metric') }
let(:running_jobs_metric) { double('running jobs metric') }
let(:redis_seconds_metric) { double('redis seconds metric') }
@@ -30,6 +31,7 @@ RSpec.shared_context 'server metrics with mocked prometheus' do
allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_elasticsearch_requests_duration_seconds, anything, anything, anything).and_return(elasticsearch_seconds_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric)
+ allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_interrupted_total, anything).and_return(interrupted_total_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_redis_requests_total, anything).and_return(redis_requests_total)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_elasticsearch_requests_total, anything).and_return(elasticsearch_requests_total)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_load_balancing_count, anything).and_return(load_balancing_metric)
diff --git a/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb b/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb
index de52b58982e..a90fe9e1723 100644
--- a/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb
+++ b/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb
@@ -5,12 +5,12 @@ require 'spec_helper'
# See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#markdown-snapshot-testing
# for documentation on this spec.
# rubocop:disable Layout/LineLength
-RSpec.shared_context 'with API::Markdown Snapshot shared context' do |glfm_specification_dir, glfm_example_snapshots_dir|
+RSpec.shared_context 'with API::Markdown Snapshot shared context' do |glfm_specification_dir|
# rubocop:enable Layout/LineLength
include ApiHelpers
markdown_examples, html_examples = %w[markdown.yml html.yml].map do |file_name|
- yaml = File.read("#{glfm_example_snapshots_dir}/#{file_name}")
+ yaml = File.read("#{glfm_specification_dir}/example_snapshots/#{file_name}")
YAML.safe_load(yaml, symbolize_names: true, aliases: true)
end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index d277a45584d..6c2ed79b343 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -83,8 +83,6 @@ RSpec.shared_context 'project navbar structure' do
nav_item: _('Monitor'),
nav_sub_items: [
_('Metrics'),
- _('Logs'),
- _('Tracing'),
_('Error Tracking'),
_('Alerts'),
_('Incidents'),
@@ -112,6 +110,7 @@ RSpec.shared_context 'project navbar structure' do
_('Access Tokens'),
_('Repository'),
_('CI/CD'),
+ _('Packages & Registries'),
_('Monitor'),
s_('UsageQuota|Usage Quotas')
]
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index 483bca07ba6..eec6e92c5fe 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -31,6 +31,7 @@ RSpec.shared_context 'GroupPolicy context' do
admin_milestone
admin_issue_board
read_container_image
+ read_harbor_registry
read_metrics_dashboard_annotation
read_prometheus
read_crm_contact
diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
index 7396643823c..789b385c435 100644
--- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -12,6 +12,7 @@ RSpec.shared_context 'ProjectPolicy context' do
let_it_be_with_refind(:private_project) { create(:project, :private, namespace: owner.namespace) }
let_it_be_with_refind(:internal_project) { create(:project, :internal, namespace: owner.namespace) }
let_it_be_with_refind(:public_project) { create(:project, :public, namespace: owner.namespace) }
+ let_it_be_with_refind(:public_project_in_group) { create(:project, :public, namespace: create(:group, :public)) }
let(:base_guest_permissions) do
%i[
@@ -29,7 +30,7 @@ RSpec.shared_context 'ProjectPolicy context' do
create_snippet create_incident daily_statistics create_merge_request_in download_code
download_wiki_code fork_project metrics_dashboard read_build
read_commit_status read_confidential_issues read_container_image
- read_deployment read_environment read_merge_request
+ read_harbor_registry read_deployment read_environment read_merge_request
read_metrics_dashboard_annotation read_pipeline read_prometheus
read_sentry_issue update_issue create_merge_request_in
]
@@ -93,7 +94,7 @@ RSpec.shared_context 'ProjectPolicy context' do
let(:owner_permissions) { base_owner_permissions + additional_owner_permissions }
before_all do
- [private_project, internal_project, public_project].each do |project|
+ [private_project, internal_project, public_project, public_project_in_group].each do |project|
project.add_guest(guest)
project.add_reporter(reporter)
project.add_developer(developer)
diff --git a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
new file mode 100644
index 00000000000..98fc52add51
--- /dev/null
+++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+#
+# Requires a context containing:
+# - subject
+# - project
+# - feature_flag_name
+# - category
+# - action
+# - namespace
+# - user
+
+shared_examples 'Snowplow event tracking' do
+ let(:label) { nil }
+
+ it 'is not emitted if FF is disabled' do
+ stub_feature_flags(feature_flag_name => false)
+
+ subject
+
+ expect_no_snowplow_event
+ end
+
+ it 'is emitted' do
+ params = {
+ category: category,
+ action: action,
+ namespace: namespace,
+ user: user,
+ project: project,
+ label: label
+ }.compact
+
+ subject
+
+ expect_snowplow_event(**params)
+ end
+end
diff --git a/spec/support/shared_examples/csp.rb b/spec/support/shared_examples/csp.rb
index 9143d0f4720..91242ae9f37 100644
--- a/spec/support/shared_examples/csp.rb
+++ b/spec/support/shared_examples/csp.rb
@@ -15,64 +15,66 @@ RSpec.shared_examples 'setting CSP' do |rule_name|
end
end
- context 'when no CSP config' do
- include_context 'csp config', nil
+ context 'csp config and feature toggle', :do_not_stub_snowplow_by_default do
+ context 'when no CSP config' do
+ include_context 'csp config', nil
- it 'does not add CSP directives' do
- is_expected.to be_blank
+ it 'does not add CSP directives' do
+ is_expected.to be_blank
+ end
end
- end
- describe "when a CSP config exists for #{rule_name}" do
- include_context 'csp config', rule_name.parameterize.underscore.to_sym
+ describe "when a CSP config exists for #{rule_name}" do
+ include_context 'csp config', rule_name.parameterize.underscore.to_sym
- context 'when feature is enabled' do
- it "appends to #{rule_name}" do
- is_expected.to eql("#{rule_name} #{default_csp_values} #{allowlisted_url}")
+ context 'when feature is enabled' do
+ it "appends to #{rule_name}" do
+ is_expected.to eql("#{rule_name} #{default_csp_values} #{allowlisted_url}")
+ end
end
- end
- context 'when feature is disabled' do
- include_context 'disable feature'
+ context 'when feature is disabled' do
+ include_context 'disable feature'
- it "keeps original #{rule_name}" do
- is_expected.to eql("#{rule_name} #{default_csp_values}")
+ it "keeps original #{rule_name}" do
+ is_expected.to eql("#{rule_name} #{default_csp_values}")
+ end
end
end
- end
- describe "when a CSP config exists for default-src but not #{rule_name}" do
- include_context 'csp config', :default_src
+ describe "when a CSP config exists for default-src but not #{rule_name}" do
+ include_context 'csp config', :default_src
- context 'when feature is enabled' do
- it "uses default-src values in #{rule_name}" do
- is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{allowlisted_url}")
+ context 'when feature is enabled' do
+ it "uses default-src values in #{rule_name}" do
+ is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{allowlisted_url}")
+ end
end
- end
- context 'when feature is disabled' do
- include_context 'disable feature'
+ context 'when feature is disabled' do
+ include_context 'disable feature'
- it "does not add #{rule_name}" do
- is_expected.to eql("default-src #{default_csp_values}")
+ it "does not add #{rule_name}" do
+ is_expected.to eql("default-src #{default_csp_values}")
+ end
end
end
- end
- describe "when a CSP config exists for font-src but not #{rule_name}" do
- include_context 'csp config', :font_src
+ describe "when a CSP config exists for font-src but not #{rule_name}" do
+ include_context 'csp config', :font_src
- context 'when feature is enabled' do
- it "uses default-src values in #{rule_name}" do
- is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{allowlisted_url}")
+ context 'when feature is enabled' do
+ it "uses default-src values in #{rule_name}" do
+ is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{allowlisted_url}")
+ end
end
- end
- context 'when feature is disabled' do
- include_context 'disable feature'
+ context 'when feature is disabled' do
+ include_context 'disable feature'
- it "does not add #{rule_name}" do
- is_expected.to eql("font-src #{default_csp_values}")
+ it "does not add #{rule_name}" do
+ is_expected.to eql("font-src #{default_csp_values}")
+ end
end
end
end
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
index 591f7973454..0ea82f37db0 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -31,8 +31,6 @@ RSpec.shared_examples 'edits content using the content editor' do
page.go_back
refresh
-
- click_button 'Edit rich text'
end
it 'applies theme classes to code blocks' do
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 6c06cbf9082..24dc4bcfc59 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -293,7 +293,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
it 'can be collapsed' do
submit_reply('another text')
- find('.js-collapse-replies').click
+ click_button s_('Notes|Collapse replies'), match: :first
expect(page).to have_css('.discussion-notes .note', count: 1)
expect(page).to have_content '1 reply'
end
diff --git a/spec/support/shared_examples/features/inviting_members_shared_examples.rb b/spec/support/shared_examples/features/inviting_members_shared_examples.rb
index 58357b262f5..bca0e02fcdd 100644
--- a/spec/support/shared_examples/features/inviting_members_shared_examples.rb
+++ b/spec/support/shared_examples/features/inviting_members_shared_examples.rb
@@ -23,6 +23,22 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
)
end
+ it 'displays the user\'s avatar in the member input token', :js do
+ visit members_page_path
+
+ input_invites(user2.name)
+
+ expect(page).to have_selector(member_token_avatar_selector)
+ end
+
+ it 'does not display an avatar in the member input token for an email address', :js do
+ visit members_page_path
+
+ input_invites('test@example.com')
+
+ expect(page).not_to have_selector(member_token_avatar_selector)
+ end
+
it 'invites user by email', :js, :snowplow, :aggregate_failures do
visit members_page_path
@@ -78,22 +94,23 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
end
context 'when member is already a member by email' do
- it 'fails with an error', :js do
+ it 'updates the member for that email', :js do
+ email = 'test@example.com'
+
visit members_page_path
- invite_member('test@example.com', role: 'Developer')
+ invite_member(email, role: 'Developer')
- invite_member('test@example.com', role: 'Reporter', refresh: false)
+ invite_member(email, role: 'Reporter', refresh: false)
- expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_content("The member's email address has already been taken")
+ expect(page).not_to have_selector(invite_modal_selector)
page.refresh
click_link 'Invited'
- page.within find_invited_member_row('test@example.com') do
- expect(page).to have_button('Developer')
+ page.within find_invited_member_row(email) do
+ expect(page).to have_button('Reporter')
end
end
end
@@ -131,8 +148,8 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
invite_member(user2.name, role: role, refresh: false)
expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_content "Access level should be greater than or equal to Developer inherited membership " \
- "from group #{group.name}"
+ expect(page).to have_content "#{user2.name}: Access level should be greater than or equal to Developer " \
+ "inherited membership from group #{group.name}"
page.refresh
@@ -149,13 +166,31 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
group.add_maintainer(user3)
end
- it 'only shows the first user error', :js do
+ it 'shows the user errors and then removes them from the form', :js do
visit subentity_members_page_path
invite_member([user2.name, user3.name], role: role, refresh: false)
expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_text("Access level should be greater than or equal to", count: 1)
+ expect(page).to have_selector(member_token_error_selector(user2.id))
+ expect(page).to have_selector(member_token_error_selector(user3.id))
+ expect(page).to have_text("The following 2 members couldn't be invited")
+ expect(page).to have_text("#{user2.name}: Access level should be greater than or equal to")
+ expect(page).to have_text("#{user3.name}: Access level should be greater than or equal to")
+
+ remove_token(user2.id)
+
+ expect(page).not_to have_selector(member_token_error_selector(user2.id))
+ expect(page).to have_selector(member_token_error_selector(user3.id))
+ expect(page).to have_text("The following member couldn't be invited")
+ expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
+
+ remove_token(user3.id)
+
+ expect(page).not_to have_selector(member_token_error_selector(user3.id))
+ expect(page).not_to have_text("The following member couldn't be invited")
+ expect(page).not_to have_text("Review the invite errors and try again")
+ expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
page.refresh
@@ -169,6 +204,19 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
expect(page).not_to have_button('Maintainer')
end
end
+
+ it 'only shows the error for an invalid formatted email and does not display other member errors', :js do
+ visit subentity_members_page_path
+
+ invite_member([user2.name, user3.name, 'bad@email'], role: role, refresh: false)
+
+ expect(page).to have_selector(invite_modal_selector)
+ expect(page).to have_text('email contains an invalid email address')
+ expect(page).not_to have_text("The following 2 members couldn't be invited")
+ expect(page).not_to have_text("Review the invite errors and try again")
+ expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
+ expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
+ end
end
end
end
diff --git a/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb
index 4565108b5e4..9d023d9514a 100644
--- a/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb
+++ b/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb
@@ -4,7 +4,7 @@ RSpec.shared_examples 'multiple assignees merge request' do |action, save_button
it "#{action} a MR with multiple assignees", :js do
find('.js-assignee-search').click
page.within '.dropdown-menu-user' do
- click_link user.name unless action == 'creates'
+ click_link user.name
click_link user2.name
end
diff --git a/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
index a44a699c878..bbde448a1a1 100644
--- a/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
+++ b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
@@ -4,7 +4,7 @@ RSpec.shared_examples 'multiple assignees widget merge request' do |action, save
it "#{action} a MR with multiple assignees", :js do
find('.js-assignee-search').click
page.within '.dropdown-menu-user' do
- click_link user.name unless action == 'creates'
+ click_link user.name
click_link user2.name
end
diff --git a/spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb b/spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb
new file mode 100644
index 00000000000..79de2aedf3b
--- /dev/null
+++ b/spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'autocompletes items' do
+ before do
+ if defined?(project)
+ create(:issue, project: project, title: 'My Cool Linked Issue')
+ create(:merge_request, source_project: project, title: 'My Cool Merge Request')
+ create(:label, project: project, title: 'My Cool Label')
+ create(:milestone, project: project, title: 'My Cool Milestone')
+
+ project.add_maintainer(create(:user, name: 'JohnDoe123'))
+ else # group wikis
+ project = create(:project, group: group)
+
+ create(:issue, project: project, title: 'My Cool Linked Issue')
+ create(:merge_request, source_project: project, title: 'My Cool Merge Request')
+ create(:group_label, group: group, title: 'My Cool Label')
+ create(:milestone, group: group, title: 'My Cool Milestone')
+
+ project.add_maintainer(create(:user, name: 'JohnDoe123'))
+ end
+ end
+
+ it 'works well for issues, labels, MRs, members, etc' do
+ fill_in :wiki_content, with: "#"
+ expect(page).to have_text 'My Cool Linked Issue'
+
+ fill_in :wiki_content, with: "~"
+ expect(page).to have_text 'My Cool Label'
+
+ fill_in :wiki_content, with: "!"
+ expect(page).to have_text 'My Cool Merge Request'
+
+ fill_in :wiki_content, with: "%"
+ expect(page).to have_text 'My Cool Milestone'
+
+ fill_in :wiki_content, with: "@"
+ expect(page).to have_text 'JohnDoe123'
+
+ fill_in :wiki_content, with: ':smil'
+ expect(page).to have_text 'smile_cat'
+ 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 12a4c6d7583..79c7c1891ac 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
@@ -146,6 +146,8 @@ RSpec.shared_examples 'User updates wiki page' do
it_behaves_like 'edits content using the content editor'
end
end
+
+ it_behaves_like 'autocompletes items'
end
context 'when the page is in a subdir', :js do
diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
index 622a88e8323..9d8f37a3e64 100644
--- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
@@ -269,6 +269,17 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns items not assigned to that milestone' do
expect(items).to contain_exactly(item2, item3, item4, item5)
end
+
+ context 'with multiple milestones' do
+ let(:milestone2) { create(:milestone, project: project2) }
+ let(:params) { { not: { milestone_title: [milestone.title, milestone2.title] } } }
+
+ it 'returns items not assigned to both milestones' do
+ item2.update!(milestone: milestone2)
+
+ expect(items).to contain_exactly(item3, item4, item5)
+ end
+ end
end
context 'filtering by group milestone' do
@@ -962,7 +973,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
group = create(:group)
project = create(:project, group: group)
item = create(factory, project: project)
- group.add_user(user, :owner)
+ group.add_member(user, :owner)
expect(items).to include(item)
end
diff --git a/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb
new file mode 100644
index 00000000000..56c2ca22e15
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'update work item description widget' do
+ it 'updates the description widget' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to change(work_item, :description).from(nil).to(new_description)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['workItem']['widgets']).to include(
+ {
+ 'description' => new_description,
+ 'type' => 'DESCRIPTION'
+ }
+ )
+ end
+
+ context 'when the updated work item is not valid' do
+ it 'returns validation errors without the work item' do
+ errors = ActiveModel::Errors.new(work_item).tap { |e| e.add(:description, 'error message') }
+
+ allow_next_found_instance_of(::WorkItem) do |instance|
+ allow(instance).to receive(:valid?).and_return(false)
+ allow(instance).to receive(:errors).and_return(errors)
+ end
+
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['workItem']).to be_nil
+ expect(mutation_response['errors']).to match_array(['Description error message'])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb
new file mode 100644
index 00000000000..3c32b7e0310
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'update work item weight widget' do
+ it 'updates the weight widget' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to change(work_item, :weight).from(nil).to(new_weight)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['workItem']['widgets']).to include(
+ {
+ 'weight' => new_weight,
+ 'type' => 'WEIGHT'
+ }
+ )
+ end
+
+ context 'when the updated work item is not valid' do
+ it 'returns validation errors without the work item' do
+ errors = ActiveModel::Errors.new(work_item).tap { |e| e.add(:weight, 'error message') }
+
+ allow_next_found_instance_of(::WorkItem) do |instance|
+ allow(instance).to receive(:valid?).and_return(false)
+ allow(instance).to receive(:errors).and_return(errors)
+ end
+
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['workItem']).to be_nil
+ expect(mutation_response['errors']).to match_array(['Weight error message'])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
index 6d6e7b761f6..59927fa1cc9 100644
--- a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
@@ -44,19 +44,25 @@
# end
# end
#
+
+# Include this context if your field does not accept a sort argument
+RSpec.shared_context 'no sort argument' do
+ let(:sort_argument) { graphql_args }
+end
+
RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
# Provided as a convenience when constructing queries using string concatenation
let(:page_info) { 'pageInfo { startCursor endCursor }' }
# Convenience for using default implementation of pagination_results_data
let(:node_path) { ['id'] }
+ let(:sort_argument) { graphql_args(sort: sort_param) }
it_behaves_like 'requires variables' do
- let(:required_variables) { [:sort_param, :first_param, :all_records, :data_path, :current_user] }
+ let(:required_variables) { [:first_param, :all_records, :data_path, :current_user] }
end
describe do
- let(:sort_argument) { graphql_args(sort: sort_param) }
- let(:params) { sort_argument }
+ let(:params) { sort_argument }
# Convenience helper for the large number of queries defined as a projection
# from some root value indexed by full_path to a collection of objects with IID
diff --git a/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
new file mode 100644
index 00000000000..85fcd426e3d
--- /dev/null
+++ b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a harbor artifacts controller' do |args|
+ include HarborHelper
+ let_it_be(:user) { create(:user) }
+ let_it_be(:unauthorized_user) { create(:user) }
+ let_it_be(:json_header) { { accept: 'application/json' } }
+
+ let(:mock_artifacts) do
+ [
+ {
+ "digest": "sha256:661e8e44e5d7290fbd42d0495ab4ff6fdf1ad251a9f358969b3264a22107c14d",
+ "icon": "sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06",
+ "id": 1,
+ "project_id": 1,
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-04-23T08:04:08.901Z",
+ "repository_id": 1,
+ "size": 126745886,
+ "tags": [
+ {
+ "artifact_id": 1,
+ "id": 1,
+ "immutable": false,
+ "name": "2",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-04-23T08:04:08.920Z",
+ "repository_id": 1,
+ "signed": false
+ }
+ ],
+ "type": "IMAGE"
+ }
+ ]
+ end
+
+ let(:repository_id) { 'test' }
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status with json' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).not_to render_template(:index)
+ end
+ end
+
+ shared_examples 'responds with 302 status' do
+ it 'returns 302' do
+ subject
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ shared_examples 'responds with 422 status with json' do
+ it 'returns 422' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ before do
+ stub_request(:get,
+ "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts"\
+ "?page=1&page_size=10&with_tag=true")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ }).to_return(status: 200, body: mock_artifacts.to_json, headers: { "x-total-count": 2 })
+ container.add_reporter(user)
+ sign_in(user)
+ end
+
+ describe 'GET #index.json' do
+ subject do
+ get harbor_artifact_url(container, repository_id), headers: json_header
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it_behaves_like "responds with #{args[:anonymous_status_code]} status"
+ end
+
+ context 'with unauthorized user' do
+ before do
+ sign_in(unauthorized_user)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with valid params' do
+ context 'with valid repository' do
+ subject do
+ get harbor_artifact_url(container, repository_id), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid page' do
+ subject do
+ get harbor_artifact_url(container, repository_id, page: '1'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid limit' do
+ subject do
+ get harbor_artifact_url(container, repository_id, limit: '10'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+ end
+
+ context 'with invalid params' do
+ context 'with invalid page' do
+ subject do
+ get harbor_artifact_url(container, repository_id, page: 'aaa'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+
+ context 'with invalid limit' do
+ subject do
+ get harbor_artifact_url(container, repository_id, limit: 'aaa'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/harbor/container_shared_examples.rb b/spec/support/shared_examples/harbor/container_shared_examples.rb
new file mode 100644
index 00000000000..57274e0b457
--- /dev/null
+++ b/spec/support/shared_examples/harbor/container_shared_examples.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'raises NotImplementedError when calling #container' do
+ describe '#container' do
+ it 'raises NotImplementedError' do
+ expect { controller.send(:container) }.to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
new file mode 100644
index 00000000000..b35595a10b2
--- /dev/null
+++ b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
@@ -0,0 +1,172 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a harbor repositories controller' do |args|
+ include HarborHelper
+ let_it_be(:user) { create(:user) }
+ let_it_be(:unauthorized_user) { create(:user) }
+ let_it_be(:json_header) { { accept: 'application/json' } }
+
+ let(:mock_repositories) do
+ [
+ {
+ "artifact_count": 6,
+ "creation_time": "2022-04-24T10:59:02.719Z",
+ "id": 33,
+ "name": "test/photon",
+ "project_id": 3,
+ "pull_count": 12,
+ "update_time": "2022-04-24T11:06:27.678Z"
+ },
+ {
+ "artifact_count": 1,
+ "creation_time": "2022-04-23T08:04:08.880Z",
+ "id": 1,
+ "name": "test/gemnasium",
+ "project_id": 3,
+ "pull_count": 0,
+ "update_time": "2022-04-23T08:04:08.880Z"
+ }
+ ]
+ end
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status with html' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ end
+ end
+
+ shared_examples 'responds with 302 status' do
+ it 'returns 302' do
+ subject
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ shared_examples 'responds with 200 status with json' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).not_to render_template(:index)
+ end
+ end
+
+ shared_examples 'responds with 422 status with json' do
+ it 'returns 422' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories?page=1&page_size=10")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ }).to_return(status: 200, body: mock_repositories.to_json, headers: { "x-total-count": 2 })
+ container.add_reporter(user)
+ sign_in(user)
+ end
+
+ describe 'GET #index.html' do
+ subject do
+ get harbor_repository_url(container)
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status with html'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it_behaves_like "responds with #{args[:anonymous_status_code]} status"
+ end
+
+ context 'with unauthorized user' do
+ before do
+ sign_in(unauthorized_user)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+
+ describe 'GET #index.json' do
+ subject do
+ get harbor_repository_url(container), headers: json_header
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with valid params' do
+ context 'with valid page params' do
+ subject do
+ get harbor_repository_url(container, page: '1'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid limit params' do
+ subject do
+ get harbor_repository_url(container, limit: '10'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+ end
+
+ context 'with invalid params' do
+ context 'with invalid page params' do
+ subject do
+ get harbor_repository_url(container, page: 'aaa'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+
+ context 'with invalid limit params' do
+ subject do
+ get harbor_repository_url(container, limit: 'aaa'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
new file mode 100644
index 00000000000..46fea7fdff6
--- /dev/null
+++ b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a harbor tags controller' do |args|
+ include HarborHelper
+ let_it_be(:user) { create(:user) }
+ let_it_be(:unauthorized_user) { create(:user) }
+ let_it_be(:json_header) { { accept: 'application/json' } }
+
+ let(:mock_artifacts) do
+ [
+ {
+ "artifact_id": 1,
+ "id": 1,
+ "immutable": false,
+ "name": "2",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-04-23T08:04:08.920Z",
+ "repository_id": 1,
+ "signed": false
+ }
+ ]
+ end
+
+ let(:repository_id) { 'test' }
+ let(:artifact_id) { '1' }
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status with json' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).not_to render_template(:index)
+ end
+ end
+
+ shared_examples 'responds with 302 status' do
+ it 'returns 302' do
+ subject
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ shared_examples 'responds with 422 status with json' do
+ it 'returns 422' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ before do
+ stub_request(:get,
+ "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts/1/tags"\
+ "?page=1&page_size=10")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ }).to_return(status: 200, body: mock_artifacts.to_json, headers: { "x-total-count": 2 })
+ container.add_reporter(user)
+ sign_in(user)
+ end
+
+ describe 'GET #index.json' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id),
+ headers: json_header)
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it_behaves_like "responds with #{args[:anonymous_status_code]} status"
+ end
+
+ context 'with unauthorized user' do
+ before do
+ sign_in(unauthorized_user)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with valid params' do
+ context 'with valid repository' do
+ subject do
+ get harbor_tag_url(container, repository_id, artifact_id), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid page' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id, page: '1'),
+ headers: json_header)
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid limit' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id, limit: '10'),
+ headers: json_header)
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+ end
+
+ context 'with invalid params' do
+ context 'with invalid page' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id, page: 'aaa'),
+ headers: json_header)
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+
+ context 'with invalid limit' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id, limit: 'aaa'),
+ headers: json_header)
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/integrations/integration_settings_form.rb b/spec/support/shared_examples/integrations/integration_settings_form.rb
index dfe5a071f91..5041ac4a660 100644
--- a/spec/support/shared_examples/integrations/integration_settings_form.rb
+++ b/spec/support/shared_examples/integrations/integration_settings_form.rb
@@ -20,6 +20,11 @@ RSpec.shared_examples 'integration settings form' do
"#{integration.title} field #{field_name} not present"
end
+ api_only_fields = integration.fields.select { _1[:api_only] }
+ api_only_fields.each do |field|
+ expect(page).not_to have_field("service[#{field.name}]", wait: 0)
+ end
+
sections = integration.sections
events = parse_json(trigger_events_for_integration(integration))
diff --git a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
index 284c129221b..b786d7e5527 100644
--- a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
@@ -265,10 +265,9 @@ RSpec.shared_examples 'common trace features' do
end
context 'build token' do
- let(:token) { 'my_secret_token' }
+ let(:token) { build.token }
before do
- build.update!(token: token)
trace.append(token, 0)
end
diff --git a/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb
index 326800e6dc2..c9300aff3e6 100644
--- a/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb
@@ -32,21 +32,7 @@ RSpec.shared_examples "position formatter" do
subject { formatter.to_h }
- context 'when file_identifier_hash is disabled' do
- before do
- stub_feature_flags(file_identifier_hash: false)
- end
-
- it { is_expected.to eq(formatter_hash.except(:file_identifier_hash)) }
- end
-
- context 'when file_identifier_hash is enabled' do
- before do
- stub_feature_flags(file_identifier_hash: true)
- end
-
- it { is_expected.to eq(formatter_hash) }
- end
+ it { is_expected.to eq(formatter_hash) }
end
describe '#==' do
diff --git a/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb
new file mode 100644
index 00000000000..a3e4379f4d3
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'search results filtered by language' do
+ let(:scope) { 'blobs' }
+ let(:filters) { { language: %w[Ruby Markdown] } }
+ let(:query) { 'def | popen | test' }
+
+ before do
+ project.repository.index_commits_and_blobs
+
+ ensure_elasticsearch_index!
+ end
+
+ subject(:blob_results) { results.objects('blobs') }
+
+ it 'filters by language', :sidekiq_inline, :aggregate_failures do
+ expected_paths = %w[
+ files/ruby/popen.rb
+ files/markdown/ruby-style-guide.md
+ files/ruby/regex.rb
+ files/ruby/version_info.rb
+ CONTRIBUTING.md
+ ]
+
+ paths = blob_results.map { |blob| blob.binary_path }
+ expect(blob_results.size).to eq(5)
+ expect(paths).to match_array(expected_paths)
+ end
+
+ context 'when the search_blobs_language_aggregation feature flag is disabled' do
+ before do
+ stub_feature_flags(search_blobs_language_aggregation: false)
+ end
+
+ it 'does not filter by language', :sidekiq_inline, :aggregate_failures do
+ expected_paths = %w[
+ CHANGELOG
+ CONTRIBUTING.md
+ bar/branch-test.txt
+ custom-highlighting/test.gitlab-custom
+ files/ruby/popen.rb
+ files/ruby/regex.rb
+ files/ruby/version_info.rb
+ files/whitespace
+ encoding/test.txt
+ files/markdown/ruby-style-guide.md
+ ]
+
+ paths = blob_results.map { |blob| blob.binary_path }
+ expect(blob_results.size).to eq(10)
+ expect(paths).to match_array(expected_paths)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
index b5d93aec1bf..9d280d9404a 100644
--- a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
@@ -32,3 +32,45 @@ RSpec.shared_examples 'does not track when feature flag is disabled' do |feature
end
end
end
+
+RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events' do
+ before do
+ stub_application_setting(usage_ping_enabled: true)
+ end
+
+ def count_unique(date_from: 1.minute.ago, date_to: 1.minute.from_now)
+ 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, project: project)).to be_truthy
+ expect(track_action(author: user1, project: project)).to be_truthy
+ expect(track_action(author: user2, project: project)).to be_truthy
+ expect(count_unique).to eq(2)
+ end
+ end
+
+ it 'does not track edit actions if author is not present' do
+ expect(track_action(author: nil, project: project)).to be_nil
+ end
+
+ it 'emits snowplow event' do
+ track_action(author: user1, project: project)
+
+ expect_snowplow_event(category: 'issues_edit', action: action, user: user1,
+ namespace: project.namespace, project: project)
+ end
+
+ context 'with route_hll_to_snowplow_phase2 disabled' do
+ before do
+ stub_feature_flags(route_hll_to_snowplow_phase2: false)
+ end
+
+ it 'does not emit snowplow event' do
+ track_action(author: user1, project: project)
+
+ expect_no_snowplow_event
+ end
+ end
+end
diff --git a/spec/support/shared_examples/merge_request_author_auto_assign_shared_examples.rb b/spec/support/shared_examples/merge_request_author_auto_assign_shared_examples.rb
deleted file mode 100644
index d4986975f03..00000000000
--- a/spec/support/shared_examples/merge_request_author_auto_assign_shared_examples.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'merge request author auto assign' do
- it 'populates merge request author as assignee' do
- expect(find('.js-assignee-search')).to have_content(user.name)
- expect(page).not_to have_content 'Assign yourself'
- end
-end
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index fa10b03fa90..d189e91effd 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -357,7 +357,8 @@ RSpec.shared_examples "chat integration" do |integration_name|
end
context 'deployment events' do
- let(:sample_data) { Gitlab::DataBuilder::Deployment.build(create(:deployment), Time.now) }
+ let(:deployment) { create(:deployment) }
+ let(:sample_data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) }
it_behaves_like "untriggered #{integration_name} integration"
end
diff --git a/spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb b/spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb
deleted file mode 100644
index 744262d79ea..00000000000
--- a/spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-# frozen_string_literal: true
-
-# Input
-# - factory: [:clusters_applications_elastic_stack, :clusters_integrations_elastic_stack]
-RSpec.shared_examples 'cluster-based #elasticsearch_client' do |factory|
- describe '#elasticsearch_client' do
- context 'cluster is nil' do
- subject { build(factory, cluster: nil) }
-
- it 'returns nil' do
- expect(subject.cluster).to be_nil
- expect(subject.elasticsearch_client).to be_nil
- end
- end
-
- context "cluster doesn't have kubeclient" do
- let(:cluster) { create(:cluster) }
-
- subject { create(factory, cluster: cluster) }
-
- it 'returns nil' do
- expect(subject.elasticsearch_client).to be_nil
- end
- end
-
- context 'cluster has kubeclient' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url }
- let(:kube_client) { subject.cluster.kubeclient.core_client }
-
- subject { create(factory, cluster: cluster) }
-
- before do
- subject.cluster.platform_kubernetes.namespace = 'a-namespace'
- stub_kubeclient_discover(cluster.platform_kubernetes.api_url)
-
- create(:cluster_kubernetes_namespace,
- cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
- end
-
- it 'creates proxy elasticsearch_client' do
- expect(subject.elasticsearch_client).to be_instance_of(Elasticsearch::Transport::Client)
- end
-
- it 'copies proxy_url, options and headers from kube client to elasticsearch_client' do
- expect(Elasticsearch::Client)
- .to(receive(:new))
- .with(url: a_valid_url, adapter: :net_http)
- .and_call_original
-
- client = subject.elasticsearch_client
- faraday_connection = client.transport.connections.first.connection
-
- expect(faraday_connection.headers["Authorization"]).to eq(kube_client.headers[:Authorization])
- expect(faraday_connection.ssl.cert_store).to be_instance_of(OpenSSL::X509::Store)
- expect(faraday_connection.ssl.verify).to eq(1)
- expect(faraday_connection.options.timeout).to be_nil
- end
-
- context 'when cluster is not reachable' do
- before do
- allow(kube_client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
- end
-
- it 'returns nil' do
- expect(subject.elasticsearch_client).to be_nil
- end
- end
-
- context 'when timeout is provided' do
- it 'sets timeout in elasticsearch_client' do
- client = subject.elasticsearch_client(timeout: 123)
- faraday_connection = client.transport.connections.first.connection
-
- expect(faraday_connection.options.timeout).to eq(123)
- end
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
index 2e062cda4e9..d80be5be3b3 100644
--- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
@@ -230,7 +230,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
context 'deployment events' do
let_it_be(:deployment) { create(:deployment) }
- let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.current) }
+ let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, 'created', Time.current) }
it_behaves_like 'calls the integration API with the event message', /Deploy to (.*?) created/
end
@@ -677,7 +677,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
create(:deployment, :success, project: project, sha: project.commit.sha, ref: project.default_branch)
end
- let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.now) }
+ let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) }
before do
allow(chat_integration).to receive_messages(
diff --git a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
index a2b4cdc33d0..d06e8391a9a 100644
--- a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
@@ -82,7 +82,7 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
it { is_expected.to belong_to(:group) }
it { is_expected.to have_many(:issues) }
it { is_expected.to have_many(:merge_requests) }
- it { is_expected.to have_many(:labels) }
+ it { is_expected.to have_many(:labels).through(:issues) }
end
describe '#timebox_name' do
diff --git a/spec/support/shared_examples/models/issuable_participants_shared_examples.rb b/spec/support/shared_examples/models/issuable_participants_shared_examples.rb
new file mode 100644
index 00000000000..c3eaae0ace2
--- /dev/null
+++ b/spec/support/shared_examples/models/issuable_participants_shared_examples.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issuable participants' do
+ context 'when resource parent is public' do
+ context 'and users are referenced on notes' do
+ let_it_be(:notes_author) { create(:user) }
+
+ let(:note_params) { params.merge(author: notes_author) }
+
+ before do
+ create(:note, note_params)
+ end
+
+ it 'includes the issue author' do
+ expect(issuable.participants).to include(issuable.author)
+ end
+
+ it 'includes the authors of the notes' do
+ expect(issuable.participants).to include(notes_author)
+ end
+
+ context 'and note is confidential' do
+ context 'and mentions users' do
+ let_it_be(:guest_1) { create(:user) }
+ let_it_be(:guest_2) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+
+ before do
+ issuable_parent.add_guest(guest_1)
+ issuable_parent.add_guest(guest_2)
+ issuable_parent.add_reporter(reporter)
+
+ confidential_note_params =
+ note_params.merge(
+ confidential: true,
+ note: "mentions #{guest_1.to_reference} and #{guest_2.to_reference} and #{reporter.to_reference}"
+ )
+
+ regular_note_params =
+ note_params.merge(note: "Mentions #{guest_2.to_reference}")
+
+ create(:note, confidential_note_params)
+ create(:note, regular_note_params)
+ end
+
+ it 'only includes users that can read the note as participants' do
+ expect(issuable.participants).to contain_exactly(issuable.author, notes_author, reporter, guest_2)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index 75fff11cecd..aa40a2c7135 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -80,7 +80,7 @@ RSpec.shared_examples_for "member creation" do
let_it_be(:admin) { create(:admin) }
it 'returns a Member object', :aggregate_failures do
- member = described_class.add_user(source, user, :maintainer)
+ member = described_class.add_member(source, user, :maintainer)
expect(member).to be_a member_type
expect(member).to be_persisted
@@ -99,7 +99,7 @@ RSpec.shared_examples_for "member creation" do
end
it 'does not update the member' do
- member = described_class.add_user(source, project_bot, :maintainer, current_user: user)
+ member = described_class.add_member(source, project_bot, :maintainer, current_user: user)
expect(source.users.reload).to include(project_bot)
expect(member).to be_persisted
@@ -110,7 +110,7 @@ RSpec.shared_examples_for "member creation" do
context 'when project_bot is not already a member' do
it 'adds the member' do
- member = described_class.add_user(source, project_bot, :maintainer, current_user: user)
+ member = described_class.add_member(source, project_bot, :maintainer, current_user: user)
expect(source.users.reload).to include(project_bot)
expect(member).to be_persisted
@@ -120,7 +120,7 @@ RSpec.shared_examples_for "member creation" do
context 'when admin mode is enabled', :enable_admin_mode, :aggregate_failures do
it 'sets members.created_by to the given admin current_user' do
- member = described_class.add_user(source, user, :maintainer, current_user: admin)
+ member = described_class.add_member(source, user, :maintainer, current_user: admin)
expect(member).to be_persisted
expect(source.users.reload).to include(user)
@@ -130,7 +130,7 @@ RSpec.shared_examples_for "member creation" do
context 'when admin mode is disabled' do
it 'rejects setting members.created_by to the given admin current_user', :aggregate_failures do
- member = described_class.add_user(source, user, :maintainer, current_user: admin)
+ member = described_class.add_member(source, user, :maintainer, current_user: admin)
expect(member).not_to be_persisted
expect(source.users.reload).not_to include(user)
@@ -139,7 +139,7 @@ RSpec.shared_examples_for "member creation" do
end
it 'sets members.expires_at to the given expires_at' do
- member = described_class.add_user(source, user, :maintainer, expires_at: Date.new(2016, 9, 22))
+ member = described_class.add_member(source, user, :maintainer, expires_at: Date.new(2016, 9, 22))
expect(member.expires_at).to eq(Date.new(2016, 9, 22))
end
@@ -148,7 +148,7 @@ RSpec.shared_examples_for "member creation" do
it "accepts the :#{sym_key} symbol as access level", :aggregate_failures do
expect(source.users).not_to include(user)
- member = described_class.add_user(source, user.id, sym_key)
+ member = described_class.add_member(source, user.id, sym_key)
expect(member.access_level).to eq(int_access_level)
expect(source.users.reload).to include(user)
@@ -157,7 +157,7 @@ RSpec.shared_examples_for "member creation" do
it "accepts the #{int_access_level} integer as access level", :aggregate_failures do
expect(source.users).not_to include(user)
- member = described_class.add_user(source, user.id, int_access_level)
+ member = described_class.add_member(source, user.id, int_access_level)
expect(member.access_level).to eq(int_access_level)
expect(source.users.reload).to include(user)
@@ -169,7 +169,7 @@ RSpec.shared_examples_for "member creation" do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, user.id, :maintainer)
+ described_class.add_member(source, user.id, :maintainer)
expect(source.users.reload).to include(user)
end
@@ -179,7 +179,7 @@ RSpec.shared_examples_for "member creation" do
it 'does not add the user as a member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, non_existing_record_id, :maintainer)
+ described_class.add_member(source, non_existing_record_id, :maintainer)
expect(source.users.reload).not_to include(user)
end
@@ -189,7 +189,7 @@ RSpec.shared_examples_for "member creation" do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, user, :maintainer)
+ described_class.add_member(source, user, :maintainer)
expect(source.users.reload).to include(user)
end
@@ -205,7 +205,7 @@ RSpec.shared_examples_for "member creation" do
expect(source.requesters.exists?(user_id: user)).to be_truthy
expect do
- described_class.add_user(source, user, :maintainer)
+ described_class.add_member(source, user, :maintainer)
end.to raise_error(Gitlab::Access::AccessDeniedError)
expect(source.users.reload).not_to include(user)
@@ -217,7 +217,7 @@ RSpec.shared_examples_for "member creation" do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, user.email, :maintainer)
+ described_class.add_member(source, user.email, :maintainer)
expect(source.users.reload).to include(user)
end
@@ -227,7 +227,7 @@ RSpec.shared_examples_for "member creation" do
it 'creates an invited member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, 'user@example.com', :maintainer)
+ described_class.add_member(source, 'user@example.com', :maintainer)
expect(source.members.invite.pluck(:invite_email)).to include('user@example.com')
end
@@ -237,7 +237,7 @@ RSpec.shared_examples_for "member creation" do
it 'creates an invited member', :aggregate_failures do
email_starting_with_number = "#{user.id}_email@example.com"
- described_class.add_user(source, email_starting_with_number, :maintainer)
+ described_class.add_member(source, email_starting_with_number, :maintainer)
expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number)
expect(source.users.reload).not_to include(user)
@@ -249,7 +249,7 @@ RSpec.shared_examples_for "member creation" do
it 'creates the member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, user, :maintainer, current_user: admin)
+ described_class.add_member(source, user, :maintainer, current_user: admin)
expect(source.users.reload).to include(user)
end
@@ -263,7 +263,7 @@ RSpec.shared_examples_for "member creation" do
expect(source.users).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
- described_class.add_user(source, user, :maintainer, current_user: admin)
+ described_class.add_member(source, user, :maintainer, current_user: admin)
expect(source.users.reload).to include(user)
expect(source.requesters.reload.exists?(user_id: user)).to be_falsy
@@ -275,7 +275,7 @@ RSpec.shared_examples_for "member creation" do
it 'does not create the member', :aggregate_failures do
expect(source.users).not_to include(user)
- member = described_class.add_user(source, user, :maintainer, current_user: user)
+ member = described_class.add_member(source, user, :maintainer, current_user: user)
expect(source.users.reload).not_to include(user)
expect(member).not_to be_persisted
@@ -290,7 +290,7 @@ RSpec.shared_examples_for "member creation" do
expect(source.users).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
- described_class.add_user(source, user, :maintainer, current_user: user)
+ described_class.add_member(source, user, :maintainer, current_user: user)
expect(source.users.reload).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
@@ -299,37 +299,51 @@ RSpec.shared_examples_for "member creation" do
end
context 'when member already exists' do
- before do
- source.add_user(user, :developer)
- end
+ context 'when member is a user' do
+ before do
+ source.add_member(user, :developer)
+ end
- context 'with no current_user' do
- it 'updates the member' do
- expect(source.users).to include(user)
+ context 'with no current_user' do
+ it 'updates the member' do
+ expect(source.users).to include(user)
- described_class.add_user(source, user, :maintainer)
+ described_class.add_member(source, user, :maintainer)
- expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
end
- end
- context 'when current_user can update member', :enable_admin_mode do
- it 'updates the member' do
- expect(source.users).to include(user)
+ context 'when current_user can update member', :enable_admin_mode do
+ it 'updates the member' do
+ expect(source.users).to include(user)
- described_class.add_user(source, user, :maintainer, current_user: admin)
+ described_class.add_member(source, user, :maintainer, current_user: admin)
- expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
end
- end
- context 'when current_user cannot update member' do
- it 'does not update the member' do
- expect(source.users).to include(user)
+ context 'when current_user cannot update member' do
+ it 'does not update the member' do
+ expect(source.users).to include(user)
+
+ described_class.add_member(source, user, :maintainer, current_user: user)
+
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+ end
- described_class.add_user(source, user, :maintainer, current_user: user)
+ context 'when member is an invite by email' do
+ let_it_be(:email) { 'user@email.com' }
+ let_it_be(:existing_member) { source.add_developer(email) }
- expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
+ it 'updates the member for that email' do
+ expect do
+ described_class.add_member(source, email, :maintainer)
+ end.to change { existing_member.reset.access_level }.from(Member::DEVELOPER).to(Member::MAINTAINER)
+ .and not_change { source.members.invite.count }
end
end
end
@@ -345,12 +359,12 @@ RSpec.shared_examples_for "bulk member creation" do
# maintainers cannot add owners
source.add_maintainer(user)
- expect(described_class.add_users(source, [user1, user2], :owner, current_user: user)).to be_empty
+ expect(described_class.add_members(source, [user1, user2], :owner, current_user: user)).to be_empty
end
end
it 'returns Member objects' do
- members = described_class.add_users(source, [user1, user2], :maintainer)
+ members = described_class.add_members(source, [user1, user2], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2)
expect(members).to all(be_a(member_type))
@@ -358,7 +372,7 @@ RSpec.shared_examples_for "bulk member creation" do
end
it 'returns an empty array' do
- members = described_class.add_users(source, [], :maintainer)
+ members = described_class.add_members(source, [], :maintainer)
expect(members).to be_a Array
expect(members).to be_empty
@@ -367,7 +381,7 @@ RSpec.shared_examples_for "bulk member creation" do
it 'supports different formats' do
list = ['joe@local.test', admin, user1.id, user2.id.to_s]
- members = described_class.add_users(source, list, :maintainer)
+ members = described_class.add_members(source, list, :maintainer)
expect(members.size).to eq(4)
expect(members.first).to be_invite
@@ -375,7 +389,7 @@ RSpec.shared_examples_for "bulk member creation" do
context 'with de-duplication' do
it 'has the same user by id and user' do
- members = described_class.add_users(source, [user1.id, user1, user1.id, user2, user2.id, user2], :maintainer)
+ members = described_class.add_members(source, [user1.id, user1, user1.id, user2, user2.id, user2], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2)
expect(members).to all(be_a(member_type))
@@ -383,7 +397,7 @@ RSpec.shared_examples_for "bulk member creation" do
end
it 'has the same user sent more than once' do
- members = described_class.add_users(source, [user1, user1], :maintainer)
+ members = described_class.add_members(source, [user1, user1], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1)
expect(members).to all(be_a(member_type))
@@ -392,7 +406,7 @@ RSpec.shared_examples_for "bulk member creation" do
end
it 'with the same user sent more than once by user and by email' do
- members = described_class.add_users(source, [user1, user1.email], :maintainer)
+ members = described_class.add_members(source, [user1, user1.email], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1)
expect(members).to all(be_a(member_type))
@@ -400,7 +414,7 @@ RSpec.shared_examples_for "bulk member creation" do
end
it 'with the same user sent more than once by user id and by email' do
- members = described_class.add_users(source, [user1.id, user1.email], :maintainer)
+ members = described_class.add_members(source, [user1.id, user1.email], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1)
expect(members).to all(be_a(member_type))
@@ -409,12 +423,12 @@ RSpec.shared_examples_for "bulk member creation" do
context 'when a member already exists' do
before do
- source.add_user(user1, :developer)
+ source.add_member(user1, :developer)
end
it 'has the same user sent more than once with the member already existing' do
expect do
- members = described_class.add_users(source, [user1, user1, user2], :maintainer)
+ members = described_class.add_members(source, [user1, user1, user2], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2)
expect(members).to all(be_a(member_type))
expect(members).to all(be_persisted)
@@ -425,7 +439,7 @@ RSpec.shared_examples_for "bulk member creation" do
user3 = create(:user)
expect do
- members = described_class.add_users(source, [user1.id, user2, user3.id], :maintainer)
+ members = described_class.add_members(source, [user1.id, user2, user3.id], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2, user3)
expect(members).to all(be_a(member_type))
expect(members).to all(be_persisted)
@@ -436,7 +450,7 @@ RSpec.shared_examples_for "bulk member creation" do
user3 = create(:user)
expect do
- members = described_class.add_users(source, [user1, user2, user3], :maintainer)
+ members = described_class.add_members(source, [user1, user2, user3], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2, user3)
expect(members).to all(be_a(member_type))
expect(members).to all(be_persisted)
@@ -448,7 +462,7 @@ RSpec.shared_examples_for "bulk member creation" do
let(:task_project) { source.is_a?(Group) ? create(:project, group: source) : source }
it 'creates a member_task with the correct attributes', :aggregate_failures do
- members = described_class.add_users(source, [user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id)
+ members = described_class.add_members(source, [user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id)
member = members.last
expect(member.tasks_to_be_done).to match_array([:ci, :code])
@@ -457,7 +471,7 @@ RSpec.shared_examples_for "bulk member creation" do
context 'with an already existing member' do
before do
- source.add_user(user1, :developer)
+ source.add_member(user1, :developer)
end
it 'does not update tasks to be done if tasks already exist', :aggregate_failures do
@@ -465,7 +479,7 @@ RSpec.shared_examples_for "bulk member creation" do
create(:member_task, member: member, project: task_project, tasks_to_be_done: %w(code ci))
expect do
- described_class.add_users(source,
+ described_class.add_members(source,
[user1.id],
:developer,
tasks_to_be_done: %w(issues),
@@ -479,7 +493,7 @@ RSpec.shared_examples_for "bulk member creation" do
it 'adds tasks to be done if they do not exist', :aggregate_failures do
expect do
- described_class.add_users(source,
+ described_class.add_members(source,
[user1.id],
:developer,
tasks_to_be_done: %w(issues),
diff --git a/spec/support/shared_examples/models/mentionable_shared_examples.rb b/spec/support/shared_examples/models/mentionable_shared_examples.rb
index e23658d1774..f9612dd61be 100644
--- a/spec/support/shared_examples/models/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/models/mentionable_shared_examples.rb
@@ -260,6 +260,25 @@ RSpec.shared_examples 'mentions in notes' do |mentionable_type|
expect(mentionable.referenced_projects(user)).to eq [mentionable.project].compact # epic.project is nil, and we want empty []
expect(mentionable.referenced_groups(user)).to eq [group]
end
+
+ if [:epic, :issue].include?(mentionable_type)
+ context 'and note is confidential' do
+ let_it_be(:guest) { create(:user) }
+
+ let(:note_desc) { "#{guest.to_reference} and #{user2.to_reference} and #{user.to_reference}" }
+
+ before do
+ note.resource_parent.add_reporter(user2)
+ note.resource_parent.add_guest(guest)
+ # Bypass :confidential update model validation for testing purposes
+ note.update_attribute(:confidential, true)
+ end
+
+ it 'returns only mentioned users that has permissions' do
+ expect(note.mentioned_users).to contain_exactly(user, user2)
+ end
+ end
+ end
end
end
@@ -294,6 +313,26 @@ RSpec.shared_examples 'load mentions from DB' do |mentionable_type|
end
end
+ if [:epic, :issue].include?(mentionable_type)
+ context 'and note is confidential' do
+ let_it_be(:guest) { create(:user) }
+
+ let(:note_desc) { "#{guest.to_reference} and #{mentioned_user.to_reference}" }
+
+ before do
+ note.resource_parent.add_reporter(mentioned_user)
+ note.resource_parent.add_guest(guest)
+ # Bypass :confidential update model validation for testing purposes
+ note.update_attribute(:confidential, true)
+ note.store_mentions!
+ end
+
+ it 'stores only mentioned users that has permissions' do
+ expect(mentionable.referenced_users).to contain_exactly(mentioned_user)
+ end
+ end
+ end
+
context 'when private projects and groups are mentioned' do
let(:mega_user) { create(:user) }
let(:private_project) { create(:project, :private) }
diff --git a/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb
index ab04692616a..d42e925ed22 100644
--- a/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb
@@ -89,10 +89,13 @@ RSpec.shared_examples 'clone quick action' do
let(:bug) { create(:label, project: project, title: 'bug') }
let(:wontfix) { create(:label, project: project, title: 'wontfix') }
- let!(:target_milestone) { create(:milestone, title: '1.0', project: target_project) }
-
before do
target_project.add_maintainer(user)
+
+ # create equivalent labels and milestones in the target project
+ create(:label, project: target_project, title: 'bug')
+ create(:label, project: target_project, title: 'wontfix')
+ create(:milestone, title: '1.0', project: target_project)
end
shared_examples 'applies the commands to issues in both projects, target and source' do
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index e6b0772aec1..bb2f8965294 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
RSpec.shared_examples 'conan ping endpoint' do
+ it_behaves_like 'conan FIPS mode' do
+ subject { get api(url) }
+ end
+
it 'responds with 200 OK when no token provided' do
get api(url)
@@ -68,7 +72,7 @@ RSpec.shared_examples 'conan search endpoint' do
project.update!(visibility: 'private')
project.team.truncate
user.project_authorizations.delete_all
- project.add_user(user, role) unless role == :anonymous
+ project.add_member(user, role) unless role == :anonymous
get api(url), params: params, headers: headers
end
@@ -85,6 +89,8 @@ end
RSpec.shared_examples 'conan authenticate endpoint' do
subject { get api(url), headers: headers }
+ it_behaves_like 'conan FIPS mode'
+
context 'when using invalid token' do
let(:auth_token) { 'invalid_token' }
@@ -159,6 +165,10 @@ RSpec.shared_examples 'conan authenticate endpoint' do
end
RSpec.shared_examples 'conan check_credentials endpoint' do
+ it_behaves_like 'conan FIPS mode' do
+ subject { get api(url), headers: headers }
+ end
+
it 'responds with a 200 OK with PAT' do
get api(url), headers: headers
@@ -390,6 +400,7 @@ end
RSpec.shared_examples 'recipe snapshot endpoint' do
subject { get api(url), headers: headers }
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'empty recipe for not found package'
@@ -415,6 +426,7 @@ end
RSpec.shared_examples 'package snapshot endpoint' do
subject { get api(url), headers: headers }
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'empty recipe for not found package'
@@ -436,6 +448,10 @@ RSpec.shared_examples 'package snapshot endpoint' do
end
RSpec.shared_examples 'recipe download_urls endpoint' do
+ it_behaves_like 'conan FIPS mode' do
+ let(:recipe_path) { package.conan_recipe_path }
+ end
+
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'recipe download_urls'
@@ -443,6 +459,10 @@ RSpec.shared_examples 'recipe download_urls endpoint' do
end
RSpec.shared_examples 'package download_urls endpoint' do
+ it_behaves_like 'conan FIPS mode' do
+ let(:recipe_path) { package.conan_recipe_path }
+ end
+
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'package download_urls'
@@ -457,6 +477,7 @@ RSpec.shared_examples 'recipe upload_urls endpoint' do
'conanmanifest.txt': 123 }
end
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
it_behaves_like 'handling empty values for username and channel'
@@ -519,6 +540,7 @@ RSpec.shared_examples 'package upload_urls endpoint' do
'conan_package.tgz': 523 }
end
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
it_behaves_like 'handling empty values for username and channel'
@@ -556,6 +578,7 @@ end
RSpec.shared_examples 'delete package endpoint' do
let(:recipe_path) { package.conan_recipe_path }
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'handling empty values for username and channel'
@@ -665,6 +688,7 @@ RSpec.shared_examples 'not found request' do
end
RSpec.shared_examples 'recipe file download endpoint' do
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'a public project with packages'
it_behaves_like 'an internal project with packages'
it_behaves_like 'a private project with packages'
@@ -672,6 +696,7 @@ RSpec.shared_examples 'recipe file download endpoint' do
end
RSpec.shared_examples 'package file download endpoint' do
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'a public project with packages'
it_behaves_like 'an internal project with packages'
it_behaves_like 'a private project with packages'
@@ -697,6 +722,7 @@ RSpec.shared_examples 'project not found by project id' do
end
RSpec.shared_examples 'workhorse authorize endpoint' do
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'workhorse authorization'
@@ -718,6 +744,7 @@ RSpec.shared_examples 'workhorse recipe file upload endpoint' do
)
end
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'uploads a package file'
@@ -979,3 +1006,9 @@ RSpec.shared_examples 'workhorse authorization' do
end
end
end
+
+RSpec.shared_examples 'conan FIPS mode' do
+ context 'when FIPS mode is enabled', :fips_mode do
+ it_behaves_like 'returning response status', :not_found
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb
index e0225070986..2ba42b8e8fa 100644
--- a/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb
@@ -15,3 +15,9 @@ RSpec.shared_examples 'rejects Debian access with unknown container id' do |anon
end
end
end
+
+RSpec.shared_examples 'Debian API FIPS mode' do
+ context 'when FIPS mode is enabled', :fips_mode do
+ it_behaves_like 'returning response status', :not_found
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb
index 5cd63c33936..f13ac05591c 100644
--- a/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb
@@ -3,6 +3,8 @@
RSpec.shared_examples 'Debian distributions GET request' do |status, body = nil|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
it "returns #{status}#{and_body}" do
subject
@@ -17,6 +19,8 @@ end
RSpec.shared_examples 'Debian distributions PUT request' do |status, body|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
if status == :success
it 'updates distribution', :aggregate_failures do
expect(::Packages::Debian::UpdateDistributionService).to receive(:new).with(distribution, api_params.except(:codename)).and_call_original
@@ -49,6 +53,8 @@ end
RSpec.shared_examples 'Debian distributions DELETE request' do |status, body|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
if status == :success
it 'updates distribution', :aggregate_failures do
expect { subject }
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 9f96cb2a164..de7032450a5 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
@@ -3,6 +3,8 @@
RSpec.shared_examples 'Debian packages GET request' do |status, body = nil|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
it "returns #{status}#{and_body}" do
subject
@@ -17,6 +19,8 @@ end
RSpec.shared_examples 'Debian packages upload request' do |status, body = nil|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
if status == :created
it 'creates package files', :aggregate_failures do
expect(::Packages::Debian::FindOrCreateIncomingService).to receive(:new).with(container, user).and_call_original
diff --git a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
index e534a02e562..8ab820e9d43 100644
--- a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
@@ -64,7 +64,8 @@ RSpec.shared_examples 'group and project boards query' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { }
+ include_context 'no sort argument'
+
let(:first_param) { 2 }
def pagination_results_data(nodes)
diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
index a42a1fda62e..b459e479c91 100644
--- a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
@@ -22,7 +22,7 @@ RSpec.shared_examples 'snippet edit usage data counters' do
context 'when user is not sessionless', :clean_gitlab_redis_sessions do
before do
- stub_session('warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]])
+ stub_session('warden.user.user.key' => [[current_user.id], current_user.authenticatable_salt])
end
it 'tracks usage data actions', :clean_gitlab_redis_sessions do
diff --git a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb
new file mode 100644
index 00000000000..013945bd578
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb
@@ -0,0 +1,415 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'web-hook API endpoints test hook' do |prefix|
+ describe "POST #{prefix}/:hook_id" do
+ it 'tests the hook' do
+ expect(WebHookService)
+ .to receive(:new).with(hook, anything, String, force: false)
+ .and_return(instance_double(WebHookService, execute: nil))
+
+ post api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+end
+
+RSpec.shared_examples 'web-hook API endpoints with branch-filter' do |prefix|
+ describe "POST #{prefix}/hooks" do
+ it "returns a 422 error if branch filter is not valid" do
+ post api(collection_uri, user),
+ params: { url: "http://example.com", push_events_branch_filter: '~badbranchname/' }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+end
+
+RSpec.shared_examples 'web-hook API endpoints' do |prefix|
+ def hooks_count
+ scope.count
+ end
+
+ def hook_param_overrides
+ if defined?(super)
+ super
+ else
+ { push_events_branch_filter: 'some-feature-branch' }
+ end
+ end
+
+ let(:hook_params) do
+ event_names.to_h { [_1, true] }.merge(hook_param_overrides).merge(
+ url: "http://example.com",
+ url_variables: [
+ { key: 'token', value: 'very-secret' },
+ { key: 'abc', value: 'other value' }
+ ]
+ )
+ end
+
+ let(:update_params) do
+ {
+ push_events: false,
+ job_events: true,
+ push_events_branch_filter: 'updated-branch-filter'
+ }
+ end
+
+ let(:default_values) { {} }
+
+ describe "GET #{prefix}/hooks" do
+ context "authorized user" do
+ it "returns all hooks" do
+ get api(collection_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_collection_schema
+ end
+ end
+
+ context "when user is forbidden" do
+ it "prevents access to hooks" do
+ get api(collection_uri, unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context "when user is unauthorized" do
+ it "prevents access to hooks" do
+ get api(collection_uri, nil)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'the hook has URL variables' do
+ before do
+ hook.update!(url_variables: { 'token' => 'supers3cret' })
+ end
+
+ it 'returns the names of the url variables' do
+ get api(collection_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to contain_exactly(
+ a_hash_including(
+ 'url_variables' => [{ 'key' => 'token' }]
+ )
+ )
+ end
+ end
+ end
+
+ describe "GET #{prefix}/hooks/:hook_id" do
+ context "authorized user" do
+ it "returns a project hook" do
+ get api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_hook_schema
+
+ expect(json_response['url']).to eq(hook.url)
+ end
+
+ it "returns a 404 error if hook id is not available" do
+ get api(hook_uri(non_existing_record_id), user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'the hook is disabled' do
+ before do
+ hook.disable!
+ end
+
+ it "has the correct alert status", :aggregate_failures do
+ get api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to include('alert_status' => 'disabled')
+ end
+ end
+
+ context 'the hook is backed-off' do
+ before do
+ hook.backoff!
+ end
+
+ it "has the correct alert status", :aggregate_failures do
+ get api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to include(
+ 'alert_status' => 'temporarily_disabled',
+ 'disabled_until' => hook.disabled_until.iso8601(3)
+ )
+ end
+ end
+ end
+
+ context "when user is forbidden" do
+ it "does not access an existing hook" do
+ get api(hook_uri, unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context "when user is unauthorized" do
+ it "does not access an existing hook" do
+ get api(hook_uri, nil)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe "POST #{prefix}/hooks" do
+ let(:hook_creation_params) { hook_params }
+
+ it "adds hook", :aggregate_failures do
+ expect do
+ post api(collection_uri, user),
+ params: hook_creation_params
+ end.to change { hooks_count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_hook_schema
+
+ expect(json_response['url']).to eq(hook_creation_params[:url])
+ hook_param_overrides.each do |k, v|
+ expect(json_response[k.to_s]).to eq(v)
+ end
+ event_names.each do |name|
+ expect(json_response[name.to_s]).to eq(true), name
+ end
+ expect(json_response['url_variables']).to match_array [
+ { 'key' => 'token' },
+ { 'key' => 'abc' }
+ ]
+ expect(json_response).not_to include('token')
+ end
+
+ it "adds the token without including it in the response" do
+ token = "secret token"
+
+ expect do
+ post api(collection_uri, user),
+ params: { url: "http://example.com", token: token }
+ end.to change { hooks_count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response["url"]).to eq("http://example.com")
+ expect(json_response).not_to include("token")
+
+ hook = scope.find(json_response["id"])
+
+ expect(hook.url).to eq("http://example.com")
+ expect(hook.token).to eq(token)
+ end
+
+ it "returns a 400 error if url not given" do
+ post api(collection_uri, user), params: { event_names.first => true }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it "returns a 400 error if no parameters are provided" do
+ post api(collection_uri, user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'sets default values for events', :aggregate_failures do
+ post api(collection_uri, user), params: { url: 'http://mep.mep' }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_hook_schema
+ expect(json_response['enable_ssl_verification']).to be true
+ event_names.each do |name|
+ expect(json_response[name.to_s]).to eq(default_values.fetch(name, false)), name
+ end
+ end
+
+ it "returns a 422 error if token not valid" do
+ post api(collection_uri, user),
+ params: { url: "http://example.com", token: "foo\nbar" }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it "returns a 422 error if url not valid" do
+ post api(collection_uri, user), params: { url: "ftp://example.com" }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ describe "PUT #{prefix}/hooks/:hook_id" do
+ it "updates an existing hook" do
+ put api(hook_uri, user), params: update_params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_hook_schema
+
+ update_params.each do |k, v|
+ expect(json_response[k.to_s]).to eq(v)
+ end
+ end
+
+ it 'updates the URL variables' do
+ hook.update!(url_variables: { 'abc' => 'some value' })
+
+ put api(hook_uri, user),
+ params: { url_variables: [{ key: 'def', value: 'other value' }] }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['url_variables']).to match_array [
+ { 'key' => 'abc' },
+ { 'key' => 'def' }
+ ]
+ end
+
+ it "adds the token without including it in the response" do
+ token = "secret token"
+
+ put api(hook_uri, user), params: { url: "http://example.org", token: token }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response["url"]).to eq("http://example.org")
+ expect(json_response).not_to include("token")
+
+ expect(hook.reload.url).to eq("http://example.org")
+ expect(hook.reload.token).to eq(token)
+ end
+
+ it "returns 404 error if hook id not found" do
+ put api(hook_uri(non_existing_record_id), user), params: { url: 'http://example.org' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns 400 error if no parameters are provided" do
+ put api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it "returns a 422 error if url is not valid" do
+ put api(hook_uri, user), params: { url: 'ftp://example.com' }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it "returns a 422 error if token is not valid" do
+ put api(hook_uri, user), params: { token: %w[foo bar].join("\n") }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ describe "DELETE /projects/:id/hooks/:hook_id" do
+ it "deletes hook from project" do
+ expect do
+ delete api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { hooks_count }.by(-1)
+ end
+
+ it "returns a 404 error when deleting non existent hook" do
+ delete api(hook_uri(non_existing_record_id), user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns a 404 error if hook id not given" do
+ delete api(collection_uri, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns forbidden if a user attempts to delete hooks they do not own" do
+ delete api(hook_uri, unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(WebHook.exists?(hook.id)).to be_truthy
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api(hook_uri, user) }
+ end
+ end
+
+ describe "PUT #{prefix}/hooks/:hook_id/url_variables/:key", :aggregate_failures do
+ it 'sets the variable' do
+ expect do
+ put api("#{hook_uri}/url_variables/abc", user),
+ params: { value: 'some secret value' }
+ end.to change { hook.reload.url_variables }.to(eq('abc' => 'some secret value'))
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'overwrites existing values' do
+ hook.update!(url_variables: { 'abc' => 'xyz', 'def' => 'other value' })
+
+ put api("#{hook_uri}/url_variables/abc", user),
+ params: { value: 'some secret value' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(hook.reload.url_variables).to eq('abc' => 'some secret value', 'def' => 'other value')
+ end
+
+ it "returns a 404 error when editing non existent hook" do
+ put api("#{hook_uri(non_existing_record_id)}/url_variables/abc", user),
+ params: { value: 'xyz' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns a 422 error when the key is illegal" do
+ put api("#{hook_uri}/url_variables/abc%20def", user),
+ params: { value: 'xyz' }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it "returns a 422 error when the value is illegal" do
+ put api("#{hook_uri}/url_variables/abc", user),
+ params: { value: '' }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ describe "DELETE #{prefix}/hooks/:hook_id/url_variables/:key", :aggregate_failures do
+ before do
+ hook.update!(url_variables: { 'abc' => 'prior value', 'def' => 'other value' })
+ end
+
+ it 'unsets the variable' do
+ expect do
+ delete api("#{hook_uri}/url_variables/abc", user)
+ end.to change { hook.reload.url_variables }.to(eq({ 'def' => 'other value' }))
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'returns 404 for keys that do not exist' do
+ hook.update!(url_variables: { 'def' => 'other value' })
+
+ delete api("#{hook_uri}/url_variables/abc", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns a 404 error when deleting a variable from a non existent hook" do
+ delete api(hook_uri(non_existing_record_id) + "/url_variables/abc", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index e7e30665b08..a59235486ec 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -275,7 +275,9 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
context 'when request exceeds the rate limit', :freeze_time, :clean_gitlab_redis_rate_limiting do
before do
stub_application_setting(notes_create_limit: 1)
- allow(::Gitlab::ApplicationRateLimiter).to receive(:increment).and_return(2)
+ allow_next_instance_of(Gitlab::ApplicationRateLimiter::BaseStrategy) do |strategy|
+ allow(strategy).to receive(:increment).and_return(2)
+ end
end
it 'prevents user from creating more notes' do
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
index 795545e4ad1..1a248bb04e7 100644
--- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member = true|
+RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member = true, md5_digest = true|
RSpec.shared_examples 'creating pypi package files' do
it 'creates package files' do
expect { subject }
@@ -14,6 +14,17 @@ RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member
expect(package.name).to eq params[:name]
expect(package.version).to eq params[:version]
expect(package.pypi_metadatum.required_python).to eq params[:requires_python]
+
+ if md5_digest
+ expect(package.package_files.first.file_md5).not_to be_nil
+ else
+ expect(package.package_files.first.file_md5).to be_nil
+ end
+ end
+
+ context 'with FIPS mode', :fips_mode do
+ it_behaves_like 'returning response status', :unprocessable_entity if md5_digest
+ it_behaves_like 'returning response status', status unless md5_digest
end
end
diff --git a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
index 70cc9b1e6b5..544a0ed8fdd 100644
--- a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
@@ -52,6 +52,24 @@ RSpec.shared_examples 'an unimplemented route' do
it_behaves_like 'when package feature is disabled'
end
+RSpec.shared_examples 'redirects to version download' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid response' do
+ subject
+
+ expect(request.url).to include 'module-1/system/download'
+ expect(response.headers).to include 'Location'
+ expect(response.headers['Location']).to include 'module-1/system/1.0.1/download'
+ end
+ end
+end
+
RSpec.shared_examples 'grants terraform module download' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
@@ -84,6 +102,22 @@ RSpec.shared_examples 'returns terraform module packages' do |user_type, status,
end
end
+RSpec.shared_examples 'returns terraform module version' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'returning a valid response' do
+ subject
+
+ expect(json_response).to match_schema('public_api/v4/packages/terraform/modules/v1/single_version')
+ end
+ end
+end
+
RSpec.shared_examples 'returns no terraform module packages' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
index 86e7da5bcbe..f8e096297d3 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
@@ -56,7 +56,7 @@ RSpec.shared_examples 'processes recovery alert' do
context 'seen for the first time' do
let(:alert) { AlertManagement::Alert.last }
- include_examples 'processes never-before-seen recovery alert'
+ it_behaves_like 'alerts service responds with an error and takes no actions', :bad_request
end
context 'for an existing alert with the same fingerprint' do
@@ -107,7 +107,7 @@ RSpec.shared_examples 'processes recovery alert' do
context 'which is resolved' do
let_it_be(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) }
- include_examples 'processes never-before-seen recovery alert'
+ it_behaves_like 'alerts service responds with an error and takes no actions', :bad_request
end
end
end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
index 132f1e0422e..3add5485fca 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
@@ -6,18 +6,24 @@
# - `alert`, alert for which related incidents should be closed
# - `project`, project of the alert
RSpec.shared_examples 'closes related incident if enabled' do
- context 'with issue' do
+ context 'with incident' do
before do
- alert.update!(issue: create(:issue, project: project))
+ alert.update!(issue: create(:incident, project: project))
end
- it { expect { subject }.to change { alert.issue.reload.closed? }.from(false).to(true) }
- it { expect { subject }.to change(ResourceStateEvent, :count).by(1) }
+ specify do
+ expect { Sidekiq::Testing.inline! { subject } }
+ .to change { alert.issue.reload.closed? }.from(false).to(true)
+ .and change(ResourceStateEvent, :count).by(1)
+ end
end
- context 'without issue' do
- it { expect { subject }.not_to change { alert.reload.issue } }
- it { expect { subject }.not_to change(ResourceStateEvent, :count) }
+ context 'without incident' do
+ specify do
+ expect(::IncidentManagement::CloseIncidentWorker).not_to receive(:perform_async)
+
+ subject
+ end
end
context 'with incident setting disabled' do
@@ -28,17 +34,23 @@ RSpec.shared_examples 'closes related incident if enabled' do
end
RSpec.shared_examples 'does not close related incident' do
- context 'with issue' do
+ context 'with incident' do
before do
- alert.update!(issue: create(:issue, project: project))
+ alert.update!(issue: create(:incident, project: project))
end
- it { expect { subject }.not_to change { alert.issue.reload.state } }
- it { expect { subject }.not_to change(ResourceStateEvent, :count) }
+ specify do
+ expect { Sidekiq::Testing.inline! { subject } }
+ .to not_change { alert.issue.reload.state }
+ .and not_change(ResourceStateEvent, :count)
+ end
end
- context 'without issue' do
- it { expect { subject }.not_to change { alert.reload.issue } }
- it { expect { subject }.not_to change(ResourceStateEvent, :count) }
+ context 'without incident' do
+ specify do
+ expect(::IncidentManagement::CloseIncidentWorker).not_to receive(:perform_async)
+
+ subject
+ end
end
end
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 f644f1a1687..571cb7dc03d 100644
--- a/spec/support/shared_examples/services/alert_management_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -68,14 +68,14 @@ RSpec.shared_examples 'processes one firing and one resolved prometheus alerts'
expect(Gitlab::AppLogger).not_to receive(:warn)
expect { subject }
- .to change(AlertManagement::Alert, :count).by(2)
- .and change(Note, :count).by(4)
+ .to change(AlertManagement::Alert, :count).by(1)
+ .and change(Note, :count).by(1)
expect(subject).to be_success
expect(subject.payload[:alerts]).to all(be_a_kind_of(AlertManagement::Alert))
- expect(subject.payload[:alerts].size).to eq(2)
+ expect(subject.payload[:alerts].size).to eq(1)
end
it_behaves_like 'processes incident issues'
- it_behaves_like 'sends alert notification emails', count: 2
+ it_behaves_like 'sends alert notification emails'
end
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index f18869fb380..3be59af6a37 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
RSpec.shared_context 'container registry auth service context' do
+ let_it_be(:rsa_key) { OpenSSL::PKey::RSA.generate(3072) }
+
let(:current_project) { nil }
let(:current_user) { nil }
let(:current_params) { {} }
- let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
let(:payload) { JWT.decode(subject[:token], rsa_key, true, { algorithm: 'RS256' }).first }
let(:authentication_abilities) do
diff --git a/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb b/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb
new file mode 100644
index 00000000000..a62cffc0e1b
--- /dev/null
+++ b/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+shared_examples_for 'update feature flag client' do
+ let!(:client) { create(:operations_feature_flags_client, project: project) }
+
+ it 'updates last feature flag updated at' do
+ freeze_time do
+ expect { subject }.to change { client.reload.last_feature_flag_updated_at }.from(nil).to(Time.current)
+ end
+ end
+end
+
+shared_examples_for 'does not update feature flag client' do
+ let!(:client) { create(:operations_feature_flags_client, project: project) }
+
+ it 'does not update last feature flag updated at' do
+ expect { subject }.not_to change { client.reload.last_feature_flag_updated_at }
+ end
+end
diff --git a/spec/support/shared_examples/usage_data_counters/work_item_activity_unique_counter_shared_examples.rb b/spec/support/shared_examples/usage_data_counters/work_item_activity_unique_counter_shared_examples.rb
new file mode 100644
index 00000000000..4655585a092
--- /dev/null
+++ b/spec/support/shared_examples/usage_data_counters/work_item_activity_unique_counter_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'counter that does not track the event' do
+ it 'does not track the event' do
+ expect { 3.times { track_event } }.to not_change {
+ Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
+ event_names: event_name,
+ start_date: 2.weeks.ago,
+ end_date: 2.weeks.from_now
+ )
+ }
+ end
+end
+
+RSpec.shared_examples 'work item unique counter' do
+ context 'when track_work_items_activity FF is enabled' do
+ it 'tracks a unique event only once' do
+ expect { 3.times { track_event } }.to change {
+ Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
+ event_names: event_name,
+ start_date: 2.weeks.ago,
+ end_date: 2.weeks.from_now
+ )
+ }.by(1)
+ end
+
+ context 'when author is nil' do
+ let(:user) { nil }
+
+ it_behaves_like 'counter that does not track the event'
+ end
+ end
+
+ context 'when track_work_items_activity FF is disabled' do
+ before do
+ stub_feature_flags(track_work_items_activity: false)
+ end
+
+ it_behaves_like 'counter that does not track the event'
+ end
+end
diff --git a/spec/support/shared_examples/views/themed_layout_examples.rb b/spec/support/shared_examples/views/themed_layout_examples.rb
new file mode 100644
index 00000000000..b6c53dce4cb
--- /dev/null
+++ b/spec/support/shared_examples/views/themed_layout_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "a layout which reflects the application theme setting", :themed_layout do
+ context 'as a themed layout' do
+ let(:default_theme_class) { ::Gitlab::Themes.default.css_class }
+
+ context 'when no theme is explicitly selected' do
+ it 'renders with the default theme' do
+ render
+
+ expect(rendered).to have_selector("body.#{default_theme_class}")
+ end
+ end
+
+ context 'when user is authenticated & has selected a specific theme' do
+ before do
+ allow(view).to receive(:user_application_theme).and_return(chosen_theme.css_class)
+ end
+
+ where(chosen_theme: ::Gitlab::Themes.available_themes)
+
+ with_them do
+ it "renders with the #{params[:chosen_theme].name} theme" do
+ render
+
+ if chosen_theme.css_class != default_theme_class
+ expect(rendered).not_to have_selector("body.#{default_theme_class}")
+ end
+
+ expect(rendered).to have_selector("body.#{chosen_theme.css_class}")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/work_items/widgetable_service_shared_examples.rb b/spec/support/shared_examples/work_items/widgetable_service_shared_examples.rb
new file mode 100644
index 00000000000..491662d17d3
--- /dev/null
+++ b/spec/support/shared_examples/work_items/widgetable_service_shared_examples.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'work item widgetable service' do
+ it 'executes callbacks for expected widgets' do
+ supported_widgets.each do |widget|
+ expect_next_instance_of(widget[:klass]) do |widget_instance|
+ expect(widget_instance).to receive(widget[:callback]).with(params: widget[:params])
+ end
+ end
+
+ service_execute
+ end
+end
diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
index 54962eac100..1da21633504 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
@@ -229,6 +229,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
describe 'executing an entire migration', :freeze_time, if: Gitlab::Database.has_config?(tracking_database) do
include Gitlab::Database::DynamicModelHelpers
+ include Database::DatabaseHelpers
let(:migration_class) do
Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
@@ -347,5 +348,20 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
it 'does not update non-matching records in the range' do
expect { full_migration_run }.not_to change { example_data.where('status <> 1 AND some_column <> 0').count }
end
+
+ context 'health status' do
+ subject(:migration_run) { described_class.new.perform }
+
+ it 'puts migration on hold when there is autovaccum activity on related tables' do
+ swapout_view_for_table(:postgres_autovacuum_activity, connection: connection)
+ create(
+ :postgres_autovacuum_activity,
+ table: migration.table_name,
+ table_identifier: "public.#{migration.table_name}"
+ )
+
+ expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true)
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb b/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb
index 77c4a3431e2..503e331ea2e 100644
--- a/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb
+++ b/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
-require 'fileutils'
-
RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
- include GitHelpers
-
let!(:lease_uuid) { SecureRandom.uuid }
let!(:lease_key) { "resource_housekeeping:#{resource.id}" }
let(:params) { [resource.id, task, lease_key, lease_uuid] }
@@ -246,39 +242,6 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
subject.perform(resource.id, 'prune', lease_key, lease_uuid)
end
-
- # Create a new commit on a random new branch
- def create_objects(resource)
- rugged = rugged_repo(resource.repository)
- old_commit = rugged.branches.first.target
- new_commit_sha = Rugged::Commit.create(
- rugged,
- message: "hello world #{SecureRandom.hex(6)}",
- author: { email: 'foo@bar', name: 'baz' },
- committer: { email: 'foo@bar', name: 'baz' },
- tree: old_commit.tree,
- parents: [old_commit]
- )
- rugged.references.create("refs/heads/#{SecureRandom.hex(6)}", new_commit_sha)
- end
-
- def packs(resource)
- Dir["#{path_to_repo}/objects/pack/*.pack"]
- end
-
- def packed_refs(resource)
- path = File.join(path_to_repo, 'packed-refs')
- FileUtils.touch(path)
- File.read(path)
- end
-
- def path_to_repo
- @path_to_repo ||= File.join(TestEnv.repos_path, resource.repository.relative_path)
- end
-
- def bitmap_path(pack)
- pack.sub(/\.pack\z/, '.bitmap')
- end
end
context 'with bitmaps enabled' do
diff --git a/spec/support/snowplow.rb b/spec/support/snowplow.rb
deleted file mode 100644
index e58be667b37..00000000000
--- a/spec/support/snowplow.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'stub_snowplow'
-
-RSpec.configure do |config|
- config.include SnowplowHelpers, :snowplow
- config.include StubSnowplow, :snowplow
-
- config.before(:each, :snowplow) do
- stub_snowplow
- end
-
- config.after(:each, :snowplow) do
- Gitlab::Tracking.send(:snowplow).send(:tracker).flush
- end
-end