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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-16 13:42:19 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-16 13:42:19 +0300
commit84d1bd786125c1c14a3ba5f63e38a4cc736a9027 (patch)
treef550fa965f507077e20dbb6d61a8269a99ef7107 /spec/requests/api/graphql
parent3a105e36e689f7b75482236712f1a47fd5a76814 (diff)
Add latest changes from gitlab-org/gitlab@16-8-stable-eev16.8.0-rc42
Diffstat (limited to 'spec/requests/api/graphql')
-rw-r--r--spec/requests/api/graphql/achievements/user_achievements_query_spec.rb6
-rw-r--r--spec/requests/api/graphql/boards/board_list_issues_query_spec.rb4
-rw-r--r--spec/requests/api/graphql/ci/catalog/resource_spec.rb248
-rw-r--r--spec/requests/api/graphql/ci/catalog/resources_spec.rb8
-rw-r--r--spec/requests/api/graphql/ci/instance_variables_spec.rb2
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb158
-rw-r--r--spec/requests/api/graphql/ci/runners_spec.rb202
-rw-r--r--spec/requests/api/graphql/container_repository/container_repository_details_spec.rb79
-rw-r--r--spec/requests/api/graphql/mutations/branch_rules/create_spec.rb68
-rw-r--r--spec/requests/api/graphql/mutations/ml/models/create_spec.rb48
-rw-r--r--spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb16
-rw-r--r--spec/requests/api/graphql/mutations/organizations/update_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_spec.rb12
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb21
-rw-r--r--spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace_query_spec.rb48
-rw-r--r--spec/requests/api/graphql/organizations/organization_query_spec.rb193
-rw-r--r--spec/requests/api/graphql/organizations/organizations_query_spec.rb56
-rw-r--r--spec/requests/api/graphql/project/container_repositories_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/tree/tree_spec.rb2
-rw-r--r--spec/requests/api/graphql/projects/projects_spec.rb6
-rw-r--r--spec/requests/api/graphql/user/user_achievements_query_spec.rb6
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb4
24 files changed, 852 insertions, 345 deletions
diff --git a/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb b/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb
index 32048ea1432..94678bd18da 100644
--- a/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb
+++ b/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb
@@ -89,14 +89,14 @@ RSpec.describe 'UserAchievements', feature_category: :user_profile do
end
it 'can lookahead to eliminate N+1 queries', :use_clean_rails_memory_store_caching do
- control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: user)
- end.count
+ end
user2 = create(:user)
create(:user_achievement, achievement: achievement, user: user2)
- expect { post_graphql(query, current_user: user) }.not_to exceed_all_query_limit(control_count)
+ expect { post_graphql(query, current_user: user) }.not_to exceed_all_query_limit(control)
end
context 'when the achievements feature flag is disabled' do
diff --git a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
index 86e2b288890..312700b1dcf 100644
--- a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
@@ -115,8 +115,8 @@ RSpec.describe 'get board lists', feature_category: :team_planning do
let(:issue_params) { { filters: { or: { assignee_usernames: [user.username, another_user.username] } } } }
it 'returns correctly filtered issues' do
- issue1.assignee_ids = user.id
- issue2.assignee_ids = another_user.id
+ IssueAssignee.create!(issue_id: issue1.id, user_id: user.id)
+ IssueAssignee.create!(issue_id: issue2.id, user_id: another_user.id)
subject
diff --git a/spec/requests/api/graphql/ci/catalog/resource_spec.rb b/spec/requests/api/graphql/ci/catalog/resource_spec.rb
index 9fe73e7ba45..836e52197a3 100644
--- a/spec/requests/api/graphql/ci/catalog/resource_spec.rb
+++ b/spec/requests/api/graphql/ci/catalog/resource_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
)
end
- let_it_be(:resource) { create(:ci_catalog_resource, :published, project: project) }
+ let_it_be_with_reload(:resource) { create(:ci_catalog_resource, :published, project: project) }
let(:query) do
<<~GQL
@@ -81,7 +81,7 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
nodes {
id
name
- path
+ includePath
inputs {
name
default
@@ -126,7 +126,7 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
a_graphql_entity_for(
components.first,
name: components.first.name,
- path: components.first.path,
+ include_path: components.first.path,
inputs: [
a_graphql_entity_for(
name: 'tags',
@@ -148,48 +148,68 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
a_graphql_entity_for(
components.last,
name: components.last.name,
- path: components.last.path
+ include_path: components.last.path
)
)
end
end
end
- describe 'versions' do
- let(:query) do
- <<~GQL
- query {
- ciCatalogResource(id: "#{resource.to_global_id}") {
- id
- versions {
- nodes {
- id
- tagName
- tagPath
- releasedAt
- author {
+ describe 'version fields' do
+ before_all do
+ # To test the readme_html field, we need to create versions with real commit shas
+ project.repository.create_branch('branch_v2', project.default_branch)
+ project.repository.update_file(
+ user, 'README.md', 'Readme v2', message: 'Update readme', branch_name: 'branch_v2')
+
+ project.repository.add_tag(user, 'v1', project.default_branch)
+ project.repository.add_tag(user, 'v2', 'branch_v2')
+ end
+
+ let_it_be(:author) { create(:user, name: 'author') }
+
+ let_it_be(:version1) do
+ create(:release, :with_catalog_resource_version,
+ project: project,
+ tag: 'v1',
+ sha: project.commit('v1').sha,
+ released_at: '2023-01-01T00:00:00Z',
+ author: author
+ ).catalog_resource_version
+ end
+
+ let_it_be(:version2) do
+ create(:release, :with_catalog_resource_version,
+ project: project,
+ tag: 'v2',
+ sha: project.commit('v2').sha,
+ released_at: '2023-02-01T00:00:00Z',
+ author: author
+ ).catalog_resource_version
+ end
+
+ describe 'versions' do
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(id: "#{resource.to_global_id}") {
+ id
+ versions {
+ nodes {
id
name
- webUrl
+ path
+ releasedAt
+ author {
+ id
+ name
+ webUrl
+ }
}
}
}
}
- }
- GQL
- end
-
- context 'when the resource has versions' do
- let_it_be(:author) { create(:user, name: 'author') }
-
- let_it_be(:version1) do
- create(:release, :with_catalog_resource_version, project: project, released_at: '2023-01-01T00:00:00Z',
- author: author).catalog_resource_version
- end
-
- let_it_be(:version2) do
- create(:release, :with_catalog_resource_version, project: project, released_at: '2023-02-01T00:00:00Z',
- author: author).catalog_resource_version
+ GQL
end
it 'returns the resource with the versions data' do
@@ -202,67 +222,124 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
expect(graphql_data_at(:ciCatalogResource, :versions, :nodes)).to contain_exactly(
a_graphql_entity_for(
version1,
- tagName: version1.name,
- tagPath: project_tag_path(project, version1.name),
+ name: version1.name,
+ path: project_tag_path(project, version1.name),
releasedAt: version1.released_at,
author: a_graphql_entity_for(author, :name)
),
a_graphql_entity_for(
version2,
- tagName: version2.name,
- tagPath: project_tag_path(project, version2.name),
+ name: version2.name,
+ path: project_tag_path(project, version2.name),
releasedAt: version2.released_at,
author: a_graphql_entity_for(author, :name)
)
)
end
- end
- context 'when the resource does not have a version' do
- it 'returns versions as an empty array' do
- post_query
+ context 'when the readmeHtml field is requested on more than one version' do
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(fullPath: "#{resource.project.full_path}") {
+ versions {
+ nodes {
+ readmeHtml
+ }
+ }
+ }
+ }
+ GQL
+ end
- expect(graphql_data_at(:ciCatalogResource)).to match(
- a_graphql_entity_for(resource, versions: { 'nodes' => [] })
- )
+ it 'limits the request to 1 version at a time' do
+ post_query
+
+ expect_graphql_errors_to_include \
+ [/"readmeHtml" field can be requested only for 1 CiCatalogResourceVersion\(s\) at a time./]
+ end
+ end
+
+ context 'when the name argument is provided' do
+ let(:name) { 'v1' }
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(fullPath: "#{resource.project.full_path}") {
+ versions(name: "#{name}") {
+ nodes {
+ id
+ name
+ path
+ releasedAt
+ readmeHtml
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'returns the version that matches the name' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource, :versions, :nodes)).to contain_exactly(
+ a_graphql_entity_for(
+ version1,
+ name: version1.name,
+ path: project_tag_path(project, version1.name),
+ releasedAt: version1.released_at,
+ readmeHtml: a_string_including(
+ "#{project.full_path}/-/blob/#{project.default_branch}/README.md"
+ )
+ )
+ )
+ end
+
+ context 'when no version matches the name' do
+ let(:name) { 'does_not_exist' }
+
+ it 'returns an empty array' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource, :versions, :nodes)).to eq([])
+ end
+ end
+ end
+
+ context 'when the resource does not have a version' do
+ it 'returns an empty array' do
+ resource.versions.delete_all(:delete_all)
+
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource, :versions, :nodes)).to eq([])
+ end
end
end
- end
- describe 'latestVersion' do
- let(:query) do
- <<~GQL
- query {
- ciCatalogResource(id: "#{resource.to_global_id}") {
- id
- latestVersion {
+ describe 'latestVersion' do
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(id: "#{resource.to_global_id}") {
id
- tagName
- tagPath
- releasedAt
- author {
+ latestVersion {
id
name
- webUrl
+ path
+ releasedAt
+ readmeHtml
+ author {
+ id
+ name
+ webUrl
+ }
}
}
}
- }
- GQL
- end
-
- context 'when the resource has versions' do
- let_it_be(:author) { create(:user, name: 'author') }
-
- let_it_be(:latest_version) do
- create(:release, :with_catalog_resource_version, project: project, released_at: '2023-02-01T00:00:00Z',
- author: author).catalog_resource_version
- end
-
- before_all do
- # Previous version of the catalog resource
- create(:release, :with_catalog_resource_version, project: project, released_at: '2023-01-01T00:00:00Z',
- author: author)
+ GQL
end
it 'returns the resource with the latest version data' do
@@ -272,24 +349,27 @@ RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_compositio
a_graphql_entity_for(
resource,
latestVersion: a_graphql_entity_for(
- latest_version,
- tagName: latest_version.name,
- tagPath: project_tag_path(project, latest_version.name),
- releasedAt: latest_version.released_at,
+ version2,
+ name: version2.name,
+ path: project_tag_path(project, version2.name),
+ releasedAt: version2.released_at,
+ readmeHtml: a_string_including('Readme v2'),
author: a_graphql_entity_for(author, :name)
)
)
)
end
- end
- context 'when the resource does not have a version' do
- it 'returns nil' do
- post_query
+ context 'when the resource does not have a version' do
+ it 'returns nil' do
+ resource.versions.delete_all(:delete_all)
- expect(graphql_data_at(:ciCatalogResource)).to match(
- a_graphql_entity_for(resource, latestVersion: nil)
- )
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(resource, latestVersion: nil)
+ )
+ end
end
end
end
diff --git a/spec/requests/api/graphql/ci/catalog/resources_spec.rb b/spec/requests/api/graphql/ci/catalog/resources_spec.rb
index 49a3f3be1d7..150507fc442 100644
--- a/spec/requests/api/graphql/ci/catalog/resources_spec.rb
+++ b/spec/requests/api/graphql/ci/catalog/resources_spec.rb
@@ -106,7 +106,7 @@ RSpec.describe 'Query.ciCatalogResources', feature_category: :pipeline_compositi
versions {
nodes {
id
- tagName
+ name
releasedAt
author {
id
@@ -153,7 +153,7 @@ RSpec.describe 'Query.ciCatalogResources', feature_category: :pipeline_compositi
id
latestVersion {
id
- tagName
+ name
releasedAt
author {
id
@@ -185,7 +185,7 @@ RSpec.describe 'Query.ciCatalogResources', feature_category: :pipeline_compositi
resource1,
latestVersion: a_graphql_entity_for(
latest_version1,
- tagName: latest_version1.name,
+ name: latest_version1.name,
releasedAt: latest_version1.released_at,
author: a_graphql_entity_for(author1, :name)
)
@@ -194,7 +194,7 @@ RSpec.describe 'Query.ciCatalogResources', feature_category: :pipeline_compositi
public_resource,
latestVersion: a_graphql_entity_for(
latest_version2,
- tagName: latest_version2.name,
+ name: latest_version2.name,
releasedAt: latest_version2.released_at,
author: a_graphql_entity_for(author2, :name)
)
diff --git a/spec/requests/api/graphql/ci/instance_variables_spec.rb b/spec/requests/api/graphql/ci/instance_variables_spec.rb
index a612b4c91b6..6731631a075 100644
--- a/spec/requests/api/graphql/ci/instance_variables_spec.rb
+++ b/spec/requests/api/graphql/ci/instance_variables_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe 'Query.ciVariables', feature_category: :secrets_management do
nodes {
id
key
+ description
value
variableType
protected
@@ -36,6 +37,7 @@ RSpec.describe 'Query.ciVariables', feature_category: :secrets_management do
expect(graphql_data.dig('ciVariables', 'nodes')).to contain_exactly({
'id' => variable.to_global_id.to_s,
'key' => 'TEST_VAR',
+ 'description' => nil,
'value' => 'test',
'variableType' => 'ENV_VAR',
'masked' => false,
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 8262640b283..1b6948d0380 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -876,107 +876,95 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :fleet_visibi
end
describe 'Query limits' do
- def runner_query(runner)
- <<~SINGLE
- runner(id: "#{runner.to_global_id}") {
- #{all_graphql_fields_for('CiRunner', excluded: excluded_fields)}
- createdBy {
- id
- username
- webPath
- webUrl
- }
- groups {
- nodes {
- id
- path
- fullPath
- webUrl
- }
- }
- projects {
- nodes {
- id
- path
- fullPath
- webUrl
- }
- }
- ownerProject {
- id
- path
- fullPath
- webUrl
- }
+ let_it_be(:user2) { another_admin }
+ let_it_be(:user3) { create(:user) }
+ let_it_be(:tag_list) { %w[n_plus_1_test some_tag] }
+ let_it_be(:args) do
+ { current_user: user, token: { personal_access_token: create(:personal_access_token, user: user) } }
+ end
+
+ let_it_be(:runner1) { create(:ci_runner, tag_list: tag_list, creator: user) }
+ let_it_be(:runner2) do
+ create(:ci_runner, :group, groups: [group], tag_list: tag_list, creator: user)
+ end
+
+ let_it_be(:runner3) do
+ create(:ci_runner, :project, projects: [project1], tag_list: tag_list, creator: user)
+ end
+
+ let(:single_discrete_runners_query) do
+ multiple_discrete_runners_query([])
+ end
+
+ let(:runner_fragment) do
+ <<~QUERY
+ #{all_graphql_fields_for('CiRunner', excluded: excluded_fields)}
+ createdBy {
+ id
+ username
+ webPath
+ webUrl
}
- SINGLE
+ QUERY
end
- let(:active_project_runner2) { create(:ci_runner, :project) }
- let(:active_group_runner2) { create(:ci_runner, :group) }
+ # Exclude fields that are already hardcoded above (or tested separately),
+ # and also some fields from deeper objects which are problematic:
+ # - createdBy: Known N+1 issues, but only on exotic fields which we don't normally use
+ # - ownerProject.pipeline: Needs arguments (iid or sha)
+ # - project.productAnalyticsState: Can be requested only for 1 Project(s) at a time.
+ let(:excluded_fields) { %w[createdBy jobs pipeline productAnalyticsState] }
+
+ it 'avoids N+1 queries', :use_sql_query_cache do
+ discrete_runners_control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post_graphql(single_discrete_runners_query, **args)
+ end
+
+ additional_runners = setup_additional_records
+
+ expect do
+ post_graphql(multiple_discrete_runners_query(additional_runners), **args)
- # Exclude fields that are already hardcoded above
- let(:excluded_fields) { %w[createdBy jobs groups projects ownerProject] }
+ raise StandardError, flattened_errors if graphql_errors # Ensure any error in query causes test to fail
+ end.not_to exceed_query_limit(discrete_runners_control)
+ end
- let(:single_query) do
+ def runner_query(runner, nr)
<<~QUERY
- {
- instance_runner1: #{runner_query(active_instance_runner)}
- group_runner1: #{runner_query(active_group_runner)}
- project_runner1: #{runner_query(active_project_runner)}
+ runner#{nr}: runner(id: "#{runner.to_global_id}") {
+ #{runner_fragment}
}
QUERY
end
- let(:double_query) do
+ def multiple_discrete_runners_query(additional_runners)
<<~QUERY
{
- instance_runner1: #{runner_query(active_instance_runner)}
- instance_runner2: #{runner_query(inactive_instance_runner)}
- group_runner1: #{runner_query(active_group_runner)}
- group_runner2: #{runner_query(active_group_runner2)}
- project_runner1: #{runner_query(active_project_runner)}
- project_runner2: #{runner_query(active_project_runner2)}
+ #{runner_query(runner1, 1)}
+ #{runner_query(runner2, 2)}
+ #{runner_query(runner3, 3)}
+ #{additional_runners.each_with_index.map { |r, i| runner_query(r, 4 + i) }.join("\n")}
}
QUERY
end
- it 'does not execute more queries per runner', :aggregate_failures, quarantine: "https://gitlab.com/gitlab-org/gitlab/-/issues/391442" do
- # warm-up license cache and so on:
- personal_access_token = create(:personal_access_token, user: user)
- args = { current_user: user, token: { personal_access_token: personal_access_token } }
- post_graphql(double_query, **args)
-
- control = ActiveRecord::QueryRecorder.new { post_graphql(single_query, **args) }
-
- personal_access_token = create(:personal_access_token, user: another_admin)
- args = { current_user: another_admin, token: { personal_access_token: personal_access_token } }
- expect { post_graphql(double_query, **args) }.not_to exceed_query_limit(control)
-
- expect(graphql_data.count).to eq 6
- expect(graphql_data).to match(
- a_hash_including(
- 'instance_runner1' => a_graphql_entity_for(active_instance_runner),
- 'instance_runner2' => a_graphql_entity_for(inactive_instance_runner),
- 'group_runner1' => a_graphql_entity_for(
- active_group_runner,
- groups: { 'nodes' => contain_exactly(a_graphql_entity_for(group)) }
- ),
- 'group_runner2' => a_graphql_entity_for(
- active_group_runner2,
- groups: { 'nodes' => active_group_runner2.groups.map { |g| a_graphql_entity_for(g) } }
- ),
- 'project_runner1' => a_graphql_entity_for(
- active_project_runner,
- projects: { 'nodes' => active_project_runner.projects.map { |p| a_graphql_entity_for(p) } },
- owner_project: a_graphql_entity_for(active_project_runner.projects[0])
- ),
- 'project_runner2' => a_graphql_entity_for(
- active_project_runner2,
- projects: { 'nodes' => active_project_runner2.projects.map { |p| a_graphql_entity_for(p) } },
- owner_project: a_graphql_entity_for(active_project_runner2.projects[0])
- )
- ))
+ def setup_additional_records
+ # Add more runners (including owned by other users)
+ runner4 = create(:ci_runner, tag_list: tag_list + %w[tag1 tag2], creator: user2)
+ runner5 = create(:ci_runner, :group, groups: [create(:group)], tag_list: tag_list + %w[tag2 tag3], creator: user3)
+ # Add one more project to runner
+ runner3.assign_to(create(:project))
+
+ # Add more runner managers (including to existing runners)
+ runner_manager1 = create(:ci_runner_machine, runner: runner1)
+ create(:ci_runner_machine, runner: runner1)
+ create(:ci_runner_machine, runner: runner2, system_xid: runner_manager1.system_xid)
+ create(:ci_runner_machine, runner: runner3)
+ create(:ci_runner_machine, runner: runner4, version: '16.4.1')
+ create(:ci_runner_machine, runner: runner5, version: '16.4.0', system_xid: runner_manager1.system_xid)
+ create(:ci_runner_machine, runner: runner3)
+
+ [runner4, runner5]
end
end
diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb
index 0fe14bef778..bfe5282cbaa 100644
--- a/spec/requests/api/graphql/ci/runners_spec.rb
+++ b/spec/requests/api/graphql/ci/runners_spec.rb
@@ -18,22 +18,34 @@ RSpec.describe 'Query.runners', feature_category: :fleet_visibility do
let(:fields) do
<<~QUERY
nodes {
- #{all_graphql_fields_for('CiRunner', excluded: %w[createdBy ownerProject])}
- createdBy {
- username
- webPath
- webUrl
- }
- ownerProject {
- id
- path
- fullPath
- webUrl
- }
+ #{all_graphql_fields_for('CiRunner', excluded: excluded_fields)}
}
QUERY
end
+ let(:query) do
+ %(
+ query {
+ runners {
+ #{fields}
+ }
+ }
+ )
+ end
+
+ # Exclude fields from deeper objects which are problematic:
+ # - ownerProject.pipeline: Needs arguments (iid or sha)
+ # - project.productAnalyticsState: Can be requested only for 1 Project(s) at a time.
+ let(:excluded_fields) { %w[pipeline productAnalyticsState] }
+
+ it 'returns expected runners' do
+ post_graphql(query, current_user: current_user)
+
+ expect(runners_graphql_data['nodes']).to contain_exactly(
+ *Ci::Runner.all.map { |expected_runner| a_graphql_entity_for(expected_runner) }
+ )
+ end
+
context 'with filters' do
shared_examples 'a working graphql query returning expected runners' do
it_behaves_like 'a working graphql query' do
@@ -49,31 +61,6 @@ RSpec.describe 'Query.runners', feature_category: :fleet_visibility do
*Array(expected_runners).map { |expected_runner| a_graphql_entity_for(expected_runner) }
)
end
-
- it 'does not execute more queries per runner', :aggregate_failures do
- # warm-up license cache and so on:
- personal_access_token = create(:personal_access_token, user: current_user)
- args = { current_user: current_user, token: { personal_access_token: personal_access_token } }
- post_graphql(query, **args)
- expect(graphql_data_at(:runners, :nodes)).not_to be_empty
-
- admin2 = create(:admin)
- personal_access_token = create(:personal_access_token, user: admin2)
- args = { current_user: admin2, token: { personal_access_token: personal_access_token } }
- control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) }
-
- runner2 = create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: admin2)
- runner3 = create(:ci_runner, :project, version: '14.0.1', projects: [project], tag_list: %w[tag3 tag8],
- creator: current_user)
-
- create(:ci_build, :failed, runner: runner2)
- create(:ci_runner_machine, runner: runner2, version: '16.4.1')
-
- create(:ci_build, :failed, runner: runner3)
- create(:ci_runner_machine, runner: runner3, version: '16.4.0')
-
- expect { post_graphql(query, **args) }.not_to exceed_query_limit(control)
- end
end
context 'when filtered on type and status' do
@@ -178,56 +165,129 @@ RSpec.describe 'Query.runners', feature_category: :fleet_visibility do
end
end
end
- end
- context 'without filters' do
- context 'with managers requested for multiple runners' do
- let(:fields) do
- <<~QUERY
- nodes {
- managers {
- nodes {
- #{all_graphql_fields_for('CiRunnerManager', max_depth: 1)}
- }
- }
- }
- QUERY
- end
+ context 'when filtered by creator' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:runner_created_by_user) { create(:ci_runner, :project, creator: user) }
let(:query) do
%(
query {
- runners {
+ runners(creatorId: "#{creator.to_global_id}") {
#{fields}
}
}
)
end
- it 'does not execute more queries per runner', :aggregate_failures do
- # warm-up license cache and so on:
- personal_access_token = create(:personal_access_token, user: current_user)
- args = { current_user: current_user, token: { personal_access_token: personal_access_token } }
- post_graphql(query, **args)
- expect(graphql_data_at(:runners, :nodes)).not_to be_empty
-
- admin2 = create(:admin)
- personal_access_token = create(:personal_access_token, user: admin2)
- args = { current_user: admin2, token: { personal_access_token: personal_access_token } }
- control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) }
-
- create(:ci_runner, :instance, :with_runner_manager, version: '14.0.0', tag_list: %w[tag5 tag6],
- creator: admin2)
- create(:ci_runner, :project, :with_runner_manager, version: '14.0.1', projects: [project],
- tag_list: %w[tag3 tag8],
- creator: current_user)
-
- expect { post_graphql(query, **args) }.not_to exceed_query_limit(control)
+ context 'when existing user id given' do
+ let(:creator) { user }
+
+ before do
+ create(:ci_runner, :project, creator: create(:user)) # Should not be returned
+ end
+
+ it_behaves_like 'a working graphql query returning expected runners' do
+ let(:expected_runners) { runner_created_by_user }
+ end
+ end
+
+ context 'when non existent user id given' do
+ let(:creator) { User.new(id: non_existing_record_id) }
+
+ it 'does not return any runners' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data_at(:runners, :nodes)).to be_empty
+ end
end
end
end
end
+ describe 'Runner query limits' do
+ let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:user2) { create(:user) }
+ let_it_be(:user3) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:tag_list) { %w[n_plus_1_test some_tag] }
+ let_it_be(:args) do
+ { current_user: user, token: { personal_access_token: create(:personal_access_token, user: user) } }
+ end
+
+ let_it_be(:runner1) { create(:ci_runner, tag_list: tag_list, creator: user) }
+ let_it_be(:runner2) do
+ create(:ci_runner, :group, groups: [group], tag_list: tag_list, creator: user)
+ end
+
+ let_it_be(:runner3) do
+ create(:ci_runner, :project, projects: [project], tag_list: tag_list, creator: user)
+ end
+
+ let(:runner_fragment) do
+ <<~QUERY
+ #{all_graphql_fields_for('CiRunner', excluded: excluded_fields)}
+ createdBy {
+ id
+ username
+ webPath
+ webUrl
+ }
+ QUERY
+ end
+
+ # Exclude fields that are already hardcoded above (or tested separately),
+ # and also some fields from deeper objects which are problematic:
+ # - createdBy: Known N+1 issues, but only on exotic fields which we don't normally use
+ # - ownerProject.pipeline: Needs arguments (iid or sha)
+ # - project.productAnalyticsState: Can be requested only for 1 Project(s) at a time.
+ let(:excluded_fields) { %w[createdBy jobs pipeline productAnalyticsState] }
+
+ let(:runners_query) do
+ <<~QUERY
+ {
+ runners {
+ nodes { #{runner_fragment} }
+ }
+ }
+ QUERY
+ end
+
+ it 'avoids N+1 queries', :use_sql_query_cache do
+ personal_access_token = create(:personal_access_token, user: user)
+ args = { current_user: user, token: { personal_access_token: personal_access_token } }
+
+ runners_control = ActiveRecord::QueryRecorder.new(skip_cached: false) { post_graphql(runners_query, **args) }
+
+ setup_additional_records
+
+ expect { post_graphql(runners_query, **args) }.not_to exceed_query_limit(runners_control)
+ end
+
+ def setup_additional_records
+ # Add more runners (including owned by other users)
+ runner4 = create(:ci_runner, tag_list: tag_list + %w[tag1 tag2], creator: user2)
+ runner5 = create(:ci_runner, :group, groups: [create(:group)], tag_list: tag_list + %w[tag2 tag3], creator: user3)
+ # Add one more project to runner
+ runner3.assign_to(create(:project))
+
+ # Add more runner managers (including to existing runners)
+ runner_manager1 = create(:ci_runner_machine, runner: runner1)
+ create(:ci_runner_machine, runner: runner1)
+ create(:ci_runner_machine, runner: runner2, system_xid: runner_manager1.system_xid)
+ create(:ci_runner_machine, runner: runner3)
+ create(:ci_runner_machine, runner: runner4, version: '16.4.1')
+ create(:ci_runner_machine, runner: runner5, version: '16.4.0', system_xid: runner_manager1.system_xid)
+ create(:ci_runner_machine, runner: runner3)
+
+ create(:ci_build, :failed, runner: runner4)
+ create(:ci_build, :failed, runner: runner5)
+
+ [runner4, runner5]
+ end
+ end
+
describe 'pagination' do
let(:data_path) { [:runners] }
diff --git a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
index 2acdd509355..46563aba992 100644
--- a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
+++ b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
@@ -430,6 +430,85 @@ RSpec.describe 'container repository details', feature_category: :container_regi
it_behaves_like 'returning an invalid value error'
end
+
+ context 'with referrers' do
+ let(:tags_response) { container_repository_details_response.dig('tags', 'edges') }
+ let(:raw_tags_response) do
+ [
+ {
+ name: 'latest',
+ digest: 'sha256:1234567892',
+ config_digest: 'sha256:3332132331',
+ media_type: 'application/vnd.oci.image.manifest.v1+json',
+ size_bytes: 1234509876,
+ created_at: 10.minutes.ago,
+ updated_at: 10.minutes.ago,
+ referrers: [
+ {
+ artifactType: 'application/vnd.example+type',
+ digest: 'sha256:57d3be92c2f857566ecc7f9306a80021c0a7fa631e0ef5146957235aea859961'
+ },
+ {
+ artifactType: 'application/vnd.example+type+2',
+ digest: 'sha256:01db72e42d61b8d2183d53475814cce2bfb9c8a254e97539a852441979cd5c90'
+ }
+ ]
+ },
+ {
+ name: 'latest',
+ digest: 'sha256:1234567893',
+ config_digest: 'sha256:3332132331',
+ media_type: 'application/vnd.oci.image.manifest.v1+json',
+ size_bytes: 1234509877,
+ created_at: 9.minutes.ago,
+ updated_at: 9.minutes.ago
+ }
+ ]
+ end
+
+ let(:query) do
+ <<~GQL
+ query($id: ContainerRepositoryID!, $n: String) {
+ containerRepository(id: $id) {
+ tags(name: $n, referrers: true) {
+ edges {
+ node {
+ #{all_graphql_fields_for('ContainerRepositoryTag')}
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ let(:url) { URI('/gitlab/v1/repositories/group1/proj1/tags/list/?before=tag1&referrers=true') }
+
+ let(:response_body) do
+ {
+ pagination: { previous: { uri: url }, next: { uri: url } },
+ response_body: ::Gitlab::Json.parse(raw_tags_response.to_json)
+ }
+ end
+
+ it 'includes referrers in response' do
+ subject
+
+ refs = tags_response.map { |tag| tag.dig('node', 'referrers') }
+
+ expect(refs.first.size).to eq(2)
+ expect(refs.first.first).to include({
+ 'artifactType' => 'application/vnd.example+type',
+ 'digest' => 'sha256:57d3be92c2f857566ecc7f9306a80021c0a7fa631e0ef5146957235aea859961'
+ })
+ expect(refs.first.second).to include({
+ 'artifactType' => 'application/vnd.example+type+2',
+ 'digest' => 'sha256:01db72e42d61b8d2183d53475814cce2bfb9c8a254e97539a852441979cd5c90'
+ })
+
+ expect(refs.second).to be_empty
+ end
+ end
end
it_behaves_like 'handling graphql network errors with the container registry'
diff --git a/spec/requests/api/graphql/mutations/branch_rules/create_spec.rb b/spec/requests/api/graphql/mutations/branch_rules/create_spec.rb
new file mode 100644
index 00000000000..85ba3d58ee5
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/branch_rules/create_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'BranchRuleCreate', feature_category: :source_code_management do
+ include GraphqlHelpers
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:current_user, reload: true) { create(:user) }
+
+ let(:params) do
+ {
+ project_path: project.full_path,
+ name: branch_name
+ }
+ end
+
+ let(:branch_name) { 'branch_name/*' }
+ let(:mutation) { graphql_mutation(:branch_rule_create, params) }
+ let(:mutation_response) { graphql_mutation_response(:branch_rule_create) }
+ let(:mutation_errors) { mutation_response['errors'] }
+
+ subject(:post_mutation) { post_graphql_mutation(mutation, current_user: current_user) }
+
+ context 'when the user does not have permission' do
+ before_all do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+
+ it 'does not create the board' do
+ expect { post_mutation }.not_to change { ProtectedBranch.count }
+ end
+ end
+
+ context 'when the user can create a branch rules' do
+ before_all do
+ project.add_maintainer(current_user)
+ end
+
+ it 'creates the protected branch' do
+ expect { post_mutation }.to change { ProtectedBranch.count }.by(1)
+ end
+
+ it 'returns the created branch rule' do
+ post_mutation
+
+ expect(mutation_response).to have_key('branchRule')
+ expect(mutation_response['branchRule']['name']).to eq(branch_name)
+ expect(mutation_errors).to be_empty
+ end
+
+ context 'when the branch rule already exist' do
+ let!(:existing_rule) { create :protected_branch, name: branch_name, project: project }
+
+ it 'does not create the protected branch' do
+ expect { post_mutation }.not_to change { ProtectedBranch.count }
+ end
+
+ it 'return an error message' do
+ post_mutation
+
+ expect(mutation_errors).to include 'Name has already been taken'
+ expect(mutation_response['branchRule']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/ml/models/create_spec.rb b/spec/requests/api/graphql/mutations/ml/models/create_spec.rb
new file mode 100644
index 00000000000..0daabeab0d1
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ml/models/create_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Creation of a machine learning model', feature_category: :mlops do
+ include GraphqlHelpers
+
+ let_it_be(:model) { create(:ml_models) }
+ let_it_be(:project) { model.project }
+ let_it_be(:current_user) { project.owner }
+
+ let(:input) { { project_path: project.full_path, name: name, description: description } }
+ let(:name) { 'some_name' }
+ let(:description) { 'A description' }
+
+ let(:mutation) { graphql_mutation(:ml_model_create, input) }
+ let(:mutation_response) { graphql_mutation_response(:ml_model_create) }
+
+ context 'when user is not allowed write changes' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(current_user, :write_model_registry, project)
+ .and_return(false)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user is allowed write changes' do
+ it 'creates a models' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['model']).to include(
+ 'name' => name,
+ 'description' => description
+ )
+ end
+
+ context 'when name already exists' do
+ err_msg = "Name has already been taken"
+ let(:name) { model.name }
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: [err_msg]
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb
index 05c1a2d96d9..7c5d86b9f5c 100644
--- a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb
@@ -23,7 +23,9 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
lock_npm_package_requests_forwarding: true,
pypi_package_requests_forwarding: true,
lock_pypi_package_requests_forwarding: true,
- nuget_symbol_server_enabled: true
+ nuget_symbol_server_enabled: true,
+ terraform_module_duplicates_allowed: true,
+ terraform_module_duplicate_exception_regex: 'foo-.*'
}
end
@@ -44,6 +46,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
pypiPackageRequestsForwarding
lockPypiPackageRequestsForwarding
nugetSymbolServerEnabled
+ terraformModuleDuplicatesAllowed
+ terraformModuleDuplicateExceptionRegex
}
errors
QL
@@ -73,6 +77,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
expect(package_settings_response['npmPackageRequestsForwarding']).to eq(params[:npm_package_requests_forwarding])
expect(package_settings_response['lockNpmPackageRequestsForwarding']).to eq(params[:lock_npm_package_requests_forwarding])
expect(package_settings_response['nugetSymbolServerEnabled']).to eq(params[:nuget_symbol_server_enabled])
+ expect(package_settings_response['terraformModuleDuplicatesAllowed']).to eq(params[:terraform_module_duplicates_allowed])
+ expect(package_settings_response['terraformModuleDuplicateExceptionRegex']).to eq(params[:terraform_module_duplicate_exception_regex])
end
end
@@ -115,7 +121,9 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
lock_npm_package_requests_forwarding: false,
pypi_package_requests_forwarding: nil,
lock_pypi_package_requests_forwarding: false,
- nuget_symbol_server_enabled: false
+ nuget_symbol_server_enabled: false,
+ terraform_module_duplicates_allowed: false,
+ terraform_module_duplicate_exception_regex: 'foo'
}, to: {
maven_duplicates_allowed: false,
maven_duplicate_exception_regex: 'foo-.*',
@@ -129,7 +137,9 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
lock_npm_package_requests_forwarding: true,
pypi_package_requests_forwarding: true,
lock_pypi_package_requests_forwarding: true,
- nuget_symbol_server_enabled: true
+ nuget_symbol_server_enabled: true,
+ terraform_module_duplicates_allowed: true,
+ terraform_module_duplicate_exception_regex: 'foo-.*'
}
it_behaves_like 'returning a success'
diff --git a/spec/requests/api/graphql/mutations/organizations/update_spec.rb b/spec/requests/api/graphql/mutations/organizations/update_spec.rb
index 4e819c280d0..33890ae4592 100644
--- a/spec/requests/api/graphql/mutations/organizations/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/organizations/update_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Mutations::Organizations::Update, feature_category: :cell do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:organization) do
- create(:organization) { |org| create(:organization_user, organization: org, user: user) }
+ create(:organization) { |org| create(:organization_user, :owner, organization: org, user: user) }
end
let(:mutation) { graphql_mutation(:organization_update, params) }
diff --git a/spec/requests/api/graphql/mutations/work_items/create_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
index 78b93c3210b..2c2cd5f2acc 100644
--- a/spec/requests/api/graphql/mutations/work_items/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
@@ -281,6 +281,18 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do
it_behaves_like 'creates work item'
+ # This is a temporary measure just to ensure the internal id migration doesn't get conflicts
+ # More info in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139367
+ context 'when making the request in a production environment' do
+ before do
+ stub_rails_env('production')
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: [
+ 'Group level work items are disabled. Only project paths allowed in `namespacePath`.'
+ ]
+ end
+
context 'when the namespace_level_work_items feature flag is disabled' do
before do
stub_feature_flags(namespace_level_work_items: false)
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index a4bc94798be..107fcd8dcdd 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe 'getting projects', feature_category: :groups_and_projects do
'namespace',
{ 'fullPath' => subject.full_path },
<<~QUERY
+ id
projects(includeSubgroups: #{include_subgroups}) {
edges {
node {
@@ -53,24 +54,30 @@ RSpec.describe 'getting projects', feature_category: :groups_and_projects do
expect(graphql_data['namespace']['projects']['edges'].size).to eq(count)
end
+ end
- context 'with no user' do
- it 'finds only public projects' do
- post_graphql(query, current_user: nil)
+ it_behaves_like 'a graphql namespace'
- expect(graphql_data['namespace']).to be_nil
- end
+ context 'when no user is given' do
+ it 'finds only public projects' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data_at(:namespace, :projects, :edges).size).to eq(1)
end
end
- it_behaves_like 'a graphql namespace'
-
context 'when the namespace is a user' do
subject { user.namespace }
let(:include_subgroups) { false }
it_behaves_like 'a graphql namespace'
+
+ it 'does not show namespace entity for anonymous user' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data['namespace']).to be_nil
+ end
end
context 'when not including subgroups' do
diff --git a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
index c8819f1e38f..273b6b8c25b 100644
--- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
+++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe 'rendering namespace statistics', feature_category: :metrics do
it 'hides statistics for unauthenticated requests' do
post_graphql(query, current_user: nil)
- expect(graphql_data['namespace']).to be_blank
+ expect(graphql_data_at(:namespace, :root_storage_statistics)).to be_blank
end
end
end
diff --git a/spec/requests/api/graphql/namespace_query_spec.rb b/spec/requests/api/graphql/namespace_query_spec.rb
index c0c7c5fee2b..86808915564 100644
--- a/spec/requests/api/graphql/namespace_query_spec.rb
+++ b/spec/requests/api/graphql/namespace_query_spec.rb
@@ -8,7 +8,8 @@ RSpec.describe 'Query', feature_category: :groups_and_projects do
let_it_be(:user) { create(:user) }
let_it_be(:other_user) { create(:user) }
- let_it_be(:group_namespace) { create(:group) }
+ let_it_be(:group_namespace) { create(:group, :private) }
+ let_it_be(:public_group_namespace) { create(:group, :public) }
let_it_be(:user_namespace) { create(:user_namespace, owner: user) }
let_it_be(:project_namespace) { create(:project_namespace, parent: group_namespace) }
@@ -60,6 +61,51 @@ RSpec.describe 'Query', feature_category: :groups_and_projects do
end
end
+ context 'when used with a public group' do
+ let(:target_namespace) { public_group_namespace }
+
+ before do
+ subject
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ context 'when user is a member' do
+ before do
+ public_group_namespace.add_developer(user)
+ end
+
+ it 'fetches the expected data' do
+ expect(query_result).to include(
+ 'fullPath' => target_namespace.full_path,
+ 'name' => target_namespace.name
+ )
+ end
+ end
+
+ context 'when user is anonymous' do
+ let(:current_user) { nil }
+
+ it 'fetches the expected data' do
+ expect(query_result).to include(
+ 'fullPath' => target_namespace.full_path,
+ 'name' => target_namespace.name
+ )
+ end
+ end
+
+ context 'when user is not a member' do
+ let(:current_user) { other_user }
+
+ it 'fetches the expected data' do
+ expect(query_result).to include(
+ 'fullPath' => target_namespace.full_path,
+ 'name' => target_namespace.name
+ )
+ end
+ end
+ end
+
it_behaves_like 'retrieving a namespace' do
let(:target_namespace) { group_namespace }
diff --git a/spec/requests/api/graphql/organizations/organization_query_spec.rb b/spec/requests/api/graphql/organizations/organization_query_spec.rb
index c485e3b170d..14becd52e93 100644
--- a/spec/requests/api/graphql/organizations/organization_query_spec.rb
+++ b/spec/requests/api/graphql/organizations/organization_query_spec.rb
@@ -7,7 +7,6 @@ RSpec.describe 'getting organization information', feature_category: :cell do
let(:query) { graphql_query_for(:organization, { id: organization.to_global_id }, organization_fields) }
let(:current_user) { user }
- let(:groups) { graphql_data_at(:organization, :groups, :nodes) }
let(:organization_fields) do
<<~FIELDS
id
@@ -23,24 +22,9 @@ RSpec.describe 'getting organization information', feature_category: :cell do
let_it_be(:organization_user) { create(:organization_user) }
let_it_be(:organization) { organization_user.organization }
let_it_be(:user) { organization_user.user }
- let_it_be(:parent_group) { create(:group, name: 'parent-group', organization: organization) }
- let_it_be(:public_group) { create(:group, name: 'public-group', parent: parent_group, organization: organization) }
- let_it_be(:other_group) { create(:group, name: 'other-group', organization: organization) }
- let_it_be(:outside_organization_group) { create(:group) }
-
- let_it_be(:private_group) do
- create(:group, :private, name: 'private-group', organization: organization)
- end
-
- let_it_be(:no_access_group_in_org) do
- create(:group, :private, name: 'no-access', organization: organization)
- end
-
- before_all do
- private_group.add_developer(user)
- public_group.add_developer(user)
- other_group.add_developer(user)
- outside_organization_group.add_developer(user)
+ let_it_be(:project) { create(:project, organization: organization) { |p| p.add_developer(user) } }
+ let_it_be(:other_group) do
+ create(:group, name: 'other-group', organization: organization) { |g| g.add_developer(user) }
end
subject(:request_organization) { post_graphql(query, current_user: current_user) }
@@ -62,25 +46,6 @@ RSpec.describe 'getting organization information', feature_category: :cell do
end
end
- context 'when resolve_organization_groups feature flag is disabled' do
- before do
- stub_feature_flags(resolve_organization_groups: false)
- end
-
- it 'returns no groups' do
- request_organization
-
- expect(graphql_data_at(:organization)).not_to be_nil
- expect(graphql_data_at(:organization, :groups, :nodes)).to be_empty
- end
- end
-
- it 'does not return ancestors of authorized groups' do
- request_organization
-
- expect(groups.pluck('id')).not_to include(parent_group.to_global_id.to_s)
- end
-
context 'when requesting organization user' do
let(:organization_fields) do
<<~FIELDS
@@ -102,13 +67,13 @@ RSpec.describe 'getting organization information', feature_category: :cell do
it 'returns correct organization user fields' do
request_organization
- organization_user_node = graphql_data_at(:organization, :organizationUsers, :nodes).first
+ organization_user_nodes = graphql_data_at(:organization, :organizationUsers, :nodes)
expected_attributes = {
"badges" => [{ "text" => "It's you!", "variant" => 'muted' }],
"id" => organization_user.to_global_id.to_s,
"user" => { "id" => user.to_global_id.to_s }
}
- expect(organization_user_node).to match(expected_attributes)
+ expect(organization_user_nodes).to include(expected_attributes)
end
it 'avoids N+1 queries for all the fields' do
@@ -116,6 +81,8 @@ RSpec.describe 'getting organization information', feature_category: :cell do
organization_user_2 = create(:organization_user, organization: organization)
other_group.add_developer(organization_user_2.user)
+ organization_user_from_project = create(:organization_user, organization: organization)
+ project.add_developer(organization_user_from_project.user)
expect { run_query }.not_to exceed_query_limit(base_query_count)
end
@@ -127,62 +94,144 @@ RSpec.describe 'getting organization information', feature_category: :cell do
end
end
- context 'with `search` argument' do
- let(:search) { 'oth' }
- let(:organization_fields) do
- <<~FIELDS
- id
- path
- groups(search: "#{search}") {
- nodes {
- id
- name
- }
- }
- FIELDS
+ context 'when requesting groups' do
+ let(:groups) { graphql_data_at(:organization, :groups, :nodes) }
+ let_it_be(:parent_group) { create(:group, name: 'parent-group', organization: organization) }
+ let_it_be(:public_group) do
+ create(:group, name: 'public-group', parent: parent_group, organization: organization)
end
- it 'filters groups by name' do
- request_organization
+ let_it_be(:private_group) do
+ create(:group, :private, name: 'private-group', organization: organization)
+ end
- expect(groups).to contain_exactly(a_graphql_entity_for(other_group))
+ before_all do
+ create(:group, :private, name: 'no-access', organization: organization)
+ private_group.add_developer(user)
+ public_group.add_developer(user)
+ create(:group) { |g| g.add_developer(user) } # outside organization
end
- end
- context 'with `sort` argument' do
- using RSpec::Parameterized::TableSyntax
+ context 'when resolve_organization_groups feature flag is disabled' do
+ before do
+ stub_feature_flags(resolve_organization_groups: false)
+ end
+
+ it 'returns no groups' do
+ request_organization
+
+ expect(graphql_data_at(:organization)).not_to be_nil
+ expect(graphql_data_at(:organization, :groups, :nodes)).to be_empty
+ end
+ end
- let(:authorized_groups) { [public_group, private_group, other_group] }
+ it 'does not return ancestors of authorized groups' do
+ request_organization
- where(:field, :direction, :sorted_groups) do
- 'id' | 'asc' | lazy { authorized_groups.sort_by(&:id) }
- 'id' | 'desc' | lazy { authorized_groups.sort_by(&:id).reverse }
- 'name' | 'asc' | lazy { authorized_groups.sort_by(&:name) }
- 'name' | 'desc' | lazy { authorized_groups.sort_by(&:name).reverse }
- 'path' | 'asc' | lazy { authorized_groups.sort_by(&:path) }
- 'path' | 'desc' | lazy { authorized_groups.sort_by(&:path).reverse }
+ expect(groups.pluck('id')).not_to include(parent_group.to_global_id.to_s)
end
- with_them do
- let(:sort) { "#{field}_#{direction}".upcase }
+ context 'with `search` argument' do
+ let(:search) { 'oth' }
let(:organization_fields) do
<<~FIELDS
id
path
- groups(sort: #{sort}) {
+ groups(search: "#{search}") {
nodes {
id
+ name
}
}
FIELDS
end
- it 'sorts the groups' do
+ it 'filters groups by name' do
request_organization
- expect(groups.pluck('id')).to eq(sorted_groups.map(&:to_global_id).map(&:to_s))
+ expect(groups).to contain_exactly(a_graphql_entity_for(other_group))
end
end
+
+ context 'with `sort` argument' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:authorized_groups) { [public_group, private_group, other_group] }
+
+ where(:field, :direction, :sorted_groups) do
+ 'id' | 'asc' | lazy { authorized_groups.sort_by(&:id) }
+ 'id' | 'desc' | lazy { authorized_groups.sort_by(&:id).reverse }
+ 'name' | 'asc' | lazy { authorized_groups.sort_by(&:name) }
+ 'name' | 'desc' | lazy { authorized_groups.sort_by(&:name).reverse }
+ 'path' | 'asc' | lazy { authorized_groups.sort_by(&:path) }
+ 'path' | 'desc' | lazy { authorized_groups.sort_by(&:path).reverse }
+ end
+
+ with_them do
+ let(:sort) { "#{field}_#{direction}".upcase }
+ let(:organization_fields) do
+ <<~FIELDS
+ id
+ path
+ groups(sort: #{sort}) {
+ nodes {
+ id
+ }
+ }
+ FIELDS
+ end
+
+ it 'sorts the groups' do
+ request_organization
+
+ expect(groups.pluck('id')).to eq(sorted_groups.map(&:to_global_id).map(&:to_s))
+ end
+ end
+ end
+ end
+
+ context 'when requesting projects' do
+ let(:projects) { graphql_data_at(:organization, :projects, :nodes) }
+ let(:organization_fields) do
+ <<~FIELDS
+ projects {
+ nodes {
+ id
+ }
+ }
+ FIELDS
+ end
+
+ before_all do
+ create(:project) { |p| p.add_developer(user) } # some other project that shouldn't show up in our results
+ end
+
+ before do
+ request_organization
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns projects' do
+ expect(projects).to contain_exactly(a_graphql_entity_for(project))
+ end
+
+ it_behaves_like 'sorted paginated query' do
+ include_context 'no sort argument'
+
+ let_it_be(:another_project) { create(:project, organization: organization) { |p| p.add_developer(user) } }
+ let_it_be(:another_project2) { create(:project, organization: organization) { |p| p.add_developer(user) } }
+ let(:first_param) { 2 }
+ let(:data_path) { [:organization, :projects] }
+ let(:all_records) { [another_project2, another_project, project].map { |p| global_id_of(p).to_s } }
+ end
+
+ def pagination_query(params)
+ graphql_query_for(
+ :organization, { id: organization.to_global_id },
+ query_nodes(:projects, :id, include_pagination_info: true, args: params)
+ )
+ end
end
end
end
diff --git a/spec/requests/api/graphql/organizations/organizations_query_spec.rb b/spec/requests/api/graphql/organizations/organizations_query_spec.rb
new file mode 100644
index 00000000000..12d81ed7412
--- /dev/null
+++ b/spec/requests/api/graphql/organizations/organizations_query_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting organizations information', feature_category: :cell do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+
+ let(:query) { graphql_query_for(:organizations, organizations_fields) }
+ let(:organizations) { graphql_data_at(:organizations, :nodes) }
+ let(:organizations_fields) do
+ <<~FIELDS
+ nodes {
+ id
+ path
+ }
+ FIELDS
+ end
+
+ before_all { create_list(:organization, 3) }
+
+ subject(:request_organization) { post_graphql(query, current_user: current_user) }
+
+ context 'without authenticated user' do
+ let(:current_user) { nil }
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ request_organization
+ end
+ end
+ end
+
+ context 'with authenticated user' do
+ let(:current_user) { user }
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ request_organization
+ end
+ end
+
+ it_behaves_like 'sorted paginated query' do
+ include_context 'no sort argument'
+
+ let(:first_param) { 2 }
+ let(:data_path) { [:organizations] }
+ let(:all_records) { Organizations::Organization.order(id: :desc).map { |o| global_id_of(o).to_s } }
+ end
+
+ def pagination_query(params)
+ graphql_query_for(:organizations, params, "#{page_info} nodes { id }")
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb
index c86d3bdd14c..2307409c383 100644
--- a/spec/requests/api/graphql/project/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/project/container_repositories_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe 'getting container repositories in a project', feature_category:
let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten }
let_it_be(:container_expiration_policy) { project.container_expiration_policy }
- let(:excluded_fields) { %w[pipeline jobs productAnalyticsState] }
+ let(:excluded_fields) { %w[pipeline jobs productAnalyticsState mlModels] }
let(:container_repositories_fields) do
<<~GQL
edges {
@@ -155,7 +155,7 @@ RSpec.describe 'getting container repositories in a project', feature_category:
it_behaves_like 'handling graphql network errors with the container registry'
it_behaves_like 'not hitting graphql network errors with the container registry' do
- let(:excluded_fields) { %w[pipeline jobs tags tagsCount productAnalyticsState] }
+ let(:excluded_fields) { %w[pipeline jobs tags tagsCount productAnalyticsState mlModels] }
end
it 'returns the total count of container repositories' do
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index 23be9fa5286..96933505838 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe 'getting merge request information nested in a project', feature_
# we exclude Project.pipeline because it needs arguments,
# codequalityReportsComparer because it is behind a feature flag
# and runners because the user is not an admin and therefore has no access
- let(:excluded) { %w[jobs pipeline runners codequalityReportsComparer] }
+ let(:excluded) { %w[jobs pipeline runners codequalityReportsComparer mlModels] }
let(:mr_fields) { all_graphql_fields_for('MergeRequest', excluded: excluded) }
before do
diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb
index 77b72bf39a1..d71908d6458 100644
--- a/spec/requests/api/graphql/project/tree/tree_spec.rb
+++ b/spec/requests/api/graphql/project/tree/tree_spec.rb
@@ -167,6 +167,8 @@ RSpec.describe 'getting a tree in a project', feature_category: :source_code_man
end
context 'when the ref points to a SSH-signed commit' do
+ let_it_be(:project) { create(:project, :repository, :in_group) }
+
let_it_be(:ref) { 'ssh-signed-commit' }
let_it_be(:commit) { project.commit(ref) }
let_it_be(:current_user) { create(:user, email: commit.committer_email).tap { |user| project.add_owner(user) } }
diff --git a/spec/requests/api/graphql/projects/projects_spec.rb b/spec/requests/api/graphql/projects/projects_spec.rb
index 84b8c2285f0..dfebcb7c42c 100644
--- a/spec/requests/api/graphql/projects/projects_spec.rb
+++ b/spec/requests/api/graphql/projects/projects_spec.rb
@@ -45,14 +45,14 @@ RSpec.describe 'getting a collection of projects', feature_category: :source_cod
it 'avoids N+1 queries', :use_sql_query_cache, :clean_gitlab_redis_cache do
post_graphql(single_project_query, current_user: current_user)
- query_count = ActiveRecord::QueryRecorder.new do
+ control = ActiveRecord::QueryRecorder.new do
post_graphql(single_project_query, current_user: current_user)
- end.count
+ end
# There is an N+1 query for max_member_access_for_user_ids
expect do
post_graphql(query, current_user: current_user)
- end.not_to exceed_all_query_limit(query_count + 5)
+ end.not_to exceed_all_query_limit(control).with_threshold(5)
end
it 'returns the expected projects' do
diff --git a/spec/requests/api/graphql/user/user_achievements_query_spec.rb b/spec/requests/api/graphql/user/user_achievements_query_spec.rb
index 2e6c3dcba61..ccff5bdf919 100644
--- a/spec/requests/api/graphql/user/user_achievements_query_spec.rb
+++ b/spec/requests/api/graphql/user/user_achievements_query_spec.rb
@@ -60,14 +60,14 @@ RSpec.describe 'UserAchievements', feature_category: :user_profile do
end
it 'can lookahead to eliminate N+1 queries', :use_clean_rails_memory_store_caching do
- control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: user)
- end.count
+ end
achievement2 = create(:achievement, namespace: group)
create_list(:user_achievement, 2, achievement: achievement2, user: user)
- expect { post_graphql(query, current_user: user) }.not_to exceed_all_query_limit(control_count)
+ expect { post_graphql(query, current_user: user) }.not_to exceed_all_query_limit(control)
end
context 'when the achievements feature flag is disabled for a namespace' do
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index fe77b7ae736..c6d44b057a7 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -199,7 +199,7 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
it 'avoids N+1 queries' do
post_graphql(query, current_user: current_user) # warm up
- control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: current_user)
end
@@ -207,7 +207,7 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
expect do
post_graphql(query, current_user: current_user)
- end.not_to exceed_all_query_limit(control_count)
+ end.not_to exceed_all_query_limit(control)
end
context 'when user is guest' do