From 311b0269b4eb9839fa63f80c8d7a58f32b8138a0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 18 Nov 2021 13:16:36 +0000 Subject: Add latest changes from gitlab-org/gitlab@14-5-stable-ee --- spec/requests/api/graphql/ci/pipelines_spec.rb | 63 +++++++ spec/requests/api/graphql/gitlab_schema_spec.rb | 23 ++- .../group/dependency_proxy_manifests_spec.rb | 22 +++ .../ci/runners_registration_token/reset_spec.rb | 2 +- .../mutations/design_management/delete_spec.rb | 2 +- .../api/graphql/mutations/issues/create_spec.rb | 4 + .../api/graphql/mutations/issues/move_spec.rb | 2 +- .../mutations/issues/set_confidential_spec.rb | 2 +- .../mutations/issues/set_crm_contacts_spec.rb | 161 ++++++++++++++++++ .../graphql/mutations/issues/set_due_date_spec.rb | 2 +- .../graphql/mutations/issues/set_severity_spec.rb | 2 +- .../mutations/merge_requests/set_draft_spec.rb | 79 +++++++++ .../mutations/merge_requests/set_wip_spec.rb | 79 --------- .../merge_requests/update_reviewer_state_spec.rb | 65 +++++++ .../api/graphql/mutations/releases/create_spec.rb | 2 +- .../api/graphql/mutations/releases/delete_spec.rb | 2 +- .../api/graphql/mutations/releases/update_spec.rb | 4 +- .../ci_configuration/configure_sast_iac_spec.rb | 26 +++ spec/requests/api/graphql/namespace_query_spec.rb | 86 ++++++++++ spec/requests/api/graphql/packages/helm_spec.rb | 59 +++++++ spec/requests/api/graphql/project/issues_spec.rb | 4 +- .../api/graphql/project/merge_request_spec.rb | 16 +- spec/requests/api/graphql/project/release_spec.rb | 186 ++++++++++++++++++++- spec/requests/api/graphql/project/releases_spec.rb | 15 +- 24 files changed, 793 insertions(+), 115 deletions(-) create mode 100644 spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb create mode 100644 spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb delete mode 100644 spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb create mode 100644 spec/requests/api/graphql/mutations/merge_requests/update_reviewer_state_spec.rb create mode 100644 spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb create mode 100644 spec/requests/api/graphql/namespace_query_spec.rb create mode 100644 spec/requests/api/graphql/packages/helm_spec.rb (limited to 'spec/requests/api/graphql') diff --git a/spec/requests/api/graphql/ci/pipelines_spec.rb b/spec/requests/api/graphql/ci/pipelines_spec.rb index 6587061094d..1f47f678898 100644 --- a/spec/requests/api/graphql/ci/pipelines_spec.rb +++ b/spec/requests/api/graphql/ci/pipelines_spec.rb @@ -186,6 +186,69 @@ RSpec.describe 'Query.project(fullPath).pipelines' do end end + describe '.job_artifacts' do + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:pipeline_job_1) { create(:ci_build, pipeline: pipeline, name: 'Job 1') } + let_it_be(:pipeline_job_artifact_1) { create(:ci_job_artifact, job: pipeline_job_1) } + let_it_be(:pipeline_job_2) { create(:ci_build, pipeline: pipeline, name: 'Job 2') } + let_it_be(:pipeline_job_artifact_2) { create(:ci_job_artifact, job: pipeline_job_2) } + + let(:path) { %i[project pipelines nodes jobArtifacts] } + + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + pipelines { + nodes { + jobArtifacts { + name + downloadPath + fileType + } + } + } + } + } + ) + end + + before do + post_graphql(query, current_user: user) + end + + it_behaves_like 'a working graphql query' + + it 'returns the job_artifacts of a pipeline' do + job_artifacts_graphql_data = graphql_data_at(*path).flatten + + expect( + job_artifacts_graphql_data.map { |pip| pip['name'] } + ).to contain_exactly(pipeline_job_artifact_1.filename, pipeline_job_artifact_2.filename) + end + + it 'avoids N+1 queries' do + first_user = create(:user) + second_user = create(:user) + + control_count = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: first_user) + end + + pipeline_2 = create(:ci_pipeline, project: project) + pipeline_2_job_1 = create(:ci_build, pipeline: pipeline_2, name: 'Pipeline 2 Job 1') + create(:ci_job_artifact, job: pipeline_2_job_1) + pipeline_2_job_2 = create(:ci_build, pipeline: pipeline_2, name: 'Pipeline 2 Job 2') + create(:ci_job_artifact, job: pipeline_2_job_2) + + expect do + post_graphql(query, current_user: second_user) + end.not_to exceed_query_limit(control_count) + + expect(response).to have_gitlab_http_status(:ok) + end + end + describe '.jobs(securityReportTypes)' do let_it_be(:query) do %( diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index b41d851439b..8bbeae97f57 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -190,19 +190,18 @@ RSpec.describe 'GitlabSchema configurations' do let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) } it 'logs the query complexity and depth' do - analyzer_memo = { - query_string: query, - variables: {}.to_s, - complexity: 181, - depth: 13, - duration_s: 7, - operation_name: 'IntrospectionQuery', - used_fields: an_instance_of(Array), - used_deprecated_fields: an_instance_of(Array) - } - expect_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:duration).and_return(7) - expect(Gitlab::GraphqlLogger).to receive(:info).with(analyzer_memo) + + expect(Gitlab::GraphqlLogger).to receive(:info).with( + hash_including( + trace_type: 'execute_query', + "query_analysis.duration_s" => 7, + "query_analysis.complexity" => 181, + "query_analysis.depth" => 13, + "query_analysis.used_deprecated_fields" => an_instance_of(Array), + "query_analysis.used_fields" => an_instance_of(Array) + ) + ) post_graphql(query, current_user: nil) end diff --git a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb index 30e704adb92..3527c8183f6 100644 --- a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb +++ b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb @@ -116,4 +116,26 @@ RSpec.describe 'getting dependency proxy manifests in a group' do expect(dependency_proxy_image_count_response).to eq(manifests.size) end + + describe 'sorting and pagination' do + let(:data_path) { ['group', :dependencyProxyManifests] } + let(:current_user) { owner } + + context 'with default sorting' do + let_it_be(:descending_manifests) { manifests.reverse.map { |manifest| global_id_of(manifest)} } + + it_behaves_like 'sorted paginated query' do + let(:sort_param) { '' } + let(:first_param) { 2 } + let(:all_records) { descending_manifests } + end + end + + def pagination_query(params) + # remove sort since the type does not accept sorting, but be future proof + graphql_query_for('group', { 'fullPath' => group.full_path }, + query_nodes(:dependencyProxyManifests, :id, include_pagination_info: true, args: params.merge(sort: nil)) + ) + end + end end diff --git a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb index 0fd8fdc3f59..322706be119 100644 --- a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'RunnersRegistrationTokenReset' do subject expect(graphql_errors).not_to be_empty - expect(graphql_errors).to include(a_hash_including('message' => "The resource that you are attempting to access does not exist or you don't have permission to perform this action")) + expect(graphql_errors).to include(a_hash_including('message' => Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)) expect(mutation_response).to be_nil end 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 e329416faee..1dffb86b344 100644 --- a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb @@ -53,7 +53,7 @@ RSpec.describe "deleting designs" do 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| OpenStruct.new(filename: fn) } } + let(:designs) { %w/foo bar baz/.map { |fn| instance_double('file', filename: fn) } } let(:the_error) { a_string_matching %r/filenames were not found/ } end end diff --git a/spec/requests/api/graphql/mutations/issues/create_spec.rb b/spec/requests/api/graphql/mutations/issues/create_spec.rb index 886f3140086..6baed352b37 100644 --- a/spec/requests/api/graphql/mutations/issues/create_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/create_spec.rb @@ -48,5 +48,9 @@ RSpec.describe 'Create an issue' do expect(mutation_response['issue']).to include('discussionLocked' => true) expect(Issue.last.work_item_type.base_type).to eq('issue') end + + it_behaves_like 'has spam protection' do + let(:mutation_class) { ::Mutations::Issues::Create } + end end end diff --git a/spec/requests/api/graphql/mutations/issues/move_spec.rb b/spec/requests/api/graphql/mutations/issues/move_spec.rb index 5bbaff61edd..20ed16879f6 100644 --- a/spec/requests/api/graphql/mutations/issues/move_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/move_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Moving an issue' do context 'when the user is not allowed to read source project' do it 'returns an error' do - error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR post_graphql_mutation(mutation, current_user: user) expect(response).to have_gitlab_http_status(:success) 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 3f804a46992..12ab504da14 100644 --- a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb @@ -36,7 +36,7 @@ RSpec.describe 'Setting an issue as confidential' do end it 'returns an error if the user is not allowed to update the issue' do - error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR post_graphql_mutation(mutation, current_user: create(:user)) expect(graphql_errors).to include(a_hash_including('message' => error)) 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 new file mode 100644 index 00000000000..3da702c55d7 --- /dev/null +++ b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Setting issues crm contacts' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:contacts) { create_list(:contact, 4, group: group) } + + let(:issue) { create(:issue, project: project) } + let(:operation_mode) { Types::MutationOperationModeEnum.default_mode } + let(:crm_contact_ids) { [global_id_of(contacts[1]), global_id_of(contacts[2])] } + let(:does_not_exist_or_no_permission) { "The resource that you are attempting to access does not exist or you don't have permission to perform this action" } + + let(:mutation) do + variables = { + project_path: issue.project.full_path, + iid: issue.iid.to_s, + operation_mode: operation_mode, + crm_contact_ids: crm_contact_ids + } + + graphql_mutation(:issue_set_crm_contacts, variables, + <<-QL.strip_heredoc + clientMutationId + errors + issue { + customerRelationsContacts { + nodes { + id + } + } + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:issue_set_crm_contacts) + end + + before do + create(:issue_customer_relations_contact, issue: issue, contact: contacts[0]) + create(:issue_customer_relations_contact, issue: issue, contact: contacts[1]) + end + + context 'when the user has no permission' do + it 'returns expected error' do + error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_errors).to include(a_hash_including('message' => error)) + end + end + + context 'when the user has permission' do + before do + group.add_reporter(user) + end + + context 'when the feature is disabled' do + before do + stub_feature_flags(customer_relations: false) + end + + it 'raises expected error' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_errors).to include(a_hash_including('message' => 'Feature disabled')) + end + end + + context 'replace' do + it 'updates the issue with correct contacts' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes, :id)) + .to match_array([global_id_of(contacts[1]), global_id_of(contacts[2])]) + end + end + + context 'append' do + let(:crm_contact_ids) { [global_id_of(contacts[3])] } + let(:operation_mode) { Types::MutationOperationModeEnum.enum[:append] } + + it 'updates the issue with correct contacts' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes, :id)) + .to match_array([global_id_of(contacts[0]), global_id_of(contacts[1]), global_id_of(contacts[3])]) + end + end + + context 'remove' do + let(:crm_contact_ids) { [global_id_of(contacts[0])] } + let(:operation_mode) { Types::MutationOperationModeEnum.enum[:remove] } + + it 'updates the issue with correct contacts' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes, :id)) + .to match_array([global_id_of(contacts[1])]) + end + end + + context 'when the contact does not exist' do + let(:crm_contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] } + + it 'returns expected error' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_data_at(:issue_set_crm_contacts, :errors)) + .to match_array(["Issue customer relations contacts #{non_existing_record_id}: #{does_not_exist_or_no_permission}"]) + end + end + + context 'when the contact belongs to a different group' do + let(:group2) { create(:group) } + let(:contact) { create(:contact, group: group2) } + let(:crm_contact_ids) { [global_id_of(contact)] } + + before do + group2.add_reporter(user) + end + + it 'returns expected error' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_data_at(:issue_set_crm_contacts, :errors)) + .to match_array(["Issue customer relations contacts #{contact.id}: #{does_not_exist_or_no_permission}"]) + end + end + + context 'when attempting to add more than 6' do + let(:operation_mode) { Types::MutationOperationModeEnum.enum[:append] } + let(:gid) { global_id_of(contacts[0]) } + let(:crm_contact_ids) { [gid, gid, gid, gid, gid, gid, gid] } + + it 'returns expected error' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_data_at(:issue_set_crm_contacts, :errors)) + .to match_array(["You can only add up to 6 contacts at one time"]) + end + end + + context 'when trying to remove non-existent contact' do + let(:operation_mode) { Types::MutationOperationModeEnum.enum[:remove] } + let(:crm_contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] } + + it 'raises expected error' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_data_at(:issue_set_crm_contacts, :errors)).to be_empty + end + end + end +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 72e47a98373..8e223b6fdaf 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 @@ -36,7 +36,7 @@ RSpec.describe 'Setting Due Date of an issue' do end it 'returns an error if the user is not allowed to update the issue' do - error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR post_graphql_mutation(mutation, current_user: create(:user)) expect(graphql_errors).to include(a_hash_including('message' => error)) 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 41997f151a2..cd9d695bd2c 100644 --- a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb @@ -35,7 +35,7 @@ RSpec.describe 'Setting severity level of an incident' do context 'when the user is not allowed to update the incident' do it 'returns an error' do - error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR post_graphql_mutation(mutation, current_user: user) expect(response).to have_gitlab_http_status(:success) 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 new file mode 100644 index 00000000000..bea2365eaa6 --- /dev/null +++ b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Setting Draft status of a merge request' do + include GraphqlHelpers + + let(:current_user) { create(:user) } + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } + let(:input) { { draft: true } } + + let(:mutation) do + variables = { + 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 + ) + end + + def mutation_response + graphql_mutation_response(:merge_request_set_draft) + end + + before do + project.add_developer(current_user) + end + + it 'returns an error if the user is not allowed to update the merge request' do + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).not_to be_empty + end + + it 'marks the merge request as Draft' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['mergeRequest']['title']).to start_with('Draft:') + end + + it 'does not do anything if the merge request was already marked `Draft`' do + merge_request.update!(title: 'draft: hello world') + + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['mergeRequest']['title']).to start_with('draft:') + end + + context 'when passing Draft false as input' do + let(:input) { { draft: false } } + + it 'does not do anything if the merge reqeust was not marked draft' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['mergeRequest']['title']).not_to start_with(/draft\:/) + end + + it 'unmarks the merge request as `Draft`' do + merge_request.update!(title: 'draft: hello world') + + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['mergeRequest']['title']).not_to start_with('/draft\:/') + end + end +end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb deleted file mode 100644 index 2143abd3031..00000000000 --- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Setting Draft status of a merge request' do - include GraphqlHelpers - - let(:current_user) { create(:user) } - let(:merge_request) { create(:merge_request) } - let(:project) { merge_request.project } - let(:input) { { wip: true } } - - let(:mutation) do - variables = { - project_path: project.full_path, - iid: merge_request.iid.to_s - } - graphql_mutation(:merge_request_set_wip, variables.merge(input), - <<-QL.strip_heredoc - clientMutationId - errors - mergeRequest { - id - title - } - QL - ) - end - - def mutation_response - graphql_mutation_response(:merge_request_set_wip) - end - - before do - project.add_developer(current_user) - end - - it 'returns an error if the user is not allowed to update the merge request' do - post_graphql_mutation(mutation, current_user: create(:user)) - - expect(graphql_errors).not_to be_empty - end - - it 'marks the merge request as Draft' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['mergeRequest']['title']).to start_with('Draft:') - end - - it 'does not do anything if the merge request was already marked `Draft`' do - merge_request.update!(title: 'draft: hello world') - - post_graphql_mutation(mutation, current_user: current_user) - - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['mergeRequest']['title']).to start_with('draft:') - end - - context 'when passing Draft false as input' do - let(:input) { { wip: false } } - - it 'does not do anything if the merge reqeust was not marked draft' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['mergeRequest']['title']).not_to start_with(/draft\:/) - end - - it 'unmarks the merge request as `Draft`' do - merge_request.update!(title: 'draft: hello world') - - post_graphql_mutation(mutation, current_user: current_user) - - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['mergeRequest']['title']).not_to start_with('/draft\:/') - end - end -end diff --git a/spec/requests/api/graphql/mutations/merge_requests/update_reviewer_state_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/update_reviewer_state_spec.rb new file mode 100644 index 00000000000..cf497cb2579 --- /dev/null +++ b/spec/requests/api/graphql/mutations/merge_requests/update_reviewer_state_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Toggle attention requested for reviewer' do + include GraphqlHelpers + + let(:current_user) { create(:user) } + let(:merge_request) { create(:merge_request, reviewers: [user]) } + let(:project) { merge_request.project } + let(:user) { create(:user) } + let(:input) { { user_id: global_id_of(user) } } + + let(:mutation) do + variables = { + project_path: project.full_path, + iid: merge_request.iid.to_s + } + graphql_mutation(:merge_request_toggle_attention_requested, variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + QL + ) + end + + def mutation_response + graphql_mutation_response(:merge_request_toggle_attention_requested) + end + + def mutation_errors + mutation_response['errors'] + end + + before do + project.add_developer(current_user) + project.add_developer(user) + end + + it 'returns an error if the user is not allowed to update the merge request' do + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).not_to be_empty + end + + describe 'reviewer does not exist' do + let(:input) { { user_id: global_id_of(create(:user)) } } + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_errors).not_to be_empty + end + end + + describe 'reviewer exists' do + it 'does not return an error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_errors).to be_empty + end + end +end diff --git a/spec/requests/api/graphql/mutations/releases/create_spec.rb b/spec/requests/api/graphql/mutations/releases/create_spec.rb index a4918cd560c..86995c10f10 100644 --- a/spec/requests/api/graphql/mutations/releases/create_spec.rb +++ b/spec/requests/api/graphql/mutations/releases/create_spec.rb @@ -342,7 +342,7 @@ RSpec.describe 'Creation of a new release' do end context "when the current user doesn't have access to create releases" do - expected_error_message = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + expected_error_message = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR context 'when the current user is a Reporter' do let(:current_user) { reporter } diff --git a/spec/requests/api/graphql/mutations/releases/delete_spec.rb b/spec/requests/api/graphql/mutations/releases/delete_spec.rb index 40063156609..eb4f0b594ea 100644 --- a/spec/requests/api/graphql/mutations/releases/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/releases/delete_spec.rb @@ -50,7 +50,7 @@ RSpec.describe 'Deleting a release' do expect(mutation_response).to be_nil expect(graphql_errors.count).to eq(1) - expect(graphql_errors.first['message']).to eq("The resource that you are attempting to access does not exist or you don't have permission to perform this action") + expect(graphql_errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) end end diff --git a/spec/requests/api/graphql/mutations/releases/update_spec.rb b/spec/requests/api/graphql/mutations/releases/update_spec.rb index c9a6c3abd57..0fa3d7de299 100644 --- a/spec/requests/api/graphql/mutations/releases/update_spec.rb +++ b/spec/requests/api/graphql/mutations/releases/update_spec.rb @@ -218,13 +218,13 @@ RSpec.describe 'Updating an existing release' do context 'when the project does not exist' do let(:mutation_arguments) { super().merge(projectPath: 'not/a/real/path') } - it_behaves_like 'top-level error with message', "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + it_behaves_like 'top-level error with message', Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR end end end context "when the current user doesn't have access to update releases" do - expected_error_message = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + expected_error_message = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR context 'when the current user is a Reporter' do let(:current_user) { reporter } diff --git a/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb new file mode 100644 index 00000000000..929609d4160 --- /dev/null +++ b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'ConfigureSastIac' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :test_repo) } + + let(:variables) { { project_path: project.full_path } } + let(:mutation) { graphql_mutation(:configure_sast_iac, variables) } + let(:mutation_response) { graphql_mutation_response(:configureSastIac) } + + context 'when authorized' do + let_it_be(:user) { project.owner } + + it 'creates a branch with sast iac configured' do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['errors']).to be_empty + expect(mutation_response['branch']).not_to be_empty + expect(mutation_response['successPath']).not_to be_empty + end + end +end diff --git a/spec/requests/api/graphql/namespace_query_spec.rb b/spec/requests/api/graphql/namespace_query_spec.rb new file mode 100644 index 00000000000..f7ee2bcb55d --- /dev/null +++ b/spec/requests/api/graphql/namespace_query_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:other_user) { create(:user) } + + let_it_be(:group_namespace) { create(:group) } + let_it_be(:user_namespace) { create(:user_namespace, owner: user) } + let_it_be(:project_namespace) { create(:project_namespace, parent: group_namespace) } + + describe '.namespace' do + subject { post_graphql(query, current_user: current_user) } + + let(:current_user) { user } + + let(:query) { graphql_query_for(:namespace, { 'fullPath' => target_namespace.full_path }, all_graphql_fields_for('Namespace')) } + let(:query_result) { graphql_data['namespace'] } + + shared_examples 'retrieving a namespace' do + context 'authorised query' do + before do + subject + end + + it_behaves_like 'a working graphql query' + + it 'fetches the expected data' do + expect(query_result).to include( + 'fullPath' => target_namespace.full_path, + 'name' => target_namespace.name + ) + end + end + + context 'unauthorised query' do + before do + subject + end + + context 'anonymous user' do + let(:current_user) { nil } + + it 'does not retrieve the record' do + expect(query_result).to be_nil + end + end + + context 'the current user does not have permission' do + let(:current_user) { other_user } + + it 'does not retrieve the record' do + expect(query_result).to be_nil + end + end + end + end + + it_behaves_like 'retrieving a namespace' do + let(:target_namespace) { group_namespace } + + before do + group_namespace.add_developer(user) + end + end + + it_behaves_like 'retrieving a namespace' do + let(:target_namespace) { user_namespace } + end + + context 'does not retrieve project namespace' do + let(:target_namespace) { project_namespace } + + before do + subject + end + + it 'does not retrieve the record' do + expect(query_result).to be_nil + end + end + end +end diff --git a/spec/requests/api/graphql/packages/helm_spec.rb b/spec/requests/api/graphql/packages/helm_spec.rb new file mode 100644 index 00000000000..397096f70db --- /dev/null +++ b/spec/requests/api/graphql/packages/helm_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'helm package details' do + include GraphqlHelpers + include_context 'package details setup' + + let_it_be(:package) { create(:helm_package, project: project) } + + let(:package_files_metadata) {query_graphql_fragment('HelmFileMetadata')} + + let(:query) do + graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) + #{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)} + packageFiles { + nodes { + #{package_files} + fileMetadata { + #{package_files_metadata} + } + } + } + FIELDS + end + + subject { post_graphql(query, current_user: user) } + + before do + subject + end + + it_behaves_like 'a package detail' + it_behaves_like 'a package with files' + + it 'has the correct file metadata' do + expect(first_file_response_metadata).to include( + 'channel' => first_file.helm_file_metadatum.channel + ) + expect(first_file_response_metadata['metadata']).to include( + 'name' => first_file.helm_file_metadatum.metadata['name'], + 'home' => first_file.helm_file_metadatum.metadata['home'], + 'sources' => first_file.helm_file_metadatum.metadata['sources'], + 'version' => first_file.helm_file_metadatum.metadata['version'], + 'description' => first_file.helm_file_metadatum.metadata['description'], + 'keywords' => first_file.helm_file_metadatum.metadata['keywords'], + 'maintainers' => first_file.helm_file_metadatum.metadata['maintainers'], + 'icon' => first_file.helm_file_metadatum.metadata['icon'], + 'apiVersion' => first_file.helm_file_metadatum.metadata['apiVersion'], + 'condition' => first_file.helm_file_metadatum.metadata['condition'], + 'tags' => first_file.helm_file_metadatum.metadata['tags'], + 'appVersion' => first_file.helm_file_metadatum.metadata['appVersion'], + 'deprecated' => first_file.helm_file_metadatum.metadata['deprecated'], + 'annotations' => first_file.helm_file_metadatum.metadata['annotations'], + 'kubeVersion' => first_file.helm_file_metadatum.metadata['kubeVersion'], + 'dependencies' => first_file.helm_file_metadatum.metadata['dependencies'], + 'type' => first_file.helm_file_metadatum.metadata['type'] + ) + end +end diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index 1c6d6ce4707..b3e91afb5b3 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -429,11 +429,11 @@ RSpec.describe 'getting an issue list for a project' do end it 'avoids N+1 queries' do - create(:contact, group_id: group.id, issues: [issue_a]) + create(:issue_customer_relations_contact, :for_issue, issue: issue_a) control = ActiveRecord::QueryRecorder.new(skip_cached: false) { clean_state_query } - create(:contact, group_id: group.id, issues: [issue_a]) + create(:issue_customer_relations_contact, :for_issue, issue: issue_a) expect { clean_state_query }.not_to exceed_all_query_limit(control) end diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index 438ea9bb4c1..353bf0356f6 100644 --- a/spec/requests/api/graphql/project/merge_request_spec.rb +++ b/spec/requests/api/graphql/project/merge_request_spec.rb @@ -347,7 +347,7 @@ RSpec.describe 'getting merge request information nested in a project' do expect(interaction_data).to contain_exactly a_hash_including( 'canMerge' => false, 'canUpdate' => can_update, - 'reviewState' => unreviewed, + 'reviewState' => attention_requested, 'reviewed' => false, 'approved' => false ) @@ -380,8 +380,8 @@ RSpec.describe 'getting merge request information nested in a project' do describe 'scalability' do let_it_be(:other_users) { create_list(:user, 3) } - let(:unreviewed) do - { 'reviewState' => 'UNREVIEWED' } + let(:attention_requested) do + { 'reviewState' => 'ATTENTION_REQUESTED' } end let(:reviewed) do @@ -413,9 +413,9 @@ RSpec.describe 'getting merge request information nested in a project' do expect { post_graphql(query) }.not_to exceed_query_limit(baseline) expect(interaction_data).to contain_exactly( - include(unreviewed), - include(unreviewed), - include(unreviewed), + include(attention_requested), + include(attention_requested), + include(attention_requested), include(reviewed) ) end @@ -444,7 +444,7 @@ RSpec.describe 'getting merge request information nested in a project' do it_behaves_like 'when requesting information about MR interactions' do let(:field) { :reviewers } - let(:unreviewed) { 'UNREVIEWED' } + let(:attention_requested) { 'ATTENTION_REQUESTED' } let(:can_update) { false } def assign_user(user) @@ -454,7 +454,7 @@ RSpec.describe 'getting merge request information nested in a project' do it_behaves_like 'when requesting information about MR interactions' do let(:field) { :assignees } - let(:unreviewed) { nil } + let(:attention_requested) { nil } let(:can_update) { true } # assignees can update MRs def assign_user(user) diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb index 7f24d051457..77abac4ef04 100644 --- a/spec/requests/api/graphql/project/release_spec.rb +++ b/spec/requests/api/graphql/project/release_spec.rb @@ -228,6 +228,189 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do end end + shared_examples 'restricted access to release fields' do + describe 'scalar fields' do + let(:path) { path_prefix } + + let(:release_fields) do + %{ + tagName + tagPath + description + descriptionHtml + name + createdAt + releasedAt + upcomingRelease + } + end + + before do + post_query + end + + it 'finds all release data' do + expect(data).to eq({ + 'tagName' => release.tag, + 'tagPath' => nil, + 'description' => release.description, + 'descriptionHtml' => release.description_html, + 'name' => release.name, + 'createdAt' => release.created_at.iso8601, + 'releasedAt' => release.released_at.iso8601, + 'upcomingRelease' => false + }) + end + end + + describe 'milestones' do + let(:path) { path_prefix + %w[milestones nodes] } + + let(:release_fields) do + query_graphql_field(:milestones, nil, 'nodes { id title }') + end + + it 'finds milestones associated to a release' do + post_query + + expected = release.milestones.order_by_dates_and_title.map do |milestone| + { 'id' => global_id_of(milestone), 'title' => milestone.title } + end + + expect(data).to eq(expected) + end + end + + describe 'author' do + let(:path) { path_prefix + %w[author] } + + let(:release_fields) do + query_graphql_field(:author, nil, 'id username') + end + + it 'finds the author of the release' do + post_query + + expect(data).to eq( + 'id' => global_id_of(release.author), + 'username' => release.author.username + ) + end + end + + describe 'commit' do + let(:path) { path_prefix + %w[commit] } + + let(:release_fields) do + query_graphql_field(:commit, nil, 'sha') + end + + it 'restricts commit associated with the release' do + post_query + + expect(data).to eq(nil) + end + end + + describe 'assets' do + describe 'count' do + let(:path) { path_prefix + %w[assets] } + + let(:release_fields) do + query_graphql_field(:assets, nil, 'count') + end + + it 'returns non source release links count' do + post_query + + expect(data).to eq('count' => release.assets_count(except: [:sources])) + end + end + + describe 'links' do + let(:path) { path_prefix + %w[assets links nodes] } + + let(:release_fields) do + query_graphql_field(:assets, nil, + query_graphql_field(:links, nil, 'nodes { id name url external, directAssetUrl }')) + end + + it 'finds all non source external release links' do + post_query + + expected = release.links.map do |link| + { + 'id' => global_id_of(link), + 'name' => link.name, + 'url' => link.url, + 'external' => true, + 'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << "/downloads#{link.filepath}" : link.url + } + end + + expect(data).to match_array(expected) + end + end + + describe 'sources' do + let(:path) { path_prefix + %w[assets sources nodes] } + + let(:release_fields) do + query_graphql_field(:assets, nil, + query_graphql_field(:sources, nil, 'nodes { format url }')) + end + + it 'restricts release sources' do + post_query + + expect(data).to match_array([]) + end + end + end + + describe 'links' do + let(:path) { path_prefix + %w[links] } + + let(:release_fields) do + query_graphql_field(:links, nil, %{ + selfUrl + openedMergeRequestsUrl + mergedMergeRequestsUrl + closedMergeRequestsUrl + openedIssuesUrl + closedIssuesUrl + }) + end + + it 'finds only selfUrl' do + post_query + + expect(data).to eq( + 'selfUrl' => project_release_url(project, release), + 'openedMergeRequestsUrl' => nil, + 'mergedMergeRequestsUrl' => nil, + 'closedMergeRequestsUrl' => nil, + 'openedIssuesUrl' => nil, + 'closedIssuesUrl' => nil + ) + end + end + + describe 'evidences' do + let(:path) { path_prefix + %w[evidences] } + + let(:release_fields) do + query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }') + end + + it 'restricts all evidence fields' do + post_query + + expect(data).to eq('nodes' => []) + end + end + end + shared_examples 'no access to the release field' do describe 'repository-related fields' do let(:path) { path_prefix } @@ -302,7 +485,8 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do context 'when the user has Guest permissions' do let(:current_user) { guest } - it_behaves_like 'no access to the release field' + it_behaves_like 'restricted access to release fields' + it_behaves_like 'no access to editUrl' end context 'when the user has Reporter permissions' do diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb index 2816ce90a6b..c28a6fa7666 100644 --- a/spec/requests/api/graphql/project/releases_spec.rb +++ b/spec/requests/api/graphql/project/releases_spec.rb @@ -129,10 +129,12 @@ RSpec.describe 'Query.project(fullPath).releases()' do end it 'does not return data for fields that expose repository information' do + tag_name = release.tag + release_name = release.name expect(data).to eq( - 'tagName' => nil, + 'tagName' => tag_name, 'tagPath' => nil, - 'name' => "Release-#{release.id}", + 'name' => release_name, 'commit' => nil, 'assets' => { 'count' => release.assets_count(except: [:sources]), @@ -143,7 +145,14 @@ RSpec.describe 'Query.project(fullPath).releases()' do 'evidences' => { 'nodes' => [] }, - 'links' => nil + 'links' => { + 'closedIssuesUrl' => nil, + 'closedMergeRequestsUrl' => nil, + 'mergedMergeRequestsUrl' => nil, + 'openedIssuesUrl' => nil, + 'openedMergeRequestsUrl' => nil, + 'selfUrl' => project_release_url(project, release) + } ) end end -- cgit v1.2.3