summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-19 07:33:21 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-19 07:33:21 +0000
commit36a59d088eca61b834191dacea009677a96c052f (patch)
treee4f33972dab5d8ef79e3944a9f403035fceea43f /spec/support/shared_examples
parenta1761f15ec2cae7c7f7bbda39a75494add0dfd6f (diff)
downloadgitlab-ce-36a59d088eca61b834191dacea009677a96c052f.tar.gz
Add latest changes from gitlab-org/gitlab@15-0-stable-eev15.0.0-rc42
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/ci/log_downstream_pipeline_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/controllers/environments_controller_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/dependency_proxy_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/inviting_groups_shared_examples.rb144
-rw-r--r--spec/support/shared_examples/features/manage_applications_shared_examples.rb4
-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/multiple_reviewers_mr_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/sidebar_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb48
-rw-r--r--spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb32
-rw-r--r--spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb162
-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.rb2
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/reset_secret_fields_shared_examples.rb110
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb403
-rw-r--r--spec/support/shared_examples/models/reviewer_state_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb233
-rw-r--r--spec/support/shared_examples/nav_sidebar_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb89
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/requests/api/milestones_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb26
-rw-r--r--spec/support/shared_examples/services/jira/requests/base_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/work_item_base_types_importer.rb42
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb119
-rw-r--r--spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb8
45 files changed, 1186 insertions, 538 deletions
diff --git a/spec/support/shared_examples/ci/log_downstream_pipeline_shared_examples.rb b/spec/support/shared_examples/ci/log_downstream_pipeline_shared_examples.rb
new file mode 100644
index 00000000000..db724dcfe99
--- /dev/null
+++ b/spec/support/shared_examples/ci/log_downstream_pipeline_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'logs downstream pipeline creation' do
+ def record_downstream_pipeline_logs
+ logs = []
+ allow(::Gitlab::AppLogger).to receive(:info) do |args|
+ logs << args
+ end
+
+ yield
+
+ logs.find { |log| log[:message] == "downstream pipeline created" }
+ end
+
+ it 'logs details' do
+ pipeline = nil
+
+ log_entry = record_downstream_pipeline_logs do
+ pipeline = subject
+ end
+
+ expect(log_entry).to be_present
+ expect(log_entry).to eq(
+ message: "downstream pipeline created",
+ class: described_class.name,
+ root_pipeline_id: expected_root_pipeline.id,
+ downstream_pipeline_id: pipeline.id,
+ downstream_pipeline_relationship: expected_downstream_relationship,
+ hierarchy_size: expected_hierarchy_size,
+ root_pipeline_plan: expected_root_pipeline.project.actual_plan_name,
+ root_pipeline_namespace_path: expected_root_pipeline.project.namespace.full_path,
+ root_pipeline_project_path: expected_root_pipeline.project.full_path)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb b/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb
index c6e880635aa..a79b94209f3 100644
--- a/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb
@@ -65,3 +65,20 @@ RSpec.shared_examples 'failed response for #cancel_auto_stop' do
end
end
end
+
+RSpec.shared_examples 'avoids N+1 queries on environment detail page' do
+ render_views
+
+ before do
+ create_deployment_with_associations(sequence: 0)
+ end
+
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new { get :show, params: environment_params }
+
+ create_deployment_with_associations(sequence: 1)
+ create_deployment_with_associations(sequence: 2)
+
+ expect { get :show, params: environment_params }.not_to exceed_query_limit(control.count).with_threshold(34)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb
index fadf428125a..9cf35325202 100644
--- a/spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_shared_examples.rb
@@ -44,8 +44,10 @@ RSpec.shared_examples 'a controller that can serve LFS files' do |options = {}|
expect(controller).to receive(:send_file)
.with(
File.join(lfs_uploader.root, lfs_uploader.store_dir, lfs_uploader.filename),
- filename: filename,
- disposition: 'attachment')
+ {
+ filename: filename,
+ disposition: 'attachment'
+ })
subject
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 5c44cb7f04b..c93d8e3d511 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -23,6 +23,8 @@ RSpec.shared_examples 'edits content using the content editor' do
describe 'code block bubble menu' do
it 'shows a code block bubble menu for a code block' do
+ find(content_editor_testid).send_keys [:enter, :enter]
+
find(content_editor_testid).send_keys '```js ' # trigger input rule
find(content_editor_testid).send_keys 'var a = 0'
find(content_editor_testid).send_keys [:shift, :left]
@@ -32,6 +34,8 @@ RSpec.shared_examples 'edits content using the content editor' do
end
it 'sets code block type to "javascript" for `js`' do
+ find(content_editor_testid).send_keys [:enter, :enter]
+
find(content_editor_testid).send_keys '```js '
find(content_editor_testid).send_keys 'var a = 0'
@@ -39,6 +43,8 @@ RSpec.shared_examples 'edits content using the content editor' do
end
it 'sets code block type to "Custom (nomnoml)" for `nomnoml`' do
+ find(content_editor_testid).send_keys [:enter, :enter]
+
find(content_editor_testid).send_keys '```nomnoml '
find(content_editor_testid).send_keys 'test'
diff --git a/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb b/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb
index 5d1488502d2..6fd844f0e5f 100644
--- a/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb
+++ b/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb
@@ -17,7 +17,7 @@ end
RSpec.shared_examples 'a successful manifest pull' do
it 'sends a file' do
- expect(controller).to receive(:send_file).with(manifest.file.path, type: manifest.content_type)
+ expect(controller).to receive(:send_file).with(manifest.file.path, { type: manifest.content_type })
subject
end
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index ccd063faac4..2fff4137934 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -48,7 +48,7 @@ RSpec.shared_examples 'an editable merge request' do
end
page.within '.reviewer' do
- expect(page).to have_content user.username
+ expect(page).to have_content user.name
end
page.within '.milestone' do
diff --git a/spec/support/shared_examples/features/inviting_groups_shared_examples.rb b/spec/support/shared_examples/features/inviting_groups_shared_examples.rb
new file mode 100644
index 00000000000..4921676a065
--- /dev/null
+++ b/spec/support/shared_examples/features/inviting_groups_shared_examples.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'inviting groups search results' do
+ context 'with instance admin considerations' do
+ let_it_be(:group_to_invite) { create(:group) }
+
+ context 'when user is an admin' do
+ let_it_be(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ end
+
+ it 'shows groups where the admin has no direct membership' do
+ visit members_page_path
+
+ click_on 'Invite a group'
+ click_on 'Select a group'
+ wait_for_requests
+
+ page.within(group_dropdown_selector) do
+ expect_to_have_group(group_to_invite)
+ expect_not_to_have_group(group)
+ end
+ end
+
+ it 'shows groups where the admin has at least guest level membership' do
+ group_to_invite.add_guest(admin)
+
+ visit members_page_path
+
+ click_on 'Invite a group'
+ click_on 'Select a group'
+ wait_for_requests
+
+ page.within(group_dropdown_selector) do
+ expect_to_have_group(group_to_invite)
+ expect_not_to_have_group(group)
+ end
+ end
+ end
+
+ context 'when user is not an admin' do
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ end
+
+ it 'does not show groups where the user has no direct membership' do
+ visit members_page_path
+
+ click_on 'Invite a group'
+ click_on 'Select a group'
+ wait_for_requests
+
+ page.within(group_dropdown_selector) do
+ expect_not_to_have_group(group_to_invite)
+ expect_not_to_have_group(group)
+ end
+ end
+
+ it 'shows groups where the user has at least guest level membership' do
+ group_to_invite.add_guest(user)
+
+ visit members_page_path
+
+ click_on 'Invite a group'
+ click_on 'Select a group'
+ wait_for_requests
+
+ page.within(group_dropdown_selector) do
+ expect_to_have_group(group_to_invite)
+ expect_not_to_have_group(group)
+ end
+ end
+ end
+ end
+
+ context 'when user is not an admin and there are hierarchy considerations' do
+ let_it_be(:group_outside_hierarchy) { create(:group) }
+
+ before_all do
+ group.add_owner(user)
+ group_within_hierarchy.add_owner(user)
+ group_outside_hierarchy.add_owner(user)
+ end
+
+ before do
+ sign_in(user)
+ end
+
+ it 'does not show self or ancestors', :aggregate_failures do
+ group_sibling = create(:group, parent: group)
+ group_sibling.add_owner(user)
+
+ visit members_page_path_within_hierarchy
+
+ click_on 'Invite a group'
+ click_on 'Select a group'
+ wait_for_requests
+
+ page.within(group_dropdown_selector) do
+ expect_to_have_group(group_outside_hierarchy)
+ expect_to_have_group(group_sibling)
+ expect_not_to_have_group(group)
+ expect_not_to_have_group(group_within_hierarchy)
+ end
+ end
+
+ context 'when sharing with groups outside the hierarchy is enabled' do
+ it 'shows groups within and outside the hierarchy in search results' do
+ visit members_page_path
+
+ click_on 'Invite a group'
+ click_on 'Select a group'
+ wait_for_requests
+
+ page.within(group_dropdown_selector) do
+ expect_to_have_group(group_within_hierarchy)
+ expect_to_have_group(group_outside_hierarchy)
+ end
+ end
+ end
+
+ context 'when sharing with groups outside the hierarchy is disabled' do
+ before do
+ group.update!(prevent_sharing_groups_outside_hierarchy: true)
+ end
+
+ it 'shows only groups within the hierarchy in search results' do
+ visit members_page_path
+
+ click_on 'Invite a group'
+ click_on 'Select a group'
+
+ page.within(group_dropdown_selector) do
+ expect_to_have_group(group_within_hierarchy)
+ expect_not_to_have_group(group_outside_hierarchy)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/manage_applications_shared_examples.rb b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
index 3a8267b21da..442264e7ae4 100644
--- a/spec/support/shared_examples/features/manage_applications_shared_examples.rb
+++ b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
@@ -9,11 +9,9 @@ RSpec.shared_examples 'manage applications' do
visit new_application_path
expect(page).to have_content 'Add new application'
- expect(find('#doorkeeper_application_expire_access_tokens')).to be_checked
fill_in :doorkeeper_application_name, with: application_name
fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri
- uncheck :doorkeeper_application_expire_access_tokens
check :doorkeeper_application_scopes_read_user
click_on 'Save application'
@@ -25,8 +23,6 @@ RSpec.shared_examples 'manage applications' do
click_on 'Edit'
- expect(find('#doorkeeper_application_expire_access_tokens')).not_to be_checked
-
application_name_changed = "#{application_name} changed"
fill_in :doorkeeper_application_name, with: application_name_changed
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 9d023d9514a..4565108b5e4 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
+ click_link user.name unless action == 'creates'
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 bbde448a1a1..a44a699c878 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
+ click_link user.name unless action == 'creates'
click_link user2.name
end
diff --git a/spec/support/shared_examples/features/multiple_reviewers_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_reviewers_mr_shared_examples.rb
index ad6ca3e1900..48cde90bd9b 100644
--- a/spec/support/shared_examples/features/multiple_reviewers_mr_shared_examples.rb
+++ b/spec/support/shared_examples/features/multiple_reviewers_mr_shared_examples.rb
@@ -40,7 +40,7 @@ RSpec.shared_examples 'multiple reviewers merge request' do |action, save_button
# Closing dropdown to persist
click_link 'Edit'
- expect(page).to have_content user2.username
+ expect(page).to have_content user2.name
end
end
end
diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb
index ded30f32314..323bd4f5171 100644
--- a/spec/support/shared_examples/features/packages_shared_examples.rb
+++ b/spec/support/shared_examples/features/packages_shared_examples.rb
@@ -97,9 +97,9 @@ def click_sort_option(option, ascending)
wait_for_requests
end
- find('button.gl-dropdown-toggle').click
+ find('[data-testid="registry-sort-dropdown"]').click
- page.within('.dropdown-menu') do
+ page.within('[data-testid="registry-sort-dropdown"] .dropdown-menu') do
click_button option
end
diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb
index 11d216ff4b6..af3ea0600a2 100644
--- a/spec/support/shared_examples/features/sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb
@@ -108,7 +108,11 @@ RSpec.shared_examples 'issue boards sidebar' do
wait_for_requests
- expect(page).to have_content('This issue is confidential')
+ expect(page).to have_content(
+ _('Only project members with at least' \
+ ' Reporter role can view or be' \
+ ' notified about this issue.')
+ )
end
end
end
diff --git a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
index 41b1964cff0..8081c51577a 100644
--- a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
@@ -233,6 +233,23 @@ RSpec.shared_examples 'User creates wiki page' do
.and have_content("Last edited by #{user.name}")
.and have_content("My awesome wiki!")
end
+
+ context 'when a server side validation error is returned' do
+ it "still displays edit form", :js do
+ click_link("New page")
+
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "home")
+ fill_in(:wiki_content, with: "My awesome home page!")
+ end
+
+ # Submits page with a name already in use to trigger a validation error
+ click_button("Create page")
+
+ expect(page).to have_field(:wiki_title)
+ expect(page).to have_field(:wiki_content)
+ end
+ end
end
it "shows the emoji autocompletion dropdown", :js do
diff --git a/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb
new file mode 100644
index 00000000000..b989dbc6524
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# Requres:
+# * subject with a 'resolve' name
+# * Defined expected timeline event via `let(:expected_timeline_event) { instance_double(...) }`
+RSpec.shared_examples 'creating an incident timeline event' do
+ it 'creates a timeline event' do
+ expect { resolve }.to change(IncidentManagement::TimelineEvent, :count).by(1)
+ end
+
+ it 'responds with a timeline event', :aggregate_failures do
+ response = resolve
+ timeline_event = IncidentManagement::TimelineEvent.last!
+
+ expect(response).to match(timeline_event: timeline_event, errors: be_empty)
+
+ expect(timeline_event.promoted_from_note).to eq(expected_timeline_event.promoted_from_note)
+ expect(timeline_event.note).to eq(expected_timeline_event.note)
+ expect(timeline_event.occurred_at.to_s).to eq(expected_timeline_event.occurred_at)
+ expect(timeline_event.incident).to eq(expected_timeline_event.incident)
+ expect(timeline_event.author).to eq(expected_timeline_event.author)
+ end
+end
+
+# Requres
+# * subject with a 'resolve' name
+# * a user factory with a 'current_user' name
+RSpec.shared_examples 'failing to create an incident timeline event' do
+ context 'when a user has no permissions to create timeline event' do
+ before do
+ project.add_guest(current_user)
+ end
+
+ it 'raises an error' do
+ expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+end
+
+# Requres:
+# * subject with a 'resolve' name
+RSpec.shared_examples 'responding with an incident timeline errors' do |errors:|
+ it 'returns errors' do
+ expect(resolve).to eq(timeline_event: nil, errors: errors)
+ end
+end
diff --git a/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
index 3d6fec85490..da8562161e7 100644
--- a/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
@@ -4,7 +4,11 @@ RSpec.shared_examples 'group and projects packages resolver' do
context 'without sort' do
let_it_be(:npm_package) { create(:package, project: project) }
- it { is_expected.to contain_exactly(npm_package) }
+ it 'returns the proper packages' do
+ expect(::Packages::Package).not_to receive(:preload_pipelines)
+
+ expect(subject).to contain_exactly(npm_package)
+ end
end
context 'with sorting and filtering' do
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 37a805902a9..6d6e7b761f6 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
@@ -101,7 +101,7 @@ RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
context 'when sorting' do
it 'sorts correctly' do
- expect(results).to eq all_records
+ expect(results).to match all_records
end
context 'when paginating' do
@@ -110,17 +110,17 @@ RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
let(:rest) { all_records.drop(first_param) }
it 'paginates correctly' do
- expect(results).to eq first_page
+ expect(results).to match first_page
fwds = pagination_query(sort_argument.merge(after: end_cursor))
post_graphql(fwds, current_user: current_user)
- expect(results).to eq rest
+ expect(results).to match rest
bwds = pagination_query(sort_argument.merge(before: start_cursor))
post_graphql(bwds, current_user: current_user)
- expect(results).to eq first_page
+ expect(results).to match first_page
end
end
@@ -130,7 +130,7 @@ RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
it 'fetches last elements without error' do
post_graphql(pagination_query(params), current_user: current_user)
- expect(results.first).to eq(all_records.last)
+ expect(results.first).to match all_records.last
end
end
end
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index 3caf153c2fa..cf9c36fafe8 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -6,7 +6,7 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
expect { subject(deprecation_reason: 'foo') }.to raise_error(
ArgumentError,
'Use `deprecated` property instead of `deprecation_reason`. ' \
- 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-arguments-and-enum-values'
+ 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-schema-items'
)
end
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 a3c67210a4a..e886ec65b02 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
@@ -796,8 +796,8 @@ RSpec.shared_examples 'trace with enabled live trace feature' do
end
end
- describe '#archived_trace_exist?' do
- subject { trace.archived_trace_exist? }
+ describe '#archived?' do
+ subject { trace.archived? }
context 'when trace does not exist' do
it { is_expected.to be_falsy }
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
index bea7cca2744..beec072e474 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
@@ -62,8 +62,8 @@ shared_examples 'deployment metrics examples' do
describe '#deployment_frequency' do
subject { stage_summary.fourth[:value] }
- it 'includes the unit: `per day`' do
- expect(stage_summary.fourth[:unit]).to eq _('per day')
+ it 'includes the unit: `/day`' do
+ expect(stage_summary.fourth[:unit]).to eq _('/day')
end
before do
diff --git a/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb
new file mode 100644
index 00000000000..67d739b79ab
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'reconfigures connection stack' do |db_config_name|
+ before do
+ skip_if_multiple_databases_not_setup
+
+ # Due to lib/gitlab/database/load_balancing/configuration.rb:92 requiring RequestStore
+ # we cannot use stub_feature_flags(force_no_sharing_primary_model: true)
+ Gitlab::Database.database_base_models.each do |_, model_class|
+ allow(model_class.load_balancer.configuration).to receive(:use_dedicated_connection?).and_return(true)
+ end
+
+ ActiveRecord::Base.establish_connection(db_config_name.to_sym) # rubocop:disable Database/EstablishConnection
+
+ expect(Gitlab::Database.db_config_name(ActiveRecord::Base.connection)) # rubocop:disable Database/MultipleDatabases
+ .to eq(db_config_name)
+ end
+
+ around do |example|
+ with_reestablished_active_record_base do
+ example.run
+ end
+ end
+
+ def validate_connections!
+ model_connections = Gitlab::Database.database_base_models.to_h do |db_config_name, model_class|
+ [model_class, Gitlab::Database.db_config_name(model_class.connection)]
+ end
+
+ expect(model_connections).to eq(Gitlab::Database.database_base_models.invert)
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb
deleted file mode 100644
index 2633a89eeee..00000000000
--- a/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb
+++ /dev/null
@@ -1,162 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'network policy common specs' do
- let(:name) { 'example-name' }
- let(:namespace) { 'example-namespace' }
- let(:labels) { nil }
-
- describe '#generate' do
- subject { policy.generate }
-
- it { is_expected.to eq(Kubeclient::Resource.new(policy.resource)) }
- end
-
- describe 'as_json' do
- let(:json_policy) do
- {
- name: name,
- namespace: namespace,
- creation_timestamp: nil,
- manifest: YAML.dump(policy.resource.deep_stringify_keys),
- is_autodevops: false,
- is_enabled: true,
- environment_ids: []
- }
- end
-
- subject { policy.as_json }
-
- it { is_expected.to eq(json_policy) }
- end
-
- describe 'autodevops?' do
- subject { policy.autodevops? }
-
- let(:labels) { { chart: chart } }
- let(:chart) { nil }
-
- it { is_expected.to be false }
-
- context 'with non-autodevops chart' do
- let(:chart) { 'foo' }
-
- it { is_expected.to be false }
- end
-
- context 'with autodevops chart' do
- let(:chart) { 'auto-deploy-app-0.6.0' }
-
- it { is_expected.to be true }
- end
- end
-
- describe 'enabled?' do
- subject { policy.enabled? }
-
- let(:selector) { nil }
-
- it { is_expected.to be true }
-
- context 'with empty selector' do
- let(:selector) { {} }
-
- it { is_expected.to be true }
- end
-
- context 'with nil matchLabels in selector' do
- let(:selector) { { matchLabels: nil } }
-
- it { is_expected.to be true }
- end
-
- context 'with empty matchLabels in selector' do
- let(:selector) { { matchLabels: {} } }
-
- it { is_expected.to be true }
- end
-
- context 'with disabled_by label in matchLabels in selector' do
- let(:selector) do
- { matchLabels: { Gitlab::Kubernetes::NetworkPolicyCommon::DISABLED_BY_LABEL => 'gitlab' } }
- end
-
- it { is_expected.to be false }
- end
- end
-
- describe 'enable' do
- subject { policy.enabled? }
-
- let(:selector) { nil }
-
- before do
- policy.enable
- end
-
- it { is_expected.to be true }
-
- context 'with empty selector' do
- let(:selector) { {} }
-
- it { is_expected.to be true }
- end
-
- context 'with nil matchLabels in selector' do
- let(:selector) { { matchLabels: nil } }
-
- it { is_expected.to be true }
- end
-
- context 'with empty matchLabels in selector' do
- let(:selector) { { matchLabels: {} } }
-
- it { is_expected.to be true }
- end
-
- context 'with disabled_by label in matchLabels in selector' do
- let(:selector) do
- { matchLabels: { Gitlab::Kubernetes::NetworkPolicyCommon::DISABLED_BY_LABEL => 'gitlab' } }
- end
-
- it { is_expected.to be true }
- end
- end
-
- describe 'disable' do
- subject { policy.enabled? }
-
- let(:selector) { nil }
-
- before do
- policy.disable
- end
-
- it { is_expected.to be false }
-
- context 'with empty selector' do
- let(:selector) { {} }
-
- it { is_expected.to be false }
- end
-
- context 'with nil matchLabels in selector' do
- let(:selector) { { matchLabels: nil } }
-
- it { is_expected.to be false }
- end
-
- context 'with empty matchLabels in selector' do
- let(:selector) { { matchLabels: {} } }
-
- it { is_expected.to be false }
- end
-
- context 'with disabled_by label in matchLabels in selector' do
- let(:selector) do
- { matchLabels: { Gitlab::Kubernetes::NetworkPolicyCommon::DISABLED_BY_LABEL => 'gitlab' } }
- end
-
- it { is_expected.to be false }
- 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
new file mode 100644
index 00000000000..d4986975f03
--- /dev/null
+++ b/spec/support/shared_examples/merge_request_author_auto_assign_shared_examples.rb
@@ -0,0 +1,8 @@
+# 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 e6b270c6188..fa10b03fa90 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -199,7 +199,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
{
title: "Awesome wiki_page",
content: "Some text describing some thing or another",
- format: "md",
+ format: :markdown,
message: "user created page: Awesome wiki_page"
}
end
diff --git a/spec/support/shared_examples/models/concerns/integrations/reset_secret_fields_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/reset_secret_fields_shared_examples.rb
new file mode 100644
index 00000000000..873f858e432
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/integrations/reset_secret_fields_shared_examples.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::ResetSecretFields do
+ describe '#exposing_secrets_fields' do
+ it 'returns an array of strings' do
+ expect(integration.exposing_secrets_fields).to be_a(Array)
+ expect(integration.exposing_secrets_fields).to all(be_a(String))
+ end
+ end
+
+ describe '#reset_secret_fields?' do
+ let(:exposing_fields) { integration.exposing_secrets_fields }
+
+ it 'returns false if no exposing field has changed' do
+ exposing_fields.each do |field|
+ allow(integration).to receive("#{field}_changed?").and_return(false)
+ end
+
+ expect(integration.send(:reset_secret_fields?)).to be(false)
+ end
+
+ it 'returns true if any exposing field has changed' do
+ exposing_fields.each do |field|
+ allow(integration).to receive("#{field}_changed?").and_return(true)
+
+ other_exposing_fields = exposing_fields.without(field)
+ other_exposing_fields.each do |other_field|
+ allow(integration).to receive("#{other_field}_changed?").and_return(false)
+ end
+
+ expect(integration.send(:reset_secret_fields?)).to be(true)
+ end
+ end
+ end
+
+ describe 'validation callback' do
+ before do
+ # Store a value in each password field
+ integration.secret_fields.each do |field|
+ integration.public_send("#{field}=", 'old value')
+ end
+
+ # Treat values as persisted
+ integration.reset_updated_properties
+ integration.instance_variable_set('@old_data_fields', nil) if integration.supports_data_fields?
+ end
+
+ context 'when an exposing field has changed' do
+ let(:exposing_field) { integration.exposing_secrets_fields.first }
+
+ before do
+ integration.public_send("#{exposing_field}=", 'new value')
+ end
+
+ it 'clears all secret fields' do
+ integration.valid?
+
+ integration.secret_fields.each do |field|
+ expect(integration.public_send(field)).to be_nil
+ expect(integration.properties[field]).to be_nil if integration.properties.present?
+ expect(integration.data_fields[field]).to be_nil if integration.supports_data_fields?
+ end
+ end
+
+ context 'when a secret field has been updated' do
+ let(:secret_field) { integration.secret_fields.first }
+ let(:other_secret_fields) { integration.secret_fields.without(secret_field) }
+ let(:new_value) { 'new value' }
+
+ before do
+ integration.public_send("#{secret_field}=", new_value)
+ end
+
+ it 'does not clear this secret field' do
+ integration.valid?
+
+ expect(integration.public_send(secret_field)).to eq('new value')
+
+ other_secret_fields.each do |field|
+ expect(integration.public_send(field)).to be_nil
+ end
+ end
+
+ context 'when a secret field has been updated with the same value' do
+ let(:new_value) { 'old value' }
+
+ it 'does not clear this secret field' do
+ integration.valid?
+
+ expect(integration.public_send(secret_field)).to eq('old value')
+
+ other_secret_fields.each do |field|
+ expect(integration.public_send(field)).to be_nil
+ end
+ end
+ end
+ end
+ end
+
+ context 'when no exposing field has changed' do
+ it 'does not clear any secret fields' do
+ integration.valid?
+
+ integration.secret_fields.each do |field|
+ expect(integration.public_send(field)).to eq('old value')
+ 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 da5c35c970a..2e062cda4e9 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
@@ -45,9 +45,33 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
it "notifies about #{event_type} events" do
+ expect(chat_integration).not_to receive(:log_error)
+
chat_integration.execute(data)
+
expect(WebMock).to have_requested(:post, stubbed_resolved_hostname)
end
+
+ context 'when the response is not successful' do
+ let!(:stubbed_resolved_hostname) do
+ stub_full_request(webhook_url, method: :post)
+ .to_return(status: 409, body: 'error message')
+ .request_pattern.uri_pattern.to_s
+ end
+
+ it 'logs an error' do
+ expect(chat_integration).to receive(:log_error).with(
+ 'SlackMattermostNotifier HTTP error response',
+ request_host: 'example.gitlab.com',
+ response_code: 409,
+ response_body: 'error message'
+ )
+
+ chat_integration.execute(data)
+
+ expect(WebMock).to have_requested(:post, stubbed_resolved_hostname)
+ end
+ end
end
shared_examples "untriggered #{integration_name} integration" do |event_type: nil, branches_to_be_notified: nil|
@@ -59,8 +83,9 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
stub_full_request(webhook_url, method: :post).request_pattern.uri_pattern.to_s
end
- it "notifies about #{event_type} events" do
+ it "does not notify about #{event_type} events" do
chat_integration.execute(data)
+
expect(WebMock).not_to have_requested(:post, stubbed_resolved_hostname)
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 a329a6dca91..e293d10964b 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -77,312 +77,309 @@ RSpec.shared_examples '#valid_level_roles' do |entity_name|
end
RSpec.shared_examples_for "member creation" do
- let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
- describe '#execute' do
- it 'returns a Member object', :aggregate_failures do
- member = described_class.new(source, user, :maintainer).execute
-
- expect(member).to be_a member_type
- expect(member).to be_persisted
- end
+ it 'returns a Member object', :aggregate_failures do
+ member = described_class.new(source, user, :maintainer).execute
- context 'when adding a project_bot' do
- let_it_be(:project_bot) { create(:user, :project_bot) }
-
- before_all do
- source.add_owner(user)
- end
+ expect(member).to be_a member_type
+ expect(member).to be_persisted
+ end
- context 'when project_bot is already a member' do
- before do
- source.add_developer(project_bot)
- end
+ context 'when adding a project_bot' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
- it 'does not update the member' do
- member = described_class.new(source, project_bot, :maintainer, current_user: user).execute
+ before_all do
+ source.add_owner(user)
+ end
- expect(source.users.reload).to include(project_bot)
- expect(member).to be_persisted
- expect(member.access_level).to eq(Gitlab::Access::DEVELOPER)
- expect(member.errors.full_messages).to include(/not authorized to update member/)
- end
+ context 'when project_bot is already a member' do
+ before do
+ source.add_developer(project_bot)
end
- context 'when project_bot is not already a member' do
- it 'adds the member' do
- member = described_class.new(source, project_bot, :maintainer, current_user: user).execute
+ it 'does not update the member' do
+ member = described_class.new(source, project_bot, :maintainer, current_user: user).execute
- expect(source.users.reload).to include(project_bot)
- expect(member).to be_persisted
- end
+ expect(source.users.reload).to include(project_bot)
+ expect(member).to be_persisted
+ expect(member.access_level).to eq(Gitlab::Access::DEVELOPER)
+ expect(member.errors.full_messages).to include(/not authorized to update member/)
end
end
- 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.new(source, user, :maintainer, current_user: admin).execute
+ context 'when project_bot is not already a member' do
+ it 'adds the member' do
+ member = described_class.new(source, project_bot, :maintainer, current_user: user).execute
+ expect(source.users.reload).to include(project_bot)
expect(member).to be_persisted
- expect(source.users.reload).to include(user)
- expect(member.created_by).to eq(admin)
end
end
+ end
- 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.new(source, user, :maintainer, current_user: admin).execute
+ 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.new(source, user, :maintainer, current_user: admin).execute
- expect(member).not_to be_persisted
- expect(source.users.reload).not_to include(user)
- expect(member.errors.full_messages).to include(/not authorized to create member/)
- end
+ expect(member).to be_persisted
+ expect(source.users.reload).to include(user)
+ expect(member.created_by).to eq(admin)
end
+ end
- it 'sets members.expires_at to the given expires_at' do
- member = described_class.new(source, user, :maintainer, expires_at: Date.new(2016, 9, 22)).execute
+ 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.new(source, user, :maintainer, current_user: admin).execute
- expect(member.expires_at).to eq(Date.new(2016, 9, 22))
+ expect(member).not_to be_persisted
+ expect(source.users.reload).not_to include(user)
+ expect(member.errors.full_messages).to include(/not authorized to create member/)
end
+ end
- described_class.access_levels.each do |sym_key, int_access_level|
- it "accepts the :#{sym_key} symbol as access level", :aggregate_failures do
- expect(source.users).not_to include(user)
+ it 'sets members.expires_at to the given expires_at' do
+ member = described_class.new(source, user, :maintainer, expires_at: Date.new(2016, 9, 22)).execute
- member = described_class.new(source, user.id, sym_key).execute
+ expect(member.expires_at).to eq(Date.new(2016, 9, 22))
+ end
- expect(member.access_level).to eq(int_access_level)
- expect(source.users.reload).to include(user)
- end
+ described_class.access_levels.each do |sym_key, int_access_level|
+ it "accepts the :#{sym_key} symbol as access level", :aggregate_failures do
+ expect(source.users).not_to include(user)
+
+ member = described_class.new(source, user.id, sym_key).execute
- it "accepts the #{int_access_level} integer as access level", :aggregate_failures do
+ expect(member.access_level).to eq(int_access_level)
+ expect(source.users.reload).to include(user)
+ end
+
+ it "accepts the #{int_access_level} integer as access level", :aggregate_failures do
+ expect(source.users).not_to include(user)
+
+ member = described_class.new(source, user.id, int_access_level).execute
+
+ expect(member.access_level).to eq(int_access_level)
+ expect(source.users.reload).to include(user)
+ end
+ end
+
+ context 'with no current_user' do
+ context 'when called with a known user id' do
+ it 'adds the user as a member' do
expect(source.users).not_to include(user)
- member = described_class.new(source, user.id, int_access_level).execute
+ described_class.new(source, user.id, :maintainer).execute
- expect(member.access_level).to eq(int_access_level)
expect(source.users.reload).to include(user)
end
end
- context 'with no current_user' do
- context 'when called with a known user id' do
- it 'adds the user as a member' do
- expect(source.users).not_to include(user)
+ context 'when called with an unknown user id' do
+ it 'does not add the user as a member' do
+ expect(source.users).not_to include(user)
- described_class.new(source, user.id, :maintainer).execute
+ described_class.new(source, non_existing_record_id, :maintainer).execute
- expect(source.users.reload).to include(user)
- end
+ expect(source.users.reload).not_to include(user)
end
+ end
- context 'when called with an unknown user id' do
- it 'does not add the user as a member' do
- expect(source.users).not_to include(user)
+ context 'when called with a user object' do
+ it 'adds the user as a member' do
+ expect(source.users).not_to include(user)
- described_class.new(source, non_existing_record_id, :maintainer).execute
+ described_class.new(source, user, :maintainer).execute
- expect(source.users.reload).not_to include(user)
- end
+ expect(source.users.reload).to include(user)
+ end
+ end
+
+ context 'when called with a requester user object' do
+ before do
+ source.request_access(user)
end
- context 'when called with a user object' do
- it 'adds the user as a member' do
- expect(source.users).not_to include(user)
+ it 'adds the requester as a member', :aggregate_failures do
+ expect(source.users).not_to include(user)
+ expect(source.requesters.exists?(user_id: user)).to be_truthy
+ expect do
described_class.new(source, user, :maintainer).execute
+ end.to raise_error(Gitlab::Access::AccessDeniedError)
- expect(source.users.reload).to include(user)
- end
+ expect(source.users.reload).not_to include(user)
+ expect(source.requesters.reload.exists?(user_id: user)).to be_truthy
end
+ end
- context 'when called with a requester user object' do
- before do
- source.request_access(user)
- end
-
- it 'adds the requester as a member', :aggregate_failures do
- expect(source.users).not_to include(user)
- expect(source.requesters.exists?(user_id: user)).to be_truthy
+ context 'when called with a known user email' do
+ it 'adds the user as a member' do
+ expect(source.users).not_to include(user)
- expect do
- described_class.new(source, user, :maintainer).execute
- end.to raise_error(Gitlab::Access::AccessDeniedError)
+ described_class.new(source, user.email, :maintainer).execute
- expect(source.users.reload).not_to include(user)
- expect(source.requesters.reload.exists?(user_id: user)).to be_truthy
- end
+ expect(source.users.reload).to include(user)
end
+ end
- context 'when called with a known user email' do
- it 'adds the user as a member' do
- expect(source.users).not_to include(user)
+ context 'when called with an unknown user email' do
+ it 'creates an invited member' do
+ expect(source.users).not_to include(user)
- described_class.new(source, user.email, :maintainer).execute
+ described_class.new(source, 'user@example.com', :maintainer).execute
- expect(source.users.reload).to include(user)
- end
+ expect(source.members.invite.pluck(:invite_email)).to include('user@example.com')
end
+ end
- context 'when called with an unknown user email' do
- it 'creates an invited member' do
- expect(source.users).not_to include(user)
+ context 'when called with an unknown user email starting with a number' do
+ it 'creates an invited member', :aggregate_failures do
+ email_starting_with_number = "#{user.id}_email@example.com"
- described_class.new(source, 'user@example.com', :maintainer).execute
+ described_class.new(source, email_starting_with_number, :maintainer).execute
- expect(source.members.invite.pluck(:invite_email)).to include('user@example.com')
- end
+ expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number)
+ expect(source.users.reload).not_to include(user)
end
+ end
+ end
- context 'when called with an unknown user email starting with a number' do
- it 'creates an invited member', :aggregate_failures do
- email_starting_with_number = "#{user.id}_email@example.com"
+ context 'when current_user can update member', :enable_admin_mode do
+ it 'creates the member' do
+ expect(source.users).not_to include(user)
- described_class.new(source, email_starting_with_number, :maintainer).execute
+ described_class.new(source, user, :maintainer, current_user: admin).execute
- expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number)
- expect(source.users.reload).not_to include(user)
- end
- end
+ expect(source.users.reload).to include(user)
end
- context 'when current_user can update member', :enable_admin_mode do
- it 'creates the member' do
+ context 'when called with a requester user object' do
+ before do
+ source.request_access(user)
+ end
+
+ it 'adds the requester as a member', :aggregate_failures do
expect(source.users).not_to include(user)
+ expect(source.requesters.exists?(user_id: user)).to be_truthy
described_class.new(source, user, :maintainer, current_user: admin).execute
expect(source.users.reload).to include(user)
+ expect(source.requesters.reload.exists?(user_id: user)).to be_falsy
end
+ end
+ end
- context 'when called with a requester user object' do
- before do
- source.request_access(user)
- end
+ context 'when current_user cannot update member' do
+ it 'does not create the member', :aggregate_failures do
+ expect(source.users).not_to include(user)
- it 'adds the requester as a member', :aggregate_failures do
- expect(source.users).not_to include(user)
- expect(source.requesters.exists?(user_id: user)).to be_truthy
+ member = described_class.new(source, user, :maintainer, current_user: user).execute
- described_class.new(source, user, :maintainer, current_user: admin).execute
+ expect(source.users.reload).not_to include(user)
+ expect(member).not_to be_persisted
+ end
- expect(source.users.reload).to include(user)
- expect(source.requesters.reload.exists?(user_id: user)).to be_falsy
- end
+ context 'when called with a requester user object' do
+ before do
+ source.request_access(user)
end
- end
- context 'when current_user cannot update member' do
- it 'does not create the member', :aggregate_failures do
+ it 'does not destroy the requester', :aggregate_failures do
expect(source.users).not_to include(user)
+ expect(source.requesters.exists?(user_id: user)).to be_truthy
- member = described_class.new(source, user, :maintainer, current_user: user).execute
+ described_class.new(source, user, :maintainer, current_user: user).execute
expect(source.users.reload).not_to include(user)
- expect(member).not_to be_persisted
+ expect(source.requesters.exists?(user_id: user)).to be_truthy
end
+ end
+ end
- context 'when called with a requester user object' do
- before do
- source.request_access(user)
- end
+ context 'when member already exists' do
+ before do
+ source.add_user(user, :developer)
+ end
- it 'does not destroy the requester', :aggregate_failures do
- expect(source.users).not_to include(user)
- expect(source.requesters.exists?(user_id: user)).to be_truthy
+ context 'with no current_user' do
+ it 'updates the member' do
+ expect(source.users).to include(user)
- described_class.new(source, user, :maintainer, current_user: user).execute
+ described_class.new(source, user, :maintainer).execute
- expect(source.users.reload).not_to include(user)
- expect(source.requesters.exists?(user_id: user)).to be_truthy
- end
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
end
end
- context 'when member already exists' do
- before do
- source.add_user(user, :developer)
- end
-
- context 'with no current_user' 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.new(source, user, :maintainer).execute
+ described_class.new(source, user, :maintainer, current_user: admin).execute
- expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
- end
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
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 cannot update member' do
+ it 'does not update the member' do
+ expect(source.users).to include(user)
- described_class.new(source, user, :maintainer, current_user: admin).execute
+ described_class.new(source, user, :maintainer, current_user: user).execute
- expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
- end
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
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 `tasks_to_be_done` and `tasks_project_id` are passed' do
+ let(:task_project) { source.is_a?(Group) ? create(:project, group: source) : source }
- described_class.new(source, user, :maintainer, current_user: user).execute
+ it 'creates a member_task with the correct attributes', :aggregate_failures do
+ described_class.new(source, user, :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id).execute
- expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
- end
- end
- end
+ member = source.members.last
- context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
- let(:task_project) { source.is_a?(Group) ? create(:project, group: source) : source }
+ expect(member.tasks_to_be_done).to match_array([:ci, :code])
+ expect(member.member_task.project).to eq(task_project)
+ end
- it 'creates a member_task with the correct attributes', :aggregate_failures do
- described_class.new(source, user, :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id).execute
+ context 'with an already existing member' do
+ before do
+ source.add_user(user, :developer)
+ end
- member = source.members.last
+ it 'does not update tasks to be done if tasks already exist', :aggregate_failures do
+ member = source.members.find_by(user_id: user.id)
+ create(:member_task, member: member, project: task_project, tasks_to_be_done: %w(code ci))
- expect(member.tasks_to_be_done).to match_array([:ci, :code])
+ expect do
+ described_class.new(source,
+ user,
+ :developer,
+ tasks_to_be_done: %w(issues),
+ tasks_project_id: task_project.id).execute
+ end.not_to change(MemberTask, :count)
+
+ member.reset
+ expect(member.tasks_to_be_done).to match_array([:code, :ci])
expect(member.member_task.project).to eq(task_project)
end
- context 'with an already existing member' do
- before do
- source.add_user(user, :developer)
- end
-
- it 'does not update tasks to be done if tasks already exist', :aggregate_failures do
- member = source.members.find_by(user_id: user.id)
- create(:member_task, member: member, project: task_project, tasks_to_be_done: %w(code ci))
-
- expect do
- described_class.new(source,
- user,
- :developer,
- tasks_to_be_done: %w(issues),
- tasks_project_id: task_project.id).execute
- end.not_to change(MemberTask, :count)
-
- member.reset
- expect(member.tasks_to_be_done).to match_array([:code, :ci])
- expect(member.member_task.project).to eq(task_project)
- end
-
- it 'adds tasks to be done if they do not exist', :aggregate_failures do
- expect do
- described_class.new(source,
- user,
- :developer,
- tasks_to_be_done: %w(issues),
- tasks_project_id: task_project.id).execute
- end.to change(MemberTask, :count).by(1)
-
- member = source.members.find_by(user_id: user.id)
- expect(member.tasks_to_be_done).to match_array([:issues])
- expect(member.member_task.project).to eq(task_project)
- end
+ it 'adds tasks to be done if they do not exist', :aggregate_failures do
+ expect do
+ described_class.new(source,
+ user,
+ :developer,
+ tasks_to_be_done: %w(issues),
+ tasks_project_id: task_project.id).execute
+ end.to change(MemberTask, :count).by(1)
+
+ member = source.members.find_by(user_id: user.id)
+ expect(member.tasks_to_be_done).to match_array([:issues])
+ expect(member.member_task.project).to eq(task_project)
end
end
end
diff --git a/spec/support/shared_examples/models/reviewer_state_shared_examples.rb b/spec/support/shared_examples/models/reviewer_state_shared_examples.rb
deleted file mode 100644
index f1392768b06..00000000000
--- a/spec/support/shared_examples/models/reviewer_state_shared_examples.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'having reviewer state' do
- describe 'mr_attention_requests feature flag is disabled' do
- before do
- stub_feature_flags(mr_attention_requests: false)
- end
-
- it { is_expected.to have_attributes(state: 'unreviewed') }
- end
-
- describe 'mr_attention_requests feature flag is enabled' do
- it { is_expected.to have_attributes(state: 'attention_requested') }
- end
-end
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index 03e9dd65e33..6f17231a040 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -392,41 +392,161 @@ RSpec.shared_examples 'wiki model' do
end
describe '#create_page' do
- it 'creates a new wiki page' do
- expect(subject.create_page('test page', 'this is content')).not_to eq(false)
- expect(subject.list_pages.count).to eq(1)
- end
+ shared_examples 'create_page tests' do
+ it 'creates a new wiki page' do
+ expect(subject.create_page('test page', 'this is content')).not_to eq(false)
+ expect(subject.list_pages.count).to eq(1)
+ end
- it 'returns false when a duplicate page exists' do
- subject.create_page('test page', 'content')
+ it 'returns false when a duplicate page exists' do
+ subject.create_page('test page', 'content')
- expect(subject.create_page('test page', 'content')).to eq(false)
- end
+ expect(subject.create_page('test page', 'content')).to eq(false)
+ end
- it 'stores an error message when a duplicate page exists' do
- 2.times { subject.create_page('test page', 'content') }
+ it 'stores an error message when a duplicate page exists' do
+ 2.times { subject.create_page('test page', 'content') }
- expect(subject.error_message).to match(/Duplicate page:/)
- end
+ expect(subject.error_message).to match(/Duplicate page:/)
+ end
+
+ it 'sets the correct commit message' do
+ subject.create_page('test page', 'some content', :markdown, 'commit message')
+
+ expect(subject.list_pages.first.page.version.message).to eq('commit message')
+ end
+
+ it 'sets the correct commit email' do
+ subject.create_page('test page', 'content')
+
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ end
+
+ it 'runs after_wiki_activity callbacks' do
+ expect(subject).to receive(:after_wiki_activity)
- it 'sets the correct commit message' do
- subject.create_page('test page', 'some content', :markdown, 'commit message')
+ subject.create_page('Test Page', 'This is content')
+ end
+
+ it 'cannot create two pages with the same title but different format' do
+ subject.create_page('test page', 'content', :markdown)
+ subject.create_page('test page', 'content', :rdoc)
+
+ expect(subject.error_message).to match(/Duplicate page:/)
+ end
+
+ it 'cannot create two pages with the same title but different capitalization' do
+ subject.create_page('test page', 'content')
+ subject.create_page('Test page', 'content')
+
+ expect(subject.error_message).to match(/Duplicate page:/)
+ end
- expect(subject.list_pages.first.page.version.message).to eq('commit message')
+ it 'cannot create two pages with the same title, different capitalization, and different format' do
+ subject.create_page('test page', 'content')
+ subject.create_page('Test page', 'content', :rdoc)
+
+ expect(subject.error_message).to match(/Duplicate page:/)
+ end
end
- it 'sets the correct commit email' do
- subject.create_page('test page', 'content')
+ it_behaves_like 'create_page tests' do
+ it 'returns false if a page exists already in the repository', :aggregate_failures do
+ subject.create_page('test page', 'content')
- expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
- expect(commit.committer_email).to eq(user.commit_email)
+ allow(subject).to receive(:file_exists_by_regex?).and_return(false)
+
+ expect(subject.create_page('test page', 'content')).to eq false
+ expect(subject.error_message).to match(/Duplicate page:/)
+ end
+
+ it 'returns false if it has an invalid format', :aggregate_failures do
+ expect(subject.create_page('test page', 'content', :foobar)).to eq false
+ expect(subject.error_message).to match(/Invalid format selected/)
+ end
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:new_file, :format, :existing_repo_files, :success) do
+ 'foo' | :markdown | [] | true
+ 'foo' | :rdoc | [] | true
+ 'foo' | :asciidoc | [] | true
+ 'foo' | :org | [] | true
+ 'foo' | :textile | [] | false
+ 'foo' | :creole | [] | false
+ 'foo' | :rest | [] | false
+ 'foo' | :mediawiki | [] | false
+ 'foo' | :pod | [] | false
+ 'foo' | :plaintext | [] | false
+ 'foo' | :markdown | ['foo.md'] | false
+ 'foo' | :markdown | ['foO.md'] | false
+ 'foO' | :markdown | ['foo.md'] | false
+ 'foo' | :markdown | ['foo.mdfoo'] | true
+ 'foo' | :markdown | ['foo.markdown'] | false
+ 'foo' | :markdown | ['foo.mkd'] | false
+ 'foo' | :markdown | ['foo.mkdn'] | false
+ 'foo' | :markdown | ['foo.mdown'] | false
+ 'foo' | :markdown | ['foo.adoc'] | false
+ 'foo' | :markdown | ['foo.asciidoc'] | false
+ 'foo' | :markdown | ['foo.org'] | false
+ 'foo' | :markdown | ['foo.rdoc'] | false
+ 'foo' | :markdown | ['foo.textile'] | false
+ 'foo' | :markdown | ['foo.creole'] | false
+ 'foo' | :markdown | ['foo.rest'] | false
+ 'foo' | :markdown | ['foo.rest.txt'] | false
+ 'foo' | :markdown | ['foo.rst'] | false
+ 'foo' | :markdown | ['foo.rst.txt'] | false
+ 'foo' | :markdown | ['foo.rst.txtfoo'] | true
+ 'foo' | :markdown | ['foo.mediawiki'] | false
+ 'foo' | :markdown | ['foo.wiki'] | false
+ 'foo' | :markdown | ['foo.pod'] | false
+ 'foo' | :markdown | ['foo.txt'] | false
+ 'foo' | :markdown | ['foo.Md'] | false
+ 'foo' | :markdown | ['foo.jpg'] | true
+ 'foo' | :rdoc | ['foo.md'] | false
+ 'foo' | :rdoc | ['foO.md'] | false
+ 'foO' | :rdoc | ['foo.md'] | false
+ 'foo' | :asciidoc | ['foo.md'] | false
+ 'foo' | :org | ['foo.md'] | false
+ 'foo' | :markdown | ['dir/foo.md'] | true
+ '/foo' | :markdown | ['foo.md'] | false
+ './foo' | :markdown | ['foo.md'] | false
+ '../foo' | :markdown | ['foo.md'] | false
+ '../../foo' | :markdown | ['foo.md'] | false
+ '../../foo' | :markdown | ['dir/foo.md'] | true
+ 'dir/foo' | :markdown | ['foo.md'] | true
+ 'dir/foo' | :markdown | ['dir/foo.md'] | false
+ 'dir/foo' | :markdown | ['dir/foo.rdoc'] | false
+ '/dir/foo' | :markdown | ['dir/foo.rdoc'] | false
+ './dir/foo' | :markdown | ['dir/foo.rdoc'] | false
+ '../dir/foo' | :markdown | ['dir/foo.rdoc'] | false
+ '../dir/../foo' | :markdown | ['dir/foo.rdoc'] | true
+ '../dir/../foo' | :markdown | ['foo.rdoc'] | false
+ '../dir/../dir/foo' | :markdown | ['dir/foo.rdoc'] | false
+ '../dir/../another/foo' | :markdown | ['dir/foo.rdoc'] | true
+ 'another/dir/foo' | :markdown | ['dir/foo.md'] | true
+ 'foo bar' | :markdown | ['foo-bar.md'] | false
+ 'foo bar' | :markdown | ['foo-bar.md'] | true
+ 'föö'.encode('ISO-8859-1') | :markdown | ['f��.md'] | false
+ end
+
+ with_them do
+ specify do
+ allow(subject.repository).to receive(:ls_files).and_return(existing_repo_files)
+
+ expect(subject.create_page(new_file, 'content', format)).to eq success
+ end
+ end
end
- it 'runs after_wiki_activity callbacks' do
- expect(subject).to receive(:after_wiki_activity)
+ context 'when feature flag :gitaly_replace_wiki_create_page is disabled' do
+ before do
+ stub_feature_flags(gitaly_replace_wiki_create_page: false)
+ end
- subject.create_page('Test Page', 'This is content')
+ it_behaves_like 'create_page tests'
end
end
@@ -452,7 +572,7 @@ RSpec.shared_examples 'wiki model' do
expect(subject).to receive(:after_wiki_activity)
expect(update_page).to eq true
- page = subject.find_page(updated_title.presence || original_title)
+ page = subject.find_page(expected_title)
expect(page.raw_content).to eq(updated_content)
expect(page.path).to eq(expected_path)
@@ -467,23 +587,25 @@ RSpec.shared_examples 'wiki model' do
shared_context 'common examples' do
using RSpec::Parameterized::TableSyntax
- where(:original_title, :original_format, :updated_title, :updated_format, :expected_path) do
- 'test page' | :markdown | 'new test page' | :markdown | 'new-test-page.md'
- 'test page' | :markdown | 'test page' | :markdown | 'test-page.md'
- 'test page' | :markdown | 'test page' | :asciidoc | 'test-page.asciidoc'
+ where(:original_title, :original_format, :updated_title, :updated_format, :expected_title, :expected_path) do
+ 'test page' | :markdown | 'new test page' | :markdown | 'new test page' | 'new-test-page.md'
+ 'test page' | :markdown | 'test page' | :markdown | 'test page' | 'test-page.md'
+ 'test page' | :markdown | 'test page' | :asciidoc | 'test page' | 'test-page.asciidoc'
+
+ 'test page' | :markdown | 'new dir/new test page' | :markdown | 'new dir/new test page' | 'new-dir/new-test-page.md'
+ 'test page' | :markdown | 'new dir/test page' | :markdown | 'new dir/test page' | 'new-dir/test-page.md'
- 'test page' | :markdown | 'new dir/new test page' | :markdown | 'new-dir/new-test-page.md'
- 'test page' | :markdown | 'new dir/test page' | :markdown | 'new-dir/test-page.md'
+ 'test dir/test page' | :markdown | 'new dir/new test page' | :markdown | 'new dir/new test page' | 'new-dir/new-test-page.md'
+ 'test dir/test page' | :markdown | 'test dir/test page' | :markdown | 'test dir/test page' | 'test-dir/test-page.md'
+ 'test dir/test page' | :markdown | 'test dir/test page' | :asciidoc | 'test dir/test page' | 'test-dir/test-page.asciidoc'
- 'test dir/test page' | :markdown | 'new dir/new test page' | :markdown | 'new-dir/new-test-page.md'
- 'test dir/test page' | :markdown | 'test dir/test page' | :markdown | 'test-dir/test-page.md'
- 'test dir/test page' | :markdown | 'test dir/test page' | :asciidoc | 'test-dir/test-page.asciidoc'
+ 'test dir/test page' | :markdown | 'new test page' | :markdown | 'new test page' | 'new-test-page.md'
+ 'test dir/test page' | :markdown | 'test page' | :markdown | 'test page' | 'test-page.md'
- 'test dir/test page' | :markdown | 'new test page' | :markdown | 'new-test-page.md'
- 'test dir/test page' | :markdown | 'test page' | :markdown | 'test-page.md'
+ 'test page' | :markdown | nil | :markdown | 'test page' | 'test-page.md'
+ 'test.page' | :markdown | nil | :markdown | 'test.page' | 'test.page.md'
- 'test page' | :markdown | nil | :markdown | 'test-page.md'
- 'test.page' | :markdown | nil | :markdown | 'test.page.md'
+ 'testpage' | :markdown | './testpage' | :markdown | 'testpage' | 'testpage.md'
end
end
@@ -497,16 +619,23 @@ RSpec.shared_examples 'wiki model' do
shared_context 'extended examples' do
using RSpec::Parameterized::TableSyntax
- where(:original_title, :original_format, :updated_title, :updated_format, :expected_path) do
- 'test page' | :markdown | 'new test page' | :asciidoc | 'new-test-page.asciidoc'
- 'test page' | :markdown | 'new dir/new test page' | :asciidoc | 'new-dir/new-test-page.asciidoc'
- 'test dir/test page' | :markdown | 'new dir/new test page' | :asciidoc | 'new-dir/new-test-page.asciidoc'
- 'test dir/test page' | :markdown | 'new test page' | :asciidoc | 'new-test-page.asciidoc'
- 'test page' | :markdown | nil | :asciidoc | 'test-page.asciidoc'
- 'test dir/test page' | :markdown | nil | :asciidoc | 'test-dir/test-page.asciidoc'
- 'test dir/test page' | :markdown | nil | :markdown | 'test-dir/test-page.md'
- 'test page' | :markdown | '' | :markdown | 'test-page.md'
- 'test.page' | :markdown | '' | :markdown | 'test.page.md'
+ where(:original_title, :original_format, :updated_title, :updated_format, :expected_title, :expected_path) do
+ 'test page' | :markdown | 'new test page' | :asciidoc | 'new test page' | 'new-test-page.asciidoc'
+ 'test page' | :markdown | 'new dir/new test page' | :asciidoc | 'new dir/new test page' | 'new-dir/new-test-page.asciidoc'
+ 'test dir/test page' | :markdown | 'new dir/new test page' | :asciidoc | 'new dir/new test page' | 'new-dir/new-test-page.asciidoc'
+ 'test dir/test page' | :markdown | 'new test page' | :asciidoc | 'new test page' | 'new-test-page.asciidoc'
+ 'test page' | :markdown | nil | :asciidoc | 'test page' | 'test-page.asciidoc'
+ 'test dir/test page' | :markdown | nil | :asciidoc | 'test dir/test page' | 'test-dir/test-page.asciidoc'
+ 'test dir/test page' | :markdown | nil | :markdown | 'test dir/test page' | 'test-dir/test-page.md'
+ 'test page' | :markdown | '' | :markdown | 'test page' | 'test-page.md'
+ 'test.page' | :markdown | '' | :markdown | 'test.page' | 'test.page.md'
+ 'testpage' | :markdown | '../testpage' | :markdown | 'testpage' | 'testpage.md'
+ 'dir/testpage' | :markdown | 'dir/../testpage' | :markdown | 'testpage' | 'testpage.md'
+ 'dir/testpage' | :markdown | './dir/testpage' | :markdown | 'dir/testpage' | 'dir/testpage.md'
+ 'dir/testpage' | :markdown | '../dir/testpage' | :markdown | 'dir/testpage' | 'dir/testpage.md'
+ 'dir/testpage' | :markdown | '../dir/../testpage' | :markdown | 'testpage' | 'testpage.md'
+ 'dir/testpage' | :markdown | '../dir/../dir/testpage' | :markdown | 'dir/testpage' | 'dir/testpage.md'
+ 'dir/testpage' | :markdown | '../dir/../another/testpage' | :markdown | 'another/testpage' | 'another/testpage.md'
end
end
@@ -547,16 +676,6 @@ RSpec.shared_examples 'wiki model' do
end
end
end
-
- context 'when feature flag :gitaly_replace_wiki_update_page is disabled' do
- before do
- stub_feature_flags(gitaly_replace_wiki_update_page: false)
- end
-
- it_behaves_like 'update_page tests' do
- include_context 'common examples'
- end
- end
end
describe '#delete_page' do
diff --git a/spec/support/shared_examples/nav_sidebar_shared_examples.rb b/spec/support/shared_examples/nav_sidebar_shared_examples.rb
index 3e500683712..4b815988bc5 100644
--- a/spec/support/shared_examples/nav_sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/nav_sidebar_shared_examples.rb
@@ -27,7 +27,7 @@ end
RSpec.shared_examples 'sidebar includes snowplow attributes' do |track_action, track_label, track_property|
specify do
- allow(view).to receive(:tracking_enabled?).and_return(true)
+ stub_application_setting(snowplow_enabled: true)
render
diff --git a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb
index c1eccafa987..f5c41416763 100644
--- a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb
@@ -21,6 +21,7 @@ RSpec.shared_examples 'returns repositories for allowed users' do |user_type, sc
expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
root_repository.id, test_repository.id)
expect(response.body).not_to include('tags')
+ expect(response.body).not_to include('tags_count')
end
it 'returns a matching schema' do
@@ -29,7 +30,11 @@ RSpec.shared_examples 'returns repositories for allowed users' do |user_type, sc
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('registry/repositories')
end
+ end
+end
+RSpec.shared_examples 'returns tags for allowed users' do |user_type, scope|
+ context "for #{user_type}" do
context 'with tags param' do
let(:url) { "/#{scope}s/#{object.id}/registry/repositories?tags=true" }
@@ -169,10 +174,12 @@ RSpec.shared_examples 'reconciling migration_state' do
end
end
- context 'import_failed response' do
- let(:status) { 'import_failed' }
+ %w[import_canceled import_failed].each do |status|
+ context "#{status} response" do
+ let(:status) { status }
- it_behaves_like 'retrying the import'
+ it_behaves_like 'retrying the import'
+ end
end
context 'pre_import_in_progress response' do
@@ -192,17 +199,11 @@ RSpec.shared_examples 'reconciling migration_state' do
end
end
- context 'pre_import_failed response' do
- let(:status) { 'pre_import_failed' }
-
- it_behaves_like 'retrying the pre_import'
- end
-
- %w[pre_import_canceled import_canceled].each do |canceled_status|
- context "#{canceled_status} response" do
- let(:status) { canceled_status }
+ %w[pre_import_canceled pre_import_failed].each do |status|
+ context "#{status} response" do
+ let(:status) { status }
- it_behaves_like 'enforcing states coherence to', 'import_skipped'
+ it_behaves_like 'retrying the pre_import'
end
end
end
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 da9d254039b..e534a02e562 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
@@ -67,11 +67,15 @@ RSpec.shared_examples 'group and project boards query' do
let(:sort_param) { }
let(:first_param) { 2 }
+ def pagination_results_data(nodes)
+ nodes
+ end
+
let(:all_records) do
if board_parent.multiple_issue_boards_available?
- boards.map { |board| global_id_of(board) }
+ boards.map { |board| a_graphql_entity_for(board) }
else
- [global_id_of(boards.first)]
+ [a_graphql_entity_for(boards.first)]
end
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb
index 7e1f4500779..9033a8b4d3a 100644
--- a/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb
@@ -12,9 +12,9 @@ RSpec.shared_examples 'a noteable graphql type we can query' do
def expected
noteable.discussions.map do |discussion|
- include(
- 'id' => global_id_of(discussion),
- 'replyId' => global_id_of(discussion, id: discussion.reply_id),
+ a_graphql_entity_for(
+ discussion,
+ 'replyId' => global_id_of(discussion, id: discussion.reply_id).to_s,
'createdAt' => discussion.created_at.iso8601,
'notes' => include(
'nodes' => have_attributes(size: discussion.notes.size)
@@ -50,8 +50,8 @@ RSpec.shared_examples 'a noteable graphql type we can query' do
post_graphql(query(fields), current_user: current_user)
- data = graphql_data_at(*path_to_noteable, :discussions, :nodes, :noteable, :id)
- expect(data[0]).to eq(global_id_of(noteable))
+ entities = graphql_data_at(*path_to_noteable, :discussions, :nodes, :noteable)
+ expect(entities).to all(match(a_graphql_entity_for(noteable)))
end
end
@@ -62,10 +62,10 @@ RSpec.shared_examples 'a noteable graphql type we can query' do
def expected
noteable.notes.map do |note|
- include(
- 'id' => global_id_of(note),
- 'project' => include('id' => global_id_of(project)),
- 'author' => include('id' => global_id_of(note.author)),
+ a_graphql_entity_for(
+ note,
+ 'project' => a_graphql_entity_for(project),
+ 'author' => a_graphql_entity_for(note.author),
'createdAt' => note.created_at.iso8601,
'body' => eq(note.note)
)
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 127b1a6d4c4..9f7ec6e90e9 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
@@ -104,7 +104,7 @@ RSpec.shared_examples 'group and project packages query' do
}
end
- let(:expected_packages) { sorted_packages.map { |package| global_id_of(package) } }
+ let(:expected_packages) { sorted_packages.map { |package| global_id_of(package).to_s } }
let(:data_path) { [resource_type, :packages] }
@@ -191,4 +191,91 @@ RSpec.shared_examples 'group and project packages query' do
it { is_expected.to include({ "name" => versionless_package.name }) }
end
end
+
+ context 'when reading pipelines' do
+ let(:npm_pipelines) { create_list(:ci_pipeline, 6, project: project1) }
+ let(:npm_pipeline_gids) { npm_pipelines.sort_by(&:id).map(&:to_gid).map(&:to_s).reverse }
+ let(:composer_pipelines) { create_list(:ci_pipeline, 6, project: project2) }
+ let(:composer_pipeline_gids) { composer_pipelines.sort_by(&:id).map(&:to_gid).map(&:to_s).reverse }
+ let(:npm_end_cursor) { graphql_data_npm_package.dig('pipelines', 'pageInfo', 'endCursor') }
+ let(:npm_start_cursor) { graphql_data_npm_package.dig('pipelines', 'pageInfo', 'startCursor') }
+ let(:pipelines_nodes) do
+ <<~QUERY
+ nodes {
+ id
+ }
+ pageInfo {
+ startCursor
+ endCursor
+ }
+ QUERY
+ end
+
+ before do
+ resource.add_maintainer(current_user)
+
+ npm_pipelines.each do |pipeline|
+ create(:package_build_info, package: npm_package, pipeline: pipeline)
+ end
+
+ composer_pipelines.each do |pipeline|
+ create(:package_build_info, package: composer_package, pipeline: pipeline)
+ end
+ end
+
+ it 'loads the second page with pagination first correctly' do
+ run_query(first: 2)
+ expect(npm_pipeline_ids).to eq(npm_pipeline_gids[0..1])
+ expect(composer_pipeline_ids).to eq(composer_pipeline_gids[0..1])
+
+ run_query(first: 2, after: npm_end_cursor)
+ expect(npm_pipeline_ids).to eq(npm_pipeline_gids[2..3])
+ expect(composer_pipeline_ids).to be_empty
+ end
+
+ it 'loads the second page with pagination last correctly' do
+ run_query(last: 2)
+ expect(npm_pipeline_ids).to eq(npm_pipeline_gids[4..5])
+ expect(composer_pipeline_ids).to eq(composer_pipeline_gids[4..5])
+
+ run_query(last: 2, before: npm_start_cursor)
+ expect(npm_pipeline_ids).to eq(npm_pipeline_gids[2..3])
+ expect(composer_pipeline_ids).to eq(composer_pipeline_gids[4..5])
+ end
+
+ def run_query(args)
+ pipelines_field = query_graphql_field('pipelines', args, pipelines_nodes)
+
+ packages_nodes = <<~QUERY
+ nodes {
+ id
+ #{pipelines_field}
+ }
+ QUERY
+
+ query = graphql_query_for(
+ resource_type,
+ { 'fullPath' => resource.full_path },
+ query_graphql_field('packages', {}, packages_nodes)
+ )
+
+ post_graphql(query, current_user: current_user)
+ end
+
+ def npm_pipeline_ids
+ graphql_data_npm_package.dig('pipelines', 'nodes').map { |pipeline| pipeline['id'] }
+ end
+
+ def composer_pipeline_ids
+ graphql_data_composer_package.dig('pipelines', 'nodes').map { |pipeline| pipeline['id'] }
+ end
+
+ def graphql_data_npm_package
+ graphql_data_at(resource_type, :packages, :nodes).find { |pkg| pkg['id'] == npm_package.to_gid.to_s }
+ end
+
+ def graphql_data_composer_package
+ graphql_data_at(resource_type, :packages, :nodes).find { |pkg| pkg['id'] == composer_package.to_gid.to_s }
+ end
+ end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb
index ab93f54111b..b4019d7c232 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb
@@ -28,14 +28,10 @@ RSpec.shared_examples 'a package with files' do
end
it 'has the basic package files data' do
- expect(first_file_response).to include(
- 'id' => global_id_of(first_file),
- 'fileName' => first_file.file_name,
- 'size' => first_file.size.to_s,
- 'downloadPath' => first_file.download_path,
- 'fileSha1' => first_file.file_sha1,
- 'fileMd5' => first_file.file_md5,
- 'fileSha256' => first_file.file_sha256
+ expect(first_file_response).to match a_graphql_entity_for(
+ first_file,
+ :file_name, :download_path, :file_sha1, :file_md5, :file_sha256,
+ 'size' => first_file.size.to_s
)
end
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb
index c134f7d1839..3c5f25baaa1 100644
--- a/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb
@@ -30,14 +30,12 @@ RSpec.shared_examples 'GraphQL query with several integrations requested' do |gr
it 'returns the correct properties of the integrations', :aggregate_failures do
post_graphql(multi_selection_query, current_user: current_user)
- expect(graphql_data.dig('project', 'ai', 'nodes')).to include(
- 'id' => global_id_of(active_http_integration),
- 'name' => active_http_integration.name
+ expect(graphql_data.dig('project', 'ai', 'nodes')).to match a_graphql_entity_for(
+ active_http_integration, :name
)
- expect(graphql_data.dig('project', 'ii', 'nodes')).to include(
- 'id' => global_id_of(inactive_http_integration),
- 'name' => inactive_http_integration.name
+ expect(graphql_data.dig('project', 'ii', 'nodes')).to match a_graphql_entity_for(
+ inactive_http_integration, :name
)
end
diff --git a/spec/support/shared_examples/requests/api/milestones_shared_examples.rb b/spec/support/shared_examples/requests/api/milestones_shared_examples.rb
index 249a7b7cdac..1ea11ba3d7c 100644
--- a/spec/support/shared_examples/requests/api/milestones_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/milestones_shared_examples.rb
@@ -203,16 +203,16 @@ RSpec.shared_examples 'group and project milestones' do |route_definition|
end
describe "DELETE #{route_definition}/:milestone_id" do
- it "rejects a member with reporter access from deleting a milestone" do
- reporter = create(:user)
- milestone.resource_parent.add_reporter(reporter)
+ it "rejects a member with guest access from deleting a milestone" do
+ guest = create(:user)
+ milestone.resource_parent.add_guest(guest)
- delete api(resource_route, reporter)
+ delete api(resource_route, guest)
expect(response).to have_gitlab_http_status(:forbidden)
end
- it 'deletes the milestone when the user has developer access to the project' do
+ it 'deletes the milestone when the user has reporter access to the project' do
delete api(resource_route, user)
expect(project.milestones.find_by_id(milestone.id)).to be_nil
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
index 68cb91d7414..d4417b23a5f 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -149,6 +149,7 @@ RSpec.shared_examples 'rate-limited token requests' do
arguments = a_hash_including({
message: 'Rack_Attack',
+ status: 429,
env: :throttle,
remote_ip: '127.0.0.1',
request_method: request_method,
@@ -314,6 +315,7 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
arguments = a_hash_including({
message: 'Rack_Attack',
+ status: 429,
env: :throttle,
remote_ip: '127.0.0.1',
request_method: request_method,
@@ -391,14 +393,16 @@ RSpec.shared_examples 'tracking when dry-run mode is set' do
end
it 'logs RackAttack info into structured logs' do
- arguments = a_hash_including({
- message: 'Rack_Attack',
- env: :track,
- remote_ip: '127.0.0.1',
- matched: throttle_name
- })
+ expect(Gitlab::AuthLogger).to receive(:error) do |arguments|
+ expect(arguments).to include(
+ message: 'Rack_Attack',
+ env: :track,
+ remote_ip: '127.0.0.1',
+ matched: throttle_name
+ )
- expect(Gitlab::AuthLogger).to receive(:error).with(arguments)
+ expect(arguments).not_to have_key(:status)
+ end
(1 + requests_per_period).times do
do_request
@@ -576,6 +580,7 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do
arguments = a_hash_including({
message: 'Rack_Attack',
+ status: 429,
env: :throttle,
remote_ip: '127.0.0.1',
request_method: 'GET',
diff --git a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
index fcd52cdf7fa..e1baa594f3c 100644
--- a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
+++ b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-RSpec.shared_examples 'avoid N+1 on environments serialization' do |ee: false|
+RSpec.shared_examples 'avoid N+1 on environments serialization' do
it 'avoids N+1 database queries with grouping', :request_store do
create_environment_with_associations(project)
diff --git a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
index e776c098fa0..31571b1ffb9 100644
--- a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
@@ -1,21 +1,33 @@
# frozen_string_literal: true
-shared_examples_for 'service deleting todos' do
+shared_examples_for 'service scheduling async deletes' do
it 'destroys associated todos asynchronously' do
- expect(TodosDestroyer::DestroyedIssuableWorker)
+ expect(worker_class)
.to receive(:perform_async)
.with(issuable.id, issuable.class.name)
subject.execute(issuable)
end
-end
-shared_examples_for 'service deleting label links' do
- it 'destroys associated label links asynchronously' do
- expect(Issuable::LabelLinksDestroyWorker)
+ it 'works inside a transaction' do
+ expect(worker_class)
.to receive(:perform_async)
.with(issuable.id, issuable.class.name)
- subject.execute(issuable)
+ ApplicationRecord.transaction do
+ subject.execute(issuable)
+ end
+ end
+end
+
+shared_examples_for 'service deleting todos' do
+ it_behaves_like 'service scheduling async deletes' do
+ let(:worker_class) { TodosDestroyer::DestroyedIssuableWorker }
+ end
+end
+
+shared_examples_for 'service deleting label links' do
+ it_behaves_like 'service scheduling async deletes' do
+ let(:worker_class) { Issuable::LabelLinksDestroyWorker }
end
end
diff --git a/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb b/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
index c4f6273b46c..5e49bdd706c 100644
--- a/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
+++ b/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
@@ -66,18 +66,12 @@ RSpec.shared_examples 'a service that handles Jira API errors' do
it 'logs the error' do
stub_client_and_raise(Timeout::Error, 'foo')
- expect(Gitlab::ProjectServiceLogger).to receive(:error).with(
- hash_including(
- client_url: be_present,
- message: 'Error sending message',
- service_class: described_class.name,
- error: hash_including(
- exception_class: Timeout::Error.name,
- exception_message: 'foo',
- exception_backtrace: be_present
- )
- )
+ expect(jira_integration).to receive(:log_exception).with(
+ kind_of(Timeout::Error),
+ message: 'Error sending message',
+ client_url: jira_integration.url
)
+
expect(subject).to be_error
end
diff --git a/spec/support/shared_examples/work_item_base_types_importer.rb b/spec/support/shared_examples/work_item_base_types_importer.rb
index 68e37171ea2..593670ac4b8 100644
--- a/spec/support/shared_examples/work_item_base_types_importer.rb
+++ b/spec/support/shared_examples/work_item_base_types_importer.rb
@@ -1,10 +1,48 @@
# frozen_string_literal: true
RSpec.shared_examples 'work item base types importer' do
- it 'creates all base work item types' do
- # Fixtures need to run on a pristine DB, but the test suite preloads the base types before(:suite)
+ it "creates all base work item types if they don't exist" do
WorkItems::Type.delete_all
expect { subject }.to change(WorkItems::Type, :count).from(0).to(WorkItems::Type::BASE_TYPES.count)
+
+ types_in_db = WorkItems::Type.all.map { |type| type.slice(:base_type, :icon_name, :name).symbolize_keys }
+ expected_types = WorkItems::Type::BASE_TYPES.map do |type, attributes|
+ attributes.slice(:icon_name, :name).merge(base_type: type.to_s)
+ end
+
+ expect(types_in_db).to match_array(expected_types)
+ expect(WorkItems::Type.all).to all(be_valid)
+ end
+
+ it 'upserts base work item types if they already exist' do
+ first_type = WorkItems::Type.first
+ original_name = first_type.name
+
+ first_type.update!(name: original_name.upcase)
+
+ expect do
+ subject
+ first_type.reload
+ end.to not_change(WorkItems::Type, :count).and(
+ change(first_type, :name).from(original_name.upcase).to(original_name)
+ )
+ end
+
+ it 'executes a single INSERT query' do
+ expect { subject }.to make_queries_matching(/INSERT/, 1)
+ end
+
+ context 'when some base types exist' do
+ before do
+ WorkItems::Type.limit(1).delete_all
+ end
+
+ it 'inserts all types and does nothing if some already existed' do
+ expect { subject }.to make_queries_matching(/INSERT/, 1).and(
+ change(WorkItems::Type, :count).by(1)
+ )
+ expect(WorkItems::Type.count).to eq(WorkItems::Type::BASE_TYPES.count)
+ end
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 26731f34ed6..3d4e840fe2d 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
@@ -205,4 +205,123 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
end
end
+
+ describe 'executing an entire migration', :freeze_time, if: Gitlab::Database.has_config?(tracking_database) do
+ include Gitlab::Database::DynamicModelHelpers
+
+ let(:migration_class) do
+ Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
+ def perform(matching_status)
+ each_sub_batch(
+ operation_name: :update_all,
+ batching_scope: -> (relation) { relation.where(status: matching_status) }
+ ) do |sub_batch|
+ sub_batch.update_all(some_column: 0)
+ end
+ end
+ end
+ end
+
+ let!(:migration) do
+ create(
+ :batched_background_migration,
+ :active,
+ table_name: table_name,
+ column_name: :id,
+ max_value: migration_records,
+ batch_size: batch_size,
+ sub_batch_size: sub_batch_size,
+ job_class_name: 'ExampleDataMigration',
+ job_arguments: [1]
+ )
+ end
+
+ let(:table_name) { 'example_data' }
+ let(:batch_size) { 5 }
+ let(:sub_batch_size) { 2 }
+ let(:number_of_batches) { 10 }
+ let(:migration_records) { batch_size * number_of_batches }
+
+ let(:connection) { Gitlab::Database.database_base_models[tracking_database].connection }
+ let(:example_data) { define_batchable_model(table_name, connection: connection) }
+
+ around do |example|
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ example.run
+ end
+ end
+
+ before do
+ # Create example table populated with test data to migrate.
+ #
+ # Test data should have two records that won't be updated:
+ # - one record beyond the migration's range
+ # - one record that doesn't match the migration job's batch condition
+ connection.execute(<<~SQL)
+ CREATE TABLE #{table_name} (
+ id integer primary key,
+ some_column integer,
+ status smallint);
+
+ INSERT INTO #{table_name} (id, some_column, status)
+ SELECT generate_series, generate_series, 1
+ FROM generate_series(1, #{migration_records + 1});
+
+ UPDATE #{table_name}
+ SET status = 0
+ WHERE some_column = #{migration_records - 5};
+ SQL
+
+ stub_feature_flags(execute_batched_migrations_on_schedule: true)
+
+ stub_const('Gitlab::BackgroundMigration::ExampleDataMigration', migration_class)
+ end
+
+ subject(:full_migration_run) do
+ # process all batches, then do an extra execution to mark the job as finished
+ (number_of_batches + 1).times do
+ described_class.new.perform
+
+ travel_to((migration.interval + described_class::INTERVAL_VARIANCE).seconds.from_now)
+ end
+ end
+
+ it 'marks the migration record as finished' do
+ expect { full_migration_run }.to change { migration.reload.status }.from(1).to(3) # active -> finished
+ end
+
+ it 'creates job records for each processed batch', :aggregate_failures do
+ expect { full_migration_run }.to change { migration.reload.batched_jobs.count }.from(0)
+
+ final_min_value = migration.batched_jobs.reduce(1) do |next_min_value, batched_job|
+ expect(batched_job.min_value).to eq(next_min_value)
+
+ batched_job.max_value + 1
+ end
+
+ final_max_value = final_min_value - 1
+ expect(final_max_value).to eq(migration_records)
+ end
+
+ it 'marks all job records as succeeded', :aggregate_failures do
+ expect { full_migration_run }.to change { migration.reload.batched_jobs.count }.from(0)
+
+ expect(migration.batched_jobs).to all(be_succeeded)
+ end
+
+ it 'updates matching records in the range', :aggregate_failures do
+ expect { full_migration_run }
+ .to change { example_data.where('status = 1 AND some_column <> 0').count }
+ .from(migration_records).to(1)
+
+ record_outside_range = example_data.last
+
+ expect(record_outside_range.status).to eq(1)
+ expect(record_outside_range.some_column).not_to eq(0)
+ end
+
+ 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
+ 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 4751d91efde..77c4a3431e2 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
@@ -202,6 +202,8 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
before do
expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
+
+ statistics_keys.delete(:repository_size)
end
it_behaves_like 'it calls Gitaly'
diff --git a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
index d9105981b4b..2741b2a9de7 100644
--- a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
+++ b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
@@ -19,7 +19,13 @@ RSpec.shared_examples 'reenqueuer' do
describe '#perform' do
it 'tries to obtain a lease' do
- expect_to_obtain_exclusive_lease(subject.lease_key)
+ lease_key = if subject.respond_to?(:set_custom_lease_key)
+ subject.set_custom_lease_key(*job_args)
+ else
+ subject.lease_key
+ end
+
+ expect_to_obtain_exclusive_lease(lease_key)
subject_perform
end