summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples/requests
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-12-20 14:22:11 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-12-20 14:22:11 +0000
commit0c872e02b2c822e3397515ec324051ff540f0cd5 (patch)
treece2fb6ce7030e4dad0f4118d21ab6453e5938cdd /spec/support/shared_examples/requests
parentf7e05a6853b12f02911494c4b3fe53d9540d74fc (diff)
downloadgitlab-ce-f7d6ced4f57b4920a666336f5a44d098faade2ea.tar.gz
Add latest changes from gitlab-org/gitlab@15-7-stable-eev15.7.0-rc42
Diffstat (limited to 'spec/support/shared_examples/requests')
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb513
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb38
-rw-r--r--spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb165
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb3
14 files changed, 760 insertions, 103 deletions
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index 629d93676eb..c76bc7c3107 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -593,7 +593,7 @@ RSpec.shared_examples 'delete package endpoint' do
project.add_maintainer(user)
end
- it_behaves_like 'a gitlab tracking event', 'API::ConanPackages', 'delete_package'
+ it_behaves_like 'a package tracking event', 'API::ConanPackages', 'delete_package'
it 'deletes a package' do
expect { subject }.to change { Packages::Package.count }.from(2).to(1)
@@ -708,7 +708,7 @@ RSpec.shared_examples 'package file download endpoint' do
context 'tracking the conan_package.tgz download' do
let(:package_file) { package.package_files.find_by(file_name: ::Packages::Conan::FileMetadatum::PACKAGE_BINARY) }
- it_behaves_like 'a gitlab tracking event', 'API::ConanPackages', 'pull_package'
+ it_behaves_like 'a package tracking event', 'API::ConanPackages', 'pull_package'
end
end
@@ -781,7 +781,7 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do
context 'tracking the conan_package.tgz upload' do
let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
- it_behaves_like 'a gitlab tracking event', 'API::ConanPackages', 'push_package'
+ it_behaves_like 'a package tracking event', 'API::ConanPackages', 'push_package'
end
end
@@ -849,12 +849,6 @@ RSpec.shared_examples 'uploads a package file' do
expect(package_file.file_name).to eq(params[:file].original_filename)
end
- it "doesn't attempt to migrate file to object storage" do
- expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
-
- subject
- end
-
context 'with existing package' do
let!(:existing_package) { create(:conan_package, name: 'foo', version: 'bar', project: project) }
@@ -936,8 +930,6 @@ RSpec.shared_examples 'uploads a package file' do
end
end
end
-
- it_behaves_like 'background upload schedules a file migration'
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
index 5469fd80a4f..d4479e462af 100644
--- a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
@@ -1,6 +1,15 @@
# frozen_string_literal: true
RSpec.shared_examples 'graphql issue list request spec' do
+ let(:issue_ids) { graphql_dig_at(issues_data, :id) }
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ #{all_graphql_fields_for('issues'.classify)}
+ }
+ QUERY
+ end
+
it_behaves_like 'a working graphql query' do
before do
post_query
@@ -109,10 +118,57 @@ RSpec.shared_examples 'graphql issue list request spec' do
let(:ids) { issue_ids }
end
end
+
+ context 'when filtering by confidentiality' do
+ context 'when fetching confidential issues' do
+ let(:issue_filter_params) { { confidential: true } }
+
+ it 'returns only confidential issues' do
+ post_query
+
+ expect(issue_ids).to match_array(to_gid_list(confidential_issues))
+ end
+
+ context 'when user cannot see confidential issues' do
+ it 'returns an empty list' do
+ post_query(external_user)
+
+ expect(issue_ids).to be_empty
+ end
+ end
+ end
+
+ context 'when fetching non-confidential issues' do
+ let(:issue_filter_params) { { confidential: false } }
+
+ it 'returns only non-confidential issues' do
+ post_query
+
+ expect(issue_ids).to match_array(to_gid_list(non_confidential_issues))
+ end
+
+ context 'when user cannot see confidential issues' do
+ it 'returns an empty list' do
+ post_query(external_user)
+
+ expect(issue_ids).to match_array(to_gid_list(public_non_confidential_issues))
+ end
+ end
+ end
+ end
end
describe 'sorting and pagination' do
context 'when sorting by severity' do
+ let(:expected_severity_sorted_asc) { [issue_c, issue_a, issue_b, issue_e, issue_d] }
+
+ before_all do
+ create(:issuable_severity, issue: issue_a, severity: :unknown)
+ create(:issuable_severity, issue: issue_b, severity: :low)
+ create(:issuable_severity, issue: issue_d, severity: :critical)
+ create(:issuable_severity, issue: issue_e, severity: :high)
+ end
+
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :SEVERITY_ASC }
@@ -147,6 +203,459 @@ RSpec.shared_examples 'graphql issue list request spec' do
end
end
end
+
+ context 'when sorting by due date' do
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :DUE_DATE_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_due_date_sorted_asc) }
+ end
+ end
+
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :DUE_DATE_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_due_date_sorted_desc) }
+ end
+ end
+ end
+
+ context 'when sorting by relative position' do
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query', is_reversible: true do
+ let(:sort_param) { :RELATIVE_POSITION_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_relative_position_sorted_asc) }
+ end
+ end
+ end
+
+ context 'when sorting by label priority' do
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :LABEL_PRIORITY_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_label_priority_sorted_asc) }
+ end
+ end
+
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :LABEL_PRIORITY_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_label_priority_sorted_desc) }
+ end
+ end
+ end
+
+ context 'when sorting by milestone due date' do
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :MILESTONE_DUE_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_milestone_sorted_asc) }
+ end
+ end
+
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :MILESTONE_DUE_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_milestone_sorted_desc) }
+ end
+ end
+ end
+ end
+
+ describe 'N+1 query checks' do
+ let(:extra_iid_for_second_query) { issue_b.iid.to_s }
+ let(:search_params) { { iids: [issue_a.iid.to_s] } }
+ let(:issue_filter_params) { search_params }
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ id
+ #{requested_fields}
+ }
+ QUERY
+ end
+
+ def execute_query
+ post_query
+ end
+
+ context 'when requesting `user_notes_count` and `user_discussions_count`' do
+ let(:requested_fields) { 'userNotesCount userDiscussionsCount' }
+
+ before do
+ create_list(:note_on_issue, 2, noteable: issue_a, project: issue_a.project)
+ create(:note_on_issue, noteable: issue_b, project: issue_b.project)
+ end
+
+ include_examples 'N+1 query check'
+ end
+
+ context 'when requesting `merge_requests_count`' do
+ let(:requested_fields) { 'mergeRequestsCount' }
+
+ before do
+ create_list(:merge_requests_closing_issues, 2, issue: issue_a)
+ create_list(:merge_requests_closing_issues, 3, issue: issue_b)
+ end
+
+ include_examples 'N+1 query check'
+ end
+
+ context 'when requesting `timelogs`' do
+ let(:requested_fields) { 'timelogs { nodes { timeSpent } }' }
+
+ before do
+ create_list(:issue_timelog, 2, issue: issue_a)
+ create(:issue_timelog, issue: issue_b)
+ end
+
+ include_examples 'N+1 query check'
+ end
+
+ context 'when requesting `closed_as_duplicate_of`' do
+ let(:requested_fields) { 'closedAsDuplicateOf { id }' }
+ let(:issue_a_dup) { create(:issue, project: issue_a.project) }
+ let(:issue_b_dup) { create(:issue, project: issue_b.project) }
+
+ before do
+ issue_a.update!(duplicated_to_id: issue_a_dup)
+ issue_b.update!(duplicated_to_id: issue_a_dup)
+ end
+
+ include_examples 'N+1 query check'
+ end
+
+ context 'when award emoji votes' do
+ let(:requested_fields) { 'upvotes downvotes' }
+
+ before do
+ create_list(:award_emoji, 2, name: 'thumbsup', awardable: issue_a)
+ create_list(:award_emoji, 2, name: 'thumbsdown', awardable: issue_b)
+ end
+
+ include_examples 'N+1 query check'
+ end
+
+ context 'when requesting participants' do
+ let(:search_params) { { iids: [issue_a.iid.to_s, issue_c.iid.to_s] } }
+ let(:requested_fields) { 'participants { nodes { name } }' }
+
+ before do
+ create(:award_emoji, :upvote, awardable: issue_a)
+ create(:award_emoji, :upvote, awardable: issue_b)
+ create(:award_emoji, :upvote, awardable: issue_c)
+
+ note_with_emoji_a = create(:note_on_issue, noteable: issue_a, project: issue_a.project)
+ note_with_emoji_b = create(:note_on_issue, noteable: issue_b, project: issue_b.project)
+ note_with_emoji_c = create(:note_on_issue, noteable: issue_c, project: issue_c.project)
+
+ create(:award_emoji, :upvote, awardable: note_with_emoji_a)
+ create(:award_emoji, :upvote, awardable: note_with_emoji_b)
+ create(:award_emoji, :upvote, awardable: note_with_emoji_c)
+ end
+
+ # Executes 3 extra queries to fetch participant_attrs
+ include_examples 'N+1 query check', threshold: 3
+ end
+
+ context 'when requesting labels', :use_sql_query_cache do
+ let(:requested_fields) { 'labels { nodes { id } }' }
+ let(:extra_iid_for_second_query) { same_project_issue2.iid.to_s }
+ let(:search_params) { { iids: [same_project_issue1.iid.to_s] } }
+
+ before do
+ current_project = same_project_issue1.project
+ project_labels = create_list(:label, 2, project: current_project)
+ group_labels = create_list(:group_label, 2, group: current_project.group)
+
+ same_project_issue1.update!(labels: [project_labels.first, group_labels.first].flatten)
+ same_project_issue2.update!(labels: [project_labels, group_labels].flatten)
+ end
+
+ include_examples 'N+1 query check', skip_cached: false
+ end
+ end
+
+ context 'when confidential issues exist' do
+ context 'when user can see confidential issues' do
+ it 'includes confidential issues' do
+ post_query
+
+ all_issues = confidential_issues + non_confidential_issues
+
+ expect(issue_ids).to match_array(to_gid_list(all_issues))
+ expect(issues_data.pluck('confidential')).to match_array(all_issues.map(&:confidential))
+ end
+ end
+
+ context 'when user cannot see confidential issues' do
+ let(:current_user) { external_user }
+
+ it 'does not include confidential issues' do
+ post_query
+
+ expect(issue_ids).to match_array(to_gid_list(public_non_confidential_issues))
+ end
+ end
+ end
+
+ context 'when limiting the number of results' do
+ let(:issue_limit) { 1 }
+ let(:issue_filter_params) { { first: issue_limit } }
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_query
+ end
+
+ it 'only returns N issues' do
+ expect(issues_data.size).to eq(issue_limit)
+ end
+ end
+
+ context 'when no limit is provided' do
+ let(:issue_limit) { nil }
+
+ it 'returns all issues' do
+ post_query
+
+ expect(issues_data.size).to be > 1
+ end
+ end
+
+ it 'is expected to check permissions on the first issue only' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ # Newest first, we only want to see the newest checked
+ expect(Ability).not_to receive(:allowed?).with(current_user, :read_issue, issues.first)
+
+ post_query
+ end
+ end
+
+ context 'when the user does not have access to the issue' do
+ let(:current_user) { external_user }
+
+ it 'returns no issues' do
+ public_projects.each do |public_project|
+ public_project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+ end
+
+ post_query
+
+ expect(issues_data).to eq([])
+ end
+ end
+
+ context 'when fetching escalation status' do
+ let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue_a) }
+
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ id
+ escalationStatus
+ }
+ QUERY
+ end
+
+ before do
+ issue_a.update_columns(issue_type: Issue.issue_types[:incident])
+ end
+
+ it 'returns the escalation status values' do
+ post_query
+
+ statuses = issues_data.pluck('escalationStatus')
+
+ expect(statuses).to contain_exactly(escalation_status.status_name.upcase.to_s, nil, nil, nil, nil)
+ end
+
+ it 'avoids N+1 queries', :aggregate_failures do
+ control = ActiveRecord::QueryRecorder.new { run_with_clean_state(query, context: { current_user: current_user }) }
+
+ new_incident = create(:incident, project: public_projects.first)
+ create(:incident_management_issuable_escalation_status, issue: new_incident)
+
+ expect { run_with_clean_state(query, context: { current_user: current_user }) }.not_to exceed_query_limit(control)
+ end
+ end
+
+ context 'when fetching alert management alert' do
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ iid
+ alertManagementAlert {
+ title
+ }
+ alertManagementAlerts {
+ nodes {
+ title
+ }
+ }
+ }
+ QUERY
+ end
+
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new { post_query }
+
+ create(:alert_management_alert, :with_incident, project: public_projects.first)
+
+ expect { post_query }.not_to exceed_query_limit(control)
+ end
+
+ it 'returns the alert data' do
+ post_query
+
+ alert_titles = issues_data.map { |issue| issue.dig('alertManagementAlert', 'title') }
+ expected_titles = issues.map { |issue| issue.alert_management_alerts.first&.title }
+
+ expect(alert_titles).to contain_exactly(*expected_titles)
+ end
+
+ it 'returns the alerts data' do
+ post_query
+
+ alert_titles = issues_data.map { |issue| issue.dig('alertManagementAlerts', 'nodes') }
+ expected_titles = issues.map do |issue|
+ issue.alert_management_alerts.map { |alert| { 'title' => alert.title } }
+ end
+
+ expect(alert_titles).to contain_exactly(*expected_titles)
+ end
+ end
+
+ context 'when fetching customer_relations_contacts' do
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ id
+ customerRelationsContacts {
+ nodes {
+ firstName
+ }
+ }
+ }
+ QUERY
+ end
+
+ def clean_state_query
+ run_with_clean_state(query, context: { current_user: current_user })
+ end
+
+ it 'avoids N+1 queries' do
+ create(:issue_customer_relations_contact, :for_issue, issue: issue_a)
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { clean_state_query }
+
+ create(:issue_customer_relations_contact, :for_issue, issue: issue_a)
+
+ expect { clean_state_query }.not_to exceed_all_query_limit(control)
+ end
+ end
+
+ context 'when fetching labels' do
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ id
+ labels {
+ nodes {
+ id
+ }
+ }
+ }
+ QUERY
+ end
+
+ before do
+ issues.each do |issue|
+ # create a label for each issue we have to properly test N+1
+ label = create(:label, project: issue.project)
+ issue.update!(labels: [label])
+ end
+ end
+
+ def response_label_ids(response_data)
+ response_data.map do |node|
+ node['labels']['nodes'].pluck('id')
+ end.flatten
+ end
+
+ def labels_as_global_ids(issues)
+ issues.map(&:labels).flatten.map(&:to_global_id).map(&:to_s)
+ end
+
+ it 'avoids N+1 queries', :aggregate_failures do
+ control = ActiveRecord::QueryRecorder.new { post_query }
+ expect(issues_data.count).to eq(5)
+ expect(response_label_ids(issues_data)).to match_array(labels_as_global_ids(issues))
+
+ public_project = public_projects.first
+ new_issues = issues + [
+ create(:issue, project: public_project, labels: [create(:label, project: public_project)])
+ ]
+
+ expect { post_query }.not_to exceed_query_limit(control)
+
+ expect(issues_data.count).to eq(6)
+ expect(response_label_ids(issues_data)).to match_array(labels_as_global_ids(new_issues))
+ end
+ end
+
+ context 'when fetching assignees' do
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ id
+ assignees {
+ nodes {
+ id
+ }
+ }
+ }
+ QUERY
+ end
+
+ before do
+ issues.each do |issue|
+ # create an assignee for each issue we have to properly test N+1
+ assignee = create(:user)
+ issue.update!(assignees: [assignee])
+ end
+ end
+
+ def response_assignee_ids(response_data)
+ response_data.map do |node|
+ node['assignees']['nodes'].pluck('id')
+ end.flatten
+ end
+
+ def assignees_as_global_ids(issues)
+ issues.map(&:assignees).flatten.map(&:to_global_id).map(&:to_s)
+ end
+
+ it 'avoids N+1 queries', :aggregate_failures do
+ control = ActiveRecord::QueryRecorder.new { post_query }
+ expect(issues_data.count).to eq(5)
+ expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(issues))
+
+ public_project = public_projects.first
+ new_issues = issues + [create(:issue, project: public_project, assignees: [create(:user)])]
+
+ expect { post_query }.not_to exceed_query_limit(control)
+
+ expect(issues_data.count).to eq(6)
+ expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(new_issues))
+ end
end
it 'includes a web_url' do
@@ -167,4 +676,8 @@ RSpec.shared_examples 'graphql issue list request spec' do
def to_gid_list(instance_list)
instance_list.map { |instance| instance.to_gid.to_s }
end
+
+ def issues_data
+ graphql_data.dig(*issue_nodes_path)
+ end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
index fb4aacfd7a9..f5835460a77 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
@@ -62,6 +62,21 @@ RSpec.shared_examples 'group and project packages query' do
it 'returns the count of the packages' do
expect(packages_count).to eq(4)
end
+
+ context '_links' do
+ let_it_be(:errored_package) { create(:maven_package, :error, project: project1) }
+
+ let(:package_web_paths) { graphql_data_at(resource_type, :packages, :nodes, :_links, :web_path) }
+
+ it 'does not contain the web path of errored package' do
+ expect(package_web_paths.compact).to contain_exactly(
+ "/#{project1.full_path}/-/packages/#{npm_package.id}",
+ "/#{project1.full_path}/-/packages/#{maven_package.id}",
+ "/#{project2.full_path}/-/packages/#{debian_package.id}",
+ "/#{project2.full_path}/-/packages/#{composer_package.id}"
+ )
+ end
+ end
end
context 'when the user does not have access to the resource' do
@@ -139,7 +154,7 @@ RSpec.shared_examples 'group and project packages query' do
end
it 'throws an error' do
- expect_graphql_errors_to_include(/Argument \'sort\' on Field \'packages\' has an invalid value/)
+ expect_graphql_errors_to_include(/Argument 'sort' on Field 'packages' has an invalid value/)
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb
index 54cc13fac94..6b4d8cae2ce 100644
--- a/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-RSpec.shared_examples 'perform graphql requests for AccessLevel type objects' do |access_level_kind|
+RSpec.shared_examples 'a GraphQL query for access levels' do |access_level_kind|
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user, maintainer_projects: [project]) }
let_it_be(:variables) { { path: project.full_path } }
- let(:fields) { all_graphql_fields_for("#{access_level_kind.to_s.classify}AccessLevel", max_depth: 2) }
+ let(:fields) { all_graphql_fields_for("#{access_level_kind.to_s.classify}AccessLevel") }
let(:access_levels) { protected_branch.public_send("#{access_level_kind}_access_levels") }
let(:access_levels_count) { access_levels.size }
let(:maintainer_access_level) { access_levels.for_role.first }
@@ -61,17 +61,35 @@ RSpec.shared_examples 'perform graphql requests for AccessLevel type objects' do
create(:protected_branch, "maintainers_can_#{access_level_kind}", project: project)
end
- before do
- post_graphql(query, current_user: current_user, variables: variables)
+ describe 'query' do
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new do
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+ expect_graphql_errors_to_be_empty
+
+ create("protected_branch_#{access_level_kind}_access_level", protected_branch: protected_branch)
+
+ expect do
+ post_graphql(query, current_user: current_user, variables: variables)
+ end.not_to exceed_all_query_limit(control)
+ expect_graphql_errors_to_be_empty
+ end
end
- it_behaves_like 'a working graphql query'
+ describe 'response' do
+ before do
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
- it 'returns all the access level attributes' do
- expect(maintainer_access_level_data['accessLevel']).to eq(maintainer_access_level.access_level)
- expect(maintainer_access_level_data['accessLevelDescription']).to eq(maintainer_access_level.humanize)
- expect(maintainer_access_level_data.dig('group', 'name')).to be_nil
- expect(maintainer_access_level_data.dig('user', 'name')).to be_nil
+ it 'returns all the access level attributes' do
+ expect(maintainer_access_level_data['accessLevel']).to eq(maintainer_access_level.access_level)
+ expect(maintainer_access_level_data['accessLevelDescription']).to eq(maintainer_access_level.humanize)
+ expect(maintainer_access_level_data.dig('group', 'name')).to be_nil
+ expect(maintainer_access_level_data.dig('user', 'name')).to be_nil
+ end
end
end
end
diff --git a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
index 8bf6b162508..7803f0ff04d 100644
--- a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
@@ -156,25 +156,13 @@ RSpec.shared_examples 'process helm upload' do |user_type, status|
end
context 'and direct upload disabled' do
- context 'and background upload disabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: false)
- end
-
- it_behaves_like 'creates helm package files'
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false)
end
- context 'and background upload enabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: true)
- end
-
- it_behaves_like 'creates helm package files'
- end
+ it_behaves_like 'creates helm package files'
end
end
-
- it_behaves_like 'background upload schedules a file migration'
end
end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index 11f9565989f..efe5ed3bcf9 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -159,7 +159,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
expect do
post api(uri, user), params: { body: 'hi!' }
- end.to change(Event, :count).by(1)
+ end.to change { Event.count }.by(1)
end
context 'setting created_at' do
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 85ac2b5e1ea..b55639a6b82 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
@@ -323,6 +323,171 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
end
end
+RSpec.shared_examples 'handling audit request' do |path:, scope: :project|
+ using RSpec::Parameterized::TableSyntax
+
+ let(:headers) { {} }
+ let(:params) do
+ ActiveSupport::Gzip.compress(
+ Gitlab::Json.dump({
+ '@gitlab-org/npm-test': ['1.0.6'],
+ 'call-bind': ['1.0.2']
+ })
+ )
+ end
+
+ let(:default_headers) do
+ { 'HTTP_CONTENT_ENCODING' => 'gzip', 'CONTENT_TYPE' => 'application/json' }
+ end
+
+ subject { post(url, headers: headers.merge(default_headers), params: params) }
+
+ shared_examples 'accept audit request' do |status:|
+ it 'accepts the audit request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to eq([])
+ end
+ end
+
+ shared_examples 'reject audit request' do |status:|
+ it 'rejects the audit request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+
+ shared_examples 'redirect audit request' do |status:|
+ it 'redirects audit request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response.headers['Location']).to eq("https://registry.npmjs.org/-/npm/v1/security/#{path}")
+ end
+ end
+
+ shared_examples 'handling all conditions' do
+ include_context 'dependency proxy helpers context'
+
+ where(:auth, :request_forward, :visibility, :user_role, :expected_result, :expected_status) do
+ nil | true | :public | nil | :reject | :unauthorized
+ nil | false | :public | nil | :reject | :unauthorized
+ nil | true | :private | nil | :reject | :unauthorized
+ nil | false | :private | nil | :reject | :unauthorized
+ nil | true | :internal | nil | :reject | :unauthorized
+ nil | false | :internal | nil | :reject | :unauthorized
+
+ :oauth | true | :public | :guest | :redirect | :temporary_redirect
+ :oauth | true | :public | :reporter | :redirect | :temporary_redirect
+ :oauth | false | :public | :guest | :accept | :ok
+ :oauth | false | :public | :reporter | :accept | :ok
+ :oauth | true | :private | :reporter | :redirect | :temporary_redirect
+ :oauth | false | :private | :guest | :reject | :forbidden
+ :oauth | false | :private | :reporter | :accept | :ok
+ :oauth | true | :private | :guest | :redirect | :temporary_redirect
+ :oauth | true | :internal | :guest | :redirect | :temporary_redirect
+ :oauth | true | :internal | :reporter | :redirect | :temporary_redirect
+ :oauth | false | :internal | :guest | :accept | :ok
+ :oauth | false | :internal | :reporter | :accept | :ok
+
+ :personal_access_token | true | :public | :guest | :redirect | :temporary_redirect
+ :personal_access_token | true | :public | :reporter | :redirect | :temporary_redirect
+ :personal_access_token | false | :public | :guest | :accept | :ok
+ :personal_access_token | false | :public | :reporter | :accept | :ok
+ :personal_access_token | true | :private | :guest | :redirect | :temporary_redirect
+ :personal_access_token | true | :private | :reporter | :redirect | :temporary_redirect
+ :personal_access_token | false | :private | :guest | :reject | :forbidden # instance might fail
+ :personal_access_token | false | :private | :reporter | :accept | :ok
+ :personal_access_token | true | :internal | :guest | :redirect | :temporary_redirect
+ :personal_access_token | true | :internal | :reporter | :redirect | :temporary_redirect
+ :personal_access_token | false | :internal | :guest | :accept | :ok
+ :personal_access_token | false | :internal | :reporter | :accept | :ok
+
+ :job_token | true | :public | :developer | :redirect | :temporary_redirect
+ :job_token | false | :public | :developer | :accept | :ok
+ :job_token | true | :private | :developer | :redirect | :temporary_redirect
+ :job_token | false | :private | :developer | :accept | :ok
+ :job_token | true | :internal | :developer | :redirect | :temporary_redirect
+ :job_token | false | :internal | :developer | :accept | :ok
+
+ :deploy_token | true | :public | nil | :redirect | :temporary_redirect
+ :deploy_token | false | :public | nil | :accept | :ok
+ :deploy_token | true | :private | nil | :redirect | :temporary_redirect
+ :deploy_token | false | :private | nil | :accept | :ok
+ :deploy_token | true | :internal | nil | :redirect | :temporary_redirect
+ :deploy_token | false | :internal | nil | :accept | :ok
+ end
+
+ with_them do
+ let(:headers) do
+ case auth
+ when :oauth
+ build_token_auth_header(token.plaintext_token)
+ when :personal_access_token
+ build_token_auth_header(personal_access_token.token)
+ when :job_token
+ build_token_auth_header(job.token)
+ when :deploy_token
+ build_token_auth_header(deploy_token.token)
+ else
+ {}
+ end
+ end
+
+ before do
+ project.send("add_#{user_role}", user) if user_role
+ project.update!(visibility: visibility.to_s)
+
+ if scope == :instance
+ allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
+ else
+ allow_fetch_cascade_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
+ end
+ end
+
+ example_name = "#{params[:expected_result]} audit request"
+ status = params[:expected_status]
+
+ if scope == :instance && params[:expected_status] != :unauthorized
+ if params[:request_forward]
+ example_name = 'redirect audit request'
+ status = :temporary_redirect
+ else
+ example_name = 'reject audit request'
+ status = :not_found
+ end
+ end
+
+ it_behaves_like example_name, status: status
+ end
+ end
+
+ context 'with a group namespace' do
+ it_behaves_like 'handling all conditions'
+ end
+
+ context 'with a developer' do
+ let(:headers) { build_token_auth_header(personal_access_token.token) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'with a job token' do
+ let(:headers) { build_token_auth_header(job.token) }
+
+ before do
+ job.update!(status: :success)
+ end
+
+ it_behaves_like 'reject audit request', status: :unauthorized
+ end
+ end
+end
+
RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
using RSpec::Parameterized::TableSyntax
include_context 'set package name from package name type'
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
index fdd55893deb..bace570e47a 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -224,25 +224,13 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
end
context 'and direct upload disabled' do
- context 'and background upload disabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: false)
- end
-
- it_behaves_like 'creates nuget package files'
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false)
end
- context 'and background upload enabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: true)
- end
-
- it_behaves_like 'creates nuget package files'
- end
+ it_behaves_like 'creates nuget package files'
end
end
-
- it_behaves_like 'background upload schedules a file migration'
end
end
@@ -507,7 +495,7 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_headers) }
- let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace } }
+ let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace, property: 'i_package_nuget_user' } }
before do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
index 860cb1b1d86..98264baa61d 100644
--- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -142,15 +142,26 @@ RSpec.shared_examples 'job token for package uploads' do |authorize_endpoint: fa
end
end
-RSpec.shared_examples 'a package tracking event' do |category, action|
+RSpec.shared_examples 'a package tracking event' do |category, action, service_ping_context = true|
before do
stub_feature_flags(collect_package_events: true)
end
+ let(:context) do
+ [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll,
+ event: snowplow_gitlab_standard_context[:property]).to_h]
+ end
+
it "creates a gitlab tracking event #{action}", :snowplow, :aggregate_failures do
expect { subject }.to change { Packages::Event.count }.by(1)
- expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context)
+ if service_ping_context
+ expect_snowplow_event(category: category, action: action,
+ label: "redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly",
+ context: context, **snowplow_gitlab_standard_context)
+ else
+ expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context)
+ end
end
end
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 a9b44015206..a267476b7cb 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
@@ -95,25 +95,13 @@ RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member
end
context 'and direct upload disabled' do
- context 'and background upload disabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: false)
- end
-
- it_behaves_like 'creating pypi package files'
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false)
end
- context 'and background upload enabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: true)
- end
-
- it_behaves_like 'creating pypi package files'
- end
+ it_behaves_like 'creating pypi package files'
end
end
-
- it_behaves_like 'background upload schedules a file migration'
end
end
@@ -285,7 +273,7 @@ RSpec.shared_examples 'pypi simple API endpoint' do
let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" }
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
- let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } }
+ let(:snowplow_gitlab_standard_context) { { project: project, namespace: group, property: 'i_package_pypi_user' } }
it_behaves_like 'PyPI package versions', :developer, :success
end
diff --git a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb
index 2d036cb2aa3..2154a76d765 100644
--- a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb
@@ -71,11 +71,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type|
get_container_repository_storage_moves
json_ids = json_response.map { |storage_move| storage_move['id'] }
- expect(json_ids).to eq([
- storage_move.id,
- storage_move_middle.id,
- storage_move_oldest.id
- ])
+ expect(json_ids).to eq([storage_move.id, storage_move_middle.id, storage_move_oldest.id])
end
describe 'permissions' do
diff --git a/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb
index f075927e7bf..da09d70c777 100644
--- a/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb
@@ -122,21 +122,11 @@ RSpec.shared_examples 'process rubygems upload' do |user_type, status, add_membe
end
context 'and direct upload disabled' do
- context 'and background upload disabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: false)
- end
-
- it_behaves_like 'creates rubygems package files'
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false)
end
- context 'and background upload enabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: true)
- end
-
- it_behaves_like 'creates rubygems package files'
- end
+ it_behaves_like 'creates rubygems package files'
end
end
end
diff --git a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
index bdff2c65691..ae2855083f6 100644
--- a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
@@ -264,21 +264,11 @@ RSpec.shared_examples 'process terraform module upload' do |user_type, status, a
end
context 'and direct upload disabled' do
- context 'and background upload disabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: false)
- end
-
- it_behaves_like 'creates terraform module package files'
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false)
end
- context 'and background upload enabled' do
- let(:fog_connection) do
- stub_package_file_object_storage(direct_upload: false, background_upload: true)
- end
-
- it_behaves_like 'creates terraform module package files'
- end
+ it_behaves_like 'creates terraform module package files'
end
end
end
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
index 11759b6671f..82ed6eb4c95 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -68,6 +68,7 @@ RSpec.shared_examples 'rate-limited token requests' do
# Set low limits
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
+ travel_back
end
after do
@@ -220,6 +221,7 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
# Set low limits
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
+ travel_back
end
after do
@@ -436,6 +438,7 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do
# Set low limits
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
+ travel_back
end
context 'when the throttle is enabled' do