diff options
Diffstat (limited to 'spec/requests/api/graphql')
24 files changed, 552 insertions, 161 deletions
diff --git a/spec/requests/api/graphql/ci/application_setting_spec.rb b/spec/requests/api/graphql/ci/application_setting_spec.rb new file mode 100644 index 00000000000..156ee550f16 --- /dev/null +++ b/spec/requests/api/graphql/ci/application_setting_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting Application Settings' do + include GraphqlHelpers + + let(:fields) do + <<~QUERY + #{all_graphql_fields_for('CiApplicationSettings', max_depth: 1)} + QUERY + end + + let(:query) do + graphql_query_for( + 'ciApplicationSettings', + fields + ) + end + + let(:settings_data) { graphql_data['ciApplicationSettings'] } + + context 'without admin permissions' do + let(:user) { create(:user) } + + before do + post_graphql(query, current_user: user) + end + + it_behaves_like 'a working graphql query' + + specify { expect(settings_data).to be nil } + end + + context 'with admin permissions' do + let(:user) { create(:user, :admin) } + + before do + post_graphql(query, current_user: user) + end + + it_behaves_like 'a working graphql query' + + it 'fetches the settings data' do + # assert against hash to ensure no additional fields are exposed + expect(settings_data).to match({ 'keepLatestArtifact' => Gitlab::CurrentSettings.current_application_settings.keep_latest_artifact }) + end + end +end diff --git a/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb index db8a412e45c..99647d0fa3a 100644 --- a/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb +++ b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb @@ -46,7 +46,7 @@ RSpec.describe 'Getting Ci Cd Setting' do it 'fetches the settings data' do expect(settings_data['mergePipelinesEnabled']).to eql project.ci_cd_settings.merge_pipelines_enabled? expect(settings_data['mergeTrainsEnabled']).to eql project.ci_cd_settings.merge_trains_enabled? - expect(settings_data['keepLatestArtifact']).to eql project.ci_keep_latest_artifact? + expect(settings_data['keepLatestArtifact']).to eql project.keep_latest_artifacts_available? end end end diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb index 42c8e0cc9c0..09e89f65882 100644 --- a/spec/requests/api/graphql/issue/issue_spec.rb +++ b/spec/requests/api/graphql/issue/issue_spec.rb @@ -24,6 +24,20 @@ RSpec.describe 'Query.issue(id)' do end end + it_behaves_like 'a noteable graphql type we can query' do + let(:noteable) { issue } + let(:project) { issue.project } + let(:path_to_noteable) { [:issue] } + + before do + project.add_guest(current_user) + end + + def query(fields) + graphql_query_for('issue', issue_params, fields) + end + end + context 'when the user does not have access to the issue' do it 'returns nil' do project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) diff --git a/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb index bf7eb3d980c..18cbb7d8b00 100644 --- a/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb +++ b/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe 'Updating an existing HTTP Integration' do include GraphqlHelpers - let_it_be(:user) { create(:user) } + let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:integration) { create(:alert_management_http_integration, project: project) } @@ -32,18 +32,8 @@ RSpec.describe 'Updating an existing HTTP Integration' do let(:mutation_response) { graphql_mutation_response(:http_integration_update) } before do - project.add_maintainer(user) + project.add_maintainer(current_user) end - it 'updates the integration' do - post_graphql_mutation(mutation, current_user: user) - - integration_response = mutation_response['integration'] - - expect(response).to have_gitlab_http_status(:success) - expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s) - expect(integration_response['name']).to eq('Modified Name') - expect(integration_response['active']).to be_falsey - expect(integration_response['url']).to include('modified-name') - end + it_behaves_like 'updating an existing HTTP integration' end diff --git a/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb index 328f4fb7b6e..fec9a8c6307 100644 --- a/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb +++ b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb @@ -3,52 +3,10 @@ require 'spec_helper' RSpec.describe 'Create a label or backlog board list' do - include GraphqlHelpers - let_it_be(:group) { create(:group, :private) } let_it_be(:board) { create(:board, group: group) } - let_it_be(:user) { create(:user) } - let_it_be(:dev_label) do - create(:group_label, title: 'Development', color: '#FFAABB', group: group) - end - - let(:current_user) { user } - let(:mutation) { graphql_mutation(:board_list_create, input) } - let(:mutation_response) { graphql_mutation_response(:board_list_create) } - - context 'the user is not allowed to read board lists' do - let(:input) { { board_id: board.to_global_id.to_s, backlog: true } } - - it_behaves_like 'a mutation that returns a top-level access error' - end - - context 'when user has permissions to admin board lists' do - before do - group.add_reporter(current_user) - end - - describe 'backlog list' do - let(:input) { { board_id: board.to_global_id.to_s, backlog: true } } - - it 'creates the list' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['list']) - .to include('position' => nil, 'listType' => 'backlog') - end - end - - describe 'label list' do - let(:input) { { board_id: board.to_global_id.to_s, label_id: dev_label.to_global_id.to_s } } - - it 'creates the list' do - post_graphql_mutation(mutation, current_user: current_user) - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['list']) - .to include('position' => 0, 'listType' => 'label', 'label' => include('title' => 'Development')) - end - end + it_behaves_like 'board lists create request' do + let(:mutation_name) { :board_list_create } end end diff --git a/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb b/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb index 283badeaf33..0dcae28ac5d 100644 --- a/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe 'CiCdSettingsUpdate' do include GraphqlHelpers - let_it_be(:project) { create(:project, ci_keep_latest_artifact: true) } + let_it_be(:project) { create(:project, keep_latest_artifact: true) } let(:variables) { { full_path: project.full_path, keep_latest_artifact: false } } let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) } @@ -42,7 +42,7 @@ RSpec.describe 'CiCdSettingsUpdate' do project.reload expect(response).to have_gitlab_http_status(:success) - expect(project.ci_keep_latest_artifact).to eq(false) + expect(project.keep_latest_artifact).to eq(false) end context 'when bad arguments are provided' do 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 new file mode 100644 index 00000000000..2e4f35cbcde --- /dev/null +++ b/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Setting assignees of a merge request' 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_reviewer_rereview, variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + QL + ) + end + + def mutation_response + graphql_mutation_response(:merge_request_reviewer_rereview) + 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/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb index 21da1332465..7dd897f6466 100644 --- a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb @@ -43,6 +43,8 @@ RSpec.describe 'Adding a DiffNote' do it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote + it_behaves_like 'a Note mutation when there are rate limit validation errors' + context do let(:diff_refs) { build(:commit).diff_refs } # Allow fake diff refs so arguments are valid diff --git a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb index 8bc68e6017c..0e5744fb64f 100644 --- a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb @@ -46,6 +46,8 @@ RSpec.describe 'Adding an image DiffNote' do it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote + it_behaves_like 'a Note mutation when there are rate limit validation errors' + context do let(:diff_refs) { build(:commit).diff_refs } # Allow fake diff refs so arguments are valid 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 6d761eb0a54..1eed1c8e2ae 100644 --- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb @@ -37,6 +37,8 @@ RSpec.describe 'Adding a Note' do it_behaves_like 'a Note mutation when the given resource id is not for a Noteable' + it_behaves_like 'a Note mutation when there are rate limit validation errors' + it 'returns the note' 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 713b26a6a9b..1ce09881fde 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 @@ -6,7 +6,7 @@ RSpec.describe 'Updating an image DiffNote' do include GraphqlHelpers using RSpec::Parameterized::TableSyntax - let_it_be(:noteable) { create(:merge_request, :with_diffs) } + let_it_be(:noteable) { create(:merge_request) } let_it_be(:original_body) { 'Original body' } let_it_be(:original_position) do Gitlab::Diff::Position.new( diff --git a/spec/requests/api/graphql/mutations/releases/create_spec.rb b/spec/requests/api/graphql/mutations/releases/create_spec.rb index 79bdcec7944..a4918cd560c 100644 --- a/spec/requests/api/graphql/mutations/releases/create_spec.rb +++ b/spec/requests/api/graphql/mutations/releases/create_spec.rb @@ -309,10 +309,7 @@ RSpec.describe 'Creation of a new release' do let(:asset_link_2) { { name: 'My link', url: 'https://example.com/2' } } let(:assets) { { links: [asset_link_1, asset_link_2] } } - # Right now the raw Postgres error message is sent to the user as the validation message. - # We should catch this validation error and return a nicer message: - # https://gitlab.com/gitlab-org/gitlab/-/issues/277087 - it_behaves_like 'errors-as-data with message', 'PG::UniqueViolation' + it_behaves_like 'errors-as-data with message', %r{Validation failed: Links have duplicate values \(My link\)} end context 'when two release assets share the same URL' do @@ -320,8 +317,7 @@ RSpec.describe 'Creation of a new release' do let(:asset_link_2) { { name: 'My second link', url: 'https://example.com' } } let(:assets) { { links: [asset_link_1, asset_link_2] } } - # Same note as above about the ugly error message - it_behaves_like 'errors-as-data with message', 'PG::UniqueViolation' + it_behaves_like 'errors-as-data with message', %r{Validation failed: Links have duplicate values \(https://example.com\)} end context 'when the provided tag name is HEAD' do diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb index fd0dc98a8d3..1c2260070ec 100644 --- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb @@ -17,6 +17,7 @@ RSpec.describe 'Creating a Snippet' do let(:actions) { [{ action: action }.merge(file_1), { action: action }.merge(file_2)] } let(:project_path) { nil } let(:uploaded_files) { nil } + let(:spam_mutation_vars) { {} } let(:mutation_vars) do { description: description, @@ -25,7 +26,7 @@ RSpec.describe 'Creating a Snippet' do project_path: project_path, uploaded_files: uploaded_files, blob_actions: actions - } + }.merge(spam_mutation_vars) end let(:mutation) do @@ -77,7 +78,20 @@ RSpec.describe 'Creating a Snippet' do expect(mutation_response['snippet']).to be_nil end - it_behaves_like 'spam flag is present' + context 'when snippet_spam flag is disabled' do + before do + stub_feature_flags(snippet_spam: false) + end + + it 'passes disable_spam_action_service param to service' do + expect(::Snippets::CreateService) + .to receive(:new) + .with(anything, anything, hash_including(disable_spam_action_service: true)) + .and_call_original + + subject + end + end end shared_examples 'creates snippet' do @@ -98,15 +112,24 @@ RSpec.describe 'Creating a Snippet' do end context 'when action is invalid' do - let(:file_1) { { filePath: 'example_file1' }} + let(:file_1) { { filePath: 'example_file1' } } it_behaves_like 'a mutation that returns errors in the response', errors: ['Snippet actions have invalid data'] it_behaves_like 'does not create snippet' end it_behaves_like 'snippet edit usage data counters' - it_behaves_like 'spam flag is present' - it_behaves_like 'can raise spam flag' do + + it_behaves_like 'a mutation which can mutate a spammable' do + let(:captcha_response) { 'abc123' } + let(:spam_log_id) { 1234 } + let(:spam_mutation_vars) do + { + captcha_response: captcha_response, + spam_log_id: spam_log_id + } + end + let(:service) { Snippets::CreateService } end end @@ -148,9 +171,6 @@ RSpec.describe 'Creating a Snippet' do it_behaves_like 'a mutation that returns errors in the response', errors: ["Title can't be blank"] it_behaves_like 'does not create snippet' - it_behaves_like 'can raise spam flag' do - let(:service) { Snippets::CreateService } - end end context 'when there non ActiveRecord errors' do diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb index 21d403c6f73..43dc8d8bc44 100644 --- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb @@ -16,6 +16,7 @@ RSpec.describe 'Updating a Snippet' do let(:updated_file) { 'CHANGELOG' } let(:deleted_file) { 'README' } let(:snippet_gid) { GitlabSchema.id_from_object(snippet).to_s } + let(:spam_mutation_vars) { {} } let(:mutation_vars) do { id: snippet_gid, @@ -26,7 +27,7 @@ RSpec.describe 'Updating a Snippet' do { action: :update, filePath: updated_file, content: updated_content }, { action: :delete, filePath: deleted_file } ] - } + }.merge(spam_mutation_vars) end let(:mutation) do @@ -81,11 +82,20 @@ RSpec.describe 'Updating a Snippet' do end end - it_behaves_like 'can raise spam flag' do - let(:service) { Snippets::UpdateService } - end + context 'when snippet_spam flag is disabled' do + before do + stub_feature_flags(snippet_spam: false) + end - it_behaves_like 'spam flag is present' + it 'passes disable_spam_action_service param to service' do + expect(::Snippets::UpdateService) + .to receive(:new) + .with(anything, anything, hash_including(disable_spam_action_service: true)) + .and_call_original + + subject + end + end context 'when there are ActiveRecord validation errors' do let(:updated_title) { '' } @@ -112,11 +122,19 @@ RSpec.describe 'Updating a Snippet' do expect(mutation_response['snippet']['visibilityLevel']).to eq('private') end end + end - it_behaves_like 'spam flag is present' - it_behaves_like 'can raise spam flag' do - let(:service) { Snippets::UpdateService } + it_behaves_like 'a mutation which can mutate a spammable' do + let(:captcha_response) { 'abc123' } + let(:spam_log_id) { 1234 } + let(:spam_mutation_vars) do + { + captcha_response: captcha_response, + spam_log_id: spam_log_id + } end + + let(:service) { Snippets::UpdateService } end def blob_at(filename) diff --git a/spec/requests/api/graphql/packages/package_composer_details_spec.rb b/spec/requests/api/graphql/packages/package_composer_details_spec.rb deleted file mode 100644 index 1a2cf4a972a..00000000000 --- a/spec/requests/api/graphql/packages/package_composer_details_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -RSpec.describe 'package composer details' do - using RSpec::Parameterized::TableSyntax - include GraphqlHelpers - - let_it_be(:project) { create(:project) } - let_it_be(:package) { create(:composer_package, project: project) } - let_it_be(:composer_metadatum) do - # we are forced to manually create the metadatum, without using the factory to force the sha to be a string - # and avoid an error where gitaly can't find the repository - create(:composer_metadatum, package: package, target_sha: 'foo_sha', composer_json: { name: 'name', type: 'type', license: 'license', version: 1 }) - end - - let(:query) do - graphql_query_for( - 'packageComposerDetails', - { id: package_global_id }, - all_graphql_fields_for('PackageComposerDetails', max_depth: 2) - ) - end - - let(:user) { project.owner } - let(:package_global_id) { package.to_global_id.to_s } - let(:package_composer_details_response) { graphql_data.dig('packageComposerDetails') } - - subject { post_graphql(query, current_user: user) } - - it_behaves_like 'a working graphql query' do - before do - subject - end - - it 'matches the JSON schema' do - expect(package_composer_details_response).to match_schema('graphql/packages/package_composer_details') - end - end -end diff --git a/spec/requests/api/graphql/packages/package_spec.rb b/spec/requests/api/graphql/packages/package_spec.rb new file mode 100644 index 00000000000..bb3ceb81f16 --- /dev/null +++ b/spec/requests/api/graphql/packages/package_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'package details' do + using RSpec::Parameterized::TableSyntax + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:package) { create(:composer_package, project: project) } + let_it_be(:composer_json) { { name: 'name', type: 'type', license: 'license', version: 1 } } + let_it_be(:composer_metadatum) do + # we are forced to manually create the metadatum, without using the factory to force the sha to be a string + # and avoid an error where gitaly can't find the repository + create(:composer_metadatum, package: package, target_sha: 'foo_sha', composer_json: composer_json) + end + + let(:depth) { 3 } + let(:excluded) { %w[metadata apiFuzzingCiConfiguration] } + + let(:query) do + graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) + #{all_graphql_fields_for('Package', max_depth: depth, excluded: excluded)} + metadata { + #{query_graphql_fragment('ComposerMetadata')} + } + FIELDS + end + + let(:user) { project.owner } + let(:package_global_id) { global_id_of(package) } + let(:package_details) { graphql_data_at(:package) } + + subject { post_graphql(query, current_user: user) } + + it_behaves_like 'a working graphql query' do + before do + subject + end + + it 'matches the JSON schema' do + expect(package_details).to match_schema('graphql/packages/package_details') + end + + it 'includes the fields of the correct package' do + expect(package_details).to include( + 'id' => package_global_id, + 'metadata' => { + 'targetSha' => 'foo_sha', + 'composerJson' => composer_json.transform_keys(&:to_s).transform_values(&:to_s) + } + ) + end + end + + context 'there are other versions of this package' do + let(:depth) { 3 } + let(:excluded) { %w[metadata project tags pipelines] } # to limit the query complexity + + let_it_be(:siblings) { create_list(:composer_package, 2, project: project, name: package.name) } + + it 'includes the sibling versions' do + subject + + expect(graphql_data_at(:package, :versions, :nodes)).to match_array( + siblings.map { |p| a_hash_including('id' => global_id_of(p)) } + ) + end + + context 'going deeper' do + let(:depth) { 6 } + + it 'does not create a cycle of versions' do + subject + + expect(graphql_data_at(:package, :versions, :nodes, :version)).to be_present + expect(graphql_data_at(:package, :versions, :nodes, :versions)).not_to be_present + end + 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 6b1c8689515..2087d8c2cc3 100644 --- a/spec/requests/api/graphql/project/container_repositories_spec.rb +++ b/spec/requests/api/graphql/project/container_repositories_spec.rb @@ -156,4 +156,51 @@ RSpec.describe 'getting container repositories in a project' do expect(container_repositories_count_response).to eq(container_repositories.size) end + + describe 'sorting and pagination' do + let_it_be(:data_path) { [:project, :container_repositories] } + let_it_be(:sort_project) { create(:project, :public) } + let_it_be(:current_user) { create(:user) } + let_it_be(:container_repository1) { create(:container_repository, name: 'b', project: sort_project) } + let_it_be(:container_repository2) { create(:container_repository, name: 'a', project: sort_project) } + let_it_be(:container_repository3) { create(:container_repository, name: 'd', project: sort_project) } + let_it_be(:container_repository4) { create(:container_repository, name: 'c', project: sort_project) } + 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) + end + + def pagination_query(params) + graphql_query_for(:project, { full_path: sort_project.full_path }, + query_nodes(:container_repositories, :name, include_pagination_info: true, args: params) + ) + end + + def pagination_results_data(data) + data.map { |container_repository| container_repository.dig('name') } + end + + context 'when sorting by name' do + context 'when ascending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :NAME_ASC } + let(:first_param) { 2 } + let(:expected_results) { [container_repository2.name, container_repository1.name, container_repository4.name, container_repository3.name, container_repository5.name] } + end + end + + context 'when descending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :NAME_DESC } + let(:first_param) { 2 } + let(:expected_results) { [container_repository5.name, container_repository3.name, container_repository4.name, container_repository1.name, container_repository2.name] } + end + end + end + end end diff --git a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb index a671ddc7ab1..7148750b6cb 100644 --- a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb +++ b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb @@ -22,6 +22,23 @@ RSpec.describe 'Getting designs related to an issue' do end end + it_behaves_like 'a noteable graphql type we can query' do + let(:noteable) { design } + let(:note_factory) { :diff_note_on_design } + let(:discussion_factory) { :diff_note_on_design } + let(:path_to_noteable) { [:issue, :design_collection, :designs, :nodes, 0] } + + before do + project.add_developer(current_user) + end + + def query(fields) + graphql_query_for(:issue, { id: global_id_of(issue) }, <<~FIELDS) + designCollection { designs { nodes { #{fields} } } } + FIELDS + end + end + it 'is not too deep for anonymous users' do note_fields = <<~FIELDS id @@ -37,7 +54,7 @@ RSpec.describe 'Getting designs related to an issue' do expect(note_data['id']).to eq(note.to_global_id.to_s) end - def query(note_fields = all_graphql_fields_for(Note)) + def query(note_fields = all_graphql_fields_for(Note, max_depth: 1)) design_node = <<~NODE designs { nodes { diff --git a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb index ac0b18a37d6..70c5bda35e1 100644 --- a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb +++ b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb @@ -8,9 +8,11 @@ RSpec.describe 'Query.project.mergeRequests.pipelines' do let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:author) { create(:user) } let_it_be(:merge_requests) do - %i[with_diffs with_image_diffs conflict].map do |trait| - create(:merge_request, trait, author: author, source_project: project) - end + [ + create(:merge_request, author: author, source_project: project), + create(:merge_request, :with_image_diffs, author: author, source_project: project), + create(:merge_request, :conflict, author: author, source_project: project) + ] end describe '.count' do diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index e1b867ad097..a4e8d0bc35e 100644 --- a/spec/requests/api/graphql/project/merge_request_spec.rb +++ b/spec/requests/api/graphql/project/merge_request_spec.rb @@ -66,14 +66,6 @@ RSpec.describe 'getting merge request information nested in a project' do expect(graphql_data_at(:project, :merge_request, :reviewers, :nodes)).to match_array(expected) expect(graphql_data_at(:project, :merge_request, :participants, :nodes)).to include(*expected) end - - it 'suppresses reviewers if reviewers are not allowed' do - stub_feature_flags(merge_request_reviewers: false) - - post_graphql(query, current_user: current_user) - - expect(graphql_data_at(:project, :merge_request, :reviewers)).to be_nil - end end it 'includes diff stats' do diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index c85cb8b2ffe..d684be91dc9 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -196,17 +196,18 @@ RSpec.describe 'getting merge request listings nested in a project' do end context 'when requesting `commit_count`' do - let(:requested_fields) { [:commit_count] } + let(:merge_request_with_commits) { create(:merge_request, source_project: project) } + let(:search_params) { { iids: [merge_request_a.iid.to_s, merge_request_with_commits.iid.to_s] } } + let(:requested_fields) { [:iid, :commit_count] } it 'exposes `commit_count`' do - merge_request_a.metrics.update!(commits_count: 5) - execute_query - expect(results).to include(a_hash_including('commitCount' => 5)) + expect(results).to match_array([ + { "iid" => merge_request_a.iid.to_s, "commitCount" => 0 }, + { "iid" => merge_request_with_commits.iid.to_s, "commitCount" => 29 } + ]) end - - include_examples 'N+1 query check' end context 'when requesting `merged_at`' do @@ -264,18 +265,6 @@ RSpec.describe 'getting merge request listings nested in a project' do }) end - context 'the feature flag is disabled' do - before do - stub_feature_flags(merge_request_reviewers: false) - end - - it 'does not return reviewers' do - execute_query - - expect(results).to all(match a_hash_including('reviewers' => be_nil)) - end - end - include_examples 'N+1 query check' end end @@ -396,4 +385,87 @@ RSpec.describe 'getting merge request listings nested in a project' do end end end + + context 'when only the count is requested' do + context 'when merged at filter is present' do + let_it_be(:merge_request) do + create(:merge_request, :unique_branches, source_project: project).tap do |mr| + mr.metrics.update!(merged_at: Time.new(2020, 1, 3)) + end + end + + let(:query) do + # Note: __typename meta field is always requested by the FE + graphql_query_for(:project, { full_path: project.full_path }, + <<~QUERY + mergeRequests(mergedAfter: "2020-01-01", mergedBefore: "2020-01-05", first: 0, sourceBranches: null, labels: null) { + count + __typename + } + QUERY + ) + end + + shared_examples 'count examples' do + it 'returns the correct count' do + post_graphql(query, current_user: current_user) + + count = graphql_data.dig('project', 'mergeRequests', 'count') + expect(count).to eq(1) + end + end + + context 'when "optimized_merge_request_count_with_merged_at_filter" feature flag is enabled' do + before do + stub_feature_flags(optimized_merge_request_count_with_merged_at_filter: true) + end + + it 'does not query the merge requests table for the count' do + query_recorder = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) } + + queries = query_recorder.data.each_value.first[:occurrences] + expect(queries).not_to include(match(/SELECT COUNT\(\*\) FROM "merge_requests"/)) + expect(queries).to include(match(/SELECT COUNT\(\*\) FROM "merge_request_metrics"/)) + end + + context 'when total_time_to_merge and count is queried' do + let(:query) do + graphql_query_for(:project, { full_path: project.full_path }, + <<~QUERY + mergeRequests(mergedAfter: "2020-01-01", mergedBefore: "2020-01-05", first: 0) { + totalTimeToMerge + count + } + QUERY + ) + end + + it 'does not query the merge requests table for the total_time_to_merge' do + query_recorder = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) } + + queries = query_recorder.data.each_value.first[:occurrences] + expect(queries).to include(match(/SELECT.+SUM.+FROM "merge_request_metrics" WHERE/)) + end + end + + it_behaves_like 'count examples' + + context 'when "optimized_merge_request_count_with_merged_at_filter" feature flag is disabled' do + before do + stub_feature_flags(optimized_merge_request_count_with_merged_at_filter: false) + end + + it 'queries the merge requests table for the count' do + query_recorder = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) } + + queries = query_recorder.data.each_value.first[:occurrences] + expect(queries).to include(match(/SELECT COUNT\(\*\) FROM "merge_requests"/)) + expect(queries).not_to include(match(/SELECT COUNT\(\*\) FROM "merge_request_metrics"/)) + end + + it_behaves_like 'count examples' + end + end + end + end end diff --git a/spec/requests/api/graphql/project/packages_spec.rb b/spec/requests/api/graphql/project/packages_spec.rb index 5df98ed1e6b..b20c96d54c8 100644 --- a/spec/requests/api/graphql/project/packages_spec.rb +++ b/spec/requests/api/graphql/project/packages_spec.rb @@ -5,16 +5,27 @@ require 'spec_helper' RSpec.describe 'getting a package list for a project' do include GraphqlHelpers - let_it_be(:project) { create(:project) } + let_it_be(:project) { create(:project, :repository) } let_it_be(:current_user) { create(:user) } + let_it_be(:package) { create(:package, project: project) } - let(:packages_data) { graphql_data['project']['packages']['edges'] } + let_it_be(:maven_package) { create(:maven_package, project: project) } + let_it_be(:debian_package) { create(:debian_package, project: project) } + let_it_be(:composer_package) { create(:composer_package, project: project) } + let_it_be(:composer_metadatum) do + create(:composer_metadatum, package: composer_package, + target_sha: 'afdeh', + composer_json: { name: 'x', type: 'y', license: 'z', version: 1 }) + end + + let(:package_names) { graphql_data_at(:project, :packages, :edges, :node, :name) } let(:fields) do <<~QUERY edges { node { - #{all_graphql_fields_for('packages'.classify)} + #{all_graphql_fields_for('packages'.classify, excluded: ['project'])} + metadata { #{query_graphql_fragment('ComposerMetadata')} } } } QUERY @@ -37,7 +48,17 @@ RSpec.describe 'getting a package list for a project' do it_behaves_like 'a working graphql query' it 'returns packages successfully' do - expect(packages_data[0]['node']['name']).to eq package.name + expect(package_names).to contain_exactly( + package.name, + maven_package.name, + debian_package.name, + composer_package.name + ) + end + + it 'deals with metadata' do + target_shas = graphql_data_at(:project, :packages, :edges, :node, :metadata, :target_sha) + expect(target_shas).to contain_exactly(composer_metadatum.target_sha) end end @@ -53,7 +74,7 @@ RSpec.describe 'getting a package list for a project' do end end - context 'when the user is not autenthicated' do + context 'when the user is not authenticated' do before do post_graphql(query) end diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb index ccc2825da25..72197f00df4 100644 --- a/spec/requests/api/graphql/project/release_spec.rb +++ b/spec/requests/api/graphql/project/release_spec.rb @@ -283,7 +283,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do let_it_be(:project) { create(:project, :repository, :private) } let_it_be(:milestone_1) { create(:milestone, project: project) } let_it_be(:milestone_2) { create(:milestone, project: project) } - let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2], released_at: released_at) } + let_it_be(:release, reload: true) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2], released_at: released_at) } let_it_be(:release_link_1) { create(:release_link, release: release) } let_it_be(:release_link_2) { create(:release_link, release: release, filepath: link_filepath) } @@ -324,7 +324,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do let_it_be(:project) { create(:project, :repository, :public) } let_it_be(:milestone_1) { create(:milestone, project: project) } let_it_be(:milestone_2) { create(:milestone, project: project) } - let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2], released_at: released_at) } + let_it_be(:release, reload: true) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2], released_at: released_at) } let_it_be(:release_link_1) { create(:release_link, release: release) } let_it_be(:release_link_2) { create(:release_link, release: release, filepath: link_filepath) } @@ -435,13 +435,13 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do let_it_be_with_reload(:release) { create(:release, project: project) } let(:release_fields) do - query_graphql_field(%{ + %{ 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 new file mode 100644 index 00000000000..9f1d9ab204a --- /dev/null +++ b/spec/requests/api/graphql/project/terraform/state_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'query a single terraform state' do + include GraphqlHelpers + include ::API::Helpers::RelatedResourcesHelpers + + let_it_be(:terraform_state) { create(:terraform_state, :with_version, :locked) } + + let(:latest_version) { terraform_state.latest_version } + let(:project) { terraform_state.project } + let(:current_user) { project.creator } + let(:data) { graphql_data.dig('project', 'terraformState') } + + let(:query) do + graphql_query_for( + :project, + { fullPath: project.full_path }, + query_graphql_field( + :terraformState, + { name: terraform_state.name }, + %{ + id + name + lockedAt + createdAt + updatedAt + + latestVersion { + id + serial + createdAt + updatedAt + + createdByUser { + id + } + + job { + name + } + } + + lockedByUser { + id + } + } + ) + ) + end + + before do + post_graphql(query, current_user: current_user) + end + + it_behaves_like 'a working graphql query' + + it 'returns terraform state data' do + expect(data).to match(a_hash_including({ + 'id' => global_id_of(terraform_state), + 'name' => terraform_state.name, + 'lockedAt' => terraform_state.locked_at.iso8601, + 'createdAt' => terraform_state.created_at.iso8601, + 'updatedAt' => terraform_state.updated_at.iso8601, + 'lockedByUser' => { 'id' => global_id_of(terraform_state.locked_by_user) }, + 'latestVersion' => { + 'id' => eq(global_id_of(latest_version)), + 'serial' => eq(latest_version.version), + 'createdAt' => eq(latest_version.created_at.iso8601), + 'updatedAt' => eq(latest_version.updated_at.iso8601), + 'createdByUser' => { 'id' => eq(global_id_of(latest_version.created_by_user)) }, + 'job' => { 'name' => eq(latest_version.build.name) } + } + })) + end + + context 'unauthorized users' do + let(:current_user) { nil } + + it { expect(data).to be_nil } + end +end |