summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
commitaee0a117a889461ce8ced6fcf73207fe017f1d99 (patch)
tree891d9ef189227a8445d83f35c1b0fc99573f4380 /spec/support/shared_examples
parent8d46af3258650d305f53b819eabf7ab18d22f59e (diff)
downloadgitlab-ce-aee0a117a889461ce8ced6fcf73207fe017f1d99.tar.gz
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb25
-rw-r--r--spec/support/shared_examples/ci/create_pipeline_service_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb30
-rw-r--r--spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/controllers/unique_visits_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/csp.rb6
-rw-r--r--spec/support/shared_examples/features/page_description_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb30
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/features/sidebar_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/features/snippets_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb108
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb50
-rw-r--r--spec/support/shared_examples/mailers/notify_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb18
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb52
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb19
-rw-r--r--spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb62
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/issuable_participants_examples.rb30
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb212
38 files changed, 823 insertions, 243 deletions
diff --git a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
index e8cc666605b..06800f7cded 100644
--- a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
+++ b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
@@ -9,16 +9,18 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
let(:extracted_data) { BulkImports::Pipeline::ExtractedData.new(data: {}) }
- context 'successfully imports wiki for an entity' do
- subject { described_class.new(context) }
+ subject { described_class.new(context) }
- before do
- allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
- allow(extractor).to receive(:extract).and_return(extracted_data)
- end
+ before do
+ allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
+ allow(extractor).to receive(:extract).and_return(extracted_data)
end
+ end
+ context 'when wiki exists' do
it 'imports new wiki into destination project' do
+ expect(subject).to receive(:source_wiki_exists?).and_return(true)
+
expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
url = "https://oauth2:token@gitlab.example/#{entity.source_full_path}.wiki.git"
expect(repository_service).to receive(:fetch_remote).with(url, any_args).and_return 0
@@ -27,5 +29,16 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
subject.run
end
end
+
+ context 'when wiki does not exist' do
+ it 'does not import wiki' do
+ expect(subject).to receive(:source_wiki_exists?).and_return(false)
+
+ expect(parent.wiki).not_to receive(:ensure_repository)
+ expect(parent.wiki.repository).not_to receive(:ensure_repository)
+
+ expect { subject.run }.not_to raise_error
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/ci/create_pipeline_service_shared_examples.rb b/spec/support/shared_examples/ci/create_pipeline_service_shared_examples.rb
new file mode 100644
index 00000000000..a72ce320e90
--- /dev/null
+++ b/spec/support/shared_examples/ci/create_pipeline_service_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'pipelines are created without N+1 SQL queries' do
+ before do
+ # warm up
+ stub_ci_pipeline_yaml_file(config1)
+ execute_service
+ end
+
+ it 'avoids N+1 queries', :aggregate_failures, :request_store, :use_sql_query_cache do
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ stub_ci_pipeline_yaml_file(config1)
+
+ pipeline = execute_service.payload
+
+ expect(pipeline).to be_created_successfully
+ end
+
+ expect do
+ stub_ci_pipeline_yaml_file(config2)
+
+ pipeline = execute_service.payload
+
+ expect(pipeline).to be_created_successfully
+ end.not_to exceed_all_query_limit(control).with_threshold(accepted_n_plus_ones)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
index 0ffa32dec9e..46fc2cbdc9b 100644
--- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
@@ -58,11 +58,12 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET new' do
end
RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
+ let(:repo_fake) { Struct.new(:id, :login, :full_name, :name, :owner, keyword_init: true) }
let(:new_import_url) { public_send("new_import_#{provider}_url") }
let(:user) { create(:user) }
- let(:repo) { OpenStruct.new(login: 'vim', full_name: 'asd/vim', name: 'vim', owner: { login: 'owner' }) }
- let(:org) { OpenStruct.new(login: 'company') }
- let(:org_repo) { OpenStruct.new(login: 'company', full_name: 'company/repo', name: 'repo', owner: { login: 'owner' }) }
+ let(:repo) { repo_fake.new(login: 'vim', full_name: 'asd/vim', name: 'vim', owner: { login: 'owner' }) }
+ let(:org) { double('org', login: 'company') }
+ let(:org_repo) { repo_fake.new(login: 'company', full_name: 'company/repo', name: 'repo', owner: { login: 'owner' }) }
before do
assign_session_token(provider)
@@ -72,7 +73,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
project = create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'example/repo')
group = create(:group)
group.add_owner(user)
- stub_client(repos: [repo, org_repo], orgs: [org], org_repos: [org_repo], each_page: [OpenStruct.new(objects: [repo, org_repo])].to_enum)
+ stub_client(repos: [repo, org_repo], orgs: [org], org_repos: [org_repo], each_page: [double('client', objects: [repo, org_repo])].to_enum)
get :status, format: :json
@@ -125,7 +126,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
end
context 'when filtering' do
- let(:repo_2) { OpenStruct.new(login: 'emacs', full_name: 'asd/emacs', name: 'emacs', owner: { login: 'owner' }) }
+ let(:repo_2) { repo_fake.new(login: 'emacs', full_name: 'asd/emacs', name: 'emacs', owner: { login: 'owner' }) }
let(:project) { create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'example/repo') }
let(:group) { create(:group) }
let(:repos) { [repo, repo_2, org_repo] }
@@ -133,7 +134,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
before do
group.add_owner(user)
client = stub_client(repos: repos, orgs: [org], org_repos: [org_repo])
- allow(client).to receive(:each_page).and_return([OpenStruct.new(objects: repos)].to_enum)
+ allow(client).to receive(:each_page).and_return([double('client', objects: repos)].to_enum)
# GitHub controller has filtering done using GitHub Search API
stub_feature_flags(remove_legacy_github_client: false)
end
@@ -172,7 +173,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
repos = [build(:project, name: 2, path: 'test')]
client = stub_client(repos: repos)
- allow(client).to receive(:each_page).and_return([OpenStruct.new(objects: repos)].to_enum)
+ allow(client).to receive(:each_page).and_return([double('client', objects: repos)].to_enum)
end
it 'does not raise an error' do
@@ -189,13 +190,14 @@ end
RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
let(:user) { create(:user) }
let(:provider_username) { user.username }
- let(:provider_user) { OpenStruct.new(login: provider_username) }
+ let(:provider_user) { double('user', login: provider_username) }
let(:project) { create(:project, import_type: provider, import_status: :finished, import_source: "#{provider_username}/vim") }
let(:provider_repo) do
- OpenStruct.new(
+ double(
+ 'provider',
name: 'vim',
full_name: "#{provider_username}/vim",
- owner: OpenStruct.new(login: provider_username)
+ owner: double('owner', login: provider_username)
)
end
@@ -265,10 +267,9 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
end
context "when the repository owner is not the provider user" do
- let(:other_username) { "someone_else" }
+ let(:provider_username) { "someone_else" }
before do
- provider_repo.owner = OpenStruct.new(login: other_username)
assign_session_token(provider)
end
@@ -277,8 +278,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
context "when the namespace is owned by the GitLab user" do
before do
- user.username = other_username
- user.save!
+ user.update!(username: provider_username)
end
it "takes the existing namespace" do
@@ -292,7 +292,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
context "when the namespace is not owned by the GitLab user" do
it "creates a project using user's namespace" do
- create(:user, username: other_username)
+ create(:user, username: provider_username)
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
diff --git a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
index 00a0fb7e4c5..3a7588a5cc9 100644
--- a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
@@ -50,7 +50,8 @@ RSpec.shared_examples Repositories::GitHttpController do
context 'with authorized user' do
before do
- request.headers.merge! auth_env(user.username, user.password, nil)
+ password = user.try(:password) || user.try(:token)
+ request.headers.merge! auth_env(user.username, password, nil)
end
it 'returns 200' do
@@ -71,9 +72,10 @@ RSpec.shared_examples Repositories::GitHttpController do
it 'adds user info to the logs' do
get :info_refs, params: params
- expect(log_data).to include('username' => user.username,
- 'user_id' => user.id,
- 'meta.user' => user.username)
+ user_log_data = { 'username' => user.username, 'user_id' => user.id }
+ user_log_data['meta.user'] = user.username if user.is_a?(User)
+
+ expect(log_data).to include(user_log_data)
end
end
end
diff --git a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
index 30914e61df0..ac7680f7ddb 100644
--- a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
@@ -6,15 +6,23 @@ RSpec.shared_examples 'tracking unique visits' do |method|
let(:request_params) { {} }
it 'tracks unique visit if the format is HTML' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter)
- .to receive(:track_event).with(target_id, values: kind_of(String))
+ ids = target_id.instance_of?(String) ? [target_id] : target_id
+
+ ids.each do |id|
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with(id, values: kind_of(String))
+ end
get method, params: request_params, format: :html
end
it 'tracks unique visit if DNT is not enabled' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter)
- .to receive(:track_event).with(target_id, values: kind_of(String))
+ ids = target_id.instance_of?(String) ? [target_id] : target_id
+
+ ids.each do |id|
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with(id, values: kind_of(String))
+ end
stub_do_not_track('0')
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
index 30710e43357..1cb52c07069 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -299,7 +299,7 @@ RSpec.shared_examples 'wiki controller actions' do
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true')
expect(response.cache_control[:public]).to be(false)
- expect(response.headers['Cache-Control']).to eq('private, no-store')
+ expect(response.headers['Cache-Control']).to eq('max-age=60, private')
end
end
end
diff --git a/spec/support/shared_examples/csp.rb b/spec/support/shared_examples/csp.rb
index c4a8c7df898..9143d0f4720 100644
--- a/spec/support/shared_examples/csp.rb
+++ b/spec/support/shared_examples/csp.rb
@@ -28,7 +28,7 @@ RSpec.shared_examples 'setting CSP' do |rule_name|
context 'when feature is enabled' do
it "appends to #{rule_name}" do
- is_expected.to eql("#{rule_name} #{default_csp_values} #{whitelisted_url}")
+ is_expected.to eql("#{rule_name} #{default_csp_values} #{allowlisted_url}")
end
end
@@ -46,7 +46,7 @@ RSpec.shared_examples 'setting CSP' do |rule_name|
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} #{whitelisted_url}")
+ is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{allowlisted_url}")
end
end
@@ -64,7 +64,7 @@ RSpec.shared_examples 'setting CSP' do |rule_name|
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} #{whitelisted_url}")
+ is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{allowlisted_url}")
end
end
diff --git a/spec/support/shared_examples/features/page_description_shared_examples.rb b/spec/support/shared_examples/features/page_description_shared_examples.rb
index 81653220b4c..e3ea36633d1 100644
--- a/spec/support/shared_examples/features/page_description_shared_examples.rb
+++ b/spec/support/shared_examples/features/page_description_shared_examples.rb
@@ -7,3 +7,13 @@ RSpec.shared_examples 'page meta description' do |expected_description|
end
end
end
+
+RSpec.shared_examples 'default brand title page meta description' do
+ include AppearancesHelper
+
+ it 'renders the page with description, og:description, and twitter:description meta tags with the default brand title', :aggregate_failures do
+ %w(name='description' property='og:description' property='twitter:description').each do |selector|
+ expect(page).to have_selector("meta[#{selector}][content='#{default_brand_title}']", visible: false)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
new file mode 100644
index 00000000000..345dfbce423
--- /dev/null
+++ b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'date sidebar widget' do
+ context 'editing due date' do
+ let(:due_date_value) { find('[data-testid="sidebar-due-date"] [data-testid="sidebar-date-value"]') }
+
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ it 'displays "None" when there is no due date' do
+ expect(due_date_value.text).to have_content 'None'
+ end
+
+ it 'updates due date' do
+ page.within('[data-testid="sidebar-due-date"]') do
+ today = Date.today.day
+
+ click_button 'Edit'
+
+ click_button today.to_s
+
+ wait_for_requests
+
+ expect(page).to have_content(today.to_s(:medium))
+ expect(due_date_value.text).to have_content Time.current.strftime('%b %-d, %Y')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb
new file mode 100644
index 00000000000..da730240e8e
--- /dev/null
+++ b/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'milestone sidebar widget' do
+ context 'editing milestone' do
+ let_it_be(:milestone_expired) { create(:milestone, project: project, title: 'Foo - expired', due_date: 5.days.ago) }
+ let_it_be(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') }
+ let_it_be(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) }
+ let_it_be(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) }
+ let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) }
+
+ let(:milestone_widget) { find('[data-testid="sidebar-milestones"]') }
+
+ before do
+ within(milestone_widget) do
+ click_button 'Edit'
+ end
+
+ wait_for_all_requests
+ end
+
+ it 'shows milestones list in the dropdown' do
+ # 5 milestones + "No milestone" = 6 items
+ expect(milestone_widget.find('.gl-new-dropdown-contents')).to have_selector('li.gl-new-dropdown-item', count: 6)
+ end
+
+ it 'shows expired milestone at the bottom of the list and milestone due earliest at the top of the list', :aggregate_failures do
+ within(milestone_widget, '.gl-new-dropdown-contents') do
+ expect(page.find('li:last-child')).to have_content milestone_expired.title
+
+ [milestone3, milestone2, milestone1, milestone_no_duedate].each_with_index do |m, i|
+ expect(page.all('li.gl-new-dropdown-item')[i + 1]).to have_content m.title
+ end
+ end
+ end
+
+ it 'adds a milestone' do
+ within(milestone_widget) do
+ click_button milestone1.title
+
+ wait_for_requests
+
+ page.within('[data-testid="select-milestone"]') do
+ expect(page).to have_content(milestone1.title)
+ end
+ end
+ end
+
+ it 'removes a milestone' do
+ within(milestone_widget) do
+ click_button "No milestone"
+
+ wait_for_requests
+
+ page.within('[data-testid="select-milestone"]') do
+ expect(page).not_to have_content(milestone1.title)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb
index d509d124de0..615f568420e 100644
--- a/spec/support/shared_examples/features/sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb
@@ -5,6 +5,7 @@ RSpec.shared_examples 'issue boards sidebar' do
before do
first_card.click
+ wait_for_requests
end
it 'shows sidebar when clicking issue' do
@@ -41,6 +42,14 @@ RSpec.shared_examples 'issue boards sidebar' do
end
end
+ context 'editing issue milestone', :js do
+ it_behaves_like 'milestone sidebar widget'
+ end
+
+ context 'editing issue due date', :js do
+ it_behaves_like 'date sidebar widget'
+ end
+
context 'in notifications subscription' do
it 'displays notifications toggle', :aggregate_failures do
page.within('[data-testid="sidebar-notifications"]') do
diff --git a/spec/support/shared_examples/features/snippets_shared_examples.rb b/spec/support/shared_examples/features/snippets_shared_examples.rb
index bd1a67f3bb5..c402333107c 100644
--- a/spec/support/shared_examples/features/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/features/snippets_shared_examples.rb
@@ -20,7 +20,7 @@ RSpec.shared_examples 'paginated snippets' do |remote: false|
end
RSpec.shared_examples 'tabs with counts' do
- let(:tabs) { page.all('.snippet-scope-menu li') }
+ let(:tabs) { page.all('.js-snippets-nav-tabs li') }
it 'shows a tab for All snippets and count' do
tab = tabs[0]
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 7ced8508a31..a456b76b324 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
@@ -138,11 +138,26 @@ RSpec.shared_examples 'User updates wiki page' do
end
context 'when using the content editor' do
- before do
- click_button 'Use the new editor'
+ context 'with feature flag on' do
+ before do
+ click_button 'Edit rich text'
+ end
+
+ it_behaves_like 'edits content using the content editor'
end
- it_behaves_like 'edits content using the content editor'
+ context 'with feature flag off' do
+ before do
+ stub_feature_flags(wiki_switch_between_content_editor_raw_markdown: false)
+ visit(wiki_path(wiki))
+
+ click_link('Edit')
+
+ click_button 'Use the new editor'
+ end
+
+ it_behaves_like 'edits content using the content editor'
+ end
end
end
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
index 96df5a5f972..eec911f3b6f 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
@@ -161,7 +161,7 @@ RSpec.shared_examples 'User views a wiki page' do
commit = wiki.commit
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
- expect(page).to have_content('by John Doe')
+ expect(page).to have_content('by Sidney Jones')
expect(page).to have_content('updated home')
expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
expect(page).to have_content('some link')
@@ -174,7 +174,7 @@ RSpec.shared_examples 'User views a wiki page' do
commit = wiki.commit('HEAD^')
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
- expect(page).to have_content('by John Doe')
+ expect(page).to have_content('by Sidney Jones')
expect(page).to have_content('updated home')
expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
expect(page).to have_content('some link')
@@ -188,7 +188,7 @@ RSpec.shared_examples 'User views a wiki page' do
commit = wiki.commit('HEAD^')
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
- expect(page).to have_content('by John Doe')
+ expect(page).to have_content('by Sidney Jones')
expect(page).to have_content('created page: home')
expect(page).to have_content('Showing 1 changed file with 4 additions and 0 deletions')
expect(page).to have_content('Look at this')
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
deleted file mode 100644
index 7707e79386c..00000000000
--- a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'resource mentions migration' do |migration_class, resource_class_name|
- it 'migrates resource mentions' do
- join = migration_class::JOIN
- conditions = migration_class::QUERY_CONDITIONS
- resource_class = "#{Gitlab::BackgroundMigration::UserMentions::Models}::#{resource_class_name}".constantize
-
- expect do
- subject.perform(resource_class_name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
- end.to change { user_mentions.count }.by(1)
-
- user_mention = user_mentions.last
- expect(user_mention.mentioned_users_ids.sort).to eq(mentioned_users.pluck(:id).sort)
- expect(user_mention.mentioned_groups_ids.sort).to eq([group.id])
- expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id)
-
- # check that performing the same job twice does not fail and does not change counts
- expect do
- subject.perform(resource_class_name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
- end.to change { user_mentions.count }.by(0)
- end
-end
-
-RSpec.shared_examples 'resource notes mentions migration' do |migration_class, resource_class_name|
- it 'migrates mentions from note' do
- join = migration_class::JOIN
- conditions = migration_class::QUERY_CONDITIONS
-
- # there are 5 notes for each noteable_type, but two do not have mentions and
- # another one's noteable_id points to an inexistent resource
- expect(notes.where(noteable_type: resource_class_name).count).to eq 5
- expect(user_mentions.count).to eq 0
-
- expect do
- subject.perform(resource_class_name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
- end.to change { user_mentions.count }.by(2)
-
- # check that the user_mention for regular note is created
- user_mention = user_mentions.first
- expect(Note.find(user_mention.note_id).system).to be false
- expect(user_mention.mentioned_users_ids.sort).to eq(users.pluck(:id).sort)
- expect(user_mention.mentioned_groups_ids.sort).to eq([group.id])
- expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id)
-
- # check that the user_mention for system note is created
- user_mention = user_mentions.second
- expect(Note.find(user_mention.note_id).system).to be true
- expect(user_mention.mentioned_users_ids.sort).to eq(users.pluck(:id).sort)
- expect(user_mention.mentioned_groups_ids.sort).to eq([group.id])
- expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id)
-
- # check that performing the same job twice does not fail and does not change counts
- expect do
- subject.perform(resource_class_name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
- end.to change { user_mentions.count }.by(0)
- end
-end
-
-RSpec.shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes|
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 1)
- end
-
- it 'schedules background migrations' do
- Sidekiq::Testing.fake! do
- freeze_time do
- resource_count = is_for_notes ? Note.count : resource_class.count
- expect(resource_count).to eq 5
-
- migrate!
-
- migration = described_class::MIGRATION
- join = described_class::JOIN
- conditions = described_class::QUERY_CONDITIONS
- delay = described_class::DELAY
-
- expect(migration).to be_scheduled_delayed_migration(1 * delay, resource_class.name, join, conditions, is_for_notes, resource1.id, resource1.id)
- expect(migration).to be_scheduled_delayed_migration(2 * delay, resource_class.name, join, conditions, is_for_notes, resource2.id, resource2.id)
- expect(migration).to be_scheduled_delayed_migration(3 * delay, resource_class.name, join, conditions, is_for_notes, resource3.id, resource3.id)
- expect(BackgroundMigrationWorker.jobs.size).to eq 3
- end
- end
- end
-end
-
-RSpec.shared_examples 'resource migration not run' do |migration_class, resource_class_name|
- it 'does not migrate mentions' do
- join = migration_class::JOIN
- conditions = migration_class::QUERY_CONDITIONS
- resource_class = "#{Gitlab::BackgroundMigration::UserMentions::Models}::#{resource_class_name}".constantize
-
- expect do
- subject.perform(resource_class_name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
- end.to change { user_mentions.count }.by(0)
- end
-end
-
-RSpec.shared_examples 'resource notes migration not run' do |migration_class, resource_class_name|
- it 'does not migrate mentions' do
- join = migration_class::JOIN
- conditions = migration_class::QUERY_CONDITIONS
-
- expect do
- subject.perform(resource_class_name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
- end.to change { user_mentions.count }.by(0)
- end
-end
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
index bd8bdd70ce5..bce889b454d 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
@@ -9,7 +9,7 @@ RSpec.shared_examples_for 'value stream analytics event' do
it { expect(described_class.identifier).to be_a_kind_of(Symbol) }
it { expect(instance.object_type.ancestors).to include(ApplicationRecord) }
it { expect(instance).to respond_to(:timestamp_projection) }
- it { expect(instance).to respond_to(:markdown_description) }
+ it { expect(instance).to respond_to(:html_description) }
it { expect(instance.column_list).to be_a_kind_of(Array) }
describe '#apply_query_customization' do
diff --git a/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb
index 41d3d76b66b..03344584361 100644
--- a/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attributes, additional_attributes = []|
- let(:prohibited_attributes) { %i[remote_url my_attributes my_ids token my_id test] }
+ let(:prohibited_attributes) { %w[remote_url my_attributes my_ids token my_id test] }
let(:import_export_config) { Gitlab::ImportExport::Config.new.to_h }
let(:project_relation_factory) { Gitlab::ImportExport::Project::RelationFactory }
@@ -8,7 +8,7 @@ RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attrib
let(:relation_hash) { (permitted_attributes + prohibited_attributes).map(&:to_s).zip([]).to_h }
let(:relation_name) { project_relation_factory.overrides[relation_sym]&.to_sym || relation_sym }
let(:relation_class) { project_relation_factory.relation_class(relation_name) }
- let(:excluded_keys) { import_export_config.dig(:excluded_keys, relation_sym) || [] }
+ let(:excluded_keys) { (import_export_config.dig(:excluded_attributes, relation_sym) || []).map(&:to_s) }
let(:cleaned_hash) do
Gitlab::ImportExport::AttributeCleaner.new(
@@ -18,7 +18,7 @@ RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attrib
).clean
end
- let(:permitted_hash) { subject.permit(relation_sym, relation_hash) }
+ let(:permitted_hash) { subject.permit(relation_sym, relation_hash).transform_keys { |k| k.to_s } }
if described_class.new.permitted_attributes_defined?(relation_sym)
it 'contains only attributes that are defined as permitted in the import/export config' do
@@ -26,11 +26,11 @@ RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attrib
end
it 'does not contain attributes that would be cleaned with AttributeCleaner' do
- expect(cleaned_hash.keys + additional_attributes.to_a).to include(*permitted_hash.keys)
+ expect(cleaned_hash.keys + additional_attributes.to_a.map(&:to_s)).to include(*permitted_hash.keys)
end
it 'does not contain prohibited attributes that are not related to given relation' do
- expect(permitted_hash.keys).not_to include(*prohibited_attributes.map(&:to_s))
+ expect(permitted_hash.keys).not_to include(*prohibited_attributes)
end
else
it 'is disabled' do
diff --git a/spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb
new file mode 100644
index 00000000000..046c70bf779
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'multi store feature flags' do |use_primary_and_secondary_stores, use_primary_store_as_default|
+ context "with feature flag :#{use_primary_and_secondary_stores} is enabled" do
+ before do
+ stub_feature_flags(use_primary_and_secondary_stores => true)
+ end
+
+ it 'multi store is enabled' do
+ expect(subject.use_primary_and_secondary_stores?).to be true
+ end
+ end
+
+ context "with feature flag :#{use_primary_and_secondary_stores} is disabled" do
+ before do
+ stub_feature_flags(use_primary_and_secondary_stores => false)
+ end
+
+ it 'multi store is disabled' do
+ expect(subject.use_primary_and_secondary_stores?).to be false
+ end
+ end
+
+ context "with feature flag :#{use_primary_store_as_default} is enabled" do
+ before do
+ stub_feature_flags(use_primary_store_as_default => true)
+ end
+
+ it 'primary store is enabled' do
+ expect(subject.use_primary_store_as_default?).to be true
+ end
+ end
+
+ context "with feature flag :#{use_primary_store_as_default} is disabled" do
+ before do
+ stub_feature_flags(use_primary_store_as_default => false)
+ end
+
+ it 'primary store is disabled' do
+ expect(subject.use_primary_store_as_default?).to be false
+ end
+ end
+end
diff --git a/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
index 7ccd9533811..8f3a93de509 100644
--- a/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
+++ b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
@@ -4,17 +4,12 @@ RSpec.shared_examples 'it has loose foreign keys' do
let(:factory_name) { nil }
let(:table_name) { described_class.table_name }
let(:connection) { described_class.connection }
-
- it 'includes the LooseForeignKey module' do
- expect(described_class.ancestors).to include(LooseForeignKey)
- end
-
- it 'responds to #loose_foreign_key_definitions' do
- expect(described_class).to respond_to(:loose_foreign_key_definitions)
- end
+ let(:fully_qualified_table_name) { "#{connection.current_schema}.#{table_name}" }
+ let(:deleted_records) { LooseForeignKeys::DeletedRecord.where(fully_qualified_table_name: fully_qualified_table_name) }
it 'has at least one loose foreign key definition' do
- expect(described_class.loose_foreign_key_definitions.size).to be > 0
+ definitions = Gitlab::Database::LooseForeignKeys.definitions_by_table[table_name]
+ expect(definitions.size).to be > 0
end
it 'has the deletion trigger present' do
@@ -32,9 +27,11 @@ RSpec.shared_examples 'it has loose foreign keys' do
it 'records record deletions' do
model = create(factory_name) # rubocop: disable Rails/SaveBang
- model.destroy!
- deleted_record = LooseForeignKeys::DeletedRecord.find_by(fully_qualified_table_name: "#{connection.current_schema}.#{table_name}", primary_key_value: model.id)
+ # using delete to avoid cross-database modification errors when associations with dependent option are present
+ model.delete
+
+ deleted_record = deleted_records.find_by(primary_key_value: model.id)
expect(deleted_record).not_to be_nil
end
@@ -42,11 +39,36 @@ RSpec.shared_examples 'it has loose foreign keys' do
it 'cleans up record deletions' do
model = create(factory_name) # rubocop: disable Rails/SaveBang
- expect { model.destroy! }.to change { LooseForeignKeys::DeletedRecord.count }.by(1)
+ expect { model.delete }.to change { deleted_records.count }.by(1)
LooseForeignKeys::ProcessDeletedRecordsService.new(connection: connection).execute
- expect(LooseForeignKeys::DeletedRecord.status_pending.count).to be(0)
- expect(LooseForeignKeys::DeletedRecord.status_processed.count).to be(1)
+ expect(deleted_records.status_pending.count).to be(0)
+ expect(deleted_records.status_processed.count).to be(1)
+ end
+end
+
+RSpec.shared_examples 'cleanup by a loose foreign key' do
+ let(:foreign_key_definition) do
+ foreign_keys_for_parent = Gitlab::Database::LooseForeignKeys.definitions_by_table[parent.class.table_name]
+ foreign_keys_for_parent.find { |definition| definition.from_table == model.class.table_name }
+ end
+
+ def find_model
+ model.class.find_by(id: model.id)
+ end
+
+ it 'deletes the model' do
+ parent.delete
+
+ expect(find_model).to be_present
+
+ LooseForeignKeys::ProcessDeletedRecordsService.new(connection: model.connection).execute
+
+ if foreign_key_definition.on_delete.eql?(:async_delete)
+ expect(find_model).not_to be_present
+ else
+ expect(find_model[foreign_key_definition.column]).to eq(nil)
+ end
end
end
diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb
index e1f7a9030e2..20ed380fb18 100644
--- a/spec/support/shared_examples/mailers/notify_shared_examples.rb
+++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb
@@ -161,6 +161,12 @@ RSpec.shared_examples 'it should not have Gmail Actions links' do
end
end
+RSpec.shared_examples 'it should show Gmail Actions Join now link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text('Join now') }
+end
+
RSpec.shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
diff --git a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
index c06083ba952..6e8c340582a 100644
--- a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
+++ b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
@@ -1,7 +1,11 @@
# frozen_string_literal: true
RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
- let(:db_config_name) { ::Gitlab::Database.db_config_names.first }
+ let(:db_config_name) do
+ db_config_name = ::Gitlab::Database.db_config_names.first
+ db_config_name += "_replica" if db_role == :secondary
+ db_config_name
+ end
let(:expected_payload_defaults) do
result = {}
@@ -39,15 +43,15 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_write_count: record_write_query ? 1 : 0,
db_cached_count: record_cached_query ? 1 : 0,
db_primary_cached_count: record_cached_query ? 1 : 0,
- "db_primary_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
+ "db_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
db_primary_count: record_query ? 1 : 0,
- "db_primary_#{db_config_name}_count": record_query ? 1 : 0,
+ "db_#{db_config_name}_count": record_query ? 1 : 0,
db_primary_duration_s: record_query ? 0.002 : 0.0,
- "db_primary_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0,
+ "db_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0,
db_primary_wal_count: record_wal_query ? 1 : 0,
- "db_primary_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
+ "db_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
db_primary_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
- "db_primary_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
+ "db_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
})
elsif db_role == :replica
transform_hash(expected_payload_defaults, {
@@ -55,15 +59,15 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_write_count: record_write_query ? 1 : 0,
db_cached_count: record_cached_query ? 1 : 0,
db_replica_cached_count: record_cached_query ? 1 : 0,
- "db_replica_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
+ "db_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
db_replica_count: record_query ? 1 : 0,
- "db_replica_#{db_config_name}_count": record_query ? 1 : 0,
+ "db_#{db_config_name}_count": record_query ? 1 : 0,
db_replica_duration_s: record_query ? 0.002 : 0.0,
- "db_replica_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0,
+ "db_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0,
db_replica_wal_count: record_wal_query ? 1 : 0,
- "db_replica_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
+ "db_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
db_replica_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
- "db_replica_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
+ "db_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
})
else
transform_hash(expected_payload_defaults, {
@@ -71,15 +75,15 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_write_count: record_write_query ? 1 : 0,
db_cached_count: record_cached_query ? 1 : 0,
db_primary_cached_count: 0,
- "db_primary_#{db_config_name}_cached_count": 0,
+ "db_#{db_config_name}_cached_count": 0,
db_primary_count: 0,
- "db_primary_#{db_config_name}_count": 0,
+ "db_#{db_config_name}_count": 0,
db_primary_duration_s: 0.0,
- "db_primary_#{db_config_name}_duration_s": 0.0,
+ "db_#{db_config_name}_duration_s": 0.0,
db_primary_wal_count: 0,
- "db_primary_#{db_config_name}_wal_count": 0,
+ "db_#{db_config_name}_wal_count": 0,
db_primary_wal_cached_count: 0,
- "db_primary_#{db_config_name}_wal_cached_count": 0
+ "db_#{db_config_name}_wal_cached_count": 0
})
end
@@ -105,7 +109,11 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
end
RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do |db_role|
- let(:db_config_name) { ::Gitlab::Database.db_config_name(ApplicationRecord.retrieve_connection) }
+ let(:db_config_name) do
+ db_config_name = ::Gitlab::Database.db_config_names.first
+ db_config_name += "_replica" if db_role == :secondary
+ db_config_name
+ end
it 'increments only db counters' do
if record_query
diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
index 03f565e0aac..fe85daa7235 100644
--- a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
+++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
@@ -80,15 +80,22 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
it 'calls InternalId.generate_next and sets internal id attribute' do
iid = rand(1..1000)
- expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid)
+ # Need to do this before evaluating instance otherwise it gets set
+ # already in factory
+ allow(InternalId).to receive(:generate_next).and_return(iid)
+
subject
expect(read_internal_id).to eq(iid)
+
+ expect(InternalId).to have_received(:generate_next).with(instance, scope_attrs, usage, any_args)
end
it 'does not overwrite an existing internal id' do
write_internal_id(4711)
- expect { subject }.not_to change { read_internal_id }
+ allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347091') do
+ expect { subject }.not_to change { read_internal_id }
+ end
end
context 'when the instance has an internal ID set' do
@@ -101,6 +108,7 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
.to receive(:track_greatest)
.with(instance, scope_attrs, usage, internal_id, any_args)
.and_return(internal_id)
+
subject
end
end
@@ -110,7 +118,11 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
context 'when the internal id has been changed' do
context 'when the internal id is automatically set' do
it 'clears it on the instance' do
- expect_iid_to_be_set_and_rollback
+ write_internal_id(nil)
+
+ allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347091') do
+ expect_iid_to_be_set_and_rollback
+ end
expect(read_internal_id).to be_nil
end
@@ -120,7 +132,9 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
it 'does not clear it on the instance' do
write_internal_id(100)
- expect_iid_to_be_set_and_rollback
+ allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347091') do
+ expect_iid_to_be_set_and_rollback
+ end
expect(read_internal_id).not_to be_nil
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 72659dd5f3b..e6b270c6188 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -71,7 +71,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
it "does not call #{integration_name} API" do
result = subject.execute(sample_data)
- expect(result).to be(false)
+ expect(result).to be_falsy
expect(WebMock).not_to have_requested(:post, webhook_url)
end
end
@@ -113,7 +113,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
context "with protected branch" do
before do
- create(:protected_branch, project: project, name: "a-protected-branch")
+ create(:protected_branch, :create_branch_on_repository, project: project, name: "a-protected-branch")
end
let(:sample_data) do
@@ -309,7 +309,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
context "with protected branch" do
before do
- create(:protected_branch, project: project, name: "a-protected-branch")
+ create(:protected_branch, :create_branch_on_repository, project: project, name: "a-protected-branch")
end
let(:sample_data) do
@@ -355,5 +355,11 @@ RSpec.shared_examples "chat integration" do |integration_name|
end
end
end
+
+ context 'deployment events' do
+ let(:sample_data) { Gitlab::DataBuilder::Deployment.build(create(:deployment), Time.now) }
+
+ it_behaves_like "untriggered #{integration_name} integration"
+ 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 2d4c0b60f2b..ad15f82be5e 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
@@ -305,7 +305,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
context 'on a protected branch' do
before do
- create(:protected_branch, project: project, name: 'a-protected-branch')
+ create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
let(:data) do
@@ -347,7 +347,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
context 'on a protected branch with protected branches defined using wildcards' do
before do
- create(:protected_branch, project: project, name: '*-stable')
+ create(:protected_branch, :create_branch_on_repository, repository_branch_name: '1-stable', project: project, name: '*-stable')
end
let(:data) do
@@ -560,7 +560,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
context 'on a protected branch' do
before do
- create(:protected_branch, project: project, name: 'a-protected-branch')
+ create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
let(:pipeline) do
@@ -590,7 +590,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
context 'on a protected branch with protected branches defined usin wildcards' do
before do
- create(:protected_branch, project: project, name: '*-stable')
+ create(:protected_branch, :create_branch_on_repository, repository_branch_name: '1-stable', project: project, name: '*-stable')
end
let(:pipeline) do
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index a2909c66e22..d5d137922eb 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -301,10 +301,6 @@ RSpec.shared_examples_for "member creation" do
end
context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
- before do
- stub_experiments(invite_members_for_task: true)
- end
-
it 'creates a member_task with the correct attributes', :aggregate_failures do
task_project = source.is_a?(Group) ? create(:project, group: source) : source
described_class.new(source, user, :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id).execute
@@ -397,10 +393,6 @@ RSpec.shared_examples_for "bulk member creation" do
end
context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
- before do
- stub_experiments(invite_members_for_task: true)
- end
-
it 'creates a member_task with the correct attributes', :aggregate_failures do
task_project = source.is_a?(Group) ? create(:project, group: source) : source
members = described_class.add_users(source, [user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id)
diff --git a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb
index f08ee820463..23026167b19 100644
--- a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb
+++ b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb
@@ -23,7 +23,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
let_it_be(:component_file_other_file_md5, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, file_md5: 'other_md5') }
let_it_be(:component_file_other_file_sha256, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, file_sha256: 'other_sha256') }
let_it_be(:component_file_other_container, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component2_1, architecture: architecture2_1) }
- let_it_be_with_refind(:component_file_with_file_type_source) { create("debian_#{container_type}_component_file", :source, component: component1_1) }
+ let_it_be_with_refind(:component_file_with_file_type_sources) { create("debian_#{container_type}_component_file", :sources, component: component1_1) }
let_it_be(:component_file_with_file_type_di_packages, freeze: can_freeze) { create("debian_#{container_type}_component_file", :di_packages, component: component1_1, architecture: architecture1_1) }
subject { component_file_with_architecture }
@@ -43,8 +43,8 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
it { is_expected.to belong_to(:architecture).class_name("Packages::Debian::#{container_type.capitalize}Architecture").inverse_of(:files) }
end
- context 'with :source file_type' do
- subject { component_file_with_file_type_source }
+ context 'with :sources file_type' do
+ subject { component_file_with_file_type_sources }
it { is_expected.to belong_to(:architecture).class_name("Packages::Debian::#{container_type.capitalize}Architecture").inverse_of(:files).optional }
end
@@ -66,8 +66,8 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
it { is_expected.to validate_presence_of(:architecture) }
end
- context 'with :source file_type' do
- subject { component_file_with_file_type_source }
+ context 'with :sources file_type' do
+ subject { component_file_with_file_type_sources }
it { is_expected.to validate_absence_of(:architecture) }
end
@@ -135,10 +135,10 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
end
describe '.with_file_type' do
- subject { described_class.with_file_type(:source) }
+ subject { described_class.with_file_type(:sources) }
it do
- expect(subject.to_a).to contain_exactly(component_file_with_file_type_source)
+ expect(subject.to_a).to contain_exactly(component_file_with_file_type_sources)
end
end
@@ -214,9 +214,9 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
end
context 'with a Source file_type' do
- subject { component_file_with_file_type_source.relative_path }
+ subject { component_file_with_file_type_sources.relative_path }
- it { is_expected.to eq("#{component1_1.name}/source/Source") }
+ it { is_expected.to eq("#{component1_1.name}/source/Sources") }
end
context 'with a DI Packages file_type' do
diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index ac6a843663f..73e22b97abc 100644
--- a/spec/support/shared_examples/namespaces/traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -205,6 +205,58 @@ RSpec.shared_examples 'namespace traversal' do
end
end
+ shared_examples '#ancestors_upto' do
+ let(:parent) { create(:group) }
+ let(:child) { create(:group, parent: parent) }
+ let(:child2) { create(:group, parent: child) }
+
+ it 'returns all ancestors when no namespace is given' do
+ expect(child2.ancestors_upto).to contain_exactly(child, parent)
+ end
+
+ it 'includes ancestors upto but excluding the given ancestor' do
+ expect(child2.ancestors_upto(parent)).to contain_exactly(child)
+ end
+
+ context 'with asc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(child2.ancestors_upto(hierarchy_order: :asc)).to eq([child, parent])
+ end
+ end
+
+ context 'with desc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(child2.ancestors_upto(hierarchy_order: :desc)).to eq([parent, child])
+ end
+ end
+
+ describe '#recursive_self_and_ancestor_ids' do
+ it 'is equivalent to ancestors_upto' do
+ recursive_result = child2.recursive_ancestors_upto(parent)
+ linear_result = child2.ancestors_upto(parent)
+ expect(linear_result).to match_array recursive_result
+ end
+
+ it 'makes a recursive query' do
+ expect { child2.recursive_ancestors_upto.try(:load) }.to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+ end
+
+ describe '#ancestors_upto' do
+ context 'with use_traversal_ids_for_ancestors_upto enabled' do
+ include_examples '#ancestors_upto'
+ end
+
+ context 'with use_traversal_ids_for_ancestors_upto disabled' do
+ before do
+ stub_feature_flags(use_traversal_ids_for_ancestors_upto: false)
+ end
+
+ include_examples '#ancestors_upto'
+ end
+ end
+
describe '#descendants' do
let!(:another_group) { create(:group) }
let!(:another_group_nested) { create(:group, parent: another_group) }
diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
index 4c09c1c2a3b..3d52ed30c62 100644
--- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
@@ -213,6 +213,12 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to contain_exactly(deep_nested_group_1, deep_nested_group_2) }
end
+
+ context 'with offset and limit' do
+ subject { described_class.where(id: [group_1, group_2]).offset(1).limit(1).self_and_descendants }
+
+ it { is_expected.to contain_exactly(group_2, nested_group_2, deep_nested_group_2) }
+ end
end
describe '.self_and_descendants' do
@@ -242,6 +248,19 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to contain_exactly(deep_nested_group_1.id, deep_nested_group_2.id) }
end
+
+ context 'with offset and limit' do
+ subject do
+ described_class
+ .where(id: [group_1, group_2])
+ .limit(1)
+ .offset(1)
+ .self_and_descendant_ids
+ .pluck(:id)
+ end
+
+ it { is_expected.to contain_exactly(group_2.id, nested_group_2.id, deep_nested_group_2.id) }
+ end
end
describe '.self_and_descendant_ids' do
diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
index e45be21f152..9f4fdcf7ba1 100644
--- a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
@@ -173,3 +173,65 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do
end
end
end
+
+RSpec.shared_examples 'Composer access with deploy tokens' do
+ shared_examples 'a deploy token for Composer GET requests' do
+ context 'with deploy token headers' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ before do
+ group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { basic_auth_header(deploy_token.username, 'bar') }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+ end
+ end
+
+ context 'group deploy token' do
+ let(:deploy_token) { deploy_token_for_group }
+
+ it_behaves_like 'a deploy token for Composer GET requests'
+ end
+
+ context 'project deploy token' do
+ let(:deploy_token) { deploy_token_for_project }
+
+ it_behaves_like 'a deploy token for Composer GET requests'
+ end
+end
+
+RSpec.shared_examples 'Composer publish with deploy tokens' do
+ shared_examples 'a deploy token for Composer publish requests' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { basic_auth_header(deploy_token.username, 'bar') }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+
+ context 'group deploy token' do
+ let(:deploy_token) { deploy_token_for_group }
+
+ it_behaves_like 'a deploy token for Composer publish requests'
+ end
+
+ context 'group deploy token' do
+ let(:deploy_token) { deploy_token_for_project }
+
+ it_behaves_like 'a deploy token for Composer publish requests'
+ end
+end
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 20606ae942d..71f3a0235be 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
@@ -178,6 +178,54 @@ RSpec.shared_examples 'rejects invalid recipe' do
end
end
+RSpec.shared_examples 'handling empty values for username and channel' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:recipe_path) { "#{package.name}/#{package.version}/#{package_username}/#{channel}" }
+
+ where(:username, :channel, :status) do
+ 'username' | 'channel' | :ok
+ 'username' | '_' | :bad_request
+ '_' | 'channel' | :bad_request_or_not_found
+ '_' | '_' | :ok_or_not_found
+ end
+
+ with_them do
+ let(:package_username) do
+ if username == 'username'
+ package.conan_metadatum.package_username
+ else
+ username
+ end
+ end
+
+ before do
+ project.add_maintainer(user) # avoid any permission issue
+ end
+
+ it 'returns the correct status code' do |example|
+ project_level = example.full_description.include?('api/v4/projects')
+
+ expected_status = case status
+ when :ok_or_not_found
+ project_level ? :ok : :not_found
+ when :bad_request_or_not_found
+ project_level ? :bad_request : :not_found
+ else
+ status
+ end
+
+ if expected_status == :ok
+ package.conan_metadatum.update!(package_username: package_username, package_channel: channel)
+ end
+
+ subject
+
+ expect(response).to have_gitlab_http_status(expected_status)
+ end
+ end
+end
+
RSpec.shared_examples 'rejects invalid file_name' do |invalid_file_name|
let(:file_name) { invalid_file_name }
@@ -300,6 +348,7 @@ RSpec.shared_examples 'recipe snapshot endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'empty recipe for not found package'
+ it_behaves_like 'handling empty values for username and channel'
context 'with existing package' do
it 'returns a hash of files with their md5 hashes' do
@@ -324,6 +373,7 @@ RSpec.shared_examples 'package snapshot endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'empty recipe for not found package'
+ it_behaves_like 'handling empty values for username and channel'
context 'with existing package' do
it 'returns a hash of md5 values for the files' do
@@ -344,12 +394,14 @@ RSpec.shared_examples 'recipe download_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'recipe download_urls'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'package download_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'package download_urls'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'recipe upload_urls endpoint' do
@@ -362,6 +414,7 @@ RSpec.shared_examples 'recipe upload_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
+ it_behaves_like 'handling empty values for username and channel'
it 'returns a set of upload urls for the files requested' do
subject
@@ -423,6 +476,7 @@ RSpec.shared_examples 'package upload_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
+ it_behaves_like 'handling empty values for username and channel'
it 'returns a set of upload urls for the files requested' do
expected_response = {
@@ -458,6 +512,7 @@ RSpec.shared_examples 'delete package endpoint' do
let(:recipe_path) { package.conan_recipe_path }
it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'handling empty values for username and channel'
it 'returns unauthorized for users without valid permission' do
subject
@@ -568,12 +623,14 @@ RSpec.shared_examples 'recipe file download endpoint' do
it_behaves_like 'a public project with packages'
it_behaves_like 'an internal project with packages'
it_behaves_like 'a private project with packages'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'package file download endpoint' do
it_behaves_like 'a public project with packages'
it_behaves_like 'an internal project with packages'
it_behaves_like 'a private project with packages'
+ it_behaves_like 'handling empty values for username and channel'
context 'tracking the conan_package.tgz download' do
let(:package_file) { package.package_files.find_by(file_name: ::Packages::Conan::FileMetadatum::PACKAGE_BINARY) }
@@ -598,6 +655,7 @@ RSpec.shared_examples 'workhorse authorize endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'workhorse authorization'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'workhorse recipe file upload endpoint' do
@@ -619,6 +677,7 @@ RSpec.shared_examples 'workhorse recipe file upload endpoint' do
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'uploads a package file'
it_behaves_like 'creates build_info when there is a job'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'workhorse package file upload endpoint' do
@@ -640,6 +699,7 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do
it_behaves_like 'rejects invalid file_name', 'conaninfo.txttest'
it_behaves_like 'uploads a package file'
it_behaves_like 'creates build_info when there is a job'
+ it_behaves_like 'handling empty values for username and channel'
context 'tracking the conan_package.tgz upload' do
let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
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 62dbac3fd4d..8bffd1f71e9 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
@@ -18,19 +18,19 @@ RSpec.shared_examples 'snippet edit usage data counters' do
end
end
- context 'when user is not sessionless' do
+ context 'when user is not sessionless', :clean_gitlab_redis_sessions do
before do
session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d')
session_hash = { 'warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]] }
- Gitlab::Redis::SharedState.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash))
end
cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id
end
- it 'tracks usage data actions', :clean_gitlab_redis_shared_state do
+ it 'tracks usage data actions', :clean_gitlab_redis_sessions do
expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_snippet_editor_edit_action)
post_graphql_mutation(mutation)
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
index 367c6d4fa3a..882c79cb03f 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
@@ -55,7 +55,7 @@ RSpec.shared_examples 'group and project packages query' do
end
it 'deals with metadata' do
- expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
+ expect(target_shas.compact).to contain_exactly(composer_metadatum.target_sha)
end
it 'returns the count of the packages' do
diff --git a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
index 673d7741017..c5e5803c0a7 100644
--- a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
+++ b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
@@ -28,4 +28,34 @@ RSpec.shared_examples 'issuable participants endpoint' do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'with a confidential note' do
+ let!(:note) do
+ create(
+ :note,
+ :confidential,
+ project: project,
+ noteable: entity,
+ author: create(:user)
+ )
+ end
+
+ it 'returns a full list of participants' do
+ get api("/projects/#{project.id}/#{area}/#{entity.iid}/participants", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ participant_ids = json_response.map { |el| el['id'] }
+ expect(participant_ids).to match_array([entity.author_id, note.author_id])
+ end
+
+ context 'when user cannot see a confidential note' do
+ it 'returns a limited list of participants' do
+ get api("/projects/#{project.id}/#{area}/#{entity.iid}/participants", create(:user))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ participant_ids = json_response.map { |el| el['id'] }
+ expect(participant_ids).to match_array([entity.author_id])
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index 19677e92001..8d6d85732be 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -41,19 +41,6 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
# query count can slightly change between the examples so we're using a custom threshold
expect { get(url, headers: headers) }.not_to exceed_query_limit(control).with_threshold(4)
end
-
- context 'with packages_npm_abbreviated_metadata disabled' do
- before do
- stub_feature_flags(packages_npm_abbreviated_metadata: false)
- end
-
- it 'calls the presenter without including metadata' do
- expect(::Packages::Npm::PackagePresenter)
- .to receive(:new).with(anything, anything, include_metadata: false).and_call_original
-
- subject
- end
- end
end
shared_examples 'reject metadata request' do |status:|
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
index 878cbc10a24..6568d51b90e 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -391,7 +391,7 @@ RSpec.shared_examples 'rejects nuget access with invalid target id' do
context 'with a target id with invalid integers' do
using RSpec::Parameterized::TableSyntax
- let(:target) { OpenStruct.new(id: id) }
+ let(:target) { double(id: id) }
where(:id, :status) do
'/../' | :bad_request
@@ -411,7 +411,7 @@ end
RSpec.shared_examples 'rejects nuget access with unknown target id' do
context 'with an unknown target' do
- let(:target) { OpenStruct.new(id: 1234567890) }
+ let(:target) { double(id: 1234567890) }
context 'as anonymous' do
it_behaves_like 'rejects nuget packages access', :anonymous, :unauthorized
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 06c51add438..aff086d1ba3 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
@@ -346,7 +346,8 @@ RSpec.shared_examples 'a pypi user namespace endpoint' do
end
with_them do
- let_it_be_with_reload(:group) { create(:namespace) }
+ # only groups are supported, so this "group" is actually the wrong namespace type
+ let_it_be_with_reload(:group) { create(:user_namespace) }
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) }
before do
diff --git a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb
index c979fdc2bb0..7fd20fc3909 100644
--- a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb
@@ -126,7 +126,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
SHA256: #{package_files[4].file_sha256}
EOF
- expected_main_source_content = <<~EOF
+ expected_main_sources_content = <<~EOF
Package: #{package.name}
Binary: sample-dev, libsample0, sample-udeb
Version: #{package.version}
@@ -158,7 +158,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
check_component_file(current_time.round, 'main', :di_packages, 'amd64', expected_main_amd64_di_content)
check_component_file(current_time.round, 'main', :di_packages, 'arm64', nil)
- check_component_file(current_time.round, 'main', :source, nil, expected_main_source_content)
+ check_component_file(current_time.round, 'main', :sources, nil, expected_main_sources_content)
check_component_file(current_time.round, 'contrib', :packages, 'all', nil)
check_component_file(current_time.round, 'contrib', :packages, 'amd64', nil)
@@ -168,7 +168,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
check_component_file(current_time.round, 'contrib', :di_packages, 'amd64', nil)
check_component_file(current_time.round, 'contrib', :di_packages, 'arm64', nil)
- check_component_file(current_time.round, 'contrib', :source, nil, nil)
+ check_component_file(current_time.round, 'contrib', :sources, nil, nil)
main_amd64_size = expected_main_amd64_content.length
main_amd64_md5sum = Digest::MD5.hexdigest(expected_main_amd64_content)
@@ -182,9 +182,9 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
main_amd64_di_md5sum = Digest::MD5.hexdigest(expected_main_amd64_di_content)
main_amd64_di_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_di_content)
- main_source_size = expected_main_source_content.length
- main_source_md5sum = Digest::MD5.hexdigest(expected_main_source_content)
- main_source_sha256 = Digest::SHA256.hexdigest(expected_main_source_content)
+ main_sources_size = expected_main_sources_content.length
+ main_sources_md5sum = Digest::MD5.hexdigest(expected_main_sources_content)
+ main_sources_sha256 = Digest::SHA256.hexdigest(expected_main_sources_content)
expected_release_content = <<~EOF
Codename: unstable
@@ -199,14 +199,14 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-amd64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-arm64/Packages
- d41d8cd98f00b204e9800998ecf8427e 0 contrib/source/Source
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/source/Sources
d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages
d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-all/Packages
#{main_amd64_md5sum} #{main_amd64_size} main/binary-amd64/Packages
#{main_amd64_di_md5sum} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-arm64/Packages
- #{main_source_md5sum} #{main_source_size} main/source/Source
+ #{main_sources_md5sum} #{main_sources_size} main/source/Sources
SHA256:
#{contrib_all_sha256} #{contrib_all_size} contrib/binary-all/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages
@@ -214,14 +214,14 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-arm64/Packages
- e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Source
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Sources
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages
#{main_amd64_sha256} #{main_amd64_size} main/binary-amd64/Packages
#{main_amd64_di_sha256} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages
- #{main_source_sha256} #{main_source_size} main/source/Source
+ #{main_sources_sha256} #{main_sources_size} main/source/Sources
EOF
check_release_files(expected_release_content)
diff --git a/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb
new file mode 100644
index 00000000000..0d3e158d358
--- /dev/null
+++ b/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb
@@ -0,0 +1,212 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'it runs background migration jobs' do |tracking_database, metric_name|
+ describe 'defining the job attributes' do
+ it 'defines the data_consistency as always' do
+ expect(described_class.get_data_consistency).to eq(:always)
+ end
+
+ it 'defines the retry count in sidekiq_options' do
+ expect(described_class.sidekiq_options['retry']).to eq(3)
+ end
+
+ it 'defines the feature_category as database' do
+ expect(described_class.get_feature_category).to eq(:database)
+ end
+
+ it 'defines the urgency as throttled' do
+ expect(described_class.get_urgency).to eq(:throttled)
+ end
+
+ it 'defines the loggable_arguments' do
+ expect(described_class.loggable_arguments).to match_array([0, 1])
+ end
+ end
+
+ describe '.tracking_database' do
+ it 'does not raise an error' do
+ expect { described_class.tracking_database }.not_to raise_error
+ end
+
+ it 'overrides the method to return the tracking database' do
+ expect(described_class.tracking_database).to eq(tracking_database)
+ end
+ end
+
+ describe '.unhealthy_metric_name' do
+ it 'does not raise an error' do
+ expect { described_class.unhealthy_metric_name }.not_to raise_error
+ end
+
+ it 'overrides the method to return the unhealthy metric name' do
+ expect(described_class.unhealthy_metric_name).to eq(metric_name)
+ end
+ end
+
+ describe '.minimum_interval' do
+ it 'returns 2 minutes' do
+ expect(described_class.minimum_interval).to eq(2.minutes.to_i)
+ end
+ end
+
+ describe '#perform' do
+ let(:worker) { described_class.new }
+
+ before do
+ allow(worker).to receive(:jid).and_return(1)
+ allow(worker).to receive(:always_perform?).and_return(false)
+
+ allow(Postgresql::ReplicationSlot).to receive(:lag_too_great?).and_return(false)
+ end
+
+ it 'performs jobs using the coordinator for the worker' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::JobCoordinator) do |coordinator|
+ allow(coordinator).to receive(:with_shared_connection).and_yield
+
+ expect(coordinator.worker_class).to eq(described_class)
+ expect(coordinator).to receive(:perform).with('Foo', [10, 20])
+ end
+
+ worker.perform('Foo', [10, 20])
+ end
+
+ context 'when lease can be obtained' do
+ let(:coordinator) { double('job coordinator') }
+
+ before do
+ allow(Gitlab::BackgroundMigration).to receive(:coordinator_for_database)
+ .with(tracking_database)
+ .and_return(coordinator)
+
+ allow(coordinator).to receive(:with_shared_connection).and_yield
+ end
+
+ it 'sets up the shared connection before checking replication' do
+ expect(coordinator).to receive(:with_shared_connection).and_yield.ordered
+ expect(Postgresql::ReplicationSlot).to receive(:lag_too_great?).and_return(false).ordered
+
+ expect(coordinator).to receive(:perform).with('Foo', [10, 20])
+
+ worker.perform('Foo', [10, 20])
+ end
+
+ it 'performs a background migration' do
+ expect(coordinator).to receive(:perform).with('Foo', [10, 20])
+
+ worker.perform('Foo', [10, 20])
+ end
+
+ context 'when lease_attempts is 1' do
+ it 'performs a background migration' do
+ expect(coordinator).to receive(:perform).with('Foo', [10, 20])
+
+ worker.perform('Foo', [10, 20], 1)
+ end
+ end
+
+ it 'can run scheduled job and retried job concurrently' do
+ expect(coordinator)
+ .to receive(:perform)
+ .with('Foo', [10, 20])
+ .exactly(2).time
+
+ worker.perform('Foo', [10, 20])
+ worker.perform('Foo', [10, 20], described_class::MAX_LEASE_ATTEMPTS - 1)
+ end
+
+ it 'sets the class that will be executed as the caller_id' do
+ expect(coordinator).to receive(:perform) do
+ expect(Gitlab::ApplicationContext.current).to include('meta.caller_id' => 'Foo')
+ end
+
+ worker.perform('Foo', [10, 20])
+ end
+ end
+
+ context 'when lease not obtained (migration of same class was performed recently)' do
+ let(:timeout) { described_class.minimum_interval }
+ let(:lease_key) { "#{described_class.name}:Foo" }
+ let(:coordinator) { double('job coordinator') }
+
+ before do
+ allow(Gitlab::BackgroundMigration).to receive(:coordinator_for_database)
+ .with(tracking_database)
+ .and_return(coordinator)
+
+ allow(coordinator).to receive(:with_shared_connection).and_yield
+
+ expect(coordinator).not_to receive(:perform)
+
+ Gitlab::ExclusiveLease.new(lease_key, timeout: timeout).try_obtain
+ end
+
+ it 'reschedules the migration and decrements the lease_attempts' do
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(a_kind_of(Numeric), 'Foo', [10, 20], 4)
+
+ worker.perform('Foo', [10, 20], 5)
+ end
+
+ context 'when lease_attempts is 1' do
+ let(:lease_key) { "#{described_class.name}:Foo:retried" }
+
+ it 'reschedules the migration and decrements the lease_attempts' do
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(a_kind_of(Numeric), 'Foo', [10, 20], 0)
+
+ worker.perform('Foo', [10, 20], 1)
+ end
+ end
+
+ context 'when lease_attempts is 0' do
+ let(:lease_key) { "#{described_class.name}:Foo:retried" }
+
+ it 'gives up performing the migration' do
+ expect(described_class).not_to receive(:perform_in)
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: 'Foo',
+ message: 'Job could not get an exclusive lease after several tries. Giving up.',
+ job_id: 1)
+
+ worker.perform('Foo', [10, 20], 0)
+ end
+ end
+ end
+
+ context 'when database is not healthy' do
+ before do
+ expect(Postgresql::ReplicationSlot).to receive(:lag_too_great?).and_return(true)
+ end
+
+ it 'reschedules a migration if the database is not healthy' do
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(a_kind_of(Numeric), 'Foo', [10, 20], 4)
+
+ worker.perform('Foo', [10, 20])
+ end
+
+ it 'increments the unhealthy counter' do
+ counter = Gitlab::Metrics.counter(metric_name, 'msg')
+
+ expect(described_class).to receive(:perform_in)
+
+ expect { worker.perform('Foo', [10, 20]) }.to change { counter.get }.by(1)
+ end
+
+ context 'when lease_attempts is 0' do
+ it 'gives up performing the migration' do
+ expect(described_class).not_to receive(:perform_in)
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: 'Foo',
+ message: 'Database was unhealthy after several tries. Giving up.',
+ job_id: 1)
+
+ worker.perform('Foo', [10, 20], 0)
+ end
+ end
+ end
+ end
+end