diff options
Diffstat (limited to 'spec/requests/api/graphql')
77 files changed, 2205 insertions, 646 deletions
diff --git a/spec/requests/api/graphql/abuse_report_spec.rb b/spec/requests/api/graphql/abuse_report_spec.rb index 7d0b8b35763..f74b1fb4061 100644 --- a/spec/requests/api/graphql/abuse_report_spec.rb +++ b/spec/requests/api/graphql/abuse_report_spec.rb @@ -2,49 +2,122 @@ require 'spec_helper' -RSpec.describe 'abuse_report', feature_category: :insider_threat do +RSpec.describe 'Querying an Abuse Report', feature_category: :insider_threat do include GraphqlHelpers let_it_be(:current_user) { create(:admin) } - let_it_be(:label) { create(:abuse_report_label, title: 'Uno') } - let_it_be(:report) { create(:abuse_report, labels: [label]) } - - let(:report_gid) { Gitlab::GlobalId.build(report, id: report.id).to_s } - - let(:fields) do - <<~GRAPHQL - labels { - nodes { - id - title - description - color - textColor - } - } - GRAPHQL - end + let_it_be(:abuse_report) { create(:abuse_report) } - let(:arguments) { { id: report_gid } } - let(:query) { graphql_query_for('abuseReport', arguments, fields) } + let(:global_id) { abuse_report.to_gid.to_s } + let(:abuse_report_fields) { all_graphql_fields_for('AbuseReport', max_depth: 2) } + let(:abuse_report_data) { graphql_data['abuseReport'] } + + let(:query) do + graphql_query_for('abuseReport', { 'id' => global_id }, abuse_report_fields) + end before do post_graphql(query, current_user: current_user) end - it_behaves_like 'a working graphql query that returns data' + context 'when the user is an admin' do + it_behaves_like 'a working graphql query that returns data' - it 'returns abuse report with labels' do - expect(graphql_data_at('abuseReport', 'labels', 'nodes', 0)).to match(a_graphql_entity_for(label)) + it 'returns all fields' do + expect(abuse_report_data).to include( + 'id' => global_id, + 'userPermissions' => { + 'readAbuseReport' => true, + 'createNote' => true + } + ) + end end - context 'when current user is not an admin' do - let_it_be(:current_user) { create(:user) } + context 'when the user is not an admin' do + let(:current_user) { create(:user) } + + it 'returns nil' do + expect(abuse_report_data).to be_nil + end + end - it_behaves_like 'a working graphql query' + describe 'labels' do + let_it_be(:abuse_report_label) { create(:abuse_report_label, title: 'Label') } + let_it_be(:abuse_report) { create(:abuse_report, labels: [abuse_report_label]) } + + let(:labels_response) do + graphql_data_at(:abuse_report, :labels, :nodes) + end + + let(:abuse_report_fields) do + <<~GRAPHQL + labels { + nodes { + id + title + description + color + textColor + } + } + GRAPHQL + end + + it 'returns labels' do + expect(labels_response).to contain_exactly( + a_graphql_entity_for(abuse_report_label) + ) + end + end + + describe 'notes' do + let_it_be(:note) { create(:note, noteable: abuse_report, author: current_user) } + + let(:notes_response) do + graphql_data_at(:abuse_report, :notes, :nodes) + end + + let(:abuse_report_fields) do + <<~GRAPHQL + notes { + nodes { + #{all_graphql_fields_for('Note', max_depth: 2)} + } + } + GRAPHQL + end + + it 'returns notes' do + expect(notes_response).to contain_exactly( + a_graphql_entity_for(note) + ) + end + end + + describe 'discussions' do + let_it_be(:discussion) do + create(:discussion_note_on_abuse_report, noteable: abuse_report, author: current_user).to_discussion + end + + let(:discussions_response) do + graphql_data_at(:abuse_report, :discussions, :nodes) + end + + let(:abuse_report_fields) do + <<~GRAPHQL + discussions { + nodes { + #{all_graphql_fields_for('Discussion', max_depth: 2)} + } + } + GRAPHQL + end - it 'does not contain any data' do - expect(graphql_data_at('abuseReportLabel')).to be_nil + it 'returns discussions' do + expect(discussions_response).to contain_exactly( + a_graphql_entity_for(discussion) + ) end end end diff --git a/spec/requests/api/graphql/ci/catalog/resource_spec.rb b/spec/requests/api/graphql/ci/catalog/resource_spec.rb new file mode 100644 index 00000000000..fce773f320b --- /dev/null +++ b/spec/requests/api/graphql/ci/catalog/resource_spec.rb @@ -0,0 +1,341 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_composition do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:namespace) { create(:group) } + + let_it_be(:project) do + create( + :project, :with_avatar, :custom_repo, + name: 'Component Repository', + description: 'A simple component', + namespace: namespace, + star_count: 1, + files: { 'README.md' => '[link](README.md)' } + ) + end + + let_it_be(:resource) { create(:ci_catalog_resource, project: project) } + + let(:query) do + <<~GQL + query { + ciCatalogResource(id: "#{resource.to_global_id}") { + #{all_graphql_fields_for('CiCatalogResource', max_depth: 1)} + } + } + GQL + end + + subject(:post_query) { post_graphql(query, current_user: user) } + + context 'when the current user has permission to read the namespace catalog' do + it 'returns the resource with the expected data' do + namespace.add_developer(user) + + post_query + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for( + resource, :name, :description, + icon: project.avatar_path, + webPath: "/#{project.full_path}", + starCount: project.star_count, + forksCount: project.forks_count, + readmeHtml: a_string_including( + "#{project.full_path}/-/blob/#{project.default_branch}/README.md" + ) + ) + ) + end + end + + context 'when the current user does not have permission to read the namespace catalog' do + it 'returns nil' do + namespace.add_guest(user) + + post_query + + expect(graphql_data_at(:ciCatalogResource)).to be_nil + end + end + + describe 'versions' do + before_all do + namespace.add_developer(user) + end + + before do + stub_licensed_features(ci_namespace_catalog: true) + end + + let(:query) do + <<~GQL + query { + ciCatalogResource(id: "#{resource.to_global_id}") { + id + versions { + nodes { + id + tagName + 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, project: project, released_at: '2023-01-01T00:00:00Z', author: author) + end + + let_it_be(:version2) do + create(:release, project: project, released_at: '2023-02-01T00:00:00Z', author: author) + end + + it 'returns the resource with the versions data' do + post_query + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for(resource) + ) + + expect(graphql_data_at(:ciCatalogResource, :versions, :nodes)).to contain_exactly( + a_graphql_entity_for( + version1, + tagName: version1.tag, + releasedAt: version1.released_at, + author: a_graphql_entity_for(author, :name) + ), + a_graphql_entity_for( + version2, + tagName: version2.tag, + 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 + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for(resource, versions: { 'nodes' => [] }) + ) + end + end + end + + describe 'latestVersion' do + before_all do + namespace.add_developer(user) + end + + before do + stub_licensed_features(ci_namespace_catalog: true) + end + + let(:query) do + <<~GQL + query { + ciCatalogResource(id: "#{resource.to_global_id}") { + id + latestVersion { + id + tagName + 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(:latest_version) do + create(:release, project: project, released_at: '2023-02-01T00:00:00Z', author: author) + end + + before_all do + # Previous version of the project + create(:release, project: project, released_at: '2023-01-01T00:00:00Z', author: author) + end + + it 'returns the resource with the latest version data' do + post_query + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for( + resource, + latestVersion: a_graphql_entity_for( + latest_version, + tagName: latest_version.tag, + releasedAt: latest_version.released_at, + 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 + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for(resource, latestVersion: nil) + ) + end + end + end + + describe 'rootNamespace' do + before_all do + namespace.add_developer(user) + end + + before do + stub_licensed_features(ci_namespace_catalog: true) + end + + let(:query) do + <<~GQL + query { + ciCatalogResource(id: "#{resource.to_global_id}") { + id + rootNamespace { + id + name + path + } + } + } + GQL + end + + it 'returns the correct root namespace data' do + post_query + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for( + resource, + rootNamespace: a_graphql_entity_for(namespace, :name, :path) + ) + ) + end + end + + describe 'openIssuesCount' do + before do + stub_licensed_features(ci_namespace_catalog: true) + end + + context 'when open_issue_count is requested' do + let(:query) do + <<~GQL + query { + ciCatalogResource(id: "#{resource.to_global_id}") { + openIssuesCount + } + } + GQL + end + + it 'returns the correct count' do + create(:issue, :opened, project: project) + create(:issue, :opened, project: project) + + namespace.add_developer(user) + + post_query + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for( + open_issues_count: 2 + ) + ) + end + + context 'when open_issue_count is zero' do + it 'returns zero' do + namespace.add_developer(user) + + post_query + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for( + open_issues_count: 0 + ) + ) + end + end + end + end + + describe 'openMergeRequestsCount' do + before do + stub_licensed_features(ci_namespace_catalog: true) + end + + context 'when merge_requests_count is requested' do + let(:query) do + <<~GQL + query { + ciCatalogResource(id: "#{resource.to_global_id}") { + openMergeRequestsCount + } + } + GQL + end + + it 'returns the correct count' do + create(:merge_request, :opened, source_project: project) + + namespace.add_developer(user) + + post_query + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for( + open_merge_requests_count: 1 + ) + ) + end + + context 'when open merge_requests_count is zero' do + it 'returns zero' do + namespace.add_developer(user) + + post_query + + expect(graphql_data_at(:ciCatalogResource)).to match( + a_graphql_entity_for( + open_merge_requests_count: 0 + ) + ) + end + 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 new file mode 100644 index 00000000000..7c955a1202c --- /dev/null +++ b/spec/requests/api/graphql/ci/catalog/resources_spec.rb @@ -0,0 +1,359 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.ciCatalogResources', feature_category: :pipeline_composition do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:namespace) { create(:group) } + let_it_be(:project2) { create(:project, namespace: namespace) } + + let_it_be(:project1) do + create( + :project, :with_avatar, :custom_repo, + name: 'Component Repository', + description: 'A simple component', + namespace: namespace, + star_count: 1, + files: { 'README.md' => '**Test**' } + ) + end + + let_it_be(:public_project) do + create( + :project, :with_avatar, :custom_repo, :public, + name: 'Public Component', + description: 'A public component', + files: { 'README.md' => '**Test**' } + ) + end + + let_it_be(:resource1) { create(:ci_catalog_resource, project: project1, latest_released_at: '2023-01-01T00:00:00Z') } + let_it_be(:public_resource) { create(:ci_catalog_resource, project: public_project) } + + let(:query) do + <<~GQL + query { + ciCatalogResources { + nodes { + id + name + description + icon + webPath + latestReleasedAt + starCount + forksCount + readmeHtml + } + } + } + GQL + end + + subject(:post_query) { post_graphql(query, current_user: user) } + + shared_examples 'avoids N+1 queries' do + it do + ctx = { current_user: user } + + control_count = ActiveRecord::QueryRecorder.new do + run_with_clean_state(query, context: ctx) + end + + create(:ci_catalog_resource, project: project2) + + expect do + run_with_clean_state(query, context: ctx) + end.not_to exceed_query_limit(control_count) + end + end + + it_behaves_like 'avoids N+1 queries' + + it 'returns the resources with the expected data' do + namespace.add_developer(user) + + post_query + + expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly( + a_graphql_entity_for( + resource1, :name, :description, + icon: project1.avatar_path, + webPath: "/#{project1.full_path}", + starCount: project1.star_count, + forksCount: project1.forks_count, + readmeHtml: a_string_including('Test</strong>'), + latestReleasedAt: resource1.latest_released_at + ), + a_graphql_entity_for(public_resource) + ) + end + + describe 'versions' do + before_all do + namespace.add_developer(user) + end + + let(:query) do + <<~GQL + query { + ciCatalogResources { + nodes { + id + versions { + nodes { + id + tagName + releasedAt + author { + id + name + webUrl + } + } + } + } + } + } + GQL + end + + it 'limits the request to 1 resource at a time' do + create(:ci_catalog_resource, project: project2) + + post_query + + expect_graphql_errors_to_include \ + [/"versions" field can be requested only for 1 CiCatalogResource\(s\) at a time./] + end + end + + describe 'latestVersion' do + let_it_be(:author1) { create(:user, name: 'author1') } + let_it_be(:author2) { create(:user, name: 'author2') } + + let_it_be(:latest_version1) do + create(:release, project: project1, released_at: '2023-02-01T00:00:00Z', author: author1) + end + + let_it_be(:latest_version2) do + create(:release, project: public_project, released_at: '2023-02-01T00:00:00Z', author: author2) + end + + let(:query) do + <<~GQL + query { + ciCatalogResources { + nodes { + id + latestVersion { + id + tagName + releasedAt + author { + id + name + webUrl + } + } + } + } + } + GQL + end + + before_all do + namespace.add_developer(user) + + # Previous versions of the projects + create(:release, project: project1, released_at: '2023-01-01T00:00:00Z', author: author1) + create(:release, project: public_project, released_at: '2023-01-01T00:00:00Z', author: author2) + end + + it 'returns all resources with the latest version data' do + post_query + + expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly( + a_graphql_entity_for( + resource1, + latestVersion: a_graphql_entity_for( + latest_version1, + tagName: latest_version1.tag, + releasedAt: latest_version1.released_at, + author: a_graphql_entity_for(author1, :name) + ) + ), + a_graphql_entity_for( + public_resource, + latestVersion: a_graphql_entity_for( + latest_version2, + tagName: latest_version2.tag, + releasedAt: latest_version2.released_at, + author: a_graphql_entity_for(author2, :name) + ) + ) + ) + end + + # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/430350 + # it_behaves_like 'avoids N+1 queries' + end + + describe 'rootNamespace' do + before_all do + namespace.add_developer(user) + end + + let(:query) do + <<~GQL + query { + ciCatalogResources { + nodes { + id + rootNamespace { + id + name + path + } + } + } + } + GQL + end + + it 'returns the correct root namespace data' do + post_query + + expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly( + a_graphql_entity_for( + resource1, + rootNamespace: a_graphql_entity_for(namespace, :name, :path) + ), + a_graphql_entity_for(public_resource, rootNamespace: nil) + ) + end + end + + describe 'openIssuesCount' do + before_all do + namespace.add_developer(user) + end + + before_all do + create(:issue, :opened, project: project1) + create(:issue, :opened, project: project1) + + create(:issue, :opened, project: public_project) + end + + let(:query) do + <<~GQL + query { + ciCatalogResources { + nodes { + openIssuesCount + } + } + } + GQL + end + + it 'returns the correct count' do + post_query + + expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly( + a_graphql_entity_for(openIssuesCount: 2), + a_graphql_entity_for(openIssuesCount: 1) + ) + end + + it_behaves_like 'avoids N+1 queries' + end + + describe 'openMergeRequestsCount' do + before_all do + namespace.add_developer(user) + end + + before_all do + create(:merge_request, :opened, source_project: project1) + create(:merge_request, :opened, source_project: public_project) + end + + let(:query) do + <<~GQL + query { + ciCatalogResources { + nodes { + openMergeRequestsCount + } + } + } + GQL + end + + it 'returns the correct count' do + post_query + + expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly( + a_graphql_entity_for(openMergeRequestsCount: 1), + a_graphql_entity_for(openMergeRequestsCount: 1) + ) + end + + it_behaves_like 'avoids N+1 queries' + end + + # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/429636 + context 'when using `projectPath` (legacy) to fetch resources' do + let(:query) do + <<~GQL + query { + ciCatalogResources(projectPath: "#{project1.full_path}") { + nodes { + #{all_graphql_fields_for('CiCatalogResource', max_depth: 1)} + } + } + } + GQL + end + + context 'when the current user has permission to read the namespace catalog' do + before_all do + namespace.add_developer(user) + end + + it 'returns catalog resources with the expected data' do + resource2 = create(:ci_catalog_resource, project: project2) + _resource_in_another_namespace = create(:ci_catalog_resource) + + post_query + + expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly( + a_graphql_entity_for(resource1), + a_graphql_entity_for( + resource2, :name, :description, + icon: project2.avatar_path, + webPath: "/#{project2.full_path}", + starCount: project2.star_count, + forksCount: project2.forks_count, + readmeHtml: '', + latestReleasedAt: resource2.latest_released_at + ) + ) + end + + it_behaves_like 'avoids N+1 queries' + end + + context 'when the current user does not have permission to read the namespace catalog' do + it 'returns no resources' do + post_query + + expect(graphql_data_at(:ciCatalogResources, :nodes)).to be_empty + end + end + end +end diff --git a/spec/requests/api/graphql/ci/manual_variables_spec.rb b/spec/requests/api/graphql/ci/manual_variables_spec.rb index 47dccc0deb6..41788881e62 100644 --- a/spec/requests/api/graphql/ci/manual_variables_spec.rb +++ b/spec/requests/api/graphql/ci/manual_variables_spec.rb @@ -90,6 +90,6 @@ RSpec.describe 'Query.project(fullPath).pipelines.jobs.manualVariables', feature variables_data = graphql_data.dig('project', 'pipelines', 'nodes').first .dig('jobs', 'nodes').flat_map { |job| job.dig('manualVariables', 'nodes') } - expect(variables_data.map { |var| var['key'] }).to match_array(%w(MANUAL_TEST_VAR_1 MANUAL_TEST_VAR_2)) + expect(variables_data.map { |var| var['key'] }).to match_array(%w[MANUAL_TEST_VAR_1 MANUAL_TEST_VAR_2]) end end diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb index c5571086700..0e2712d742d 100644 --- a/spec/requests/api/graphql/ci/runners_spec.rb +++ b/spec/requests/api/graphql/ci/runners_spec.rb @@ -35,28 +35,16 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do end context 'with filters' do - let(:query) do - %( - query { - runners(type: #{runner_type}, status: #{status}) { - #{fields} - } - } - ) - end - - before do - allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance| - allow(instance).to receive(:check_runner_upgrade_suggestion) - end - - post_graphql(query, current_user: current_user) - end - shared_examples 'a working graphql query returning expected runner' do - it_behaves_like 'a working graphql query' + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end it 'returns expected runner' do + post_graphql(query, current_user: current_user) + expect(runners_graphql_data['nodes']).to contain_exactly(a_graphql_entity_for(expected_runner)) end @@ -86,22 +74,63 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do end end - context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do - let(:runner_type) { 'INSTANCE_TYPE' } - let(:status) { 'ACTIVE' } + context 'when filtered on type and status' do + let(:query) do + %( + query { + runners(type: #{runner_type}, status: #{status}) { + #{fields} + } + } + ) + end - let!(:expected_runner) { instance_runner } + before do + allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance| + allow(instance).to receive(:check_runner_upgrade_suggestion) + end + end - it_behaves_like 'a working graphql query returning expected runner' + context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do + let(:runner_type) { 'INSTANCE_TYPE' } + let(:status) { 'ACTIVE' } + + let!(:expected_runner) { instance_runner } + + it_behaves_like 'a working graphql query returning expected runner' + end + + context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do + let(:runner_type) { 'PROJECT_TYPE' } + let(:status) { 'NEVER_CONTACTED' } + + let!(:expected_runner) { project_runner } + + it_behaves_like 'a working graphql query returning expected runner' + end end - context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do - let(:runner_type) { 'PROJECT_TYPE' } - let(:status) { 'NEVER_CONTACTED' } + context 'when filtered on version prefix' do + let_it_be(:version_runner) { create(:ci_runner, :project, active: false, description: 'Runner with machine') } + let_it_be(:version_runner_machine) { create(:ci_runner_machine, runner: version_runner, version: '15.11.0') } + + let(:query) do + %( + query { + runners(versionPrefix: "#{version_prefix}") { + #{fields} + } + } + ) + end + + context 'version_prefix is "15."' do + let(:version_prefix) { '15.' } - let!(:expected_runner) { project_runner } + let!(:expected_runner) { version_runner } - it_behaves_like 'a working graphql query returning expected runner' + it_behaves_like 'a working graphql query returning expected runner' + end end end 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 118a11851dd..20277c7e27b 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 @@ -313,4 +313,124 @@ RSpec.describe 'container repository details', feature_category: :container_regi end it_behaves_like 'handling graphql network errors with the container registry' + + context 'when list tags API is enabled', :saas do + before do + stub_container_registry_config(enabled: true) + allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(true) + + allow_next_instances_of(ContainerRegistry::GitlabApiClient, nil) do |client| + allow(client).to receive(:tags).and_return(response_body) + end + end + + let_it_be(:raw_tags_response) do + [ + { + name: 'latest', + digest: 'sha256:1234567892', + config_digest: 'sha256:3332132331', + media_type: 'application/vnd.oci.image.manifest.v1+json', + size_bytes: 1234567892, + created_at: 10.minutes.ago, + updated_at: 10.minutes.ago + } + ] + end + + let_it_be(:url) { URI('/gitlab/v1/repositories/group1/proj1/tags/list/?before=tag1') } + + let_it_be(:response_body) do + { + pagination: { previous: { uri: url }, next: { uri: url } }, + response_body: ::Gitlab::Json.parse(raw_tags_response.to_json) + } + end + + it_behaves_like 'a working graphql query' do # OK + before do + subject + end + + it 'matches the JSON schema' do + expect(container_repository_details_response).to match_schema('graphql/container_repository_details') + end + end + + context 'with different permissions' do # OK + let_it_be(:user) { create(:user) } + + let(:tags_response) { container_repository_details_response.dig('tags', 'nodes') } + + where(:project_visibility, :role, :access_granted, :can_delete) do + :private | :maintainer | true | true + :private | :developer | true | true + :private | :reporter | true | false + :private | :guest | false | false + :private | :anonymous | false | false + :public | :maintainer | true | true + :public | :developer | true | true + :public | :reporter | true | false + :public | :guest | true | false + :public | :anonymous | true | false + end + + with_them do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility.to_s.upcase, false)) + project.add_member(user, role) unless role == :anonymous + end + + it 'return the proper response' do + subject + + if access_granted + expect(tags_response.size).to eq(raw_tags_response.size) + expect(container_repository_details_response.dig('canDelete')).to eq(can_delete) + else + expect(container_repository_details_response).to eq(nil) + end + end + end + end + + context 'querying' do + let(:name) { 'l' } + let(:tags_response) { container_repository_details_response.dig('tags', 'edges') } + let(:variables) do + { id: container_repository_global_id, n: name } + end + + let(:query) do + <<~GQL + query($id: ContainerRepositoryID!, $n: String) { + containerRepository(id: $id) { + tags(name: $n) { + edges { + node { + #{all_graphql_fields_for('ContainerRepositoryTag')} + } + } + } + } + } + GQL + end + + it 'returns the tag response', :aggregate_failures do + subject + + expect(tags_response.size).to eq(1) + expect(tags_response.first.dig('node', 'name')).to eq('latest') + end + + context 'invalid filter' do + let(:name) { 1 } + + it_behaves_like 'returning an invalid value error' + end + end + + it_behaves_like 'handling graphql network errors with the container registry' + end end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index d55a70f503c..060a1b42cb6 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -113,7 +113,7 @@ RSpec.describe 'GitlabSchema configurations', feature_category: :integrations do context 'regular queries' do subject do - query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) + query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w[id name description]) post_graphql(query) end @@ -125,7 +125,7 @@ RSpec.describe 'GitlabSchema configurations', feature_category: :integrations do subject do queries = [ - { query: graphql_query_for('project', { 'fullPath' => '$fullPath' }, %w(id name description)) }, # Complexity 4 + { query: graphql_query_for('project', { 'fullPath' => '$fullPath' }, %w[id name description]) }, # Complexity 4 { query: graphql_query_for('echo', { 'text' => "$test" }, []), variables: { "test" => "Hello world" } }, # Complexity 1 { query: graphql_query_for('project', { 'fullPath' => project.full_path }, "userPermissions { createIssue }") } # Complexity 3 ] @@ -215,7 +215,7 @@ RSpec.describe 'GitlabSchema configurations', feature_category: :integrations do context "global id's" do it 'uses GlobalID to expose ids' do - post_graphql(graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id)), + post_graphql(graphql_query_for('project', { 'fullPath' => project.full_path }, %w[id]), current_user: project.first_owner) parsed_id = GlobalID.parse(graphql_data['project']['id']) diff --git a/spec/requests/api/graphql/group/container_repositories_spec.rb b/spec/requests/api/graphql/group/container_repositories_spec.rb index 51d12261247..9206ead1534 100644 --- a/spec/requests/api/graphql/group/container_repositories_spec.rb +++ b/spec/requests/api/graphql/group/container_repositories_spec.rb @@ -49,7 +49,7 @@ RSpec.describe 'getting container repositories in a group', feature_category: :s group.add_owner(owner) stub_container_registry_config(enabled: true) container_repositories.each do |repository| - stub_container_registry_tags(repository: repository.path, tags: %w(tag1 tag2 tag3), with_manifest: false) + stub_container_registry_tags(repository: repository.path, tags: %w[tag1 tag2 tag3], with_manifest: false) end end @@ -142,7 +142,7 @@ RSpec.describe 'getting container repositories in a group', feature_category: :s end before do - stub_container_registry_tags(repository: container_repository.path, tags: %w(tag4 tag5 tag6), with_manifest: false) + stub_container_registry_tags(repository: container_repository.path, tags: %w[tag4 tag5 tag6], with_manifest: false) end it 'returns the searched container repository' do diff --git a/spec/requests/api/graphql/group/data_transfer_spec.rb b/spec/requests/api/graphql/group/data_transfer_spec.rb index b7c038afa54..e17074a0247 100644 --- a/spec/requests/api/graphql/group/data_transfer_spec.rb +++ b/spec/requests/api/graphql/group/data_transfer_spec.rb @@ -71,45 +71,21 @@ RSpec.describe 'group data transfers', feature_category: :source_code_management context 'when user has enough permissions' do before do group.add_owner(current_user) + subject end - context 'when data_transfer_monitoring_mock_data is NOT enabled' do - before do - stub_feature_flags(data_transfer_monitoring_mock_data: false) - subject - end - - it 'returns real results' do - expect(response).to have_gitlab_http_status(:ok) + it 'returns real results' do + expect(response).to have_gitlab_http_status(:ok) - expect(egress_data.count).to eq(2) + expect(egress_data.count).to eq(2) - expect(egress_data.first.keys).to match_array( - %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress] - ) + expect(egress_data.first.keys).to match_array( + %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress] + ) - expect(egress_data.pluck('repositoryEgress')).to match_array(%w[1 6]) - end - - it_behaves_like 'a working graphql query' + expect(egress_data.pluck('repositoryEgress')).to match_array(%w[1 6]) end - context 'when data_transfer_monitoring_mock_data is enabled' do - before do - stub_feature_flags(data_transfer_monitoring_mock_data: true) - subject - end - - it 'returns mock results' do - expect(response).to have_gitlab_http_status(:ok) - - expect(egress_data.count).to eq(12) - expect(egress_data.first.keys).to match_array( - %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress] - ) - end - - it_behaves_like 'a working graphql query' - end + it_behaves_like 'a working graphql query' end end diff --git a/spec/requests/api/graphql/group/milestones_spec.rb b/spec/requests/api/graphql/group/milestones_spec.rb index 209588835f2..6063e6d5293 100644 --- a/spec/requests/api/graphql/group/milestones_spec.rb +++ b/spec/requests/api/graphql/group/milestones_spec.rb @@ -136,7 +136,7 @@ RSpec.describe 'Milestones through GroupQuery', feature_category: :team_planning let_it_be(:closed_issue) { create(:issue, :closed, project: project, milestone: milestone) } let(:milestone_query) do - %{ + %( id title description @@ -149,7 +149,7 @@ RSpec.describe 'Milestones through GroupQuery', feature_category: :team_planning projectMilestone groupMilestone subgroupMilestone - } + ) end def post_query @@ -180,12 +180,12 @@ RSpec.describe 'Milestones through GroupQuery', feature_category: :team_planning context 'milestone statistics' do let(:milestone_query) do - %{ + %( stats { totalIssuesCount closedIssuesCount } - } + ) end it 'returns the correct milestone statistics' do diff --git a/spec/requests/api/graphql/merge_requests/codequality_reports_comparer_spec.rb b/spec/requests/api/graphql/merge_requests/codequality_reports_comparer_spec.rb index 2939e9307e9..09a229c2098 100644 --- a/spec/requests/api/graphql/merge_requests/codequality_reports_comparer_spec.rb +++ b/spec/requests/api/graphql/merge_requests/codequality_reports_comparer_spec.rb @@ -54,6 +54,7 @@ RSpec.describe 'Query.project.mergeRequest.codequalityReportsComparer', feature_ let(:codequality_reports_comparer_fields) do <<~QUERY codequalityReportsComparer { + status report { status newErrors { @@ -138,6 +139,7 @@ RSpec.describe 'Query.project.mergeRequest.codequalityReportsComparer', feature_ expect(result).to match( a_hash_including( { + status: 'PARSED', report: { status: 'FAILED', newErrors: [ diff --git a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb index e3a7442ffe6..316b0f3755d 100644 --- a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb +++ b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb @@ -20,7 +20,7 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate let(:current_user) { create(:user) } it_behaves_like 'a mutation that returns top-level errors', - errors: ['You must be an admin to use this mutation'] + errors: ['You must be an admin to use this mutation'] end context 'when the user is an admin' do @@ -43,7 +43,7 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate raise 'Not enqueued!' if Sidekiq::Queue.new(queue).size.zero? end - it 'returns info about the deleted jobs', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/425824' do + it 'returns info about the deleted jobs' do add_job(admin, [1]) add_job(admin, [2]) add_job(create(:user), [3]) @@ -51,9 +51,7 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate post_graphql_mutation(mutation, current_user: admin) expect(mutation_response['errors']).to be_empty - expect(mutation_response['result']).to eq('completed' => true, - 'deletedJobs' => 2, - 'queueSize' => 1) + expect(mutation_response['result']).to eq('completed' => true, 'deletedJobs' => 2, 'queueSize' => 1) end end @@ -61,14 +59,14 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate let(:variables) { { queue_name: queue } } it_behaves_like 'a mutation that returns errors in the response', - errors: ['No metadata provided'] + errors: ['No metadata provided'] end context 'when the queue does not exist' do let(:variables) { { user: admin.username, queue_name: 'authorized_projects_2' } } it_behaves_like 'a mutation that returns top-level errors', - errors: ['Queue authorized_projects_2 not found'] + errors: ['Queue authorized_projects_2 not found'] end end end diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb index fbe6d95dfff..f2b516783e5 100644 --- a/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb +++ b/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb @@ -14,21 +14,23 @@ RSpec.describe 'Create an alert issue from an alert', feature_category: :inciden project_path: project.full_path, iid: alert.iid.to_s } - graphql_mutation(:create_alert_issue, variables, - <<~QL - clientMutationId - errors - alert { - iid - issue { - iid - } - } - issue { - iid - title - } - QL + graphql_mutation( + :create_alert_issue, + variables, + <<~QL + clientMutationId + errors + alert { + iid + issue { + iid + } + } + issue { + iid + title + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb index 65a5fb87f9a..e7e23304d81 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb @@ -6,9 +6,11 @@ RSpec.describe 'Toggling an AwardEmoji', feature_category: :shared do include GraphqlHelpers let_it_be(:current_user) { create(:user) } - let_it_be(:project, reload: true) { create(:project) } - let_it_be(:awardable) { create(:note, project: project) } + let_it_be(:group) { create(:group) } + let_it_be(:project, reload: true) { create(:project, group: group) } + let_it_be(:issue_note) { create(:note, project: project) } + let(:awardable) { issue_note } let(:emoji_name) { 'thumbsup' } let(:mutation) do variables = { @@ -36,8 +38,8 @@ RSpec.describe 'Toggling an AwardEmoji', feature_category: :shared do end context 'when the user has permission' do - before do - project.add_developer(current_user) + before_all do + group.add_developer(current_user) end context 'when the given awardable is not an Awardable' do @@ -60,6 +62,33 @@ RSpec.describe 'Toggling an AwardEmoji', feature_category: :shared do end context 'when the given awardable is an Awardable' do + context 'when the awardable is a work item' do + context 'when the work item is associated directly with a group' do + let_it_be(:group_work_item) { create(:work_item, :group_level, namespace: group) } + let(:awardable) { group_work_item } + + context 'when no emoji has been awarded by the current_user yet' do + it 'creates an emoji' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change { AwardEmoji.count }.by(1) + end + end + + context 'when an emoji has been awarded by the current_user' do + before do + create_award_emoji(current_user) + end + + it 'removes the emoji' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change { AwardEmoji.count }.by(-1) + end + end + end + end + context 'when no emoji has been awarded by the current_user yet' do # Create an award emoji for another user. This therefore tests that # toggling is correctly scoped to the user's emoji only. diff --git a/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb b/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb index df64caa1cfb..8e71d77f7bc 100644 --- a/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb +++ b/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb @@ -131,22 +131,24 @@ RSpec.describe 'Reposition and move issue within board lists', feature_category: end def mutation(additional_params = {}) - graphql_mutation(mutation_name, issue_move_params.merge(additional_params), - <<-QL.strip_heredoc - clientMutationId - issue { - iid, - relativePosition - labels { - edges { - node{ - title - } - } - } - } - errors - QL + graphql_mutation( + mutation_name, + issue_move_params.merge(additional_params), + <<-QL.strip_heredoc + clientMutationId + issue { + iid, + relativePosition + labels { + edges { + node{ + title + } + } + } + } + errors + QL ) end end diff --git a/spec/requests/api/graphql/mutations/ci/catalog/resources/create_spec.rb b/spec/requests/api/graphql/mutations/ci/catalog/resources/create_spec.rb new file mode 100644 index 00000000000..f990cab55f4 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/catalog/resources/create_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'CatalogResourcesCreate', feature_category: :pipeline_composition do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project, :catalog_resource_with_components) } + + let(:mutation) do + variables = { + project_path: project.full_path + } + graphql_mutation(:catalog_resources_create, variables, + <<-QL.strip_heredoc + errors + QL + ) + end + + context 'when unauthorized' do + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when authorized' do + context 'with a valid project' do + before_all do + project.add_owner(current_user) + end + + it 'creates a catalog resource' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_mutation_response(:catalog_resources_create)['errors']).to be_empty + expect(response).to have_gitlab_http_status(:success) + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb b/spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb new file mode 100644 index 00000000000..07465777263 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'CatalogResourceUnpublish', feature_category: :pipeline_composition do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be_with_reload(:resource) { create(:ci_catalog_resource) } + + let(:mutation) do + graphql_mutation( + :catalog_resource_unpublish, + id: resource.to_gid.to_s + ) + end + + subject(:post_query) { post_graphql_mutation(mutation, current_user: current_user) } + + context 'when unauthorized' do + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when authorized' do + before_all do + resource.project.add_owner(current_user) + end + + context 'when the catalog resource is in published state' do + it 'updates the state to draft' do + resource.update!(state: :published) + expect(resource.state).to eq('published') + + post_query + + expect(resource.reload.state).to eq('draft') + expect_graphql_errors_to_be_empty + end + end + + context 'when the catalog resource is already in draft state' do + it 'leaves the state as draft' do + expect(resource.state).to eq('draft') + + post_query + + expect(resource.reload.state).to eq('draft') + expect_graphql_errors_to_be_empty + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb index e7edc86bea0..70b154946ef 100644 --- a/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb @@ -13,13 +13,15 @@ RSpec.describe 'PipelineRetry', feature_category: :continuous_integration do variables = { id: pipeline.to_global_id.to_s } - graphql_mutation(:pipeline_retry, variables, - <<-QL - errors - pipeline { - id - } - QL + graphql_mutation( + :pipeline_retry, + variables, + <<-QL + errors + pipeline { + id + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb b/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb index ef0d44395bf..dd4b015409b 100644 --- a/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb +++ b/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb @@ -23,8 +23,8 @@ RSpec.describe 'Create a new cluster agent token', feature_category: :deployment context 'without user permissions' do it_behaves_like 'a mutation that returns top-level errors', - errors: ["The resource that you are attempting to access does not exist "\ - "or you don't have permission to perform this action"] + errors: ["The resource that you are attempting to access does not exist "\ + "or you don't have permission to perform this action"] it 'does not create a token' do expect { post_graphql_mutation(mutation, current_user: current_user) }.not_to change(Clusters::AgentToken, :count) diff --git a/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb index b70a6282a7a..a2a093d63e6 100644 --- a/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb @@ -22,8 +22,8 @@ RSpec.describe 'Delete a cluster agent', feature_category: :deployment_managemen context 'without project permissions' do it_behaves_like 'a mutation that returns top-level errors', - errors: ['The resource that you are attempting to access does not exist '\ - 'or you don\'t have permission to perform this action'] + errors: ['The resource that you are attempting to access does not exist '\ + 'or you don\'t have permission to perform this action'] it 'does not delete cluster agent' do expect { cluster_agent.reload }.not_to raise_error diff --git a/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb b/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb new file mode 100644 index 00000000000..0c708c3dc41 --- /dev/null +++ b/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Creating the container registry protection rule', :aggregate_failures, feature_category: :container_registry do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user, maintainer_projects: [project]) } + + let(:container_registry_protection_rule_attributes) do + build_stubbed(:container_registry_protection_rule, project: project) + end + + let(:kwargs) do + { + project_path: project.full_path, + container_path_pattern: container_registry_protection_rule_attributes.container_path_pattern, + push_protected_up_to_access_level: 'MAINTAINER', + delete_protected_up_to_access_level: 'MAINTAINER' + } + end + + let(:mutation) do + graphql_mutation(:create_container_registry_protection_rule, kwargs, + <<~QUERY + containerRegistryProtectionRule { + id + containerPathPattern + } + clientMutationId + errors + QUERY + ) + end + + let(:mutation_response) { graphql_mutation_response(:create_container_registry_protection_rule) } + + subject { post_graphql_mutation(mutation, current_user: user) } + + shared_examples 'a successful response' do + it { subject.tap { expect_graphql_errors_to_be_empty } } + + it do + subject + + expect(mutation_response).to include( + 'errors' => be_blank, + 'containerRegistryProtectionRule' => { + 'id' => be_present, + 'containerPathPattern' => kwargs[:container_path_pattern] + } + ) + end + + it 'creates container registry protection rule in the database' do + expect { subject }.to change { ::ContainerRegistry::Protection::Rule.count }.by(1) + + expect(::ContainerRegistry::Protection::Rule.where(project: project, + container_path_pattern: kwargs[:container_path_pattern])).to exist + end + end + + shared_examples 'an erroneous response' do + it { expect { subject }.not_to change { ::ContainerRegistry::Protection::Rule.count } } + end + + it_behaves_like 'a successful response' + + context 'with invalid input fields `pushProtectedUpToAccessLevel` and `deleteProtectedUpToAccessLevel`' do + let(:kwargs) do + super().merge( + push_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL', + delete_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL' + ) + end + + it_behaves_like 'an erroneous response' + + it { + subject + + expect_graphql_errors_to_include([/pushProtectedUpToAccessLevel/, /deleteProtectedUpToAccessLevel/]) + } + end + + context 'with invalid input field `containerPathPattern`' do + let(:kwargs) do + super().merge(container_path_pattern: '') + end + + it_behaves_like 'an erroneous response' + + it { subject.tap { expect_graphql_errors_to_be_empty } } + + it { + subject.tap do + expect(mutation_response['errors']).to eq ["Container path pattern can't be blank"] + end + } + end + + context 'with existing containers protection rule' do + let_it_be(:existing_container_registry_protection_rule) do + create(:container_registry_protection_rule, project: project, + push_protected_up_to_access_level: Gitlab::Access::DEVELOPER) + end + + context 'when container name pattern is slightly different' do + let(:kwargs) do + # The field `container_path_pattern` is unique; this is why we change the value in a minimum way + super().merge( + container_path_pattern: "#{existing_container_registry_protection_rule.container_path_pattern}-unique" + ) + end + + it_behaves_like 'a successful response' + + it 'adds another container registry protection rule to the database' do + expect { subject }.to change { ::ContainerRegistry::Protection::Rule.count }.from(1).to(2) + end + end + + context 'when field `container_path_pattern` is taken' do + let(:kwargs) do + super().merge(container_path_pattern: existing_container_registry_protection_rule.container_path_pattern, + push_protected_up_to_access_level: 'MAINTAINER') + end + + it_behaves_like 'an erroneous response' + + it { subject.tap { expect_graphql_errors_to_be_empty } } + + it 'returns without error' do + subject + + expect(mutation_response['errors']).to eq ['Container path pattern has already been taken'] + end + + it 'does not create new container protection rules' do + expect(::ContainerRegistry::Protection::Rule.where(project: project, + container_path_pattern: kwargs[:container_path_pattern], + push_protected_up_to_access_level: Gitlab::Access::MAINTAINER)).not_to exist + end + end + end + + context 'when user does not have permission' do + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:anonymous) { create(:user) } + + where(:user) do + [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)] + end + + with_them do + it_behaves_like 'an erroneous response' + + it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + end + + context "when feature flag ':container_registry_protected_containers' disabled" do + before do + stub_feature_flags(container_registry_protected_containers: false) + end + + it_behaves_like 'an erroneous response' + + it { subject.tap { expect(::ContainerRegistry::Protection::Rule.where(project: project)).not_to exist } } + + it 'returns error of disabled feature flag' do + subject.tap do + expect_graphql_errors_to_include(/'container_registry_protected_containers' feature flag is disabled/) + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb b/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb index 0cb607e13ec..7ced22890df 100644 --- a/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb +++ b/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb @@ -27,7 +27,7 @@ RSpec.describe 'Destroying a container repository tags', feature_category: :cont shared_examples 'destroying the container repository tags' do before do stub_delete_reference_requests(tags) - expect_delete_tag_by_names(tags) + expect_delete_tags(tags) allow_next_instance_of(ContainerRegistry::Client) do |client| allow(client).to receive(:supports_tag_delete?).and_return(true) end diff --git a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb index 7ea32ae6d19..6f421abc489 100644 --- a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb @@ -47,28 +47,28 @@ RSpec.describe "deleting designs", feature_category: :design_management do context 'the designs list is empty' do it_behaves_like 'a failed request' do let(:designs) { [] } - let(:the_error) { a_string_matching %r/no filenames/ } + let(:the_error) { a_string_matching %r{no filenames} } end end context 'the designs list contains filenames we cannot find' do it_behaves_like 'a failed request' do - let(:designs) { %w/foo bar baz/.map { |fn| double('file', filename: fn) } } - let(:the_error) { a_string_matching %r/filenames were not found/ } + let(:designs) { %w[foo bar baz].map { |fn| double('file', filename: fn) } } + let(:the_error) { a_string_matching %r{filenames were not found} } end end context 'the current user does not have developer access' do it_behaves_like 'a failed request' do let(:current_user) { create(:user) } - let(:the_error) { a_string_matching %r/you don't have permission/ } + let(:the_error) { a_string_matching %r{you don't have permission} } end end context "when the issue does not exist" do it_behaves_like 'a failed request' do let(:variables) { { iid: "1234567890" } } - let(:the_error) { a_string_matching %r/does not exist/ } + let(:the_error) { a_string_matching %r{does not exist} } end end diff --git a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb index 9b42b32c150..82a88a2c593 100644 --- a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb +++ b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb @@ -36,10 +36,11 @@ RSpec.describe "uploading designs", feature_category: :design_management do end it 'returns an error' do - workhorse_post_with_file(api('/', current_user, version: 'graphql'), - params: params, - file_key: '1' - ) + workhorse_post_with_file( + api('/', current_user, version: 'graphql'), + params: params, + file_key: '1' + ) expect(response).to have_attributes( code: eq('400'), diff --git a/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb b/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb index 85e21952f47..df6c20d6176 100644 --- a/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb @@ -19,19 +19,21 @@ RSpec.describe 'Link alerts to an incident', feature_category: :incident_managem alert_references: [alert1.to_reference, alert2.details_url] } - graphql_mutation(:issue_link_alerts, variables, - <<-QL.strip_heredoc - clientMutationId - errors - issue { - iid - alertManagementAlerts { - nodes { - iid - } - } - } - QL + graphql_mutation( + :issue_link_alerts, + variables, + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + alertManagementAlerts { + nodes { + iid + } + } + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/issues/move_spec.rb b/spec/requests/api/graphql/mutations/issues/move_spec.rb index 7d9579067b6..24188d5341d 100644 --- a/spec/requests/api/graphql/mutations/issues/move_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/move_spec.rb @@ -16,14 +16,16 @@ RSpec.describe 'Moving an issue', feature_category: :team_planning do iid: issue.iid.to_s } - graphql_mutation(:issue_move, variables, - <<-QL.strip_heredoc - clientMutationId - errors - issue { - title - } - QL + graphql_mutation( + :issue_move, + variables, + <<-QL.strip_heredoc + clientMutationId + errors + issue { + title + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb index c5e6901d8f8..c62995c0b9b 100644 --- a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb @@ -15,15 +15,17 @@ RSpec.describe 'Setting an issue as confidential', feature_category: :team_plann project_path: project.full_path, iid: issue.iid.to_s } - graphql_mutation(:issue_set_confidential, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - issue { - iid - confidential - } - QL + graphql_mutation( + :issue_set_confidential, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + confidential + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb index 497ae1cc13f..cdab267162e 100644 --- a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb @@ -26,18 +26,20 @@ RSpec.describe 'Setting issues crm contacts', feature_category: :service_desk do contact_ids: contact_ids } - graphql_mutation(:issue_set_crm_contacts, variables, - <<-QL.strip_heredoc - clientMutationId - errors - issue { - customerRelationsContacts { - nodes { - id - } - } - } - QL + graphql_mutation( + :issue_set_crm_contacts, + variables, + <<-QL.strip_heredoc + clientMutationId + errors + issue { + customerRelationsContacts { + nodes { + id + } + } + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb index 1a5a64e4196..f7c5febe56f 100644 --- a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb @@ -15,15 +15,17 @@ RSpec.describe 'Setting Due Date of an issue', feature_category: :team_planning project_path: project.full_path, iid: issue.iid.to_s } - graphql_mutation(:issue_set_due_date, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - issue { - iid - dueDate - } - QL + graphql_mutation( + :issue_set_due_date, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + dueDate + } + QL ) end @@ -68,7 +70,7 @@ RSpec.describe 'Setting Due Date of an issue', feature_category: :team_planning it 'returns an error' do post_graphql_mutation(mutation, current_user: current_user) - expect(graphql_errors).to include(a_hash_including('message' => /Arguments must be provided: dueDate/)) + expect(graphql_errors).to include(a_hash_including('message' => 'issueSetDueDate has the wrong arguments')) end end diff --git a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb index a8025894b1e..547ec280150 100644 --- a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb @@ -16,15 +16,17 @@ RSpec.describe 'Setting an issue as locked', feature_category: :team_planning do project_path: project.full_path, iid: issue.iid.to_s } - graphql_mutation(:issue_set_locked, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - issue { - iid - discussionLocked - } - QL + graphql_mutation( + :issue_set_locked, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + discussionLocked + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb index 77262c7f64f..d53b938a983 100644 --- a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb @@ -17,15 +17,17 @@ RSpec.describe 'Setting severity level of an incident', feature_category: :incid iid: incident.iid.to_s } - graphql_mutation(:issue_set_severity, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - issue { - iid - severity - } - QL + graphql_mutation( + :issue_set_severity, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + severity + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb b/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb index 7f6f968b1dd..807afdfb812 100644 --- a/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb @@ -21,19 +21,21 @@ RSpec.describe 'Unlink alert from an incident', feature_category: :incident_mana alert_id: alert_to_unlink.to_global_id.to_s } - graphql_mutation(:issue_unlink_alert, variables, - <<-QL.strip_heredoc - clientMutationId - errors - issue { - iid - alertManagementAlerts { - nodes { - id - } - } - } - QL + graphql_mutation( + :issue_unlink_alert, + variables, + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + alertManagementAlerts { + nodes { + id + } + } + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb index 7a1b3982111..ec82941b094 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb @@ -16,11 +16,13 @@ RSpec.describe 'Setting assignees of a merge request', feature_category: :code_r project_path: project.full_path, iid: merge_request.iid.to_s } - graphql_mutation(:merge_request_reviewer_rereview, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - QL + graphql_mutation( + :merge_request_reviewer_rereview, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + QL ) end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb index 4a7d1083f2e..cb7bac771b3 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb @@ -21,19 +21,21 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur project_path: project.full_path, iid: merge_request.iid.to_s } - graphql_mutation(:merge_request_set_assignees, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - mergeRequest { - id - assignees { - nodes { - username - } - } - } - QL + graphql_mutation( + :merge_request_set_assignees, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + mergeRequest { + id + assignees { + nodes { + username + } + } + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb index 0c2e2975350..a2c5c235d25 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb @@ -15,15 +15,17 @@ RSpec.describe 'Setting Draft status of a merge request', feature_category: :cod project_path: project.full_path, iid: merge_request.iid.to_s } - graphql_mutation(:merge_request_set_draft, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - mergeRequest { - id - title - } - QL + graphql_mutation( + :merge_request_set_draft, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + mergeRequest { + id + title + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb index e40a3cf7ce9..4ddd10b1734 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb @@ -17,19 +17,21 @@ RSpec.describe 'Setting labels of a merge request' do project_path: project.full_path, iid: merge_request.iid.to_s } - graphql_mutation(:merge_request_set_labels, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - mergeRequest { - id - labels { - nodes { - id - } - } - } - QL + graphql_mutation( + :merge_request_set_labels, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + mergeRequest { + id + labels { + nodes { + id + } + } + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb index 73a38adf723..a6ddb9beb5c 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb @@ -15,15 +15,17 @@ RSpec.describe 'Setting locked status of a merge request', feature_category: :co project_path: project.full_path, iid: merge_request.iid.to_s } - graphql_mutation(:merge_request_set_locked, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - mergeRequest { - id - discussionLocked - } - QL + graphql_mutation( + :merge_request_set_locked, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + mergeRequest { + id + discussionLocked + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb index 1898ee5a62d..9debfbd474b 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb @@ -16,17 +16,19 @@ RSpec.describe 'Setting milestone of a merge request', feature_category: :code_r project_path: project.full_path, iid: merge_request.iid.to_s } - graphql_mutation(:merge_request_set_milestone, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - mergeRequest { - id - milestone { - id - } - } - QL + graphql_mutation( + :merge_request_set_milestone, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + mergeRequest { + id + milestone { + id + } + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb index fd87112be33..c9efba689c2 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb @@ -21,19 +21,21 @@ RSpec.describe 'Setting reviewers of a merge request', :assume_throttled, featur project_path: project.full_path, iid: merge_request.iid.to_s } - graphql_mutation(:merge_request_set_reviewers, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - mergeRequest { - id - reviewers { - nodes { - username - } - } - } - QL + graphql_mutation( + :merge_request_set_reviewers, + variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + mergeRequest { + id + reviewers { + nodes { + username + } + } + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_time_estimate_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_time_estimate_spec.rb index 6bc130a97cf..541cdf0660d 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_time_estimate_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_time_estimate_spec.rb @@ -17,8 +17,11 @@ RSpec.describe 'Setting time estimate of a merge request', feature_category: :co let(:extra_params) { { project_path: project.full_path } } let(:input_params) { input.merge(extra_params) } - let(:mutation) { graphql_mutation(:merge_request_update, input_params, nil, ['productAnalyticsState']) } let(:mutation_response) { graphql_mutation_response(:merge_request_update) } + let(:mutation) do + # exclude codequalityReportsComparer because it's behind a feature flag + graphql_mutation(:merge_request_update, input_params, nil, %w[productAnalyticsState codequalityReportsComparer]) + end context 'when the user is not allowed to update a merge request' do before_all do diff --git a/spec/requests/api/graphql/mutations/merge_requests/update_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/update_spec.rb index 48db23569b6..ef21f77d818 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/update_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/update_spec.rb @@ -12,8 +12,11 @@ RSpec.describe 'Update of an existing merge request', feature_category: :code_re let(:input) { { 'iid' => merge_request.iid.to_s } } let(:extra_params) { { project_path: project.full_path } } let(:input_params) { input.merge(extra_params) } - let(:mutation) { graphql_mutation(:merge_request_update, input_params, nil, ['productAnalyticsState']) } let(:mutation_response) { graphql_mutation_response(:merge_request_update) } + let(:mutation) do + # exclude codequalityReportsComparer because it's behind a feature flag + graphql_mutation(:merge_request_update, input_params, nil, %w[productAnalyticsState codequalityReportsComparer]) + end context 'when the user is not allowed to update the merge request' do it_behaves_like 'a mutation that returns a top-level access error' @@ -28,5 +31,17 @@ RSpec.describe 'Update of an existing merge request', feature_category: :code_re let(:resource) { merge_request } let(:mutation_name) { 'mergeRequestUpdate' } end + + context 'when required arguments are missing' do + let(:input_params) { {} } + + it_behaves_like 'a mutation that returns top-level errors' do + let(:match_errors) do + include(end_with( + 'invalid value for projectPath (Expected value to not be null), iid (Expected value to not be null)' + )) + end + end + 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 480e184a60c..738dc3078e7 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 @@ -129,26 +129,6 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis it_behaves_like 'returning a success' it_behaves_like 'rejecting invalid regex' - - context 'when nuget_duplicates_option FF is disabled' do - let(:params) do - { - namespace_path: namespace.full_path, - 'nugetDuplicatesAllowed' => false - } - end - - before do - stub_feature_flags(nuget_duplicates_option: false) - end - - it 'raises an error', :aggregate_failures do - subject - - expect(graphql_errors.size).to eq(1) - expect(graphql_errors.first['message']).to include('feature flag is disabled') - end - end end RSpec.shared_examples 'accepting the mutation request creating the package settings' do diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb index 37bcdf61d23..33d840cafd7 100644 --- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb @@ -5,13 +5,15 @@ require 'spec_helper' RSpec.describe 'Adding a Note', feature_category: :team_planning do include GraphqlHelpers - let_it_be(:current_user) { create(:user) } - + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group).tap { |g| g.add_developer(developer) } } + let_it_be_with_reload(:project) { create(:project, :repository, group: group) } + let_it_be(:developer) { create(:user).tap { |u| group.add_developer(u) } } let(:noteable) { create(:merge_request, source_project: project, target_project: project) } - let(:project) { create(:project, :repository) } let(:discussion) { nil } let(:head_sha) { nil } let(:body) { 'Body text' } + let(:current_user) { user } let(:mutation) do variables = { noteable_id: GitlabSchema.id_from_object(noteable).to_s, @@ -30,9 +32,7 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do it_behaves_like 'a Note mutation when the user does not have permission' context 'when the user has permission' do - before do - project.add_developer(current_user) - end + let(:current_user) { developer } it_behaves_like 'a working GraphQL mutation' @@ -78,8 +78,10 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do end context 'for an issue' do - let(:noteable) { create(:issue, project: project) } + let_it_be_with_reload(:issue) { create(:issue, project: project) } + let(:noteable) { issue } let(:mutation) { graphql_mutation(:create_note, variables) } + let(:variables_extra) { {} } let(:variables) do { noteable_id: GitlabSchema.id_from_object(noteable).to_s, @@ -87,10 +89,6 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do }.merge(variables_extra) end - before do - project.add_developer(current_user) - end - context 'when using internal param' do let(:variables_extra) { { internal: true } } @@ -104,8 +102,8 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do end context 'as work item' do - let_it_be(:project) { create(:project) } - let_it_be(:noteable) { create(:work_item, project: project) } + let_it_be_with_reload(:work_item) { create(:work_item, :task, project: project) } + let(:noteable) { work_item } context 'when using internal param' do let(:variables_extra) { { internal: true } } @@ -120,10 +118,8 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do end context 'without notes widget' do - let(:variables_extra) { {} } - before do - WorkItems::Type.default_by_type(:issue).widget_definitions.find_by_widget_type(:notes) + WorkItems::Type.default_by_type(:task).widget_definitions.find_by_widget_type(:notes) .update!(disabled: true) end @@ -133,10 +129,6 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do end context 'when body contains quick actions' do - let_it_be(:noteable) { create(:work_item, :task, project: project) } - - let(:variables_extra) { {} } - it_behaves_like 'work item supports labels widget updates via quick actions' it_behaves_like 'work item does not support labels widget updates via quick actions' it_behaves_like 'work item supports assignee widget updates via quick actions' @@ -145,6 +137,13 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do it_behaves_like 'work item does not support start and due date widget updates via quick actions' it_behaves_like 'work item supports type change via quick actions' end + + context 'when work item is directly associated with a group' do + let_it_be_with_reload(:group_work_item) { create(:work_item, :group_level, :task, namespace: group) } + let(:noteable) { group_work_item } + + it_behaves_like 'a Note mutation that creates a Note' + end end end @@ -152,10 +151,6 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do let(:head_sha) { noteable.diff_head_sha } let(:body) { '/merge' } - before do - project.add_developer(current_user) - end - # NOTE: Known issue https://gitlab.com/gitlab-org/gitlab/-/issues/346557 it 'returns a nil note and info about the command in errors' do post_graphql_mutation(mutation, current_user: current_user) diff --git a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb index a5cd3c8b019..3f071a6d987 100644 --- a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb @@ -37,11 +37,13 @@ RSpec.describe 'Updating an image DiffNote', feature_category: :team_planning do end let!(:diff_note) do - create(:image_diff_note_on_merge_request, - noteable: noteable, - project: noteable.project, - note: original_body, - position: original_position) + create( + :image_diff_note_on_merge_request, + noteable: noteable, + project: noteable.project, + note: original_body, + position: original_position + ) end let(:mutation) do diff --git a/spec/requests/api/graphql/mutations/organizations/create_spec.rb b/spec/requests/api/graphql/mutations/organizations/create_spec.rb new file mode 100644 index 00000000000..ac6b04104ba --- /dev/null +++ b/spec/requests/api/graphql/mutations/organizations/create_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Organizations::Create, feature_category: :cell do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + + let(:mutation) { graphql_mutation(:organization_create, params) } + let(:name) { 'Name' } + let(:path) { 'path' } + let(:params) do + { + name: name, + path: path + } + end + + subject(:create_organization) { post_graphql_mutation(mutation, current_user: current_user) } + + it { expect(described_class).to require_graphql_authorizations(:create_organization) } + + def mutation_response + graphql_mutation_response(:organization_create) + end + + context 'when the user does not have permission' do + let(:current_user) { nil } + + it_behaves_like 'a mutation that returns a top-level access error' + + it 'does not create an organization' do + expect { create_organization }.not_to change { Organizations::Organization.count } + end + end + + context 'when the user has permission' do + let(:current_user) { user } + + context 'when the params are invalid' do + let(:name) { '' } + + it 'returns the validation error' do + create_organization + + expect(mutation_response).to include('errors' => ["Name can't be blank"]) + end + end + + it 'creates an organization' do + expect { create_organization }.to change { Organizations::Organization.count }.by(1) + end + + it 'returns the new organization' do + create_organization + + expect(graphql_data_at(:organization_create, :organization)).to match a_hash_including( + 'name' => name, + 'path' => path + ) + end + end +end diff --git a/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb b/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb index 2540e06be9a..5843109f356 100644 --- a/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb @@ -17,14 +17,16 @@ RSpec.describe 'Updating the packages cleanup policy', feature_category: :packag end let(:mutation) do - graphql_mutation(:update_packages_cleanup_policy, params, - <<~QUERY - packagesCleanupPolicy { - keepNDuplicatedPackageFiles - nextRunAt - } - errors - QUERY + graphql_mutation( + :update_packages_cleanup_policy, + params, + <<~QUERY + packagesCleanupPolicy { + keepNDuplicatedPackageFiles + nextRunAt + } + errors + QUERY ) end diff --git a/spec/requests/api/graphql/mutations/packages/protection/rule/create_spec.rb b/spec/requests/api/graphql/mutations/packages/protection/rule/create_spec.rb index b0c8526fa1c..ae5b6a5af95 100644 --- a/spec/requests/api/graphql/mutations/packages/protection/rule/create_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/protection/rule/create_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe 'Creating the packages protection rule', :aggregate_failures, feature_category: :package_registry do include GraphqlHelpers - using RSpec::Parameterized::TableSyntax let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user, maintainer_projects: [project]) } @@ -15,151 +14,162 @@ RSpec.describe 'Creating the packages protection rule', :aggregate_failures, fea { project_path: project.full_path, package_name_pattern: package_protection_rule_attributes.package_name_pattern, - package_type: "NPM", - push_protected_up_to_access_level: "MAINTAINER" + package_type: 'NPM', + push_protected_up_to_access_level: 'MAINTAINER' } end let(:mutation) do graphql_mutation(:create_packages_protection_rule, kwargs, <<~QUERY - clientMutationId + packageProtectionRule { + id + packageNamePattern + packageType + pushProtectedUpToAccessLevel + } errors QUERY ) end - let(:mutation_response) { graphql_mutation_response(:create_packages_protection_rule) } + let(:mutation_response_package_protection_rule) do + graphql_data_at(:createPackagesProtectionRule, :packageProtectionRule) + end - describe 'post graphql mutation' do - subject { post_graphql_mutation(mutation, current_user: user) } + let(:mutation_response_errors) { graphql_data_at(:createPackagesProtectionRule, :errors) } - context 'without existing packages protection rule' do - it 'returns without error' do - subject + subject { post_graphql_mutation(mutation, current_user: user) } - expect_graphql_errors_to_be_empty - end + shared_examples 'a successful response' do + it 'returns without error' do + subject - it 'returns the created packages protection rule' do - expect { subject }.to change { ::Packages::Protection::Rule.count }.by(1) + expect_graphql_errors_to_be_empty + expect(mutation_response_errors).to be_empty + end - expect_graphql_errors_to_be_empty - expect(Packages::Protection::Rule.where(project: project).count).to eq 1 + it 'returns the created packages protection rule' do + subject - expect(Packages::Protection::Rule.where(project: project, - package_name_pattern: kwargs[:package_name_pattern])).to exist - end + expect(mutation_response_package_protection_rule).to include( + 'id' => be_present, + 'packageNamePattern' => kwargs[:package_name_pattern], + 'packageType' => kwargs[:package_type], + 'pushProtectedUpToAccessLevel' => kwargs[:push_protected_up_to_access_level] + ) + end - context 'when invalid fields are given' do - let(:kwargs) do - { - project_path: project.full_path, - package_name_pattern: '', - package_type: 'UNKNOWN_PACKAGE_TYPE', - push_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL' - } - end - - it 'returns error about required argument' do - subject - - expect_graphql_errors_to_include(/was provided invalid value for packageType/) - expect_graphql_errors_to_include(/pushProtectedUpToAccessLevel/) - end - end + it 'creates one package protection rule' do + expect { subject }.to change { ::Packages::Protection::Rule.count }.by(1) + + expect(Packages::Protection::Rule.last).to have_attributes( + project: project, + package_name_pattern: kwargs[:package_name_pattern], + package_type: kwargs[:package_type].downcase, + push_protected_up_to_access_level: kwargs[:push_protected_up_to_access_level].downcase + ) end + end - context 'when user does not have permission' do - let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } - let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } - let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } - let_it_be(:anonymous) { create(:user) } + shared_examples 'an erroneous response' do + it 'does not create one package protection rule' do + expect { subject }.not_to change { ::Packages::Protection::Rule.count } + end + end - where(:user) do - [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)] - end + it_behaves_like 'a successful response' - with_them do - it 'returns an error' do - expect { subject }.not_to change { ::Packages::Protection::Rule.count } + context 'with invalid kwargs leading to error from graphql' do + let(:kwargs) do + super().merge!( + package_name_pattern: '', + package_type: 'UNKNOWN_PACKAGE_TYPE', + push_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL' + ) + end - expect_graphql_errors_to_include(/you don't have permission to perform this action/) - end - end + it_behaves_like 'an erroneous response' + + it 'returns error about required argument' do + subject + + expect_graphql_errors_to_include(/was provided invalid value for packageType/) + expect_graphql_errors_to_include(/pushProtectedUpToAccessLevel/) end + end - context 'with existing packages protection rule' do - let_it_be(:existing_package_protection_rule) do - create(:package_protection_rule, project: project, push_protected_up_to_access_level: Gitlab::Access::DEVELOPER) - end + context 'with invalid kwargs leading to error from business model' do + let(:kwargs) { super().merge!(package_name_pattern: '') } - context 'when package name pattern is slightly different' do - let(:kwargs) do - { - project_path: project.full_path, - # The field `package_name_pattern` is unique; this is why we change the value in a minimum way - package_name_pattern: "#{existing_package_protection_rule.package_name_pattern}-unique", - package_type: "NPM", - push_protected_up_to_access_level: "DEVELOPER" - } - end - - it 'returns the created packages protection rule' do - expect { subject }.to change { ::Packages::Protection::Rule.count }.by(1) - - expect(Packages::Protection::Rule.where(project: project).count).to eq 2 - expect(Packages::Protection::Rule.where(project: project, - package_name_pattern: kwargs[:package_name_pattern])).to exist - end - - it 'returns without error' do - subject - - expect_graphql_errors_to_be_empty - end - end + it_behaves_like 'an erroneous response' - context 'when field `package_name_pattern` is taken' do - let(:kwargs) do - { - project_path: project.full_path, - package_name_pattern: existing_package_protection_rule.package_name_pattern, - package_type: 'NPM', - push_protected_up_to_access_level: 'MAINTAINER' - } - end - - it 'returns without error' do - subject - - expect(mutation_response).to include 'errors' => ['Package name pattern has already been taken'] - end - - it 'does not create new package protection rules' do - expect { subject }.to change { Packages::Protection::Rule.count }.by(0) - - expect(Packages::Protection::Rule.where(project: project, - package_name_pattern: kwargs[:package_name_pattern], - push_protected_up_to_access_level: Gitlab::Access::MAINTAINER)).not_to exist - end - end + it 'returns an error' do + subject.tap { expect(mutation_response_errors).to include(/Package name pattern can't be blank/) } end + end - context "when feature flag ':packages_protected_packages' disabled" do - before do - stub_feature_flags(packages_protected_packages: false) + context 'with existing packages protection rule' do + let_it_be(:existing_package_protection_rule) do + create(:package_protection_rule, project: project, push_protected_up_to_access_level: :maintainer) + end + + let(:kwargs) { super().merge!(package_name_pattern: existing_package_protection_rule.package_name_pattern) } + + it_behaves_like 'an erroneous response' + + it 'returns an error' do + subject.tap { expect(mutation_response_errors).to include(/Package name pattern has already been taken/) } + end + + context 'when field `package_name_pattern` is different than existing one' do + let(:kwargs) do + # The field `package_name_pattern` is unique; this is why we change the value in a minimum way + super().merge!(package_name_pattern: "#{existing_package_protection_rule.package_name_pattern}-unique") end - it 'does not create any package protection rules' do - expect { subject }.to change { Packages::Protection::Rule.count }.by(0) + it_behaves_like 'a successful response' + end + + context 'when field `push_protected_up_to_access_level` is different than existing one' do + let(:kwargs) { super().merge!(push_protected_up_to_access_level: 'DEVELOPER') } + + it_behaves_like 'an erroneous response' - expect(Packages::Protection::Rule.where(project: project)).not_to exist + it 'returns an error' do + subject.tap { expect(mutation_response_errors).to include(/Package name pattern has already been taken/) } end + end + end - it 'returns error of disabled feature flag' do - subject.tap { expect_graphql_errors_to_include(/'packages_protected_packages' feature flag is disabled/) } + context 'when user does not have permission' do + let_it_be(:anonymous) { create(:user) } + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } + + where(:user) do + [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)] + end + + with_them do + it_behaves_like 'an erroneous response' + + it 'returns an error' do + subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } end end end + + context "when feature flag ':packages_protected_packages' disabled" do + before do + stub_feature_flags(packages_protected_packages: false) + end + + it_behaves_like 'an erroneous response' + + it 'returns error of disabled feature flag' do + subject.tap { expect_graphql_errors_to_include(/'packages_protected_packages' feature flag is disabled/) } + end + end end diff --git a/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb b/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb new file mode 100644 index 00000000000..1d94d520674 --- /dev/null +++ b/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Deleting a package protection rule', :aggregate_failures, feature_category: :package_registry do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be_with_refind(:package_protection_rule) { create(:package_protection_rule, project: project) } + let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } + + let(:mutation) { graphql_mutation(:delete_packages_protection_rule, input) } + let(:mutation_response) { graphql_mutation_response(:delete_packages_protection_rule) } + let(:input) { { id: package_protection_rule.to_global_id } } + + subject { post_graphql_mutation(mutation, current_user: current_user) } + + shared_examples 'an erroneous reponse' do + it { subject.tap { expect(mutation_response).to be_blank } } + it { expect { subject }.not_to change { ::Packages::Protection::Rule.count } } + end + + it_behaves_like 'a working GraphQL mutation' + + it 'responds with deleted package protection rule' do + subject + + expect(mutation_response).to include( + 'errors' => be_blank, + 'packageProtectionRule' => { + 'id' => package_protection_rule.to_global_id.to_s, + 'packageNamePattern' => package_protection_rule.package_name_pattern, + 'packageType' => package_protection_rule.package_type.upcase, + 'pushProtectedUpToAccessLevel' => package_protection_rule.push_protected_up_to_access_level.upcase + } + ) + end + + it { is_expected.tap { expect_graphql_errors_to_be_empty } } + it { expect { subject }.to change { ::Packages::Protection::Rule.count }.from(1).to(0) } + + context 'with existing package protection rule belonging to other project' do + let_it_be(:package_protection_rule) do + create(:package_protection_rule, package_name_pattern: 'protection_rule_other_project') + end + + it_behaves_like 'an erroneous reponse' + + it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + + context 'with deleted package protection rule' do + let!(:package_protection_rule) do + create(:package_protection_rule, project: project, package_name_pattern: 'protection_rule_deleted').destroy! + end + + it_behaves_like 'an erroneous reponse' + + it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + + context 'when current_user does not have permission' do + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:anonymous) { create(:user) } + + where(:current_user) do + [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)] + end + + with_them do + it_behaves_like 'an erroneous reponse' + + it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + end + + context "when feature flag ':packages_protected_packages' disabled" do + before do + stub_feature_flags(packages_protected_packages: false) + end + + it_behaves_like 'an erroneous reponse' + + it { subject.tap { expect_graphql_errors_to_include(/'packages_protected_packages' feature flag is disabled/) } } + end +end diff --git a/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb b/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb index 45028cba3ae..fdd4de865ad 100644 --- a/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb +++ b/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb @@ -10,12 +10,14 @@ RSpec.describe 'Updating an existing release asset link', feature_category: :rel let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } let_it_be(:release_link) do - create(:release_link, - release: release, - name: 'link name', - url: 'https://example.com/url', - filepath: '/permanent/path', - link_type: 'package') + create( + :release_link, + release: release, + name: 'link name', + url: 'https://example.com/url', + filepath: '/permanent/path', + link_type: 'package' + ) end let(:current_user) { developer } diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb index a6d727ae6d3..7094cb807b2 100644 --- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb @@ -42,7 +42,7 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d let(:current_user) { nil } it_behaves_like 'a mutation that returns top-level errors', - errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] it 'does not create the Snippet' do expect do @@ -122,7 +122,7 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d let(:project_path) { 'foobar' } it_behaves_like 'a mutation that returns top-level errors', - errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] end context 'when the feature is disabled' do @@ -131,7 +131,7 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d end it_behaves_like 'a mutation that returns top-level errors', - errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] end it_behaves_like 'snippet edit usage data counters' @@ -169,8 +169,8 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d end it_behaves_like 'expected files argument', nil, nil - it_behaves_like 'expected files argument', %w(foo bar), %w(foo bar) - it_behaves_like 'expected files argument', 'foo', %w(foo) + it_behaves_like 'expected files argument', %w[foo bar], %w[foo bar] + it_behaves_like 'expected files argument', 'foo', %w[foo] context 'when files has an invalid value' do let(:uploaded_files) { [1] } diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb index c3f818b6627..7b0de7a9fba 100644 --- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb @@ -24,7 +24,7 @@ RSpec.describe 'Destroying a Snippet', feature_category: :source_code_management let(:current_user) { create(:user) } it_behaves_like 'a mutation that returns top-level errors', - errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] it 'does not destroy the Snippet' do expect do @@ -53,7 +53,7 @@ RSpec.describe 'Destroying a Snippet', feature_category: :source_code_management let!(:snippet_gid) { project.to_gid.to_s } it 'returns an error' do - err_message = %["#{snippet_gid}" does not represent an instance of Snippet] + err_message = %("#{snippet_gid}" does not represent an instance of Snippet) post_graphql_mutation(mutation, current_user: current_user) diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb index 9a8c027da8a..6fd41437ce4 100644 --- a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb @@ -40,7 +40,7 @@ RSpec.describe 'Mark snippet as spam', feature_category: :source_code_management let(:current_user) { other_user } it_behaves_like 'a mutation that returns top-level errors', - errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] it_behaves_like 'does not mark the snippet as spam' end diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb index 78df78cb2a0..0bc475c7105 100644 --- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb @@ -46,7 +46,7 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d let(:current_user) { create(:user) } it_behaves_like 'a mutation that returns top-level errors', - errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] it 'does not update the Snippet' do expect do @@ -118,13 +118,15 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d describe 'PersonalSnippet' do let(:snippet) do - create(:personal_snippet, - :private, - :repository, - file_name: original_file_name, - title: original_title, - content: original_content, - description: original_description) + create( + :personal_snippet, + :private, + :repository, + file_name: original_file_name, + title: original_title, + content: original_content, + description: original_description + ) end it_behaves_like 'graphql update actions' @@ -139,15 +141,17 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d let_it_be(:project) { create(:project, :private) } let(:snippet) do - create(:project_snippet, - :private, - :repository, - project: project, - author: create(:user), - file_name: original_file_name, - title: original_title, - content: original_content, - description: original_description) + create( + :project_snippet, + :private, + :repository, + project: project, + author: create(:user), + file_name: original_file_name, + title: original_title, + content: original_content, + description: original_description + ) end context 'when the author is not a member of the project' do diff --git a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb index c611c6ee2a1..429aa06d9f1 100644 --- a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb +++ b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb @@ -21,12 +21,14 @@ RSpec.describe 'Marking all todos done', feature_category: :team_planning do let(:input) { {} } let(:mutation) do - graphql_mutation(:todos_mark_all_done, input, - <<-QL.strip_heredoc - clientMutationId - todos { id } - errors - QL + graphql_mutation( + :todos_mark_all_done, + input, + <<-QL.strip_heredoc + clientMutationId + todos { id } + errors + QL ) end diff --git a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb index 60700d8024c..c09f89ef567 100644 --- a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb +++ b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb @@ -19,15 +19,17 @@ RSpec.describe 'Marking todos done', feature_category: :team_planning do let(:input) { { id: todo1.to_global_id.to_s } } let(:mutation) do - graphql_mutation(:todo_mark_done, input, - <<-QL.strip_heredoc - clientMutationId - errors - todo { - id - state - } - QL + graphql_mutation( + :todo_mark_done, + input, + <<-QL.strip_heredoc + clientMutationId + errors + todo { + id + state + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb index 9daa243cf8e..4bbfc7b2f1d 100644 --- a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb +++ b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb @@ -20,15 +20,17 @@ RSpec.describe 'Restoring many Todos', feature_category: :team_planning do let(:input) { { ids: input_ids } } let(:mutation) do - graphql_mutation(:todo_restore_many, input, - <<-QL.strip_heredoc - clientMutationId - errors - todos { - id - state - } - QL + graphql_mutation( + :todo_restore_many, + input, + <<-QL.strip_heredoc + clientMutationId + errors + todos { + id + state + } + QL ) end diff --git a/spec/requests/api/graphql/mutations/todos/restore_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_spec.rb index 868298763ec..1ebd04432be 100644 --- a/spec/requests/api/graphql/mutations/todos/restore_spec.rb +++ b/spec/requests/api/graphql/mutations/todos/restore_spec.rb @@ -19,15 +19,17 @@ RSpec.describe 'Restoring Todos', feature_category: :team_planning do let(:input) { { id: todo1.to_global_id.to_s } } let(:mutation) do - graphql_mutation(:todo_restore, input, - <<-QL.strip_heredoc - clientMutationId - errors - todo { - id - state - } - QL + graphql_mutation( + :todo_restore, + input, + <<-QL.strip_heredoc + clientMutationId + errors + todo { + id + state + } + QL ) end 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 7c48f324d24..c8819f1e38f 100644 --- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb +++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb @@ -6,8 +6,15 @@ RSpec.describe 'rendering namespace statistics', feature_category: :metrics do include GraphqlHelpers let(:namespace) { user.namespace } - let!(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace, packages_size: 5.gigabytes, uploads_size: 3.gigabytes) } let(:user) { create(:user) } + let!(:statistics) do + create( + :namespace_root_storage_statistics, + namespace: namespace, + packages_size: 5.gigabytes, + uploads_size: 3.gigabytes + ) + end let(:query) do graphql_query_for( diff --git a/spec/requests/api/graphql/organizations/organization_query_spec.rb b/spec/requests/api/graphql/organizations/organization_query_spec.rb index d02158382eb..c243e0613ad 100644 --- a/spec/requests/api/graphql/organizations/organization_query_spec.rb +++ b/spec/requests/api/graphql/organizations/organization_query_spec.rb @@ -79,7 +79,10 @@ RSpec.describe 'getting organization information', feature_category: :cell do <<~FIELDS organizationUsers { nodes { - badges + badges { + text + variant + } id user { id @@ -94,7 +97,7 @@ RSpec.describe 'getting organization information', feature_category: :cell do organization_user_node = graphql_data_at(:organization, :organizationUsers, :nodes).first expected_attributes = { - "badges" => ["It's you!"], + "badges" => [{ "text" => "It's you!", "variant" => 'muted' }], "id" => organization_user.to_global_id.to_s, "user" => { "id" => user.to_global_id.to_s } } diff --git a/spec/requests/api/graphql/project/base_service_spec.rb b/spec/requests/api/graphql/project/base_service_spec.rb index b27cddea07b..646ff8dd8a8 100644 --- a/spec/requests/api/graphql/project/base_service_spec.rb +++ b/spec/requests/api/graphql/project/base_service_spec.rb @@ -41,7 +41,7 @@ RSpec.describe 'query Jira service', feature_category: :system_access do it 'retuns list of jira imports' do service_types = services.map { |s| s['type'] } - expect(service_types).to match_array(%w(BugzillaService JiraService RedmineService)) + expect(service_types).to match_array(%w[BugzillaService JiraService RedmineService]) 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 9a40a972256..c86d3bdd14c 100644 --- a/spec/requests/api/graphql/project/container_repositories_spec.rb +++ b/spec/requests/api/graphql/project/container_repositories_spec.rb @@ -46,7 +46,7 @@ RSpec.describe 'getting container repositories in a project', feature_category: before do stub_container_registry_config(enabled: true) container_repositories.each do |repository| - stub_container_registry_tags(repository: repository.path, tags: %w(tag1 tag2 tag3), with_manifest: false) + stub_container_registry_tags(repository: repository.path, tags: %w[tag1 tag2 tag3], with_manifest: false) end end @@ -141,7 +141,7 @@ RSpec.describe 'getting container repositories in a project', feature_category: end before do - stub_container_registry_tags(repository: container_repository.path, tags: %w(tag4 tag5 tag6), with_manifest: false) + stub_container_registry_tags(repository: container_repository.path, tags: %w[tag4 tag5 tag6], with_manifest: false) end it 'returns the searched container repository' do @@ -175,11 +175,11 @@ RSpec.describe 'getting container repositories in a project', feature_category: let_it_be(:container_repository5) { create(:container_repository, name: 'e', project: sort_project) } before do - stub_container_registry_tags(repository: container_repository1.path, tags: %w(tag1 tag1 tag3), with_manifest: false) - stub_container_registry_tags(repository: container_repository2.path, tags: %w(tag4 tag5 tag6), with_manifest: false) - stub_container_registry_tags(repository: container_repository3.path, tags: %w(tag7 tag8), with_manifest: false) - stub_container_registry_tags(repository: container_repository4.path, tags: %w(tag9), with_manifest: false) - stub_container_registry_tags(repository: container_repository5.path, tags: %w(tag10 tag11), with_manifest: false) + stub_container_registry_tags(repository: container_repository1.path, tags: %w[tag1 tag1 tag3], with_manifest: false) + stub_container_registry_tags(repository: container_repository2.path, tags: %w[tag4 tag5 tag6], with_manifest: false) + stub_container_registry_tags(repository: container_repository3.path, tags: %w[tag7 tag8], with_manifest: false) + stub_container_registry_tags(repository: container_repository4.path, tags: %w[tag9], with_manifest: false) + stub_container_registry_tags(repository: container_repository5.path, tags: %w[tag10 tag11], with_manifest: false) end def pagination_query(params) diff --git a/spec/requests/api/graphql/project/data_transfer_spec.rb b/spec/requests/api/graphql/project/data_transfer_spec.rb index aafa8d65eb9..79b2b10419b 100644 --- a/spec/requests/api/graphql/project/data_transfer_spec.rb +++ b/spec/requests/api/graphql/project/data_transfer_spec.rb @@ -68,45 +68,21 @@ RSpec.describe 'project data transfers', feature_category: :source_code_manageme context 'when user has enough permissions' do before do project.add_owner(current_user) + subject end - context 'when data_transfer_monitoring_mock_data is NOT enabled' do - before do - stub_feature_flags(data_transfer_monitoring_mock_data: false) - subject - end - - it 'returns real results' do - expect(response).to have_gitlab_http_status(:ok) + it 'returns real results' do + expect(response).to have_gitlab_http_status(:ok) - expect(egress_data.count).to eq(2) + expect(egress_data.count).to eq(2) - expect(egress_data.first.keys).to match_array( - %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress] - ) + expect(egress_data.first.keys).to match_array( + %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress] + ) - expect(egress_data.pluck('repositoryEgress')).to match_array(%w[1 2]) - end - - it_behaves_like 'a working graphql query' + expect(egress_data.pluck('repositoryEgress')).to match_array(%w[1 2]) end - context 'when data_transfer_monitoring_mock_data is enabled' do - before do - stub_feature_flags(data_transfer_monitoring_mock_data: true) - subject - end - - it 'returns mock results' do - expect(response).to have_gitlab_http_status(:ok) - - expect(egress_data.count).to eq(12) - expect(egress_data.first.keys).to match_array( - %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress] - ) - end - - it_behaves_like 'a working graphql query' - end + it_behaves_like 'a working graphql query' end end diff --git a/spec/requests/api/graphql/project/environments_spec.rb b/spec/requests/api/graphql/project/environments_spec.rb index 3a863bd3d77..94ce6b797cd 100644 --- a/spec/requests/api/graphql/project/environments_spec.rb +++ b/spec/requests/api/graphql/project/environments_spec.rb @@ -150,7 +150,7 @@ RSpec.describe 'Project Environments query', feature_category: :continuous_deliv end describe 'last deployments of environments' do - ::Deployment.statuses.each do |status, _| # rubocop:disable RSpec/UselessDynamicDefinition + ::Deployment.statuses.each_key do |status| # rubocop:disable RSpec/UselessDynamicDefinition -- `status` used in `let_it_be` let_it_be(:"production_#{status}_deployment") do create(:deployment, status.to_sym, environment: production, project: project) end diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb index 6cbc70022ed..c2910938acf 100644 --- a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb +++ b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb @@ -119,7 +119,7 @@ RSpec.describe 'sentry errors requests', feature_category: :error_tracking do end let(:error_data) { graphql_data.dig('project', 'sentryErrors', 'errors', 'nodes') } - let(:pagination_data) { graphql_data.dig('project', 'sentryErrors', 'errors', 'pageInfo') } + let(:pagination_data) { graphql_data.dig('project', 'sentryErrors', 'errors', 'pageInfo') } it_behaves_like 'a working graphql query' do before do diff --git a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb index a15e4c1e792..bc56df5b6ff 100644 --- a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb +++ b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb @@ -73,7 +73,7 @@ RSpec.describe 'Getting versions related to an issue', feature_category: :design post_graphql(query, current_user: current_user) keys = graphql_data.dig(*edges_path).first['node'].keys - expect(keys).to match_array(%w(id sha createdAt author)) + expect(keys).to match_array(%w[id sha createdAt author]) end end diff --git a/spec/requests/api/graphql/project/issue_spec.rb b/spec/requests/api/graphql/project/issue_spec.rb index bc90f9e89e6..8d21a9f0394 100644 --- a/spec/requests/api/graphql/project/issue_spec.rb +++ b/spec/requests/api/graphql/project/issue_spec.rb @@ -108,7 +108,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid)', feature_category: :team_pla let(:object_field_name) { :design } let(:no_argument_error) do - custom_graphql_error(path, a_string_matching(%r/id or filename/)) + custom_graphql_error(path, a_string_matching(%r{id or filename})) end let_it_be(:object_on_other_issue) { create(:design, issue: issue_b) } @@ -134,7 +134,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid)', feature_category: :team_pla it 'raises an error' do post_query - expect(graphql_errors).to include(custom_graphql_error(path, a_string_matching(%r/id or sha/))) + expect(graphql_errors).to include(custom_graphql_error(path, a_string_matching(%r{id or sha}))) end end diff --git a/spec/requests/api/graphql/project/jira_import_spec.rb b/spec/requests/api/graphql/project/jira_import_spec.rb index 25cea0238ef..a1d340b3500 100644 --- a/spec/requests/api/graphql/project/jira_import_spec.rb +++ b/spec/requests/api/graphql/project/jira_import_spec.rb @@ -96,7 +96,7 @@ RSpec.describe 'query Jira import data', feature_category: :importers do total_issue_count = jira_imports.map { |ji| ji.dig('totalIssueCount') } expect(jira_imports.size).to eq 2 - expect(jira_proket_keys).to eq %w(BB AA) + expect(jira_proket_keys).to eq %w[BB AA] expect(usernames).to eq [current_user.username, current_user.username] expect(imported_issues_count).to eq [2, 2] expect(failed_issues_count).to eq [1, 2] diff --git a/spec/requests/api/graphql/project/jira_projects_spec.rb b/spec/requests/api/graphql/project/jira_projects_spec.rb index 3cd689deda5..2859e8a7c99 100644 --- a/spec/requests/api/graphql/project/jira_projects_spec.rb +++ b/spec/requests/api/graphql/project/jira_projects_spec.rb @@ -55,8 +55,8 @@ RSpec.describe 'query Jira projects', feature_category: :integrations do project_ids = jira_projects.map { |jp| jp['projectId'] } expect(jira_projects.size).to eq(2) - expect(project_keys).to eq(%w(EX ABC)) - expect(project_names).to eq(%w(Example Alphabetical)) + expect(project_keys).to eq(%w[EX ABC]) + expect(project_names).to eq(%w[Example Alphabetical]) expect(project_ids).to eq([10000, 10001]) end @@ -69,8 +69,8 @@ RSpec.describe 'query Jira projects', feature_category: :integrations do project_ids = jira_projects.map { |jp| jp['projectId'] } expect(jira_projects.size).to eq(1) - expect(project_keys).to eq(%w(EX)) - expect(project_names).to eq(%w(Example)) + expect(project_keys).to eq(%w[EX]) + expect(project_names).to eq(%w[Example]) expect(project_ids).to eq([10000]) end end diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index c274199e65b..23be9fa5286 100644 --- a/spec/requests/api/graphql/project/merge_request_spec.rb +++ b/spec/requests/api/graphql/project/merge_request_spec.rb @@ -22,7 +22,7 @@ RSpec.describe 'getting merge request information nested in a project', feature_ it_behaves_like 'a working graphql query' do # we exclude Project.pipeline because it needs arguments, - # codequalityReportsComparer because no pipeline exist yet + # 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(:mr_fields) { all_graphql_fields_for('MergeRequest', excluded: excluded) } diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index 543de43bcf3..176a02df0be 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -48,8 +48,11 @@ RSpec.describe 'getting merge request listings nested in a project', feature_cat end it_behaves_like 'a working graphql query' do + # we exclude codequalityReportsComparer because it is behind feature flag + let(:excluded) { %w[codequalityReportsComparer] } + let(:query) do - query_merge_requests(all_graphql_fields_for('MergeRequest', max_depth: 2)) + query_merge_requests(all_graphql_fields_for('MergeRequest', max_depth: 2, excluded: excluded)) end before do diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb index 8d4a39d6b30..8206d076d2e 100644 --- a/spec/requests/api/graphql/project/release_spec.rb +++ b/spec/requests/api/graphql/project/release_spec.rb @@ -36,7 +36,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re let(:path) { path_prefix } let(:release_fields) do - %{ + %( tagName tagPath description @@ -45,7 +45,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re createdAt releasedAt upcomingRelease - } + ) end before do @@ -176,14 +176,14 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re let(:path) { path_prefix + %w[links] } let(:release_fields) do - query_graphql_field(:links, nil, %{ + query_graphql_field(:links, nil, %( selfUrl openedMergeRequestsUrl mergedMergeRequestsUrl closedMergeRequestsUrl openedIssuesUrl closedIssuesUrl - }) + )) end it 'finds all release links' do @@ -225,7 +225,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re let(:path) { path_prefix } let(:release_fields) do - %{ + %( tagName tagPath description @@ -234,7 +234,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re createdAt releasedAt upcomingRelease - } + ) end before do @@ -358,14 +358,14 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re let(:path) { path_prefix + %w[links] } let(:release_fields) do - query_graphql_field(:links, nil, %{ + query_graphql_field(:links, nil, %( selfUrl openedMergeRequestsUrl mergedMergeRequestsUrl closedMergeRequestsUrl openedIssuesUrl closedIssuesUrl - }) + )) end it 'finds only selfUrl' do @@ -547,10 +547,10 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re let(:current_user) { developer } let(:release_fields) do - %{ + %( releasedAt upcomingRelease - } + ) end before do @@ -588,13 +588,13 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re let_it_be_with_reload(:release) { create(:release, project: project) } let(:release_fields) do - %{ + %( milestones { nodes { title } } - } + ) end let(:actual_milestone_title_order) do diff --git a/spec/requests/api/graphql/project/terraform/state_spec.rb b/spec/requests/api/graphql/project/terraform/state_spec.rb index 1889e7a1064..28f3868c7cf 100644 --- a/spec/requests/api/graphql/project/terraform/state_spec.rb +++ b/spec/requests/api/graphql/project/terraform/state_spec.rb @@ -20,7 +20,7 @@ RSpec.describe 'query a single terraform state', feature_category: :infrastructu query_graphql_field( :terraformState, { name: terraform_state.name }, - %{ + %( id name lockedAt @@ -45,7 +45,7 @@ RSpec.describe 'query a single terraform state', feature_category: :infrastructu lockedByUser { id } - } + ) ) ) end diff --git a/spec/requests/api/graphql/project/terraform/states_spec.rb b/spec/requests/api/graphql/project/terraform/states_spec.rb index 7a789a5d481..d6cf3a52649 100644 --- a/spec/requests/api/graphql/project/terraform/states_spec.rb +++ b/spec/requests/api/graphql/project/terraform/states_spec.rb @@ -14,7 +14,7 @@ RSpec.describe 'query terraform states', feature_category: :infrastructure_as_co graphql_query_for( :project, { fullPath: project.full_path }, - %{ + %( terraformStates { count nodes { @@ -45,7 +45,7 @@ RSpec.describe 'query terraform states', feature_category: :infrastructure_as_co } } } - } + ) ) end diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb index d5d3d6c578f..d0f80bcfebe 100644 --- a/spec/requests/api/graphql/project/work_items_spec.rb +++ b/spec/requests/api/graphql/project/work_items_spec.rb @@ -350,7 +350,9 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team end context 'when fetching work item linked items widget' do - let_it_be(:related_items) { create_list(:work_item, 3, project: project, milestone: milestone1) } + let_it_be(:other_project) { create(:project, :repository, :public, group: group) } + let_it_be(:other_milestone) { create(:milestone, project: other_project) } + let_it_be(:related_items) { create_list(:work_item, 3, project: other_project, milestone: other_milestone) } let(:fields) do <<~GRAPHQL @@ -384,21 +386,24 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team before do create(:work_item_link, source: item1, target: related_items[0], link_type: 'relates_to') + create(:work_item_link, source: item2, target: related_items[0], link_type: 'relates_to') end it 'executes limited number of N+1 queries', :use_sql_query_cache do + post_graphql(query, current_user: current_user) # warm-up + control = ActiveRecord::QueryRecorder.new(skip_cached: false) do post_graphql(query, current_user: current_user) end - create(:work_item_link, source: item1, target: related_items[1], link_type: 'relates_to') - create(:work_item_link, source: item1, target: related_items[2], link_type: 'relates_to') + [item1, item2].each do |item| + create(:work_item_link, source: item, target: related_items[1], link_type: 'relates_to') + create(:work_item_link, source: item, target: related_items[2], link_type: 'relates_to') + end expect_graphql_errors_to_be_empty - # TODO: Fix N+1 queries executed for the linked work item widgets - # https://gitlab.com/gitlab-org/gitlab/-/issues/420605 expect { post_graphql(query, current_user: current_user) } - .not_to exceed_all_query_limit(control).with_threshold(11) + .not_to exceed_all_query_limit(control) end end diff --git a/spec/requests/api/graphql/projects/projects_spec.rb b/spec/requests/api/graphql/projects/projects_spec.rb new file mode 100644 index 00000000000..84b8c2285f0 --- /dev/null +++ b/spec/requests/api/graphql/projects/projects_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting a collection of projects', feature_category: :source_code_management do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:group) { create(:group, name: 'public-group') } + let_it_be(:projects) { create_list(:project, 5, :public, group: group) } + let_it_be(:other_project) { create(:project, :public, group: group) } + + let(:filters) { {} } + + let(:query) do + graphql_query_for( + :projects, + filters, + "nodes {#{all_graphql_fields_for('Project', max_depth: 1, excluded: ['productAnalyticsState'])} }" + ) + end + + before_all do + group.add_developer(current_user) + end + + context 'when providing full_paths filter' do + let(:project_full_paths) { projects.map(&:full_path) } + let(:filters) { { full_paths: project_full_paths } } + + let(:single_project_query) do + graphql_query_for( + :projects, + { full_paths: [project_full_paths.first] }, + "nodes {#{all_graphql_fields_for('Project', max_depth: 1, excluded: ['productAnalyticsState'])} }" + ) + end + + it_behaves_like 'a working graphql query that returns data' do + before do + post_graphql(query, current_user: current_user) + end + end + + 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 + post_graphql(single_project_query, current_user: current_user) + end.count + + # 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 + + it 'returns the expected projects' do + post_graphql(query, current_user: current_user) + returned_full_paths = graphql_data_at(:projects, :nodes).pluck('fullPath') + + expect(returned_full_paths).to match_array(project_full_paths) + end + + context 'when users provides more than 50 full_paths' do + let(:filters) { { full_paths: Array.new(51) { other_project.full_path } } } + + it 'returns an error' do + post_graphql(query, current_user: current_user) + + expect(graphql_errors).to contain_exactly( + hash_including('message' => _('You cannot provide more than 50 full_paths')) + ) + end + end + end +end diff --git a/spec/requests/api/graphql/user_spec.rb b/spec/requests/api/graphql/user_spec.rb index 41ee233dfc5..22ebc1be964 100644 --- a/spec/requests/api/graphql/user_spec.rb +++ b/spec/requests/api/graphql/user_spec.rb @@ -113,4 +113,36 @@ RSpec.describe 'User', feature_category: :user_profile do end end end + + describe 'organizations field' do + let_it_be(:organization_user) { create(:organization_user, user: current_user) } + let_it_be(:organization) { organization_user.organization } + let_it_be(:another_organization) { create(:organization) } + let_it_be(:another_user) { create(:user) } + + let(:query) do + graphql_query_for( + :user, + { username: current_user.username.to_s.upcase }, + 'organizations { nodes { path } }' + ) + end + + context 'with permission' do + it 'returns the relevant organization details' do + post_graphql(query, current_user: current_user) + + expect(graphql_data.dig('user', 'organizations', 'nodes').pluck('path')) + .to match_array(organization.path) + end + end + + context 'without permission' do + it 'does not return organization details' do + post_graphql(query, current_user: another_user) + + expect(graphql_data.dig('user', 'organizations', 'nodes')).to be_nil + end + end + end end diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb index b8575b25e0a..36a27abd982 100644 --- a/spec/requests/api/graphql/work_item_spec.rb +++ b/spec/requests/api/graphql/work_item_spec.rb @@ -619,6 +619,47 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do ) end + context 'when inaccessible links are present' do + let_it_be(:no_access_item) { create(:work_item, title: "PRIVATE", project: create(:project, :private)) } + + before do + create(:work_item_link, source: work_item, target: no_access_item, link_type: 'relates_to') + end + + it 'returns only items that the user has access to' do + expect(graphql_dig_at(work_item_data, :widgets, "linkedItems", "nodes", "linkId")) + .to match_array([link1.to_gid.to_s, link2.to_gid.to_s]) + end + end + + context 'when limiting the number of results' do + it_behaves_like 'sorted paginated query' do + include_context 'no sort argument' + + let(:first_param) { 1 } + let(:all_records) { [link1, link2] } + let(:data_path) { ['workItem', 'widgets', "linkedItems", -1] } + + def widget_fields(args) + query_graphql_field( + :widgets, {}, query_graphql_field( + '... on WorkItemWidgetLinkedItems', {}, query_graphql_field( + 'linkedItems', args, "#{page_info} nodes { linkId }" + ) + ) + ) + end + + def pagination_query(params) + graphql_query_for('workItem', { 'id' => global_id }, widget_fields(params)) + end + + def pagination_results_data(nodes) + nodes.map { |item| GlobalID::Locator.locate(item['linkId']) } + end + end + end + context 'when filtering by link type' do let(:work_item_fields) do <<~GRAPHQL @@ -664,20 +705,6 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do end describe 'notes widget' do - let(:work_item_fields) do - <<~GRAPHQL - id - widgets { - type - ... on WorkItemWidgetNotes { - system: discussions(filter: ONLY_ACTIVITY, first: 10) { nodes { id notes { nodes { id system internal body } } } }, - comments: discussions(filter: ONLY_COMMENTS, first: 10) { nodes { id notes { nodes { id system internal body } } } }, - all_notes: discussions(filter: ALL_NOTES, first: 10) { nodes { id notes { nodes { id system internal body } } } } - } - } - GRAPHQL - end - context 'when fetching award emoji from notes' do let(:work_item_fields) do <<~GRAPHQL @@ -768,6 +795,26 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do expect { post_graphql(query, current_user: developer) }.not_to exceed_query_limit(control).with_threshold(4) expect_graphql_errors_to_be_empty end + + context 'when work item is associated with a group' do + let_it_be(:group_work_item) { create(:work_item, :group_level, namespace: group) } + let_it_be(:group_work_item_note) { create(:note, noteable: group_work_item, author: developer, project: nil) } + let(:global_id) { group_work_item.to_gid.to_s } + + before_all do + create(:award_emoji, awardable: group_work_item_note, name: 'rocket', user: developer) + end + + it 'returns notes for the group work item' do + all_widgets = graphql_dig_at(work_item_data, :widgets) + notes_widget = all_widgets.find { |x| x['type'] == 'NOTES' } + notes = graphql_dig_at(notes_widget['discussions'], :nodes).flat_map { |d| d['notes']['nodes'] } + + expect(notes).to contain_exactly( + hash_including('body' => group_work_item_note.note) + ) + end + end end end |