summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/controllers/unique_visits_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/features/dependency_proxy_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb4
-rw-r--r--spec/support/shared_examples/features/manage_applications_shared_examples.rb67
-rw-r--r--spec/support/shared_examples/features/packages_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb6
-rw-r--r--spec/support/shared_examples/features/rss_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/finders/security/jobs_finder_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/reports/security/locations/locations_shared_examples.rb68
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/migration_helpers_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/lib/menus_shared_examples.rb (renamed from spec/support/shared_examples/helpers/groups_shared_examples.rb)28
-rw-r--r--spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb164
-rw-r--r--spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/models/concerns/incident_management/escalatable_shared_examples.rb246
-rw-r--r--spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb62
-rw-r--r--spec/support/shared_examples/models/mentionable_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/models/update_project_statistics_shared_examples.rb110
-rw-r--r--spec/support/shared_examples/namespaces/linear_traversal_examples.rb23
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb86
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb68
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb178
-rw-r--r--spec/support/shared_examples/services/jira/requests/base_shared_examples.rb85
-rw-r--r--spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb398
41 files changed, 1434 insertions, 505 deletions
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
index 9c8006ce4f1..cadc753513d 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -109,6 +109,18 @@ RSpec.shared_examples 'multiple issue boards' do
assert_boards_nav_active
end
+
+ it 'switches current board back' do
+ in_boards_switcher_dropdown do
+ click_link board.name
+ end
+
+ wait_for_requests
+
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board.name)
+ end
+ end
end
context 'unauthorized user' do
diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
index 422282da4d8..a9c6da7bc2b 100644
--- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
@@ -80,7 +80,6 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
expect(json_response.dig("provider_repos", 0, "id")).to eq(repo.id)
expect(json_response.dig("provider_repos", 1, "id")).to eq(org_repo.id)
- expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
end
it "does not show already added project" do
@@ -156,7 +155,6 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
expect(json_response.dig("imported_projects").count).to eq(0)
expect(json_response.dig("provider_repos").count).to eq(1)
expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_2.id)
- expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
end
it 'filters the list, ignoring the case of the name' do
@@ -166,7 +164,6 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
expect(json_response.dig("imported_projects").count).to eq(0)
expect(json_response.dig("provider_repos").count).to eq(1)
expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_2.id)
- expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
end
context 'when user input contains html' do
diff --git a/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
index ecb9abc5c46..b9ae0e23e26 100644
--- a/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
@@ -18,7 +18,6 @@ RSpec.shared_examples 'import controller status' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id)
- expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
end
it "does not show already added project" do
diff --git a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
index 3f97c031e27..30914e61df0 100644
--- a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'tracking unique visits' do |method|
+ include TrackingHelpers
+
let(:request_params) { {} }
it 'tracks unique visit if the format is HTML' do
@@ -14,14 +16,15 @@ RSpec.shared_examples 'tracking unique visits' do |method|
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
.to receive(:track_event).with(target_id, values: kind_of(String))
- request.headers['DNT'] = '0'
+ stub_do_not_track('0')
get method, params: request_params, format: :html
end
it 'does not track unique visit if DNT is enabled' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
- request.headers['DNT'] = '1'
+
+ stub_do_not_track('1')
get method, params: request_params, format: :html
end
diff --git a/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb b/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb
new file mode 100644
index 00000000000..d29c677a962
--- /dev/null
+++ b/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a successful blob pull' do
+ it 'sends a file' do
+ expect(controller).to receive(:send_file).with(blob.file.path, {})
+
+ subject
+ end
+
+ it 'returns Content-Disposition: attachment', :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['Content-Disposition']).to match(/^attachment/)
+ end
+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)
+
+ subject
+ end
+
+ it 'returns Content-Disposition: attachment', :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['Docker-Content-Digest']).to eq(manifest.digest)
+ expect(response.headers['Content-Length']).to eq(manifest.size)
+ expect(response.headers['Docker-Distribution-Api-Version']).to eq(DependencyProxy::DISTRIBUTION_API_VERSION)
+ expect(response.headers['Etag']).to eq("\"#{manifest.digest}\"")
+ expect(response.headers['Content-Disposition']).to match(/^attachment/)
+ end
+end
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index ff2878f77b4..fb2e422559d 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -308,7 +308,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] }
it 'can be replied to after resolving' do
- find('button[data-qa-selector="resolve_discussion_button"]').click
+ find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
wait_for_requests
refresh
@@ -320,7 +320,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
it 'shows resolved thread when toggled' do
submit_reply('a')
- find('button[data-qa-selector="resolve_discussion_button"]').click
+ find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
wait_for_requests
expect(page).to have_selector(".note-row-#{note_id}", visible: true)
diff --git a/spec/support/shared_examples/features/manage_applications_shared_examples.rb b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
new file mode 100644
index 00000000000..38bb87eaed2
--- /dev/null
+++ b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'manage applications' do
+ let_it_be(:application_name) { 'application foo bar' }
+ let_it_be(:application_name_changed) { "#{application_name} changed" }
+ let_it_be(:application_redirect_uri) { 'https://foo.bar' }
+
+ it 'allows user to manage applications' do
+ visit new_application_path
+
+ expect(page).to have_content 'Add new application'
+
+ fill_in :doorkeeper_application_name, with: application_name
+ fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri
+ check :doorkeeper_application_scopes_read_user
+ click_on 'Save application'
+
+ validate_application(application_name, 'Yes')
+
+ application = Doorkeeper::Application.find_by(name: application_name)
+ expect(page).to have_css("button[title=\"Copy secret\"][data-clipboard-text=\"#{application.secret}\"]", text: 'Copy')
+
+ click_on 'Edit'
+
+ application_name_changed = "#{application_name} changed"
+
+ fill_in :doorkeeper_application_name, with: application_name_changed
+ uncheck :doorkeeper_application_confidential
+ click_on 'Save application'
+
+ validate_application(application_name_changed, 'No')
+
+ visit_applications_path
+
+ page.within '.oauth-applications' do
+ click_on 'Destroy'
+ end
+ expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
+ end
+
+ context 'when scopes are blank' do
+ it 'returns an error' do
+ visit new_application_path
+
+ expect(page).to have_content 'Add new application'
+
+ fill_in :doorkeeper_application_name, with: application_name
+ fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri
+ click_on 'Save application'
+
+ expect(page).to have_content("Scopes can't be blank")
+ end
+ end
+
+ def visit_applications_path
+ visit defined?(applications_path) ? applications_path : new_application_path
+ end
+
+ def validate_application(name, confidential)
+ aggregate_failures do
+ expect(page).to have_content name
+ expect(page).to have_content 'Application ID'
+ expect(page).to have_content 'Secret'
+ expect(page).to have_content "Confidential #{confidential}"
+ 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 9e88db2e1c0..96be30b9f1f 100644
--- a/spec/support/shared_examples/features/packages_shared_examples.rb
+++ b/spec/support/shared_examples/features/packages_shared_examples.rb
@@ -14,7 +14,7 @@ RSpec.shared_examples 'packages list' do |check_project_name: false|
end
def package_table_row(index)
- page.all("#{packages_table_selector} > [data-qa-selector=\"package_row\"]")[index].text
+ page.all("#{packages_table_selector} > [data-qa-selector=\"package_row\"]")[index].text # rubocop:disable QA/SelectorUsage
end
end
@@ -34,10 +34,8 @@ RSpec.shared_examples 'package details link' do |property|
expect(page).to have_css('.packages-app h1[data-testid="title"]', text: package.name)
- page.within(%Q([name="#{package.name}"])) do
- expect(page).to have_content('Installation')
- expect(page).to have_content('Registry setup')
- end
+ expect(page).to have_content('Installation')
+ expect(page).to have_content('Registry setup')
end
end
@@ -92,7 +90,7 @@ RSpec.shared_examples 'shared package sorting' do
end
def packages_table_selector
- '[data-qa-selector="packages-table"]'
+ '[data-qa-selector="packages-table"]' # rubocop:disable QA/SelectorUsage
end
def click_sort_option(option, ascending)
@@ -100,7 +98,7 @@ def click_sort_option(option, ascending)
# Reset the sort direction
click_button 'Sort direction' if page.has_selector?('svg[aria-label="Sorting Direction: Ascending"]', wait: 0)
- find('button.dropdown-menu-toggle').click
+ find('button.gl-dropdown-toggle').click
page.within('.dropdown-menu') do
click_button option
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
index 56154c7cd03..8212f14d6be 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
@@ -23,6 +23,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
end
click_on_protect
+ wait_for_requests
expect(ProtectedBranch.count).to eq(1)
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to eq([access_type_id])
diff --git a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb
index 28fe198c9c3..14142793a0d 100644
--- a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb
@@ -23,7 +23,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do
find(".js-allowed-to-push").click
wait_for_requests
- within('.qa-allowed-to-push-dropdown') do
+ within('.qa-allowed-to-push-dropdown') do # rubocop:disable QA/SelectorUsage
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*all_dropdown_sections)
@@ -38,7 +38,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do
find(".js-allowed-to-merge").click
wait_for_requests
- within('.qa-allowed-to-merge-dropdown') do
+ within('.qa-allowed-to-merge-dropdown') do # rubocop:disable QA/SelectorUsage
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys)
@@ -68,7 +68,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do
find(".js-allowed-to-push").click
wait_for_requests
- within('.qa-allowed-to-push-dropdown') do
+ within('.qa-allowed-to-push-dropdown') do # rubocop:disable QA/SelectorUsage
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys)
diff --git a/spec/support/shared_examples/features/rss_shared_examples.rb b/spec/support/shared_examples/features/rss_shared_examples.rb
index 1b0d3f9605a..c7c2aeea358 100644
--- a/spec/support/shared_examples/features/rss_shared_examples.rb
+++ b/spec/support/shared_examples/features/rss_shared_examples.rb
@@ -9,7 +9,7 @@ end
RSpec.shared_examples "it has an RSS button with current_user's feed token" do
it "shows the RSS button with current_user's feed token" do
expect(page)
- .to have_css("a:has(.qa-rss-icon)[href*='feed_token=#{user.feed_token}']")
+ .to have_css("a:has(.qa-rss-icon)[href*='feed_token=#{user.feed_token}']") # rubocop:disable QA/SelectorUsage
end
end
@@ -22,6 +22,6 @@ end
RSpec.shared_examples "it has an RSS button without a feed token" do
it "shows the RSS button without a feed token" do
expect(page)
- .to have_css("a:has(.qa-rss-icon):not([href*='feed_token'])")
+ .to have_css("a:has(.qa-rss-icon):not([href*='feed_token'])") # rubocop:disable QA/SelectorUsage
end
end
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index 997500415a9..52451839281 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -91,7 +91,7 @@ RSpec.shared_examples 'variable list' do
end
page.within('#add-ci-variable') do
- find('[data-qa-selector="ci_variable_key_field"] input').set('new_key')
+ find('[data-qa-selector="ci_variable_key_field"] input').set('new_key') # rubocop:disable QA/SelectorUsage
click_button('Update variable')
end
@@ -173,7 +173,7 @@ RSpec.shared_examples 'variable list' do
click_button('Add variable')
page.within('#add-ci-variable') do
- find('[data-qa-selector="ci_variable_key_field"] input').set('empty_mask_key')
+ find('[data-qa-selector="ci_variable_key_field"] input').set('empty_mask_key') # rubocop:disable QA/SelectorUsage
find('[data-testid="ci-variable-protected-checkbox"]').click
find('[data-testid="ci-variable-masked-checkbox"]').click
@@ -286,8 +286,8 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
page.within('#add-ci-variable') do
- find('[data-qa-selector="ci_variable_key_field"] input').set(key)
- find('[data-qa-selector="ci_variable_value_field"]').set(value) if value.present?
+ find('[data-qa-selector="ci_variable_key_field"] input').set(key) # rubocop:disable QA/SelectorUsage
+ find('[data-qa-selector="ci_variable_value_field"]').set(value) if value.present? # rubocop:disable QA/SelectorUsage
find('[data-testid="ci-variable-protected-checkbox"]').click if protected
find('[data-testid="ci-variable-masked-checkbox"]').click if masked
diff --git a/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb
index 3b2fda4e05b..6fdc5ecae73 100644
--- a/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.shared_examples 'User views AsciiDoc page with includes' do
- let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' }
+ let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' } # rubocop:disable QA/SelectorUsage
let!(:included_wiki_page) { create_wiki_page('included_page', content: 'Content from the included page')}
let!(:wiki_page) { create_wiki_page('home', content: "Content from the main page.\ninclude::included_page.asciidoc[]") }
diff --git a/spec/support/shared_examples/finders/security/jobs_finder_shared_examples.rb b/spec/support/shared_examples/finders/security/jobs_finder_shared_examples.rb
index a332b213866..117b35201f6 100644
--- a/spec/support/shared_examples/finders/security/jobs_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/security/jobs_finder_shared_examples.rb
@@ -68,20 +68,6 @@ RSpec.shared_examples ::Security::JobsFinder do |default_job_types|
end
end
- context 'when using legacy CI build metadata config storage' do
- before do
- stub_feature_flags(ci_build_metadata_config: false)
- end
-
- it_behaves_like 'JobsFinder core functionality'
- end
-
- context 'when using the new CI build metadata config storage' do
- before do
- stub_feature_flags(ci_build_metadata_config: true)
- end
-
- it_behaves_like 'JobsFinder core functionality'
- end
+ it_behaves_like 'JobsFinder core functionality'
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/ci/reports/security/locations/locations_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/reports/security/locations/locations_shared_examples.rb
new file mode 100644
index 00000000000..3aa04a77e57
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/ci/reports/security/locations/locations_shared_examples.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'vulnerability location' do
+ describe '#initialize' do
+ subject { described_class.new(**params) }
+
+ context 'when all params are given' do
+ it 'initializes an instance' do
+ expect { subject }.not_to raise_error
+
+ expect(subject).to have_attributes(**params)
+ end
+ end
+
+ where(:param) do
+ mandatory_params
+ end
+
+ with_them do
+ context "when param #{params[:param]} is missing" do
+ before do
+ params.delete(param)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+ end
+ end
+
+ describe '#fingerprint' do
+ subject { described_class.new(**params).fingerprint }
+
+ it "generates expected fingerprint" do
+ expect(subject).to eq(expected_fingerprint)
+ end
+ end
+
+ describe '#fingerprint_path' do
+ subject { described_class.new(**params).fingerprint_path }
+
+ it "generates expected fingerprint" do
+ expect(subject).to eq(expected_fingerprint_path)
+ end
+ end
+
+ describe '#==' do
+ let(:location_1) { create(:ci_reports_security_locations_sast) }
+ let(:location_2) { create(:ci_reports_security_locations_sast) }
+
+ subject { location_1 == location_2 }
+
+ it "returns true when fingerprints are equal" do
+ allow(location_1).to receive(:fingerprint).and_return('fingerprint')
+ allow(location_2).to receive(:fingerprint).and_return('fingerprint')
+
+ expect(subject).to eq(true)
+ end
+
+ it "returns false when fingerprints are different" do
+ allow(location_1).to receive(:fingerprint).and_return('fingerprint')
+ allow(location_2).to receive(:fingerprint).and_return('another_fingerprint')
+
+ expect(subject).to eq(false)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
index 20f3270526e..7888ade56eb 100644
--- a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
@@ -21,3 +21,46 @@ RSpec.shared_examples 'marks background migration job records' do
expect(jobs_updated).to eq(1)
end
end
+
+RSpec.shared_examples 'finalized background migration' do
+ it 'processed the scheduled sidekiq queue' do
+ queued = Sidekiq::ScheduledSet
+ .new
+ .select do |scheduled|
+ scheduled.klass == 'BackgroundMigrationWorker' &&
+ scheduled.args.first == job_class_name
+ end
+ expect(queued.size).to eq(0)
+ end
+
+ it 'processed the async sidekiq queue' do
+ queued = Sidekiq::Queue.new('BackgroundMigrationWorker')
+ .select { |scheduled| scheduled.klass == job_class_name }
+ expect(queued.size).to eq(0)
+ end
+
+ include_examples 'removed tracked jobs', 'pending'
+end
+
+RSpec.shared_examples 'finalized tracked background migration' do
+ include_examples 'finalized background migration'
+ include_examples 'removed tracked jobs', 'succeeded'
+end
+
+RSpec.shared_examples 'removed tracked jobs' do |status|
+ it "removes '#{status}' tracked jobs" do
+ jobs = Gitlab::Database::BackgroundMigrationJob
+ .where(status: Gitlab::Database::BackgroundMigrationJob.statuses[status])
+ .for_migration_class(job_class_name)
+ expect(jobs).to be_empty
+ end
+end
+
+RSpec.shared_examples 'retained tracked jobs' do |status|
+ it "retains '#{status}' tracked jobs" do
+ jobs = Gitlab::Database::BackgroundMigrationJob
+ .where(status: Gitlab::Database::BackgroundMigrationJob.statuses[status])
+ .for_migration_class(job_class_name)
+ expect(jobs).to be_present
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
index 88e6ffd15a8..a617342ff8c 100644
--- a/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
@@ -11,7 +11,7 @@ RSpec.shared_examples 'CTE with MATERIALIZED keyword examples' do
context 'when PG version is <12' do
it 'does not add MATERIALIZE keyword' do
- allow(Gitlab::Database).to receive(:version).and_return('11.1')
+ allow(Gitlab::Database.main).to receive(:version).and_return('11.1')
expect(query).to include(expected_query_block_without_materialized)
end
@@ -19,14 +19,14 @@ RSpec.shared_examples 'CTE with MATERIALIZED keyword examples' do
context 'when PG version is >=12' do
it 'adds MATERIALIZE keyword' do
- allow(Gitlab::Database).to receive(:version).and_return('12.1')
+ allow(Gitlab::Database.main).to receive(:version).and_return('12.1')
expect(query).to include(expected_query_block_with_materialized)
end
context 'when version is higher than 12' do
it 'adds MATERIALIZE keyword' do
- allow(Gitlab::Database).to receive(:version).and_return('15.1')
+ allow(Gitlab::Database.main).to receive(:version).and_return('15.1')
expect(query).to include(expected_query_block_with_materialized)
end
diff --git a/spec/support/shared_examples/lib/gitlab/migration_helpers_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/migration_helpers_shared_examples.rb
index 72d672fd36c..69a1f7ad11e 100644
--- a/spec/support/shared_examples/lib/gitlab/migration_helpers_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/migration_helpers_shared_examples.rb
@@ -14,10 +14,10 @@ RSpec.shared_examples 'performs validation' do |validation_option|
it 'performs validation' do
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
- expect(model).to receive(:execute).with(/statement_timeout/)
+ expect(model).to receive(:execute).with(/SET statement_timeout TO/)
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
- expect(model).to receive(:execute).ordered.with(/RESET ALL/)
+ expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
model.add_concurrent_foreign_key(*args, **options.merge(validation_option))
end
diff --git a/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb
index 8d758ed1655..ead8b174d46 100644
--- a/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb
@@ -22,7 +22,7 @@ RSpec.shared_examples 'SQL set operator' do |operator_keyword|
end
it 'skips Model.none segments' do
- empty_relation = User.none
+ empty_relation = User.none.select(:id)
set_operator = described_class.new([empty_relation, relation_1, relation_2])
expect {User.where("users.id IN (#{set_operator.to_sql})").to_a}.not_to raise_error
@@ -44,6 +44,17 @@ RSpec.shared_examples 'SQL set operator' do |operator_keyword|
end
end
+ context 'when uneven select values are used' do
+ let(:relation_1) { User.where(email: 'alice@example.com').select(*User.column_names) }
+ let(:relation_2) { User.where(email: 'bob@example.com') }
+
+ it 'raises error' do
+ expect do
+ described_class.new([relation_1, relation_2])
+ end.to raise_error /Relations with uneven select values were passed/
+ end
+ end
+
describe 'remove_order parameter' do
let(:scopes) do
[
diff --git a/spec/support/shared_examples/helpers/groups_shared_examples.rb b/spec/support/shared_examples/lib/menus_shared_examples.rb
index 9c74d25b31f..2c2cb362b07 100644
--- a/spec/support/shared_examples/helpers/groups_shared_examples.rb
+++ b/spec/support/shared_examples/lib/menus_shared_examples.rb
@@ -1,30 +1,16 @@
# frozen_string_literal: true
-# This shared_example requires the following variables:
-# - current_user
-# - group
-# - type, the issuable type (ie :issues, :merge_requests)
-# - count_service, the Service used by the specified issuable type
-
-RSpec.shared_examples 'cached issuables count' do
- subject { helper.cached_issuables_count(group, type: type) }
-
- before do
- allow(helper).to receive(:current_user) { current_user }
- allow(count_service).to receive(:new).and_call_original
- end
+RSpec.shared_examples_for 'pill_count formatted results' do
+ let(:count_service) { raise NotImplementedError }
- it 'calls the correct service class' do
- subject
- expect(count_service).to have_received(:new).with(group, current_user)
- end
+ subject(:pill_count) { menu.pill_count }
it 'returns all digits for count value under 1000' do
allow_next_instance_of(count_service) do |service|
allow(service).to receive(:count).and_return(999)
end
- expect(subject).to eq('999')
+ expect(pill_count).to eq('999')
end
it 'returns truncated digits for count value over 1000' do
@@ -32,7 +18,7 @@ RSpec.shared_examples 'cached issuables count' do
allow(service).to receive(:count).and_return(2300)
end
- expect(subject).to eq('2.3k')
+ expect(pill_count).to eq('2.3k')
end
it 'returns truncated digits for count value over 10000' do
@@ -40,7 +26,7 @@ RSpec.shared_examples 'cached issuables count' do
allow(service).to receive(:count).and_return(12560)
end
- expect(subject).to eq('12.6k')
+ expect(pill_count).to eq('12.6k')
end
it 'returns truncated digits for count value over 100000' do
@@ -48,6 +34,6 @@ RSpec.shared_examples 'cached issuables count' do
allow(service).to receive(:count).and_return(112560)
end
- expect(subject).to eq('112.6k')
+ expect(pill_count).to eq('112.6k')
end
end
diff --git a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
index a84658780b9..c6d6ff6bc1d 100644
--- a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
+++ b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
@@ -1,106 +1,126 @@
# frozen_string_literal: true
RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
+ let(:db_config_name) { ::Gitlab::Database.db_config_names.first }
+
+ let(:expected_payload_defaults) do
+ metrics =
+ ::Gitlab::Metrics::Subscribers::ActiveRecord.load_balancing_metric_counter_keys +
+ ::Gitlab::Metrics::Subscribers::ActiveRecord.load_balancing_metric_duration_keys +
+ ::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_keys
+
+ metrics.each_with_object({}) do |key, result|
+ result[key] = 0
+ end
+ end
+
+ def transform_hash(hash, another_hash)
+ another_hash.each do |key, value|
+ raise "Unexpected key: #{key}" unless hash[key]
+ end
+
+ hash.merge(another_hash)
+ end
+
it 'prevents db counters from leaking to the next transaction' do
2.times do
Gitlab::WithRequestStore.with_request_store do
subscriber.sql(event)
- connection = event.payload[:connection]
-
- if db_role == :primary
- expected = {
- db_count: record_query ? 1 : 0,
- db_write_count: record_write_query ? 1 : 0,
- db_cached_count: record_cached_query ? 1 : 0,
- db_primary_cached_count: record_cached_query ? 1 : 0,
- db_primary_count: record_query ? 1 : 0,
- db_primary_duration_s: record_query ? 0.002 : 0,
- db_replica_cached_count: 0,
- db_replica_count: 0,
- db_replica_duration_s: 0.0,
- db_primary_wal_count: record_wal_query ? 1 : 0,
- db_primary_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
- db_replica_wal_cached_count: 0,
- db_replica_wal_count: 0
- }
- expected[:"db_primary_#{::Gitlab::Database.dbname(connection)}_duration_s"] = 0.002 if record_query
- elsif db_role == :replica
- expected = {
- db_count: record_query ? 1 : 0,
- db_write_count: record_write_query ? 1 : 0,
- db_cached_count: record_cached_query ? 1 : 0,
- db_primary_cached_count: 0,
- db_primary_count: 0,
- db_primary_duration_s: 0.0,
- db_replica_cached_count: record_cached_query ? 1 : 0,
- db_replica_count: record_query ? 1 : 0,
- db_replica_duration_s: record_query ? 0.002 : 0,
- db_replica_wal_count: record_wal_query ? 1 : 0,
- db_replica_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
- db_primary_wal_cached_count: 0,
- db_primary_wal_count: 0
- }
- expected[:"db_replica_#{::Gitlab::Database.dbname(connection)}_duration_s"] = 0.002 if record_query
- else
- expected = {
- db_count: record_query ? 1 : 0,
- db_write_count: record_write_query ? 1 : 0,
- db_cached_count: record_cached_query ? 1 : 0
- }
- end
+
+ expected = if db_role == :primary
+ transform_hash(expected_payload_defaults, {
+ db_count: record_query ? 1 : 0,
+ db_write_count: record_write_query ? 1 : 0,
+ db_cached_count: record_cached_query ? 1 : 0,
+ db_primary_cached_count: record_cached_query ? 1 : 0,
+ "db_primary_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
+ db_primary_count: record_query ? 1 : 0,
+ "db_primary_#{db_config_name}_count": record_query ? 1 : 0,
+ db_primary_duration_s: record_query ? 0.002 : 0,
+ "db_primary_#{db_config_name}_duration_s": record_query ? 0.002 : 0,
+ db_primary_wal_count: record_wal_query ? 1 : 0,
+ "db_primary_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
+ db_primary_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
+ "db_primary_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
+ })
+ elsif db_role == :replica
+ transform_hash(expected_payload_defaults, {
+ db_count: record_query ? 1 : 0,
+ db_write_count: record_write_query ? 1 : 0,
+ db_cached_count: record_cached_query ? 1 : 0,
+ db_replica_cached_count: record_cached_query ? 1 : 0,
+ "db_replica_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
+ db_replica_count: record_query ? 1 : 0,
+ "db_replica_#{db_config_name}_count": record_query ? 1 : 0,
+ db_replica_duration_s: record_query ? 0.002 : 0,
+ "db_replica_#{db_config_name}_duration_s": record_query ? 0.002 : 0,
+ db_replica_wal_count: record_wal_query ? 1 : 0,
+ "db_replica_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
+ db_replica_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
+ "db_replica_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
+ })
+ else
+ {
+ db_count: record_query ? 1 : 0,
+ db_write_count: record_write_query ? 1 : 0,
+ db_cached_count: record_cached_query ? 1 : 0
+ }
+ end
expect(described_class.db_counter_payload).to eq(expected)
end
end
end
- context 'when multiple_database_metrics is disabled' do
+ context 'when the GITLAB_MULTIPLE_DATABASE_METRICS env var is disabled' do
before do
- stub_feature_flags(multiple_database_metrics: false)
+ stub_env('GITLAB_MULTIPLE_DATABASE_METRICS', nil)
end
it 'does not include per database metrics' do
Gitlab::WithRequestStore.with_request_store do
subscriber.sql(event)
- connection = event.payload[:connection]
- expect(described_class.db_counter_payload).not_to include(:"db_replica_#{::Gitlab::Database.dbname(connection)}_duration_s")
+ expect(described_class.db_counter_payload).not_to include(:"db_replica_#{db_config_name}_duration_s")
+ expect(described_class.db_counter_payload).not_to include(:"db_replica_#{db_config_name}_count")
end
end
end
end
RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do |db_role|
+ let(:db_config_name) { ::Gitlab::Database.db_config_name(ApplicationRecord.connection) }
+
it 'increments only db counters' do
if record_query
- expect(transaction).to receive(:increment).with(:gitlab_transaction_db_count_total, 1)
- expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_count_total".to_sym, 1) if db_role
+ expect(transaction).to receive(:increment).with(:gitlab_transaction_db_count_total, 1, { db_config_name: db_config_name })
+ expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_count_total".to_sym, 1, { db_config_name: db_config_name }) if db_role
else
- expect(transaction).not_to receive(:increment).with(:gitlab_transaction_db_count_total, 1)
- expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_count_total".to_sym, 1) if db_role
+ expect(transaction).not_to receive(:increment).with(:gitlab_transaction_db_count_total, 1, { db_config_name: db_config_name })
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_count_total".to_sym, 1, { db_config_name: db_config_name }) if db_role
end
if record_write_query
- expect(transaction).to receive(:increment).with(:gitlab_transaction_db_write_count_total, 1)
+ expect(transaction).to receive(:increment).with(:gitlab_transaction_db_write_count_total, 1, { db_config_name: db_config_name })
else
- expect(transaction).not_to receive(:increment).with(:gitlab_transaction_db_write_count_total, 1)
+ expect(transaction).not_to receive(:increment).with(:gitlab_transaction_db_write_count_total, 1, { db_config_name: db_config_name })
end
if record_cached_query
- expect(transaction).to receive(:increment).with(:gitlab_transaction_db_cached_count_total, 1)
- expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_cached_count_total".to_sym, 1) if db_role
+ expect(transaction).to receive(:increment).with(:gitlab_transaction_db_cached_count_total, 1, { db_config_name: db_config_name })
+ expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_cached_count_total".to_sym, 1, { db_config_name: db_config_name }) if db_role
else
- expect(transaction).not_to receive(:increment).with(:gitlab_transaction_db_cached_count_total, 1)
- expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_cached_count_total".to_sym, 1) if db_role
+ expect(transaction).not_to receive(:increment).with(:gitlab_transaction_db_cached_count_total, 1, { db_config_name: db_config_name })
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_cached_count_total".to_sym, 1, { db_config_name: db_config_name }) if db_role
end
if record_wal_query
if db_role
- expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1)
- expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_cached_count_total".to_sym, 1) if record_cached_query
+ expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1, { db_config_name: db_config_name })
+ expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_cached_count_total".to_sym, 1, { db_config_name: db_config_name }) if record_cached_query
end
else
- expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1) if db_role
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1, { db_config_name: db_config_name }) if db_role
end
subscriber.sql(event)
@@ -108,14 +128,34 @@ RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do
it 'observes sql_duration metric' do
if record_query
- expect(transaction).to receive(:observe).with(:gitlab_sql_duration_seconds, 0.002)
- expect(transaction).to receive(:observe).with("gitlab_sql_#{db_role}_duration_seconds".to_sym, 0.002) if db_role
+ expect(transaction).to receive(:observe).with(:gitlab_sql_duration_seconds, 0.002, { db_config_name: db_config_name })
+ expect(transaction).to receive(:observe).with("gitlab_sql_#{db_role}_duration_seconds".to_sym, 0.002, { db_config_name: db_config_name }) if db_role
else
expect(transaction).not_to receive(:observe)
end
subscriber.sql(event)
end
+
+ context 'when the GITLAB_MULTIPLE_DATABASE_METRICS env var is disabled' do
+ before do
+ stub_env('GITLAB_MULTIPLE_DATABASE_METRICS', nil)
+ end
+
+ it 'does not include db_config_name label' do
+ allow(transaction).to receive(:increment) do |*args|
+ labels = args[2] || {}
+ expect(labels).not_to include(:db_config_name)
+ end
+
+ allow(transaction).to receive(:observe) do |*args|
+ labels = args[2] || {}
+ expect(labels).not_to include(:db_config_name)
+ end
+
+ subscriber.sql(event)
+ end
+ end
end
RSpec.shared_examples 'record ActiveRecord metrics' do |db_role|
diff --git a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
index 99a09993900..f92ed3d7396 100644
--- a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
@@ -62,26 +62,6 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
.to raise_error(ActiveModel::MissingAttributeError)
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(efficient_counter_attribute: false)
- end
-
- it 'delegates to ActiveRecord update!' do
- expect { subject }
- .to change { model.reset.read_attribute(attribute) }.by(increment)
- end
-
- it 'does not increment the counter in Redis' do
- subject
-
- Gitlab::Redis::SharedState.with do |redis|
- counter = redis.get(model.counter_key(attribute))
- expect(counter).to be_nil
- end
- end
- end
end
end
end
diff --git a/spec/support/shared_examples/models/concerns/incident_management/escalatable_shared_examples.rb b/spec/support/shared_examples/models/concerns/incident_management/escalatable_shared_examples.rb
new file mode 100644
index 00000000000..7b33a95bfa1
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/incident_management/escalatable_shared_examples.rb
@@ -0,0 +1,246 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a model including Escalatable' do
+ # rubocop:disable Rails/SaveBang -- Usage of factory symbol as argument causes a false-positive
+ let_it_be(:escalatable_factory) { factory_from_class(described_class) }
+ let_it_be(:triggered_escalatable, reload: true) { create(escalatable_factory, :triggered) }
+ let_it_be(:acknowledged_escalatable, reload: true) { create(escalatable_factory, :acknowledged) }
+ let_it_be(:resolved_escalatable, reload: true) { create(escalatable_factory, :resolved) }
+ let_it_be(:ignored_escalatable, reload: true) { create(escalatable_factory, :ignored) }
+
+ context 'validations' do
+ it { is_expected.to validate_presence_of(:status) }
+
+ context 'when status is triggered' do
+ subject { triggered_escalatable }
+
+ context 'when resolved_at is blank' do
+ it { is_expected.to be_valid }
+ end
+
+ context 'when resolved_at is present' do
+ before do
+ triggered_escalatable.resolved_at = Time.current
+ end
+
+ it { is_expected.to be_invalid }
+ end
+ end
+
+ context 'when status is acknowledged' do
+ subject { acknowledged_escalatable }
+
+ context 'when resolved_at is blank' do
+ it { is_expected.to be_valid }
+ end
+
+ context 'when resolved_at is present' do
+ before do
+ acknowledged_escalatable.resolved_at = Time.current
+ end
+
+ it { is_expected.to be_invalid }
+ end
+ end
+
+ context 'when status is resolved' do
+ subject { resolved_escalatable }
+
+ context 'when resolved_at is blank' do
+ before do
+ resolved_escalatable.resolved_at = nil
+ end
+
+ it { is_expected.to be_invalid }
+ end
+
+ context 'when resolved_at is present' do
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'when status is ignored' do
+ subject { ignored_escalatable }
+
+ context 'when resolved_at is blank' do
+ it { is_expected.to be_valid }
+ end
+
+ context 'when resolved_at is present' do
+ before do
+ ignored_escalatable.resolved_at = Time.current
+ end
+
+ it { is_expected.to be_invalid }
+ end
+ end
+ end
+
+ context 'scopes' do
+ let(:all_escalatables) { described_class.where(id: [triggered_escalatable, acknowledged_escalatable, ignored_escalatable, resolved_escalatable])}
+
+ describe '.order_status' do
+ subject { all_escalatables.order_status(order) }
+
+ context 'descending' do
+ let(:order) { :desc }
+
+ # Downward arrow in UI always corresponds to default sort
+ it { is_expected.to eq([triggered_escalatable, acknowledged_escalatable, resolved_escalatable, ignored_escalatable]) }
+ end
+
+ context 'ascending' do
+ let(:order) { :asc }
+
+ it { is_expected.to eq([ignored_escalatable, resolved_escalatable, acknowledged_escalatable, triggered_escalatable]) }
+ end
+ end
+ end
+
+ describe '.status_value' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:status, :status_value) do
+ :triggered | 0
+ :acknowledged | 1
+ :resolved | 2
+ :ignored | 3
+ :unknown | nil
+ end
+
+ with_them do
+ it 'returns status value by its name' do
+ expect(described_class.status_value(status)).to eq(status_value)
+ end
+ end
+ end
+
+ describe '.status_name' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:raw_status, :status) do
+ 0 | :triggered
+ 1 | :acknowledged
+ 2 | :resolved
+ 3 | :ignored
+ -1 | nil
+ end
+
+ with_them do
+ it 'returns status name by its values' do
+ expect(described_class.status_name(raw_status)).to eq(status)
+ end
+ end
+ end
+
+ describe '#trigger' do
+ subject { escalatable.trigger }
+
+ context 'when escalatable is in triggered state' do
+ let(:escalatable) { triggered_escalatable }
+
+ it 'does not change the escalatable status' do
+ expect { subject }.not_to change { escalatable.reload.status }
+ end
+ end
+
+ context 'when escalatable is not in triggered state' do
+ let(:escalatable) { resolved_escalatable }
+
+ it 'changes the escalatable status to triggered' do
+ expect { subject }.to change { escalatable.triggered? }.to(true)
+ end
+
+ it 'resets resolved at' do
+ expect { subject }.to change { escalatable.reload.resolved_at }.to nil
+ end
+ end
+ end
+
+ describe '#acknowledge' do
+ subject { escalatable.acknowledge }
+
+ let(:escalatable) { resolved_escalatable }
+
+ it 'changes the escalatable status to acknowledged' do
+ expect { subject }.to change { escalatable.acknowledged? }.to(true)
+ end
+
+ it 'resets ended at' do
+ expect { subject }.to change { escalatable.reload.resolved_at }.to nil
+ end
+ end
+
+ describe '#resolve' do
+ let!(:resolved_at) { Time.current }
+
+ subject do
+ escalatable.resolved_at = resolved_at
+ escalatable.resolve
+ end
+
+ context 'when escalatable is already resolved' do
+ let(:escalatable) { resolved_escalatable }
+
+ it 'does not change the escalatable status' do
+ expect { subject }.not_to change { resolved_escalatable.reload.status }
+ end
+ end
+
+ context 'when escalatable is not resolved' do
+ let(:escalatable) { triggered_escalatable }
+
+ it 'changes escalatable status to "resolved"' do
+ expect { subject }.to change { escalatable.resolved? }.to(true)
+ end
+ end
+ end
+
+ describe '#ignore' do
+ subject { escalatable.ignore }
+
+ let(:escalatable) { resolved_escalatable }
+
+ it 'changes the escalatable status to ignored' do
+ expect { subject }.to change { escalatable.ignored? }.to(true)
+ end
+
+ it 'resets ended at' do
+ expect { subject }.to change { escalatable.reload.resolved_at }.to nil
+ end
+ end
+
+ describe '#status_event_for' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:for_status, :event) do
+ :triggered | :trigger
+ 'triggered' | :trigger
+ :acknowledged | :acknowledge
+ 'acknowledged' | :acknowledge
+ :resolved | :resolve
+ 'resolved' | :resolve
+ :ignored | :ignore
+ 'ignored' | :ignore
+ :unknown | nil
+ nil | nil
+ '' | nil
+ 1 | nil
+ end
+
+ with_them do
+ let(:escalatable) { build(escalatable_factory) }
+
+ it 'returns event by status name' do
+ expect(escalatable.status_event_for(for_status)).to eq(event)
+ end
+ end
+ end
+
+ private
+
+ def factory_from_class(klass)
+ klass.name.underscore.tr('/', '_')
+ end
+end
+# rubocop:enable Rails/SaveBang
diff --git a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
index cf38a583944..457ee49938f 100644
--- a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
+++ b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
@@ -13,6 +13,7 @@ RSpec.shared_examples 'value stream analytics stage' do
describe 'associations' do
it { is_expected.to belong_to(:end_event_label) }
it { is_expected.to belong_to(:start_event_label) }
+ it { is_expected.to belong_to(:stage_event_hash) }
end
describe 'validation' do
@@ -138,6 +139,67 @@ RSpec.shared_examples 'value stream analytics stage' do
expect(stage_1.events_hash_code).not_to eq(stage_2.events_hash_code)
end
end
+
+ # rubocop: disable Rails/SaveBang
+ describe '#event_hash' do
+ it 'associates the same stage event hash record' do
+ first = create(factory)
+ second = create(factory)
+
+ expect(first.stage_event_hash_id).to eq(second.stage_event_hash_id)
+ end
+
+ it 'does not introduce duplicated stage event hash records' do
+ expect do
+ create(factory)
+ create(factory)
+ end.to change { Analytics::CycleAnalytics::StageEventHash.count }.from(0).to(1)
+ end
+
+ it 'creates different hash record for different event configurations' do
+ expect do
+ create(factory, start_event_identifier: :issue_created, end_event_identifier: :issue_stage_end)
+ create(factory, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged)
+ end.to change { Analytics::CycleAnalytics::StageEventHash.count }.from(0).to(2)
+ end
+
+ context 'when the stage event hash changes' do
+ let(:stage) { create(factory, start_event_identifier: :issue_created, end_event_identifier: :issue_stage_end) }
+
+ it 'deletes the old, unused stage event hash record' do
+ old_stage_event_hash = stage.stage_event_hash
+
+ stage.update!(end_event_identifier: :issue_deployed_to_production)
+
+ expect(stage.stage_event_hash_id).not_to eq(old_stage_event_hash.id)
+
+ old_stage_event_hash_from_db = Analytics::CycleAnalytics::StageEventHash.find_by_id(old_stage_event_hash.id)
+ expect(old_stage_event_hash_from_db).to be_nil
+ end
+
+ it 'does not delete used stage event hash record' do
+ other_stage = create(factory, start_event_identifier: :issue_created, end_event_identifier: :issue_stage_end)
+
+ stage.update!(end_event_identifier: :issue_deployed_to_production)
+
+ expect(stage.stage_event_hash_id).not_to eq(other_stage.stage_event_hash_id)
+
+ old_stage_event_hash_from_db = Analytics::CycleAnalytics::StageEventHash.find_by_id(other_stage.stage_event_hash_id)
+ expect(old_stage_event_hash_from_db).not_to be_nil
+ end
+ end
+
+ context 'when the stage events hash code does not change' do
+ it 'does not trigger extra query on save' do
+ stage = create(factory, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged)
+
+ expect(Analytics::CycleAnalytics::StageEventHash).not_to receive(:record_id_by_hash_sha256)
+
+ stage.update!(name: 'new title')
+ end
+ end
+ end
+ # rubocop: enable Rails/SaveBang
end
RSpec.shared_examples 'value stream analytics label based stage' do
diff --git a/spec/support/shared_examples/models/mentionable_shared_examples.rb b/spec/support/shared_examples/models/mentionable_shared_examples.rb
index 04630484964..07c5f730e95 100644
--- a/spec/support/shared_examples/models/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/models/mentionable_shared_examples.rb
@@ -207,7 +207,7 @@ RSpec.shared_examples 'an editable mentionable' do
end
RSpec.shared_examples 'mentions in description' do |mentionable_type|
- describe 'when storing user mentions' do
+ shared_examples 'when storing user mentions' do
before do
mentionable.store_mentions!
end
@@ -238,10 +238,26 @@ RSpec.shared_examples 'mentions in description' do |mentionable_type|
end
end
end
+
+ context 'when store_mentions_without_subtransaction is enabled' do
+ before do
+ stub_feature_flags(store_mentions_without_subtransaction: true)
+ end
+
+ it_behaves_like 'when storing user mentions'
+ end
+
+ context 'when store_mentions_without_subtransaction is disabled' do
+ before do
+ stub_feature_flags(store_mentions_without_subtransaction: false)
+ end
+
+ it_behaves_like 'when storing user mentions'
+ end
end
RSpec.shared_examples 'mentions in notes' do |mentionable_type|
- context 'when mentionable notes contain mentions' do
+ shared_examples 'when mentionable notes contain mentions' do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:group) { create(:group) }
@@ -261,6 +277,22 @@ RSpec.shared_examples 'mentions in notes' do |mentionable_type|
expect(mentionable.referenced_groups(user)).to eq [group]
end
end
+
+ context 'when store_mentions_without_subtransaction is enabled' do
+ before do
+ stub_feature_flags(store_mentions_without_subtransaction: true)
+ end
+
+ it_behaves_like 'when mentionable notes contain mentions'
+ end
+
+ context 'when store_mentions_without_subtransaction is disabled' do
+ before do
+ stub_feature_flags(store_mentions_without_subtransaction: false)
+ end
+
+ it_behaves_like 'when mentionable notes contain mentions'
+ end
end
RSpec.shared_examples 'load mentions from DB' do |mentionable_type|
@@ -278,7 +310,7 @@ RSpec.shared_examples 'load mentions from DB' do |mentionable_type|
context 'when stored user mention contains ids of inexistent records' do
before do
- user_mention = note.send(:model_user_mention)
+ user_mention = note.user_mentions.first
mention_ids = {
mentioned_users_ids: user_mention.mentioned_users_ids.to_a << non_existing_record_id,
mentioned_projects_ids: user_mention.mentioned_projects_ids.to_a << non_existing_record_id,
@@ -302,7 +334,7 @@ RSpec.shared_examples 'load mentions from DB' do |mentionable_type|
let(:group_member) { create(:group_member, user: create(:user), group: private_group) }
before do
- user_mention = note.send(:model_user_mention)
+ user_mention = note.user_mentions.first
mention_ids = {
mentioned_projects_ids: user_mention.mentioned_projects_ids.to_a << private_project.id,
mentioned_groups_ids: user_mention.mentioned_groups_ids.to_a << private_group.id
diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb
index 5459d17b1df..274fbae3dfd 100644
--- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb
+++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb
@@ -128,10 +128,6 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze|
it { is_expected.not_to allow_value(12.hours.to_i).for(:valid_time_duration_seconds) }
end
- describe '#signing_keys' do
- it { is_expected.to validate_absence_of(:signing_keys) }
- end
-
describe '#file' do
it { is_expected.not_to validate_presence_of(:file) }
end
@@ -141,7 +137,15 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze|
end
describe '#file_signature' do
- it { is_expected.to validate_absence_of(:file_signature) }
+ it { is_expected.not_to validate_absence_of(:file_signature) }
+ end
+
+ describe '#signed_file' do
+ it { is_expected.not_to validate_presence_of(:signed_file) }
+ end
+
+ describe '#signed_file_store' do
+ it { is_expected.to validate_presence_of(:signed_file_store) }
end
end
diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
index 7b591ad84d1..2e01de2ea84 100644
--- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
+++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
@@ -22,116 +22,6 @@ RSpec.shared_examples 'UpdateProjectStatistics' do |with_counter_attribute|
it { is_expected.to be_new_record }
- context 'when feature flag efficient_counter_attribute is disabled' do
- before do
- stub_feature_flags(efficient_counter_attribute: false)
- end
-
- context 'when creating' do
- it 'updates the project statistics' do
- delta0 = reload_stat
-
- subject.save!
-
- delta1 = reload_stat
-
- expect(delta1).to eq(delta0 + read_attribute)
- expect(delta1).to be > delta0
- end
-
- it 'schedules a namespace statistics worker' do
- expect(Namespaces::ScheduleAggregationWorker)
- .to receive(:perform_async).once
-
- subject.save!
- end
- end
-
- context 'when updating' do
- let(:delta) { 42 }
-
- before do
- subject.save!
- end
-
- it 'updates project statistics' do
- expect(ProjectStatistics)
- .to receive(:increment_statistic)
- .and_call_original
-
- subject.write_attribute(statistic_attribute, read_attribute + delta)
-
- expect { subject.save! }
- .to change { reload_stat }
- .by(delta)
- end
-
- it 'schedules a namespace statistics worker' do
- expect(Namespaces::ScheduleAggregationWorker)
- .to receive(:perform_async).once
-
- subject.write_attribute(statistic_attribute, read_attribute + delta)
- subject.save!
- end
-
- it 'avoids N + 1 queries' do
- subject.write_attribute(statistic_attribute, read_attribute + delta)
-
- control_count = ActiveRecord::QueryRecorder.new do
- subject.save!
- end
-
- subject.write_attribute(statistic_attribute, read_attribute + delta)
-
- expect do
- subject.save!
- end.not_to exceed_query_limit(control_count)
- end
- end
-
- context 'when destroying' do
- before do
- subject.save!
- end
-
- it 'updates the project statistics' do
- delta0 = reload_stat
-
- subject.destroy!
-
- delta1 = reload_stat
-
- expect(delta1).to eq(delta0 - read_attribute)
- expect(delta1).to be < delta0
- end
-
- it 'schedules a namespace statistics worker' do
- expect(Namespaces::ScheduleAggregationWorker)
- .to receive(:perform_async).once
-
- subject.destroy!
- end
-
- context 'when it is destroyed from the project level' do
- it 'does not update the project statistics' do
- expect(ProjectStatistics)
- .not_to receive(:increment_statistic)
-
- project.update!(pending_delete: true)
- project.destroy!
- end
-
- it 'does not schedule a namespace statistics worker' do
- expect(Namespaces::ScheduleAggregationWorker)
- .not_to receive(:perform_async)
-
- project.update!(pending_delete: true)
- project.destroy!
- end
- end
- end
- end
-
def expect_flush_counter_increments_worker_performed
expect(FlushCounterIncrementsWorker)
.to receive(:perform_in)
diff --git a/spec/support/shared_examples/namespaces/linear_traversal_examples.rb b/spec/support/shared_examples/namespaces/linear_traversal_examples.rb
deleted file mode 100644
index 2fd90c36953..00000000000
--- a/spec/support/shared_examples/namespaces/linear_traversal_examples.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-# Traversal examples common to linear and recursive methods are in
-# spec/support/shared_examples/namespaces/traversal_examples.rb
-
-RSpec.shared_examples 'linear namespace traversal' do
- context 'when use_traversal_ids feature flag is enabled' do
- before do
- stub_feature_flags(use_traversal_ids: true)
- end
-
- context 'scopes' do
- describe '.as_ids' do
- let_it_be(:namespace1) { create(:group) }
- let_it_be(:namespace2) { create(:group) }
-
- subject { Namespace.where(id: [namespace1, namespace2]).as_ids.pluck(:id) }
-
- it { is_expected.to contain_exactly(namespace1.id, namespace2.id) }
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index f09634556c3..d126b242fb0 100644
--- a/spec/support/shared_examples/namespaces/traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -55,12 +55,34 @@ RSpec.shared_examples 'namespace traversal' do
end
describe '#ancestors' do
- it 'returns the correct ancestors' do
+ before do
# #reload is called to make sure traversal_ids are reloaded
- expect(very_deep_nested_group.reload.ancestors).to contain_exactly(group, nested_group, deep_nested_group)
- expect(deep_nested_group.reload.ancestors).to contain_exactly(group, nested_group)
- expect(nested_group.reload.ancestors).to contain_exactly(group)
- expect(group.reload.ancestors).to eq([])
+ reload_models(group, nested_group, deep_nested_group, very_deep_nested_group)
+ end
+
+ it 'returns the correct ancestors' do
+ expect(very_deep_nested_group.ancestors).to contain_exactly(group, nested_group, deep_nested_group)
+ expect(deep_nested_group.ancestors).to contain_exactly(group, nested_group)
+ expect(nested_group.ancestors).to contain_exactly(group)
+ expect(group.ancestors).to eq([])
+ end
+
+ context 'with asc hierarchy_order' do
+ it 'returns the correct ancestors' do
+ expect(very_deep_nested_group.ancestors(hierarchy_order: :asc)).to eq [deep_nested_group, nested_group, group]
+ expect(deep_nested_group.ancestors(hierarchy_order: :asc)).to eq [nested_group, group]
+ expect(nested_group.ancestors(hierarchy_order: :asc)).to eq [group]
+ expect(group.ancestors(hierarchy_order: :asc)).to eq([])
+ end
+ end
+
+ context 'with desc hierarchy_order' do
+ it 'returns the correct ancestors' do
+ expect(very_deep_nested_group.ancestors(hierarchy_order: :desc)).to eq [group, nested_group, deep_nested_group]
+ expect(deep_nested_group.ancestors(hierarchy_order: :desc)).to eq [group, nested_group]
+ expect(nested_group.ancestors(hierarchy_order: :desc)).to eq [group]
+ expect(group.ancestors(hierarchy_order: :desc)).to eq([])
+ end
end
describe '#recursive_ancestors' do
@@ -78,6 +100,24 @@ RSpec.shared_examples 'namespace traversal' do
expect(group.ancestor_ids).to be_empty
end
+ context 'with asc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(very_deep_nested_group.ancestor_ids(hierarchy_order: :asc)).to eq [deep_nested_group.id, nested_group.id, group.id]
+ expect(deep_nested_group.ancestor_ids(hierarchy_order: :asc)).to eq [nested_group.id, group.id]
+ expect(nested_group.ancestor_ids(hierarchy_order: :asc)).to eq [group.id]
+ expect(group.ancestor_ids(hierarchy_order: :asc)).to eq([])
+ end
+ end
+
+ context 'with desc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(very_deep_nested_group.ancestor_ids(hierarchy_order: :desc)).to eq [group.id, nested_group.id, deep_nested_group.id]
+ expect(deep_nested_group.ancestor_ids(hierarchy_order: :desc)).to eq [group.id, nested_group.id]
+ expect(nested_group.ancestor_ids(hierarchy_order: :desc)).to eq [group.id]
+ expect(group.ancestor_ids(hierarchy_order: :desc)).to eq([])
+ end
+ end
+
describe '#recursive_ancestor_ids' do
let_it_be(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
@@ -93,6 +133,24 @@ RSpec.shared_examples 'namespace traversal' do
expect(group.self_and_ancestors).to contain_exactly(group)
end
+ context 'with asc hierarchy_order' do
+ it 'returns the correct ancestors' do
+ expect(very_deep_nested_group.self_and_ancestors(hierarchy_order: :asc)).to eq [very_deep_nested_group, deep_nested_group, nested_group, group]
+ expect(deep_nested_group.self_and_ancestors(hierarchy_order: :asc)).to eq [deep_nested_group, nested_group, group]
+ expect(nested_group.self_and_ancestors(hierarchy_order: :asc)).to eq [nested_group, group]
+ expect(group.self_and_ancestors(hierarchy_order: :asc)).to eq([group])
+ end
+ end
+
+ context 'with desc hierarchy_order' do
+ it 'returns the correct ancestors' do
+ expect(very_deep_nested_group.self_and_ancestors(hierarchy_order: :desc)).to eq [group, nested_group, deep_nested_group, very_deep_nested_group]
+ expect(deep_nested_group.self_and_ancestors(hierarchy_order: :desc)).to eq [group, nested_group, deep_nested_group]
+ expect(nested_group.self_and_ancestors(hierarchy_order: :desc)).to eq [group, nested_group]
+ expect(group.self_and_ancestors(hierarchy_order: :desc)).to eq([group])
+ end
+ end
+
describe '#recursive_self_and_ancestors' do
let_it_be(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
@@ -108,6 +166,24 @@ RSpec.shared_examples 'namespace traversal' do
expect(group.self_and_ancestor_ids).to contain_exactly(group.id)
end
+ context 'with asc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(very_deep_nested_group.self_and_ancestor_ids(hierarchy_order: :asc)).to eq [very_deep_nested_group.id, deep_nested_group.id, nested_group.id, group.id]
+ expect(deep_nested_group.self_and_ancestor_ids(hierarchy_order: :asc)).to eq [deep_nested_group.id, nested_group.id, group.id]
+ expect(nested_group.self_and_ancestor_ids(hierarchy_order: :asc)).to eq [nested_group.id, group.id]
+ expect(group.self_and_ancestor_ids(hierarchy_order: :asc)).to eq([group.id])
+ end
+ end
+
+ context 'with desc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(very_deep_nested_group.self_and_ancestor_ids(hierarchy_order: :desc)).to eq [group.id, nested_group.id, deep_nested_group.id, very_deep_nested_group.id]
+ expect(deep_nested_group.self_and_ancestor_ids(hierarchy_order: :desc)).to eq [group.id, nested_group.id, deep_nested_group.id]
+ expect(nested_group.self_and_ancestor_ids(hierarchy_order: :desc)).to eq [group.id, nested_group.id]
+ expect(group.self_and_ancestor_ids(hierarchy_order: :desc)).to eq([group.id])
+ end
+ end
+
describe '#recursive_self_and_ancestor_ids' do
let_it_be(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
new file mode 100644
index 00000000000..4d328c03641
--- /dev/null
+++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'namespace traversal scopes' do
+ # Hierarchy 1
+ let_it_be(:group_1) { create(:group) }
+ let_it_be(:nested_group_1) { create(:group, parent: group_1) }
+ let_it_be(:deep_nested_group_1) { create(:group, parent: nested_group_1) }
+
+ # Hierarchy 2
+ let_it_be(:group_2) { create(:group) }
+ let_it_be(:nested_group_2) { create(:group, parent: group_2) }
+ let_it_be(:deep_nested_group_2) { create(:group, parent: nested_group_2) }
+
+ # All groups
+ let_it_be(:groups) do
+ [
+ group_1, nested_group_1, deep_nested_group_1,
+ group_2, nested_group_2, deep_nested_group_2
+ ]
+ end
+
+ describe '.as_ids' do
+ subject { described_class.where(id: [group_1, group_2]).as_ids.pluck(:id) }
+
+ it { is_expected.to contain_exactly(group_1.id, group_2.id) }
+ end
+
+ describe '.without_sti_condition' do
+ subject { described_class.without_sti_condition }
+
+ it { expect(subject.where_values_hash).not_to have_key(:type) }
+ end
+
+ describe '.self_and_descendants' do
+ subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendants }
+
+ it { is_expected.to contain_exactly(nested_group_1, deep_nested_group_1, nested_group_2, deep_nested_group_2) }
+
+ context 'with duplicate descendants' do
+ subject { described_class.where(id: [group_1, group_2, nested_group_1]).self_and_descendants }
+
+ it { is_expected.to match_array(groups) }
+ end
+
+ context 'when include_self is false' do
+ subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendants(include_self: false) }
+
+ it { is_expected.to contain_exactly(deep_nested_group_1, deep_nested_group_2) }
+ end
+ end
+
+ describe '.self_and_descendant_ids' do
+ subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendant_ids.pluck(:id) }
+
+ it { is_expected.to contain_exactly(nested_group_1.id, deep_nested_group_1.id, nested_group_2.id, deep_nested_group_2.id) }
+
+ context 'when include_self is false' do
+ subject do
+ described_class
+ .where(id: [nested_group_1, nested_group_2])
+ .self_and_descendant_ids(include_self: false)
+ .pluck(:id)
+ end
+
+ it { is_expected.to contain_exactly(deep_nested_group_1.id, deep_nested_group_2.id) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
index 013c9b61b99..a4243db6bc9 100644
--- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -330,3 +330,18 @@ RSpec.shared_examples 'project policies as admin without admin mode' do
end
end
end
+
+RSpec.shared_examples 'package access with repository disabled' do
+ context 'when repository is disabled' do
+ before do
+ project.project_feature.update!(
+ # Disable merge_requests and builds as well, since merge_requests and
+ # builds cannot have higher visibility than repository.
+ merge_requests_access_level: ProjectFeature::DISABLED,
+ builds_access_level: ProjectFeature::DISABLED,
+ repository_access_level: ProjectFeature::DISABLED)
+ end
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
index 1f68dd7a382..a3ed74085fb 100644
--- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
@@ -4,7 +4,7 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_
include_context 'workhorse headers'
before do
- stub_feature_flags(debian_packages: true)
+ stub_feature_flags(debian_packages: true, debian_group_packages: true)
end
let_it_be(:private_container, freeze: can_freeze) { create(container_type, :private) }
@@ -29,6 +29,8 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_
let_it_be(:public_project) { create(:project, :public, group: public_container) }
let_it_be(:private_project_distribution) { create(:debian_project_distribution, container: private_project, codename: 'existing-codename') }
let_it_be(:public_project_distribution) { create(:debian_project_distribution, container: public_project, codename: 'existing-codename') }
+
+ let(:project) { { private: private_project, public: public_project }[visibility_level] }
else
let_it_be(:private_project) { private_container }
let_it_be(:public_project) { public_container }
@@ -45,12 +47,8 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_
let(:architecture) { { private: private_architecture, public: public_architecture }[visibility_level] }
let(:component) { { private: private_component, public: public_component }[visibility_level] }
let(:component_file) { { private: private_component_file, public: public_component_file }[visibility_level] }
-
- let(:source_package) { 'sample' }
- let(:letter) { source_package[0..2] == 'lib' ? source_package[0..3] : source_package[0] }
- let(:package_name) { 'libsample0' }
- let(:package_version) { '1.2.3~alpha2' }
- let(:file_name) { "#{package_name}_#{package_version}_#{architecture.name}.deb" }
+ let(:package) { { private: private_package, public: public_package }[visibility_level] }
+ let(:letter) { package.name[0..2] == 'lib' ? package.name[0..3] : package.name[0] }
let(:method) { :get }
@@ -94,6 +92,10 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_
end
end
+RSpec.shared_context 'with file_name' do |file_name|
+ let(:file_name) { file_name }
+end
+
RSpec.shared_context 'Debian repository auth headers' do |user_role, user_token, auth_method = :token|
let(:token) { user_token ? personal_access_token.token : 'wrong' }
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index c15c59e1a1d..0390e60747f 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -46,6 +46,8 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
end
shared_examples 'handling all conditions' do
+ include_context 'dependency proxy helpers context'
+
where(:auth, :package_name_type, :request_forward, :visibility, :user_role, :expected_result, :expected_status) do
nil | :scoped_naming_convention | true | :public | nil | :accept | :ok
nil | :scoped_naming_convention | false | :public | nil | :accept | :ok
@@ -243,7 +245,7 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
project.send("add_#{user_role}", user) if user_role
project.update!(visibility: visibility.to_s)
package.update!(name: package_name) unless package_name == 'non-existing-package'
- stub_application_setting(npm_package_requests_forwarding: request_forward)
+ allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
end
example_name = "#{params[:expected_result]} metadata request"
diff --git a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb
index e6b3dc74b74..86b6975bf9f 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb
@@ -10,9 +10,10 @@ end
RSpec.shared_examples 'accept package tags request' do |status:|
using RSpec::Parameterized::TableSyntax
+ include_context 'dependency proxy helpers context'
before do
- stub_application_setting(npm_package_requests_forwarding: false)
+ allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: false)
end
context 'with valid package name' do
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
index 8a351226123..ed6d9ed43c8 100644
--- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -228,6 +228,35 @@ RSpec.shared_examples 'pypi simple API endpoint' do
it_behaves_like 'PyPI package versions', :developer, :success
end
+
+ context 'package request forward' do
+ include_context 'dependency proxy helpers context'
+
+ where(:forward, :package_in_project, :shared_examples_name, :expected_status) do
+ true | true | 'PyPI package versions' | :success
+ true | false | 'process PyPI api request' | :redirect
+ false | true | 'PyPI package versions' | :success
+ false | false | 'process PyPI api request' | :not_found
+ end
+
+ with_them do
+ let_it_be(:package) { create(:pypi_package, project: project, name: 'foobar') }
+
+ let(:package_name) do
+ if package_in_project
+ 'foobar'
+ else
+ 'barfoo'
+ end
+ end
+
+ before do
+ allow_fetch_application_setting(attribute: "pypi_package_requests_forwarding", return_value: forward)
+ end
+
+ it_behaves_like params[:shared_examples_name], :reporter, params[:expected_status]
+ end
+ end
end
RSpec.shared_examples 'pypi file download endpoint' do
diff --git a/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb b/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb
index afc902dd184..104e91add8b 100644
--- a/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb
@@ -128,17 +128,25 @@ RSpec.shared_examples 'time tracking endpoints' do |issuable_name|
if issuable_name == 'merge_request'
it 'calls update service with :use_specialized_service param' do
- expect(::MergeRequests::UpdateService).to receive(:new).with(project: project, current_user: user, params: hash_including(use_specialized_service: true))
-
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '2h' }
+ expect(::MergeRequests::UpdateService).to receive(:new).with(
+ project: project,
+ current_user: user,
+ params: hash_including(
+ use_specialized_service: true,
+ spend_time: hash_including(duration: 7200, summary: 'summary')))
+
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '2h', summary: 'summary' }
end
end
if issuable_name == 'issue'
it 'calls update service without :use_specialized_service param' do
- expect(::Issues::UpdateService).to receive(:new).with(project: project, current_user: user, params: hash_not_including(use_specialized_service: true))
+ expect(::Issues::UpdateService).to receive(:new).with(
+ project: project,
+ current_user: user,
+ params: { spend_time: { duration: 3600, summary: 'summary', user_id: user.id } })
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '2h' }
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '1h', summary: 'summary' }
end
end
end
diff --git a/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb b/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb
index 7608f1c7f8a..32adf98969c 100644
--- a/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb
+++ b/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb
@@ -63,3 +63,19 @@ end
RSpec.shared_examples 'diff file discussion entity' do
it_behaves_like 'diff file base entity'
end
+
+RSpec.shared_examples 'diff file with conflict_type' do
+ describe '#conflict_type' do
+ it 'returns nil by default' do
+ expect(subject[:conflict_type]).to be_nil
+ end
+
+ context 'when there is matching conflict file' do
+ let(:options) { { conflicts: { diff_file.new_path => double(diff_lines_for_serializer: [], conflict_type: :both_modified) } } }
+
+ it 'returns false' do
+ expect(subject[:conflict_type]).to eq(:both_modified)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb b/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb
index 7d4fbeea0dc..d9b837258ce 100644
--- a/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb
@@ -100,8 +100,8 @@ RSpec.shared_examples 'issues move service' do |group|
create(:labeled_issue, project: project, labels: [bug, development], assignees: [assignee])
end
- it 'returns false' do
- expect(described_class.new(parent, user, params).execute(issue)).to eq false
+ it 'returns nil' do
+ expect(described_class.new(parent, user, params).execute(issue)).to be_nil
end
it 'keeps issues labels' do
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index eafcbd77040..f6e25ee6647 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -69,6 +69,10 @@ RSpec.shared_examples 'a browsable' do
end
RSpec.shared_examples 'an accessible' do
+ before do
+ stub_feature_flags(container_registry_migration_phase1: false)
+ end
+
let(:access) do
[{ 'type' => 'repository',
'name' => project.full_path,
@@ -203,9 +207,7 @@ RSpec.shared_examples 'a container registry auth service' do
end
end
- context 'for private project' do
- let_it_be(:project) { create(:project) }
-
+ shared_examples 'private project' do
context 'allow to use scope-less authentication' do
it_behaves_like 'a valid token'
end
@@ -345,8 +347,20 @@ RSpec.shared_examples 'a container registry auth service' do
end
end
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
+ context 'for private project' do
+ let_it_be_with_reload(:project) { create(:project) }
+
+ it_behaves_like 'private project'
+ end
+
+ context 'for public project with private container registry' do
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_private) }
+
+ it_behaves_like 'private project'
+ end
+
+ context 'for public project with container_registry `enabled`' do
+ let_it_be(:project) { create(:project, :public, :container_registry_enabled) }
context 'allow anyone to pull images' do
let(:current_params) do
@@ -394,8 +408,8 @@ RSpec.shared_examples 'a container registry auth service' do
end
end
- context 'for internal project' do
- let_it_be(:project) { create(:project, :internal) }
+ context 'for internal project with container_registry `enabled`' do
+ let_it_be(:project) { create(:project, :internal, :container_registry_enabled) }
context 'for internal user' do
context 'allow anyone to pull images' do
@@ -470,6 +484,12 @@ RSpec.shared_examples 'a container registry auth service' do
end
end
end
+
+ context 'for internal project with private container registry' do
+ let_it_be_with_reload(:project) { create(:project, :internal, :container_registry_private) }
+
+ it_behaves_like 'private project'
+ end
end
context 'delete authorized as maintainer' do
@@ -630,12 +650,8 @@ RSpec.shared_examples 'a container registry auth service' do
end
end
- context 'for project with private container registry' do
- let_it_be(:project, reload: true) { create(:project, :public) }
-
- before do
- project.project_feature.update!(container_registry_access_level: ProjectFeature::PRIVATE)
- end
+ context 'for public project with private container registry' do
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_private) }
it_behaves_like 'pullable for being team member'
@@ -675,11 +691,7 @@ RSpec.shared_examples 'a container registry auth service' do
end
context 'for project without container registry' do
- let_it_be(:project) { create(:project, :public, container_registry_enabled: false) }
-
- before do
- project.update!(container_registry_enabled: false)
- end
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_disabled) }
context 'disallow when pulling' do
let(:current_params) do
@@ -719,12 +731,16 @@ RSpec.shared_examples 'a container registry auth service' do
context 'support for multiple scopes' do
let_it_be(:internal_project) { create(:project, :internal) }
let_it_be(:private_project) { create(:project, :private) }
+ let_it_be(:public_project) { create(:project, :public) }
+ let_it_be(:public_project_private_container_registry) { create(:project, :public, :container_registry_private) }
let(:current_params) do
{
scopes: [
"repository:#{internal_project.full_path}:pull",
- "repository:#{private_project.full_path}:pull"
+ "repository:#{private_project.full_path}:pull",
+ "repository:#{public_project.full_path}:pull",
+ "repository:#{public_project_private_container_registry.full_path}:pull"
]
}
end
@@ -744,13 +760,19 @@ RSpec.shared_examples 'a container registry auth service' do
'actions' => ['pull'] },
{ 'type' => 'repository',
'name' => private_project.full_path,
+ 'actions' => ['pull'] },
+ { 'type' => 'repository',
+ 'name' => public_project.full_path,
+ 'actions' => ['pull'] },
+ { 'type' => 'repository',
+ 'name' => public_project_private_container_registry.full_path,
'actions' => ['pull'] }
]
end
end
end
- context 'user only has access to internal project' do
+ context 'user only has access to internal and public projects' do
let_it_be(:current_user) { create(:user) }
it_behaves_like 'a browsable' do
@@ -758,16 +780,35 @@ RSpec.shared_examples 'a container registry auth service' do
[
{ 'type' => 'repository',
'name' => internal_project.full_path,
+ 'actions' => ['pull'] },
+ { 'type' => 'repository',
+ 'name' => public_project.full_path,
'actions' => ['pull'] }
]
end
end
end
- context 'anonymous access is rejected' do
+ context 'anonymous user has access only to public project' do
let(:current_user) { nil }
- it_behaves_like 'a forbidden'
+ it_behaves_like 'a browsable' do
+ let(:access) do
+ [
+ { 'type' => 'repository',
+ 'name' => public_project.full_path,
+ 'actions' => ['pull'] }
+ ]
+ end
+ end
+
+ context 'with no public container registry' do
+ before do
+ public_project.project_feature.update_column(:container_registry_access_level, ProjectFeature::PRIVATE)
+ end
+
+ it_behaves_like 'a forbidden'
+ end
end
end
@@ -796,8 +837,8 @@ RSpec.shared_examples 'a container registry auth service' do
it_behaves_like 'a forbidden'
end
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
+ context 'for public project with container registry `enabled`' do
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_enabled) }
context 'when pulling and pushing' do
let(:current_params) do
@@ -818,6 +859,19 @@ RSpec.shared_examples 'a container registry auth service' do
end
end
+ context 'for public project with container registry `private`' do
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_private) }
+
+ context 'when pulling and pushing' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull,push"] }
+ end
+
+ it_behaves_like 'a forbidden'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
context 'for registry catalog' do
let(:current_params) do
{ scopes: ["registry:catalog:*"] }
@@ -830,15 +884,15 @@ RSpec.shared_examples 'a container registry auth service' do
context 'for deploy tokens' do
let(:current_params) do
- { scopes: ["repository:#{project.full_path}:pull"] }
+ { scopes: ["repository:#{project.full_path}:pull"], deploy_token: deploy_token }
end
context 'when deploy token has read and write registry as scopes' do
- let(:current_user) { create(:deploy_token, write_registry: true, projects: [project]) }
+ let(:deploy_token) { create(:deploy_token, write_registry: true, projects: [project]) }
shared_examples 'able to login' do
context 'registry provides read_container_image authentication_abilities' do
- let(:current_params) { {} }
+ let(:current_params) { { deploy_token: deploy_token } }
let(:authentication_abilities) { [:read_container_image] }
it_behaves_like 'an authenticated'
@@ -854,7 +908,7 @@ RSpec.shared_examples 'a container registry auth service' do
context 'when pushing' do
let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
+ { scopes: ["repository:#{project.full_path}:push"], deploy_token: deploy_token }
end
it_behaves_like 'a pushable'
@@ -872,7 +926,7 @@ RSpec.shared_examples 'a container registry auth service' do
context 'when pushing' do
let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
+ { scopes: ["repository:#{project.full_path}:push"], deploy_token: deploy_token }
end
it_behaves_like 'a pushable'
@@ -890,7 +944,25 @@ RSpec.shared_examples 'a container registry auth service' do
context 'when pushing' do
let(:current_params) do
- { scopes: ["repository:#{project.full_path}:push"] }
+ { scopes: ["repository:#{project.full_path}:push"], deploy_token: deploy_token }
+ end
+
+ it_behaves_like 'a pushable'
+ end
+
+ it_behaves_like 'able to login'
+ end
+
+ context 'for public project with private container registry' do
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_private) }
+
+ context 'when pulling' do
+ it_behaves_like 'a pullable'
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:push"], deploy_token: deploy_token }
end
it_behaves_like 'a pushable'
@@ -901,26 +973,26 @@ RSpec.shared_examples 'a container registry auth service' do
end
context 'when deploy token does not have read_registry scope' do
- let(:current_user) { create(:deploy_token, projects: [project], read_registry: false) }
+ let(:deploy_token) do
+ create(:deploy_token, projects: [project], read_registry: false)
+ end
shared_examples 'unable to login' do
context 'registry provides no container authentication_abilities' do
- let(:current_params) { {} }
let(:authentication_abilities) { [] }
it_behaves_like 'a forbidden'
end
context 'registry provides inapplicable container authentication_abilities' do
- let(:current_params) { {} }
let(:authentication_abilities) { [:download_code] }
it_behaves_like 'a forbidden'
end
end
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
+ context 'for public project with container registry `enabled`' do
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_enabled) }
context 'when pulling' do
it_behaves_like 'a pullable'
@@ -929,6 +1001,16 @@ RSpec.shared_examples 'a container registry auth service' do
it_behaves_like 'unable to login'
end
+ context 'for public project with container registry `private`' do
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_private) }
+
+ context 'when pulling' do
+ it_behaves_like 'an inaccessible'
+ end
+
+ it_behaves_like 'unable to login'
+ end
+
context 'for internal project' do
let_it_be(:project) { create(:project, :internal) }
@@ -958,16 +1040,24 @@ RSpec.shared_examples 'a container registry auth service' do
end
context 'when deploy token is not related to the project' do
- let_it_be(:current_user) { create(:deploy_token, read_registry: false) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_registry: false) }
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
+ context 'for public project with container registry `enabled`' do
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_enabled) }
context 'when pulling' do
it_behaves_like 'a pullable'
end
end
+ context 'for public project with container registry `private`' do
+ let_it_be_with_reload(:project) { create(:project, :public, :container_registry_private) }
+
+ context 'when pulling' do
+ it_behaves_like 'an inaccessible'
+ end
+ end
+
context 'for internal project' do
let_it_be(:project) { create(:project, :internal) }
@@ -986,14 +1076,20 @@ RSpec.shared_examples 'a container registry auth service' do
end
context 'when deploy token has been revoked' do
- let(:current_user) { create(:deploy_token, :revoked, projects: [project]) }
+ let(:deploy_token) { create(:deploy_token, :revoked, projects: [project]) }
- context 'for public project' do
- let_it_be(:project) { create(:project, :public) }
+ context 'for public project with container registry `enabled`' do
+ let_it_be(:project) { create(:project, :public, :container_registry_enabled) }
it_behaves_like 'a pullable'
end
+ context 'for public project with container registry `private`' do
+ let_it_be(:project) { create(:project, :public, :container_registry_private) }
+
+ it_behaves_like 'an inaccessible'
+ end
+
context 'for internal project' do
let_it_be(:project) { create(:project, :internal) }
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
new file mode 100644
index 00000000000..56a6d24d557
--- /dev/null
+++ b/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a service that handles Jira API errors' do
+ include AfterNextHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ where(:exception_class, :exception_message, :expected_message) do
+ Errno::ECONNRESET | '' | 'A connection error occurred'
+ Errno::ECONNREFUSED | '' | 'A connection error occurred'
+ Errno::ETIMEDOUT | '' | 'A timeout error occurred'
+ Timeout::Error | '' | 'A timeout error occurred'
+ URI::InvalidURIError | '' | 'The Jira API URL'
+ SocketError | '' | 'The Jira API URL'
+ OpenSSL::SSL::SSLError | 'foo' | 'An SSL error occurred while connecting to Jira: foo'
+ JIRA::HTTPError | 'Unauthorized' | 'The credentials for accessing Jira are not valid'
+ JIRA::HTTPError | 'Forbidden' | 'The credentials for accessing Jira are not allowed'
+ JIRA::HTTPError | 'Bad Request' | 'An error occurred while requesting data from Jira'
+ JIRA::HTTPError | 'Foo' | 'An error occurred while requesting data from Jira.'
+ JIRA::HTTPError | '{"errorMessages":["foo","bar"]}' | 'An error occurred while requesting data from Jira: foo and bar'
+ JIRA::HTTPError | '{"errorMessages":[""]}' | 'An error occurred while requesting data from Jira.'
+ end
+
+ with_them do
+ it 'handles the error' do
+ stub_client_and_raise(exception_class, exception_message)
+
+ expect(subject).to be_a(ServiceResponse)
+ expect(subject).to be_error
+ expect(subject.message).to include(expected_message)
+ end
+ end
+
+ context 'when the JSON in JIRA::HTTPError is unsafe' do
+ before do
+ stub_client_and_raise(JIRA::HTTPError, error)
+ end
+
+ context 'when JSON is malformed' do
+ let(:error) { '{"errorMessages":' }
+
+ it 'returns the default error message' do
+ expect(subject.message).to eq('An error occurred while requesting data from Jira. Check your Jira integration configuration and try again.')
+ end
+ end
+
+ context 'when JSON contains tags' do
+ let(:error) { '{"errorMessages":["<script>alert(true)</script>foo"]}' }
+
+ it 'sanitizes it' do
+ expect(subject.message).to eq('An error occurred while requesting data from Jira: foo. Check your Jira integration configuration and try again.')
+ end
+ end
+ end
+
+ it 'allows unknown exception classes to bubble' do
+ stub_client_and_raise(StandardError)
+
+ expect { subject }.to raise_exception(StandardError)
+ end
+
+ 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(subject).to be_error
+ end
+
+ def stub_client_and_raise(exception_class, message = '')
+ # `JIRA::HTTPError` classes take a response from the JIRA API, rather than a `String`.
+ message = double(body: message) if exception_class == JIRA::HTTPError
+
+ allow_next(JIRA::Client).to receive(:get).and_raise(exception_class, message)
+ end
+end
diff --git a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb
index 9ffeba1b1d0..c979fdc2bb0 100644
--- a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb
@@ -1,165 +1,259 @@
# frozen_string_literal: true
RSpec.shared_examples 'Generate Debian Distribution and component files' do
- let_it_be(:component_main) { create("debian_#{container_type}_component", distribution: distribution, name: 'main') }
- let_it_be(:component_contrib) { create("debian_#{container_type}_component", distribution: distribution, name: 'contrib') }
-
- let_it_be(:architecture_all) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'all') }
- let_it_be(:architecture_amd64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'amd64') }
- let_it_be(:architecture_arm64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'arm64') }
-
- let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T08:00:00Z', file_sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', file_md5: 'd41d8cd98f00b204e9800998ecf8427e', file_fixture: nil, size: 0) } # updated
- let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_all, updated_at: '2020-01-24T09:00:00Z', file_sha256: 'a') } # destroyed
- let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_amd64, updated_at: '2020-01-24T10:54:59Z', file_sha256: 'b') } # destroyed, 1 second before last generation
- let_it_be(:component_file4) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'c') } # kept, last generation
- let_it_be(:component_file5) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'd') } # kept, last generation
- let_it_be(:component_file6) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_amd64, updated_at: '2020-01-25T15:17:18Z', file_sha256: 'e') } # kept, less than 1 hour ago
-
- def check_component_file(release_date, component_name, component_file_type, architecture_name, expected_content)
- component_file = distribution
- .component_files
- .with_component_name(component_name)
- .with_file_type(component_file_type)
- .with_architecture_name(architecture_name)
- .order_updated_asc
- .last
-
- expect(component_file).not_to be_nil
- expect(component_file.updated_at).to eq(release_date)
-
- unless expected_content.nil?
- component_file.file.use_file do |file_path|
- expect(File.read(file_path)).to eq(expected_content)
- end
+ def check_release_files(expected_release_content)
+ distribution.reload
+
+ distribution.file.use_file do |file_path|
+ expect(File.read(file_path)).to eq(expected_release_content)
+ end
+
+ expect(distribution.file_signature).to start_with("-----BEGIN PGP SIGNATURE-----\n")
+ expect(distribution.file_signature).to end_with("\n-----END PGP SIGNATURE-----\n")
+
+ distribution.signed_file.use_file do |file_path|
+ expect(File.read(file_path)).to start_with("-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA512\n\n#{expected_release_content}-----BEGIN PGP SIGNATURE-----\n")
+ expect(File.read(file_path)).to end_with("\n-----END PGP SIGNATURE-----\n")
end
end
- it 'generates Debian distribution and component files', :aggregate_failures do
- current_time = Time.utc(2020, 01, 25, 15, 17, 18, 123456)
-
- travel_to(current_time) do
- expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
-
- initial_count = 6
- destroyed_count = 2
- # updated_count = 1
- created_count = 5
-
- expect { subject }
- .to not_change { Packages::Package.count }
- .and not_change { Packages::PackageFile.count }
- .and change { distribution.reload.updated_at }.to(current_time.round)
- .and change { distribution.component_files.reset.count }.from(initial_count).to(initial_count - destroyed_count + created_count)
- .and change { component_file1.reload.updated_at }.to(current_time.round)
-
- debs = package.package_files.with_debian_file_type(:deb).preload_debian_file_metadata.to_a
- pool_prefix = "pool/unstable/#{project.id}/p/#{package.name}"
- expected_main_amd64_content = <<~EOF
- Package: libsample0
- Source: #{package.name}
- Version: #{package.version}
- Installed-Size: 7
- Maintainer: #{debs[0].debian_fields['Maintainer']}
- Architecture: amd64
- Description: Some mostly empty lib
- Used in GitLab tests.
- .
- Testing another paragraph.
- Multi-Arch: same
- Homepage: #{debs[0].debian_fields['Homepage']}
- Section: libs
- Priority: optional
- Filename: #{pool_prefix}/libsample0_1.2.3~alpha2_amd64.deb
- Size: 409600
- MD5sum: #{debs[0].file_md5}
- SHA256: #{debs[0].file_sha256}
-
- Package: sample-dev
- Source: #{package.name} (#{package.version})
- Version: 1.2.3~binary
- Installed-Size: 7
- Maintainer: #{debs[1].debian_fields['Maintainer']}
- Architecture: amd64
- Depends: libsample0 (= 1.2.3~binary)
- Description: Some mostly empty development files
- Used in GitLab tests.
- .
- Testing another paragraph.
- Multi-Arch: same
- Homepage: #{debs[1].debian_fields['Homepage']}
- Section: libdevel
- Priority: optional
- Filename: #{pool_prefix}/sample-dev_1.2.3~binary_amd64.deb
- Size: 409600
- MD5sum: #{debs[1].file_md5}
- SHA256: #{debs[1].file_sha256}
- EOF
-
- check_component_file(current_time.round, 'main', :packages, 'all', nil)
- check_component_file(current_time.round, 'main', :packages, 'amd64', expected_main_amd64_content)
- check_component_file(current_time.round, 'main', :packages, 'arm64', nil)
-
- check_component_file(current_time.round, 'contrib', :packages, 'all', nil)
- check_component_file(current_time.round, 'contrib', :packages, 'amd64', nil)
- check_component_file(current_time.round, 'contrib', :packages, 'arm64', nil)
-
- main_amd64_size = expected_main_amd64_content.length
- main_amd64_md5sum = Digest::MD5.hexdigest(expected_main_amd64_content)
- main_amd64_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_content)
-
- contrib_all_size = component_file1.size
- contrib_all_md5sum = component_file1.file_md5
- contrib_all_sha256 = component_file1.file_sha256
-
- expected_release_content = <<~EOF
- Codename: unstable
- Date: Sat, 25 Jan 2020 15:17:18 +0000
- Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000
- Architectures: all amd64 arm64
- Components: contrib main
- MD5Sum:
- #{contrib_all_md5sum} #{contrib_all_size} contrib/binary-all/Packages
- d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-amd64/Packages
- d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages
- d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages
- #{main_amd64_md5sum} #{main_amd64_size} main/binary-amd64/Packages
- d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages
- SHA256:
- #{contrib_all_sha256} #{contrib_all_size} contrib/binary-all/Packages
- e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages
- e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
- e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
- #{main_amd64_sha256} #{main_amd64_size} main/binary-amd64/Packages
- e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages
- EOF
-
- distribution.file.use_file do |file_path|
- expect(File.read(file_path)).to eq(expected_release_content)
+ context 'with Debian components and architectures' do
+ let_it_be(:component_main) { create("debian_#{container_type}_component", distribution: distribution, name: 'main') }
+ let_it_be(:component_contrib) { create("debian_#{container_type}_component", distribution: distribution, name: 'contrib') }
+
+ let_it_be(:architecture_all) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'all') }
+ let_it_be(:architecture_amd64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'amd64') }
+ let_it_be(:architecture_arm64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'arm64') }
+
+ let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T08:00:00Z', file_sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', file_md5: 'd41d8cd98f00b204e9800998ecf8427e', file_fixture: nil, size: 0) } # updated
+ let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_all, updated_at: '2020-01-24T09:00:00Z', file_sha256: 'a') } # destroyed
+ let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_amd64, updated_at: '2020-01-24T10:54:59Z', file_sha256: 'b') } # destroyed, 1 second before last generation
+ let_it_be(:component_file4) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'c') } # kept, last generation
+ let_it_be(:component_file5) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'd') } # kept, last generation
+ let_it_be(:component_file6) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_amd64, updated_at: '2020-01-25T15:17:18Z', file_sha256: 'e') } # kept, less than 1 hour ago
+
+ def check_component_file(release_date, component_name, component_file_type, architecture_name, expected_content)
+ component_file = distribution
+ .component_files
+ .with_component_name(component_name)
+ .with_file_type(component_file_type)
+ .with_architecture_name(architecture_name)
+ .order_updated_asc
+ .last
+
+ expect(component_file).not_to be_nil
+ expect(component_file.updated_at).to eq(release_date)
+
+ unless expected_content.nil?
+ component_file.file.use_file do |file_path|
+ expect(File.read(file_path)).to eq(expected_content)
+ end
end
end
+
+ it 'generates Debian distribution and component files', :aggregate_failures do
+ current_time = Time.utc(2020, 01, 25, 15, 17, 18, 123456)
+
+ travel_to(current_time) do
+ expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
+
+ components_count = 2
+ architectures_count = 3
+
+ initial_count = 6
+ destroyed_count = 2
+ updated_count = 1
+ created_count = components_count * (architectures_count * 2 + 1) - updated_count
+
+ expect { subject }
+ .to not_change { Packages::Package.count }
+ .and not_change { Packages::PackageFile.count }
+ .and change { distribution.reload.updated_at }.to(current_time.round)
+ .and change { distribution.component_files.reset.count }.from(initial_count).to(initial_count - destroyed_count + created_count)
+ .and change { component_file1.reload.updated_at }.to(current_time.round)
+
+ package_files = package.package_files.order(id: :asc).preload_debian_file_metadata.to_a
+ pool_prefix = 'pool/unstable'
+ pool_prefix += "/#{project.id}" if container_type == :group
+ pool_prefix += "/p/#{package.name}/#{package.version}"
+ expected_main_amd64_content = <<~EOF
+ Package: libsample0
+ Source: #{package.name}
+ Version: #{package.version}
+ Installed-Size: 7
+ Maintainer: #{package_files[2].debian_fields['Maintainer']}
+ Architecture: amd64
+ Description: Some mostly empty lib
+ Used in GitLab tests.
+ .
+ Testing another paragraph.
+ Multi-Arch: same
+ Homepage: #{package_files[2].debian_fields['Homepage']}
+ Section: libs
+ Priority: optional
+ Filename: #{pool_prefix}/libsample0_1.2.3~alpha2_amd64.deb
+ Size: 409600
+ MD5sum: #{package_files[2].file_md5}
+ SHA256: #{package_files[2].file_sha256}
+
+ Package: sample-dev
+ Source: #{package.name} (#{package.version})
+ Version: 1.2.3~binary
+ Installed-Size: 7
+ Maintainer: #{package_files[3].debian_fields['Maintainer']}
+ Architecture: amd64
+ Depends: libsample0 (= 1.2.3~binary)
+ Description: Some mostly empty development files
+ Used in GitLab tests.
+ .
+ Testing another paragraph.
+ Multi-Arch: same
+ Homepage: #{package_files[3].debian_fields['Homepage']}
+ Section: libdevel
+ Priority: optional
+ Filename: #{pool_prefix}/sample-dev_1.2.3~binary_amd64.deb
+ Size: 409600
+ MD5sum: #{package_files[3].file_md5}
+ SHA256: #{package_files[3].file_sha256}
+ EOF
+
+ expected_main_amd64_di_content = <<~EOF
+ Section: misc
+ Priority: extra
+ Filename: #{pool_prefix}/sample-udeb_1.2.3~alpha2_amd64.udeb
+ Size: 409600
+ MD5sum: #{package_files[4].file_md5}
+ SHA256: #{package_files[4].file_sha256}
+ EOF
+
+ expected_main_source_content = <<~EOF
+ Package: #{package.name}
+ Binary: sample-dev, libsample0, sample-udeb
+ Version: #{package.version}
+ Maintainer: #{package_files[1].debian_fields['Maintainer']}
+ Build-Depends: debhelper-compat (= 13)
+ Architecture: any
+ Standards-Version: 4.5.0
+ Format: 3.0 (native)
+ Files:
+ #{package_files[1].file_md5} #{package_files[1].size} #{package_files[1].file_name}
+ d5ca476e4229d135a88f9c729c7606c9 864 sample_1.2.3~alpha2.tar.xz
+ Checksums-Sha256:
+ #{package_files[1].file_sha256} #{package_files[1].size} #{package_files[1].file_name}
+ 40e4682bb24a73251ccd7c7798c0094a649091e5625d6a14bcec9b4e7174f3da 864 sample_1.2.3~alpha2.tar.xz
+ Checksums-Sha1:
+ #{package_files[1].file_sha1} #{package_files[1].size} #{package_files[1].file_name}
+ c5cfc111ea924842a89a06d5673f07dfd07de8ca 864 sample_1.2.3~alpha2.tar.xz
+ Homepage: #{package_files[1].debian_fields['Homepage']}
+ Section: misc
+ Priority: extra
+ Directory: #{pool_prefix}
+ EOF
+
+ check_component_file(current_time.round, 'main', :packages, 'all', nil)
+ check_component_file(current_time.round, 'main', :packages, 'amd64', expected_main_amd64_content)
+ check_component_file(current_time.round, 'main', :packages, 'arm64', nil)
+
+ check_component_file(current_time.round, 'main', :di_packages, 'all', nil)
+ check_component_file(current_time.round, 'main', :di_packages, 'amd64', expected_main_amd64_di_content)
+ check_component_file(current_time.round, 'main', :di_packages, 'arm64', nil)
+
+ check_component_file(current_time.round, 'main', :source, nil, expected_main_source_content)
+
+ check_component_file(current_time.round, 'contrib', :packages, 'all', nil)
+ check_component_file(current_time.round, 'contrib', :packages, 'amd64', nil)
+ check_component_file(current_time.round, 'contrib', :packages, 'arm64', nil)
+
+ check_component_file(current_time.round, 'contrib', :di_packages, 'all', nil)
+ check_component_file(current_time.round, 'contrib', :di_packages, 'amd64', nil)
+ check_component_file(current_time.round, 'contrib', :di_packages, 'arm64', nil)
+
+ check_component_file(current_time.round, 'contrib', :source, nil, nil)
+
+ main_amd64_size = expected_main_amd64_content.length
+ main_amd64_md5sum = Digest::MD5.hexdigest(expected_main_amd64_content)
+ main_amd64_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_content)
+
+ contrib_all_size = component_file1.size
+ contrib_all_md5sum = component_file1.file_md5
+ contrib_all_sha256 = component_file1.file_sha256
+
+ main_amd64_di_size = expected_main_amd64_di_content.length
+ main_amd64_di_md5sum = Digest::MD5.hexdigest(expected_main_amd64_di_content)
+ main_amd64_di_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_di_content)
+
+ main_source_size = expected_main_source_content.length
+ main_source_md5sum = Digest::MD5.hexdigest(expected_main_source_content)
+ main_source_sha256 = Digest::SHA256.hexdigest(expected_main_source_content)
+
+ expected_release_content = <<~EOF
+ Codename: unstable
+ Date: Sat, 25 Jan 2020 15:17:18 +0000
+ Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000
+ Architectures: all amd64 arm64
+ Components: contrib main
+ MD5Sum:
+ #{contrib_all_md5sum} #{contrib_all_size} contrib/binary-all/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-all/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-amd64/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-amd64/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-arm64/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/source/Source
+ d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-all/Packages
+ #{main_amd64_md5sum} #{main_amd64_size} main/binary-amd64/Packages
+ #{main_amd64_di_md5sum} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-arm64/Packages
+ #{main_source_md5sum} #{main_source_size} main/source/Source
+ SHA256:
+ #{contrib_all_sha256} #{contrib_all_size} contrib/binary-all/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-arm64/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Source
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages
+ #{main_amd64_sha256} #{main_amd64_size} main/binary-amd64/Packages
+ #{main_amd64_di_sha256} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages
+ #{main_source_sha256} #{main_source_size} main/source/Source
+ EOF
+
+ check_release_files(expected_release_content)
+ end
+
+ create_list(:debian_package, 10, project: project, published_in: project_distribution)
+ control_count = ActiveRecord::QueryRecorder.new { subject2 }.count
+
+ create_list(:debian_package, 10, project: project, published_in: project_distribution)
+ expect { subject3 }.not_to exceed_query_limit(control_count)
+ end
end
-end
-RSpec.shared_examples 'Generate minimal Debian Distribution' do
- it 'generates minimal distribution', :aggregate_failures do
- travel_to(Time.utc(2020, 01, 25, 15, 17, 18, 123456)) do
- expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
-
- expect { subject }
- .to not_change { Packages::Package.count }
- .and not_change { Packages::PackageFile.count }
- .and not_change { distribution.component_files.reset.count }
-
- expected_release_content = <<~EOF
- Codename: unstable
- Date: Sat, 25 Jan 2020 15:17:18 +0000
- Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000
- MD5Sum:
- SHA256:
- EOF
-
- distribution.file.use_file do |file_path|
- expect(File.read(file_path)).to eq(expected_release_content)
+ context 'without components and architectures' do
+ it 'generates minimal distribution', :aggregate_failures do
+ travel_to(Time.utc(2020, 01, 25, 15, 17, 18, 123456)) do
+ expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
+
+ expect { subject }
+ .to not_change { Packages::Package.count }
+ .and not_change { Packages::PackageFile.count }
+ .and not_change { distribution.component_files.reset.count }
+
+ expected_release_content = <<~EOF
+ Codename: unstable
+ Date: Sat, 25 Jan 2020 15:17:18 +0000
+ Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000
+ MD5Sum:
+ SHA256:
+ EOF
+
+ check_release_files(expected_release_content)
end
end
end