diff options
Diffstat (limited to 'spec/graphql')
68 files changed, 788 insertions, 216 deletions
diff --git a/spec/graphql/graphql_triggers_spec.rb b/spec/graphql/graphql_triggers_spec.rb new file mode 100644 index 00000000000..0b53c633077 --- /dev/null +++ b/spec/graphql/graphql_triggers_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GraphqlTriggers do + describe '.issuable_assignees_updated' do + it 'triggers the issuableAssigneesUpdated subscription' do + assignees = create_list(:user, 2) + issue = create(:issue, assignees: assignees) + + expect(GitlabSchema.subscriptions).to receive(:trigger).with( + 'issuableAssigneesUpdated', + { issuable_id: issue.to_gid }, + issue + ) + + GraphqlTriggers.issuable_assignees_updated(issue) + end + end +end diff --git a/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb b/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb index a10c3725ba2..8ec99070c91 100644 --- a/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb +++ b/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Mutations::AlertManagement::Alerts::Todo::Create do let_it_be(:alert) { create(:alert_management_alert) } let_it_be(:project) { alert.project } + let(:current_user) { project.owner } let(:args) { { project_path: project.full_path, iid: alert.iid } } diff --git a/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb b/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb index 47ee338ad34..4758ac526a5 100644 --- a/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb +++ b/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Mutations::AlertManagement::CreateAlertIssue do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered') } + let(:args) { { project_path: project.full_path, iid: alert.iid } } specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) } diff --git a/spec/graphql/mutations/alert_management/http_integration/create_spec.rb b/spec/graphql/mutations/alert_management/http_integration/create_spec.rb index 9aa89761aaf..be6c627e376 100644 --- a/spec/graphql/mutations/alert_management/http_integration/create_spec.rb +++ b/spec/graphql/mutations/alert_management/http_integration/create_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Mutations::AlertManagement::HttpIntegration::Create do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } + let(:args) { { project_path: project.full_path, active: true, name: 'HTTP Integration' } } specify { expect(described_class).to require_graphql_authorizations(:admin_operations) } diff --git a/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb index acd7070d0d3..1aeeba1009e 100644 --- a/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb +++ b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Mutations::AlertManagement::HttpIntegration::Destroy do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } + let(:integration) { create(:alert_management_http_integration, project: project) } let(:args) { { id: GitlabSchema.id_from_object(integration) } } diff --git a/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb b/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb index 96974c2aa6f..5a2af9e0be8 100644 --- a/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb +++ b/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::ResetToken do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:integration) { create(:alert_management_http_integration, project: project) } + let(:args) { { id: GitlabSchema.id_from_object(integration) } } specify { expect(described_class).to require_graphql_authorizations(:admin_operations) } diff --git a/spec/graphql/mutations/alert_management/http_integration/update_spec.rb b/spec/graphql/mutations/alert_management/http_integration/update_spec.rb index d6318e3161d..805996bf9e9 100644 --- a/spec/graphql/mutations/alert_management/http_integration/update_spec.rb +++ b/spec/graphql/mutations/alert_management/http_integration/update_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::Update do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:integration) { create(:alert_management_http_integration, project: project) } + let(:args) { { id: GitlabSchema.id_from_object(integration), active: false, name: 'New Name' } } specify { expect(described_class).to require_graphql_authorizations(:admin_operations) } diff --git a/spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb index 02a5e2e74e2..7ab0f43d674 100644 --- a/spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb +++ b/spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } + let(:args) { { project_path: project.full_path, active: true, api_url: 'http://prometheus.com/' } } specify { expect(described_class).to require_graphql_authorizations(:admin_project) } diff --git a/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb index ddf23909035..c9e1bf4162c 100644 --- a/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb +++ b/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:integration) { create(:prometheus_service, project: project) } + let(:args) { { id: GitlabSchema.id_from_object(integration) } } specify { expect(described_class).to require_graphql_authorizations(:admin_project) } diff --git a/spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb index eab4474d827..19e0d53b75f 100644 --- a/spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb +++ b/spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Update do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:integration) { create(:prometheus_service, project: project) } + let(:args) { { id: GitlabSchema.id_from_object(integration), active: false, api_url: 'http://new-url.com' } } specify { expect(described_class).to require_graphql_authorizations(:admin_project) } diff --git a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb index 8465393f299..2c2518e046a 100644 --- a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb +++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do let_it_be(:current_user) { create(:user) } let_it_be(:alert) { create(:alert_management_alert, :triggered) } let_it_be(:project) { alert.project } + let(:new_status) { Types::AlertManagement::StatusEnum.values['ACKNOWLEDGED'].value } let(:args) { { status: new_status, project_path: project.full_path, iid: alert.iid } } diff --git a/spec/graphql/mutations/boards/lists/update_spec.rb b/spec/graphql/mutations/boards/lists/update_spec.rb index d5d8a2af6bf..c82cbbfdd83 100644 --- a/spec/graphql/mutations/boards/lists/update_spec.rb +++ b/spec/graphql/mutations/boards/lists/update_spec.rb @@ -3,54 +3,14 @@ require 'spec_helper' RSpec.describe Mutations::Boards::Lists::Update do - let_it_be(:group) { create(:group, :private) } - let_it_be(:board) { create(:board, group: group) } - let_it_be(:reporter) { create(:user) } - let_it_be(:guest) { create(:user) } - let_it_be(:list) { create(:list, board: board, position: 0) } - let_it_be(:list2) { create(:list, board: board) } - let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } - let(:list_update_params) { { position: 1, collapsed: true } } - - before_all do - group.add_reporter(reporter) - group.add_guest(guest) - list.update_preferences_for(reporter, collapsed: false) - end - - subject { mutation.resolve(list: list, **list_update_params) } - - describe '#resolve' do - context 'with permission to admin board lists' do - let(:current_user) { reporter } - - it 'updates the list position and collapsed state as expected' do - subject - - reloaded_list = list.reload - expect(reloaded_list.position).to eq(1) - expect(reloaded_list.collapsed?(current_user)).to eq(true) - end - end - - context 'with permission to read board lists' do - let(:current_user) { guest } - - it 'updates the list collapsed state but not the list position' do - subject - - reloaded_list = list.reload - expect(reloaded_list.position).to eq(0) - expect(reloaded_list.collapsed?(current_user)).to eq(true) - end - end - - context 'without permission to read board lists' do - let(:current_user) { create(:user) } - - it 'raises Resource Not Found error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end + context 'on group issue boards' do + let_it_be(:group) { create(:group, :private) } + let_it_be(:board) { create(:board, group: group) } + let_it_be(:reporter) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:list) { create(:list, board: board, position: 0) } + let_it_be(:list2) { create(:list, board: board) } + + it_behaves_like 'update board list mutation' end end diff --git a/spec/graphql/mutations/commits/create_spec.rb b/spec/graphql/mutations/commits/create_spec.rb index 82a5e3a62f5..152b5d87da0 100644 --- a/spec/graphql/mutations/commits/create_spec.rb +++ b/spec/graphql/mutations/commits/create_spec.rb @@ -24,11 +24,12 @@ RSpec.describe Mutations::Commits::Create do let(:branch) { 'master' } let(:start_branch) { nil } let(:message) { 'Commit message' } + let(:file_path) { "#{SecureRandom.uuid}.md" } let(:actions) do [ { action: 'create', - file_path: 'NEW_FILE.md', + file_path: file_path, content: 'Hello' } ] @@ -68,12 +69,17 @@ RSpec.describe Mutations::Commits::Create do end context 'when service successfully creates a new commit' do + it "returns the ETag path for the commit's pipeline" do + commit_pipeline_path = subject[:commit_pipeline_path] + expect(commit_pipeline_path).to match(%r(pipelines/sha/\w+)) + end + it 'returns a new commit' do expect(mutated_commit).to have_attributes(message: message, project: project) expect(subject[:errors]).to be_empty expect_to_contain_deltas([ - a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: 'NEW_FILE.md') + a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: file_path) ]) end end diff --git a/spec/graphql/mutations/issues/create_spec.rb b/spec/graphql/mutations/issues/create_spec.rb index 422ad40a9cb..b32f0991959 100644 --- a/spec/graphql/mutations/issues/create_spec.rb +++ b/spec/graphql/mutations/issues/create_spec.rb @@ -19,7 +19,8 @@ RSpec.describe Mutations::Issues::Create do description: 'new description', confidential: true, due_date: Date.tomorrow, - discussion_locked: true + discussion_locked: true, + issue_type: 'issue' } end @@ -93,6 +94,16 @@ RSpec.describe Mutations::Issues::Create do expect(mutated_issue.iid).not_to eq(special_params[:iid]) end end + + context 'when creating a non-default issue type' do + before do + mutation_params[:issue_type] = 'incident' + end + + it 'creates issue with correct values' do + expect(mutated_issue.issue_type).to eq('incident') + end + end end context 'when creating an issue as owner' do diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb index 9f8d0d6c405..263122e5d5f 100644 --- a/spec/graphql/mutations/issues/set_due_date_spec.rb +++ b/spec/graphql/mutations/issues/set_due_date_spec.rb @@ -3,8 +3,9 @@ require 'spec_helper' RSpec.describe Mutations::Issues::SetDueDate do - let(:issue) { create(:issue) } - let(:user) { create(:user) } + let(:issue) { create(:issue, due_date: '2021-05-01') } + + let_it_be(:user) { create(:user) } subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } @@ -23,17 +24,25 @@ RSpec.describe Mutations::Issues::SetDueDate do issue.project.add_developer(user) end - it 'returns the issue with updated due date' do + it 'returns the issue with updated due date', :aggregate_failures do expect(mutated_issue).to eq(issue) expect(mutated_issue.due_date).to eq(Date.today + 2.days) expect(subject[:errors]).to be_empty end + context 'when due date is nil' do + let(:due_date) { nil } + + it 'updates due date to be nil' do + expect(mutated_issue.due_date).to be nil + end + end + context 'when passing incorrect due date value' do let(:due_date) { 'test' } - it 'does not update due date' do - expect(mutated_issue.due_date).to eq(issue.due_date) + it 'updates due date to be nil' do + expect(mutated_issue.due_date).to be nil end end end diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb index f10e257e153..6d6a5b94219 100644 --- a/spec/graphql/mutations/issues/update_spec.rb +++ b/spec/graphql/mutations/issues/update_spec.rb @@ -69,17 +69,33 @@ RSpec.describe Mutations::Issues::Update do context 'when changing state' do let_it_be_with_refind(:issue) { create(:issue, project: project, state: :opened) } - it 'closes issue' do - mutation_params[:state_event] = 'close' + before do + mutation_params[:state_event] = state_event + end + + context 'when state_event is close' do + let_it_be(:removable_label) { create(:label, project: project, remove_on_close: true, issues: [issue]) } - expect { subject }.to change { issue.reload.state }.from('opened').to('closed') + let(:state_event) { 'close' } + + it 'closes issue' do + expect do + subject + issue.reload + end.to change(issue, :state).from('opened').to('closed').and( + change { issue.label_ids }.from([removable_label.id]).to([]) + ) + end end - it 'reopens issue' do - issue.close - mutation_params[:state_event] = 'reopen' + context 'when state_event is reopen' do + let(:state_event) { 'reopen' } + + it 'reopens issue' do + issue.close - expect { subject }.to change { issue.reload.state }.from('closed').to('opened') + expect { subject }.to change { issue.reload.state }.from('closed').to('opened') + end end end @@ -128,6 +144,14 @@ RSpec.describe Mutations::Issues::Update do expect(issue.reload.labels).to match_array([project_label, label_2]) end end + + context 'when changing type' do + it 'changes the type of the issue' do + mutation_params[:issue_type] = 'incident' + + expect { subject }.to change { issue.reload.issue_type }.from('issue').to('incident') + end + end end end end diff --git a/spec/graphql/mutations/merge_requests/set_draft_spec.rb b/spec/graphql/mutations/merge_requests/set_draft_spec.rb new file mode 100644 index 00000000000..697b2e5b007 --- /dev/null +++ b/spec/graphql/mutations/merge_requests/set_draft_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::MergeRequests::SetDraft do + let_it_be(:merge_request) { create(:merge_request) } + let_it_be(:user) { create(:user) } + + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + + specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) } + + describe '#resolve' do + let(:draft) { true } + let(:mutated_merge_request) { subject[:merge_request] } + + subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, draft: draft) } + + it_behaves_like 'permission level for merge request mutation is correctly verified' + + context 'when the user can update the merge request' do + before do + merge_request.project.add_developer(user) + end + + it 'returns the merge request as a draft' do + expect(mutated_merge_request).to eq(merge_request) + expect(mutated_merge_request).to be_draft + expect(subject[:errors]).to be_empty + end + + it 'returns errors if/when merge request could not be updated' do + # Make the merge request invalid + merge_request.allow_broken = true + merge_request.update!(source_project: nil) + + expect(subject[:errors]).not_to be_empty + end + + context 'when passing draft as false' do + let(:draft) { false } + + it 'removes `Draft` from the title' do + merge_request.update!(title: "Draft: working on it") + + expect(mutated_merge_request).not_to be_draft + end + + it 'does not do anything if the title did not start with draft' do + expect(mutated_merge_request).not_to be_draft + end + end + end + end +end diff --git a/spec/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/graphql/mutations/merge_requests/set_locked_spec.rb index 03c709e9bb3..68bb7aa0aa4 100644 --- a/spec/graphql/mutations/merge_requests/set_locked_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_locked_spec.rb @@ -41,7 +41,7 @@ RSpec.describe Mutations::MergeRequests::SetLocked do let(:locked) { false } it 'unlocks the discussion' do - merge_request.update(discussion_locked: true) + merge_request.update!(discussion_locked: true) expect(mutated_merge_request).not_to be_discussion_locked end diff --git a/spec/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/graphql/mutations/merge_requests/set_wip_spec.rb index 69f6a4328b8..fae9c4f7fe0 100644 --- a/spec/graphql/mutations/merge_requests/set_wip_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_wip_spec.rb @@ -41,7 +41,7 @@ RSpec.describe Mutations::MergeRequests::SetWip do let(:wip) { false } it 'removes `wip` from the title' do - merge_request.update(title: "WIP: working on it") + merge_request.update!(title: "WIP: working on it") expect(mutated_merge_request).not_to be_work_in_progress end diff --git a/spec/graphql/mutations/namespace/package_settings/update_spec.rb b/spec/graphql/mutations/namespace/package_settings/update_spec.rb index bd0d38cb49f..978c81fadfa 100644 --- a/spec/graphql/mutations/namespace/package_settings/update_spec.rb +++ b/spec/graphql/mutations/namespace/package_settings/update_spec.rb @@ -25,7 +25,9 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update do end RSpec.shared_examples 'updating the namespace package setting' do - it_behaves_like 'updating the namespace package setting attributes', from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT' }, to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' } + it_behaves_like 'updating the namespace package setting attributes', + from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT', generic_duplicates_allowed: true, generic_duplicate_exception_regex: 'foo' }, + to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE', generic_duplicates_allowed: false, generic_duplicate_exception_regex: 'bar' } it_behaves_like 'returning a success' @@ -56,7 +58,13 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update do context 'with existing namespace package setting' do let_it_be(:package_settings) { create(:namespace_package_setting, namespace: namespace) } - let_it_be(:params) { { namespace_path: namespace.full_path, maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' } } + let_it_be(:params) do + { namespace_path: namespace.full_path, + maven_duplicates_allowed: false, + maven_duplicate_exception_regex: 'RELEASE', + generic_duplicates_allowed: false, + generic_duplicate_exception_regex: 'bar' } + end where(:user_role, :shared_examples_name) do :maintainer | 'updating the namespace package setting' diff --git a/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb index ed03a1cb906..7c3b552480f 100644 --- a/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb +++ b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb @@ -3,118 +3,11 @@ require 'spec_helper' RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do - subject(:mutation) { described_class.new(object: nil, context: context, field: nil) } + include GraphqlHelpers - let_it_be(:project) { create(:project, :public, :repository) } - let_it_be(:user) { create(:user) } + let(:service) { ::Security::CiConfiguration::SastCreateService } - let_it_be(:service_result_json) do - { - status: "success", - success_path: "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?", - errors: nil - } - end + subject { resolve(described_class, args: { project_path: project.full_path, configuration: {} }, ctx: { current_user: user }) } - let_it_be(:service_error_result_json) do - { - status: "error", - success_path: nil, - errors: %w(error1 error2) - } - end - - let(:context) do - GraphQL::Query::Context.new( - query: OpenStruct.new(schema: nil), - values: { current_user: user }, - object: nil - ) - end - - specify { expect(described_class).to require_graphql_authorizations(:push_code) } - - describe '#resolve' do - subject { mutation.resolve(project_path: project.full_path, configuration: {}) } - - let(:result) { subject } - - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - - context 'when user does not have enough permissions' do - before do - project.add_guest(user) - end - - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end - - context 'when user is a maintainer of a different project' do - before do - create(:project_empty_repo).add_maintainer(user) - end - - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end - - context 'when the user does not have permission to create a new branch' do - before_all do - project.add_developer(user) - end - - let(:error_message) { 'You are not allowed to create protected branches on this project.' } - - it 'returns an array of errors' do - allow_next_instance_of(::Files::MultiService) do |multi_service| - allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}")) - end - - expect(result).to match( - status: :error, - success_path: nil, - errors: match_array([error_message]) - ) - end - end - - context 'when the user can create a merge request' do - before_all do - project.add_developer(user) - end - - context 'when service successfully generates a path to create a new merge request' do - it 'returns a success path' do - allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service| - allow(service).to receive(:execute).and_return(service_result_json) - end - - expect(result).to match( - status: 'success', - success_path: service_result_json[:success_path], - errors: [] - ) - end - end - - context 'when service can not generate any path to create a new merge request' do - it 'returns an array of errors' do - allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service| - allow(service).to receive(:execute).and_return(service_error_result_json) - end - - expect(result).to match( - status: 'error', - success_path: be_nil, - errors: match_array(service_error_result_json[:errors]) - ) - end - end - end - end + include_examples 'graphql mutations security ci configuration' end diff --git a/spec/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb b/spec/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb new file mode 100644 index 00000000000..5b4a7d5918c --- /dev/null +++ b/spec/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Security::CiConfiguration::ConfigureSecretDetection do + include GraphqlHelpers + + let(:service) { ::Security::CiConfiguration::SecretDetectionCreateService } + + subject { resolve(described_class, args: { project_path: project.full_path }, ctx: { current_user: user }) } + + include_examples 'graphql mutations security ci configuration' +end diff --git a/spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb b/spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb index 269a1fb1758..b63eca4359d 100644 --- a/spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb +++ b/spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver do include GraphqlHelpers let_it_be(:admin_user) { create(:user, :admin) } + let(:current_user) { admin_user } describe '#resolve' do diff --git a/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb index b72e692f2e8..3bc6043a849 100644 --- a/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb +++ b/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Resolvers::AlertManagement::AlertStatusCountsResolver do describe '#resolve' do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } + let(:args) { {} } subject { resolve_alert_status_counts(args) } diff --git a/spec/graphql/resolvers/boards_resolver_spec.rb b/spec/graphql/resolvers/boards_resolver_spec.rb index cb3bcb002ec..221e905f441 100644 --- a/spec/graphql/resolvers/boards_resolver_spec.rb +++ b/spec/graphql/resolvers/boards_resolver_spec.rb @@ -54,7 +54,7 @@ RSpec.describe Resolvers::BoardsResolver do end it 'returns nil if board not found' do - outside_parent = create(board_parent.class.underscore.to_sym) + outside_parent = create(board_parent.class.underscore.to_sym) # rubocop:disable Rails/SaveBang outside_board = create(:board, name: 'outside board', resource_parent: outside_parent) expect(resolve_boards(args: { id: global_id_of(outside_board) })).to eq Board.none diff --git a/spec/graphql/resolvers/branch_commit_resolver_spec.rb b/spec/graphql/resolvers/branch_commit_resolver_spec.rb index 346c9e01088..3d5702539fa 100644 --- a/spec/graphql/resolvers/branch_commit_resolver_spec.rb +++ b/spec/graphql/resolvers/branch_commit_resolver_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Resolvers::BranchCommitResolver do subject(:commit) { resolve(described_class, obj: branch) } let_it_be(:repository) { create(:project, :repository).repository } + let(:branch) { repository.find_branch('master') } describe '#resolve' do diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb new file mode 100644 index 00000000000..006d6785506 --- /dev/null +++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::RunnersResolver do + include GraphqlHelpers + + let_it_be(:user) { create_default(:user, :admin) } + let_it_be(:group) { create(:group, :public) } + let_it_be(:project) { create(:project, :repository, :public) } + + let_it_be(:inactive_project_runner) do + create(:ci_runner, :project, projects: [project], active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner)) + end + + let_it_be(:offline_project_runner) do + create(:ci_runner, :project, projects: [project], contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner)) + end + + let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], contacted_at: 1.second.ago) } + let_it_be(:instance_runner) { create(:ci_runner, :instance, contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) } + + describe '#resolve' do + subject { resolve(described_class, ctx: { current_user: user }, args: args).items.to_a } + + let(:args) do + {} + end + + context 'without sort' do + it 'returns all the runners' do + is_expected.to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, instance_runner) + end + end + + context 'with a sort argument' do + context "set to :contacted_asc" do + let(:args) do + { sort: :contacted_asc } + end + + it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner]) } + end + + context "set to :created_date" do + let(:args) do + { sort: :created_date } + end + + it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner]) } + end + end + + context 'when type is filtered' do + let(:args) do + { type: runner_type.to_s } + end + + context 'to instance runners' do + let(:runner_type) { :instance_type } + + it 'returns the instance runner' do + is_expected.to contain_exactly(instance_runner) + end + end + + context 'to group runners' do + let(:runner_type) { :group_type } + + it 'returns the group runner' do + is_expected.to contain_exactly(group_runner) + end + end + + context 'to project runners' do + let(:runner_type) { :project_type } + + it 'returns the project runner' do + is_expected.to contain_exactly(inactive_project_runner, offline_project_runner) + end + end + end + + context 'when status is filtered' do + let(:args) do + { status: runner_status.to_s } + end + + context 'to active runners' do + let(:runner_status) { :active } + + it 'returns the instance and group runners' do + is_expected.to contain_exactly(offline_project_runner, group_runner, instance_runner) + end + end + + context 'to offline runners' do + let(:runner_status) { :offline } + + it 'returns the offline project runner' do + is_expected.to contain_exactly(offline_project_runner) + end + end + end + + context 'when tag list is filtered' do + let(:args) do + { tag_list: tag_list } + end + + context 'with "project_runner" tag' do + let(:tag_list) { ['project_runner'] } + + it 'returns the project_runner runners' do + is_expected.to contain_exactly(offline_project_runner, inactive_project_runner) + end + end + + context 'with "project_runner" and "active_runner" tags as comma-separated string' do + let(:tag_list) { ['project_runner,active_runner'] } + + it 'returns the offline_project_runner runner' do + is_expected.to contain_exactly(offline_project_runner) + end + end + + context 'with "active_runner" and "instance_runner" tags as array' do + let(:tag_list) { %w[instance_runner active_runner] } + + it 'returns the offline_project_runner runner' do + is_expected.to contain_exactly(instance_runner) + end + end + end + end +end diff --git a/spec/graphql/resolvers/ci/template_resolver_spec.rb b/spec/graphql/resolvers/ci/template_resolver_spec.rb new file mode 100644 index 00000000000..bec25640c7f --- /dev/null +++ b/spec/graphql/resolvers/ci/template_resolver_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::TemplateResolver do + include GraphqlHelpers + + describe '#resolve' do + let(:user) { create(:user) } + let(:project) { create(:project) } + + subject(:resolve_subject) { resolve(described_class, obj: project, ctx: { current_user: user }, args: { name: template_name }) } + + context 'when template exists' do + let(:template_name) { 'Android' } + + it 'returns the found template' do + found_template = resolve_subject + + expect(found_template).to be_an_instance_of(Gitlab::Template::GitlabCiYmlTemplate) + expect(found_template.name).to eq('Android') + end + end + + context 'when template does not exist' do + let(:template_name) { 'invalidname' } + + it 'returns nil' do + expect(resolve_subject).to eq(nil) + end + end + end +end diff --git a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb index 28e963c88a9..b091e58b06f 100644 --- a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb @@ -20,6 +20,7 @@ RSpec.describe Resolvers::DesignManagement::DesignsResolver do let_it_be(:first_version) { create(:design_version) } let_it_be(:first_design) { create(:design, issue: issue, versions: [first_version]) } let_it_be(:current_user) { create(:user) } + let(:gql_context) { { current_user: current_user } } let(:args) { {} } diff --git a/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb index c038216ce0b..4e8f5e5fc1d 100644 --- a/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Resolvers::DesignManagement::Version::DesignsAtVersionResolver do include_context 'four designs in three versions' let_it_be(:current_user) { authorized_user } + let(:gql_context) { { current_user: current_user } } let(:version) { third_version } diff --git a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb index 23d4d86c79a..2c9c3a47650 100644 --- a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb @@ -41,6 +41,20 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do it 'returns the ordered versions' do expect(result.to_a).to eq(all_versions) end + + context 'loading associations' do + it 'prevents N+1 queries when loading author' do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + resolve_versions(object).items.map(&:author) + end.count + + create_list(:design_version, 3, issue: issue) + + expect do + resolve_versions(object).items.map(&:author) + end.not_to exceed_all_query_limit(control_count) + end + end end context 'when constrained' do diff --git a/spec/graphql/resolvers/group_milestones_resolver_spec.rb b/spec/graphql/resolvers/group_milestones_resolver_spec.rb index dd3f1676538..78d89054efd 100644 --- a/spec/graphql/resolvers/group_milestones_resolver_spec.rb +++ b/spec/graphql/resolvers/group_milestones_resolver_spec.rb @@ -119,6 +119,7 @@ RSpec.describe Resolvers::GroupMilestonesResolver do context 'when including descendant milestones in a public group' do let_it_be(:group) { create(:group, :public) } + let(:args) { { include_descendants: true } } it 'finds milestones only in accessible projects and groups' do diff --git a/spec/graphql/resolvers/group_packages_resolver_spec.rb b/spec/graphql/resolvers/group_packages_resolver_spec.rb index 59438b8d5ad..48f4c8ec4ca 100644 --- a/spec/graphql/resolvers/group_packages_resolver_spec.rb +++ b/spec/graphql/resolvers/group_packages_resolver_spec.rb @@ -8,11 +8,14 @@ RSpec.describe Resolvers::GroupPackagesResolver do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :public, group: group) } - let_it_be(:package) { create(:package, project: project) } + + let(:args) do + { sort: :created_desc } + end describe '#resolve' do - subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: group) } + subject { resolve(described_class, ctx: { current_user: user }, obj: group, args: args).to_a } - it { is_expected.to contain_exactly(package) } + it_behaves_like 'group and projects packages resolver' end end diff --git a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb index 84ef906b72f..3aadbc76be8 100644 --- a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb @@ -17,6 +17,7 @@ RSpec.describe Resolvers::MergeRequestPipelinesResolver do let_it_be(:other_project_pipeline) { create(:ci_pipeline, project: merge_request.source_project, ref: 'other-ref') } let_it_be(:other_pipeline) { create(:ci_pipeline) } + let(:current_user) { create(:user) } before do diff --git a/spec/graphql/resolvers/metadata_resolver_spec.rb b/spec/graphql/resolvers/metadata_resolver_spec.rb index f8c01f9d531..56875e185e7 100644 --- a/spec/graphql/resolvers/metadata_resolver_spec.rb +++ b/spec/graphql/resolvers/metadata_resolver_spec.rb @@ -7,7 +7,10 @@ RSpec.describe Resolvers::MetadataResolver do describe '#resolve' do it 'returns version and revision' do - expect(resolve(described_class)).to have_attributes(version: Gitlab::VERSION, revision: Gitlab.revision) + expect(resolve(described_class)).to have_attributes( + version: Gitlab::VERSION, + revision: Gitlab.revision, + kas: kind_of(InstanceMetadata::Kas)) end end end diff --git a/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb b/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb index f90869c52bc..a83cef40bdf 100644 --- a/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb +++ b/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb @@ -12,6 +12,7 @@ RSpec.describe Resolvers::Metrics::Dashboards::AnnotationResolver do let_it_be(:current_user) { create(:user) } let_it_be(:environment) { create(:environment) } let_it_be(:path) { 'config/prometheus/common_metrics.yml' } + let(:dashboard) { PerformanceMonitoring::PrometheusDashboard.new(path: path, environment: environment) } let(:args) do { diff --git a/spec/graphql/resolvers/packages_base_resolver_spec.rb b/spec/graphql/resolvers/packages_base_resolver_spec.rb new file mode 100644 index 00000000000..8f9865c3785 --- /dev/null +++ b/spec/graphql/resolvers/packages_base_resolver_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::PackagesBaseResolver do + include GraphqlHelpers + + describe '#resolve' do + subject { resolve(described_class) } + + it 'throws an error' do + expect { subject }.to raise_error(NotImplementedError) + end + end +end diff --git a/spec/graphql/resolvers/project_packages_resolver_spec.rb b/spec/graphql/resolvers/project_packages_resolver_spec.rb index c8105ed2a38..66a94bd42dd 100644 --- a/spec/graphql/resolvers/project_packages_resolver_spec.rb +++ b/spec/graphql/resolvers/project_packages_resolver_spec.rb @@ -6,12 +6,15 @@ RSpec.describe Resolvers::ProjectPackagesResolver do include GraphqlHelpers let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public) } - let_it_be(:package) { create(:package, project: project) } + let_it_be_with_reload(:project) { create(:project, :public) } + + let(:args) do + { sort: :created_desc } + end describe '#resolve' do - subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: project) } + subject { resolve(described_class, ctx: { current_user: user }, obj: project, args: args).to_a } - it { is_expected.to contain_exactly(package) } + it_behaves_like 'group and projects packages resolver' end end diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb index 3d33e0b500d..6a8aa39f3b2 100644 --- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb +++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Resolvers::ProjectPipelineResolver do let_it_be(:pipeline) { create(:ci_pipeline, project: project, iid: '1234', sha: 'sha') } let_it_be(:other_project_pipeline) { create(:ci_pipeline, project: project, iid: '1235', sha: 'sha2') } let_it_be(:other_pipeline) { create(:ci_pipeline) } + let(:current_user) { create(:user) } specify do diff --git a/spec/graphql/resolvers/project_pipelines_resolver_spec.rb b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb index b2e8fed2441..c7c00f54c0c 100644 --- a/spec/graphql/resolvers/project_pipelines_resolver_spec.rb +++ b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Resolvers::ProjectPipelinesResolver do let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let_it_be(:other_pipeline) { create(:ci_pipeline) } + let(:current_user) { create(:user) } before do diff --git a/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb b/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb index 398dd7a2e2e..004e0411e51 100644 --- a/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb +++ b/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb @@ -8,29 +8,50 @@ RSpec.describe Resolvers::RepositoryBranchNamesResolver do let(:project) { create(:project, :repository) } describe '#resolve' do - subject(:resolve_branch_names) do - resolve( - described_class, - obj: project.repository, - args: { search_pattern: pattern }, - ctx: { current_user: project.creator } - ) - end - context 'with empty search pattern' do let(:pattern) { '' } it 'returns nil' do - expect(resolve_branch_names).to eq(nil) + expect(resolve_branch_names(pattern, 0, 100)).to eq(nil) end end context 'with a valid search pattern' do - let(:pattern) { 'mas*' } + let(:pattern) { 'snippet/*' } it 'returns matching branches' do - expect(resolve_branch_names).to match_array(['master']) + expect(resolve_branch_names(pattern, 0, 100)).to contain_exactly( + 'snippet/edit-file', + 'snippet/multiple-files', + 'snippet/no-files', + 'snippet/rename-and-edit-file', + 'snippet/single-file' + ) + end + + it 'properly offsets and limits branch name results' do + starting_names = resolve_branch_names(pattern, 0, 3) + offset_names = resolve_branch_names(pattern, 3, 2) + + expect(starting_names.count).to eq(3) + expect(offset_names.count).to eq(2) + + expect(offset_names).not_to include(*starting_names) + + all_names = resolve_branch_names(pattern, 0, 100) + expect(all_names).to contain_exactly(*starting_names, *offset_names) end end end + + private + + def resolve_branch_names(pattern, offset, limit) + resolve( + described_class, + obj: project.repository, + args: { search_pattern: pattern, offset: offset, limit: limit }, + ctx: { current_user: project.creator } + ) + end end diff --git a/spec/graphql/subscriptions/issuable_updated_spec.rb b/spec/graphql/subscriptions/issuable_updated_spec.rb new file mode 100644 index 00000000000..cc88b37627d --- /dev/null +++ b/spec/graphql/subscriptions/issuable_updated_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Subscriptions::IssuableUpdated do + include GraphqlHelpers + + it { expect(described_class).to have_graphql_arguments(:issuable_id) } + it { expect(described_class.payload_type).to eq(Types::IssuableType) } + + describe '#resolve' do + let_it_be(:unauthorized_user) { create(:user) } + let_it_be(:issue) { create(:issue) } + + let(:current_user) { issue.author } + let(:issuable_id) { issue.to_gid } + + subject { resolver.resolve_with_support(issuable_id: issuable_id) } + + context 'initial subscription' do + let(:resolver) { resolver_instance(described_class, ctx: { current_user: current_user }, subscription_update: false) } + + it 'returns nil' do + expect(subject).to eq(nil) + end + + context 'when user is unauthorized' do + let(:current_user) { unauthorized_user } + + it 'raises an exception' do + expect { subject }.to raise_error(GraphQL::ExecutionError) + end + end + + context 'when issue does not exist' do + let(:issuable_id) { GlobalID.parse("gid://gitlab/Issue/#{non_existing_record_id}") } + + it 'raises an exception' do + expect { subject }.to raise_error(GraphQL::ExecutionError) + end + end + + context 'when a GraphQL::ID_TYPE is provided' do + let(:issuable_id) { issue.to_gid.to_s } + + it 'raises an exception' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end + end + end + + context 'subscription updates' do + let(:resolver) { resolver_instance(described_class, obj: issue, ctx: { current_user: current_user }, subscription_update: true) } + + it 'returns the resolved object' do + expect(subject).to eq(issue) + end + + context 'when user is unauthorized' do + let(:current_user) { unauthorized_user } + + it 'unsubscribes the user' do + expect { subject }.to throw_symbol(:graphql_subscription_unsubscribed) + end + end + end + end +end diff --git a/spec/graphql/types/blob_viewer_type_spec.rb b/spec/graphql/types/blob_viewer_type_spec.rb new file mode 100644 index 00000000000..1c020c63535 --- /dev/null +++ b/spec/graphql/types/blob_viewer_type_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['BlobViewer'] do + it 'has the correct fields' do + expected_fields = [:type, :load_async, :too_large, :collapsed, + :render_error, :file_type, :loading_partial_name] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb index 787e2174070..54fe0c4b707 100644 --- a/spec/graphql/types/ci/job_type_spec.rb +++ b/spec/graphql/types/ci/job_type_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Types::Ci::JobType do specify { expect(described_class.graphql_name).to eq('CiJob') } specify { expect(described_class).to require_graphql_authorizations(:read_commit_status) } + specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Ci::Job) } it 'exposes the expected fields' do expected_fields = %i[ @@ -15,15 +16,18 @@ RSpec.describe Types::Ci::JobType do commitPath coverage created_at + created_by_tag detailedStatus duration finished_at id + manual_job name needs pipeline playable queued_at + queued_duration refName refPath retryable @@ -33,7 +37,10 @@ RSpec.describe Types::Ci::JobType do stage started_at status + stuck tags + triggered + userPermissions ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb index c7d2cbdb765..35d48229fa4 100644 --- a/spec/graphql/types/ci/pipeline_type_spec.rb +++ b/spec/graphql/types/ci/pipeline_type_spec.rb @@ -9,7 +9,8 @@ RSpec.describe Types::Ci::PipelineType do it 'contains attributes related to a pipeline' do expected_fields = %w[ - id iid sha before_sha status detailed_status config_source duration + id iid sha before_sha complete status detailed_status config_source + duration queued_duration coverage created_at updated_at started_at finished_at committed_at stages user retryable cancelable jobs source_job job downstream upstream path project active user_permissions warnings commit_path uses_needs @@ -17,7 +18,7 @@ RSpec.describe Types::Ci::PipelineType do ] if Gitlab.ee? - expected_fields += %w[security_report_summary security_report_findings] + expected_fields += %w[security_report_summary security_report_findings code_quality_reports] end expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb new file mode 100644 index 00000000000..dfe4a30c5b7 --- /dev/null +++ b/spec/graphql/types/ci/runner_type_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::RunnerType do + specify { expect(described_class.graphql_name).to eq('CiRunner') } + + it 'contains attributes related to a runner' do + expected_fields = %w[ + id description contacted_at maximum_timeout access_level active status + version short_sha revision locked run_untagged ip_address runner_type tag_list + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/template_type_spec.rb b/spec/graphql/types/ci/template_type_spec.rb new file mode 100644 index 00000000000..95ac9f97e31 --- /dev/null +++ b/spec/graphql/types/ci/template_type_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::TemplateType do + specify { expect(described_class.graphql_name).to eq('CiTemplate') } + + it 'exposes the expected fields' do + expected_fields = %i[ + name + content + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/design_management/version_type_spec.rb b/spec/graphql/types/design_management/version_type_spec.rb index 017cc1775a1..62335a65fdf 100644 --- a/spec/graphql/types/design_management/version_type_spec.rb +++ b/spec/graphql/types/design_management/version_type_spec.rb @@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['DesignVersion'] do it { expect(described_class).to require_graphql_authorizations(:read_design) } it 'has the expected fields' do - expected_fields = %i[id sha designs design_at_version designs_at_version] + expected_fields = %i[id sha designs design_at_version designs_at_version author created_at] expect(described_class).to have_graphql_fields(*expected_fields) end diff --git a/spec/graphql/types/duration_type_spec.rb b/spec/graphql/types/duration_type_spec.rb new file mode 100644 index 00000000000..5b88819f157 --- /dev/null +++ b/spec/graphql/types/duration_type_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['Duration'] do + let(:duration) { 17.minutes } + + it 'presents information as a floating point number' do + expect(described_class.coerce_isolated_result(duration)).to eq(duration.to_f) + end + + it 'accepts integers as input' do + expect(described_class.coerce_isolated_input(100)).to eq(100.0) + end + + it 'accepts floats as input' do + expect(described_class.coerce_isolated_input(0.5)).to eq(0.5) + end + + it 'rejects invalid input' do + expect { described_class.coerce_isolated_input('not valid') } + .to raise_error(GraphQL::CoercionError) + end + + it 'rejects nil' do + expect { described_class.coerce_isolated_input(nil) } + .to raise_error(GraphQL::CoercionError) + end +end diff --git a/spec/graphql/types/issuable_type_spec.rb b/spec/graphql/types/issuable_type_spec.rb new file mode 100644 index 00000000000..992a58f524b --- /dev/null +++ b/spec/graphql/types/issuable_type_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['Issuable'] do + it 'returns possible types' do + expect(described_class.possible_types).to include(Types::IssueType, Types::MergeRequestType) + end + + describe '.resolve_type' do + it 'resolves issues' do + expect(described_class.resolve_type(build(:issue), {})).to eq(Types::IssueType) + end + + it 'resolves merge requests' do + expect(described_class.resolve_type(build(:merge_request), {})).to eq(Types::MergeRequestType) + end + + it 'raises an error for invalid types' do + expect { described_class.resolve_type(build(:user), {}) }.to raise_error 'Unsupported issuable type' + end + end +end diff --git a/spec/graphql/types/label_type_spec.rb b/spec/graphql/types/label_type_spec.rb index 427b5d2dcef..475b2a2ad34 100644 --- a/spec/graphql/types/label_type_spec.rb +++ b/spec/graphql/types/label_type_spec.rb @@ -11,6 +11,7 @@ RSpec.describe GitlabSchema.types['Label'] do :color, :text_color, :created_at, + :remove_on_close, :updated_at ] diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb index 3314ea62324..fa33b32c6c8 100644 --- a/spec/graphql/types/merge_request_type_spec.rb +++ b/spec/graphql/types/merge_request_type_spec.rb @@ -16,7 +16,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do notes discussions user_permissions id iid title title_html description description_html state created_at updated_at source_project target_project project project_id source_project_id target_project_id source_branch - target_branch work_in_progress merge_when_pipeline_succeeds diff_head_sha + target_branch work_in_progress draft merge_when_pipeline_succeeds diff_head_sha merge_commit_sha user_notes_count user_discussions_count should_remove_source_branch diff_refs diff_stats diff_stats_summary force_remove_source_branch merge_status in_progress_merge_commit_sha diff --git a/spec/graphql/types/metadata/kas_type_spec.rb b/spec/graphql/types/metadata/kas_type_spec.rb new file mode 100644 index 00000000000..f90c64f0068 --- /dev/null +++ b/spec/graphql/types/metadata/kas_type_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['Kas'] do + specify { expect(described_class.graphql_name).to eq('Kas') } + specify { expect(described_class).to require_graphql_authorizations(:read_instance_metadata) } +end diff --git a/spec/graphql/types/mutation_type_spec.rb b/spec/graphql/types/mutation_type_spec.rb index 41993327577..e4144e4fa97 100644 --- a/spec/graphql/types/mutation_type_spec.rb +++ b/spec/graphql/types/mutation_type_spec.rb @@ -3,8 +3,16 @@ require 'spec_helper' RSpec.describe Types::MutationType do - it 'is expected to have the MergeRequestSetWip' do - expect(described_class).to have_graphql_mutation(Mutations::MergeRequests::SetWip) + it 'is expected to have the deprecated MergeRequestSetWip' do + field = get_field('MergeRequestSetWip') + + expect(field).to be_present + expect(field.deprecation_reason).to be_present + expect(field.resolver).to eq(Mutations::MergeRequests::SetWip) + end + + it 'is expected to have the MergeRequestSetDraft' do + expect(described_class).to have_graphql_mutation(Mutations::MergeRequests::SetDraft) end describe 'deprecated and aliased mutations' do @@ -27,9 +35,9 @@ RSpec.describe Types::MutationType do it { expect(alias_field.resolver.fields).to eq(canonical_field.resolver.fields) } it { expect(alias_field.resolver.arguments).to eq(canonical_field.resolver.arguments) } end + end - def get_field(name) - described_class.fields[GraphqlHelpers.fieldnamerize(name)] - end + def get_field(name) + described_class.fields[GraphqlHelpers.fieldnamerize(name)] end end diff --git a/spec/graphql/types/packages/maven/metadatum_type_spec.rb b/spec/graphql/types/packages/maven/metadatum_type_spec.rb new file mode 100644 index 00000000000..cf24af1456e --- /dev/null +++ b/spec/graphql/types/packages/maven/metadatum_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['MavenMetadata'] do + it 'includes maven metadatum fields' do + expected_fields = %w[ + id created_at updated_at path app_group app_version app_name + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/packages/nuget/metadatum_type_spec.rb b/spec/graphql/types/packages/nuget/metadatum_type_spec.rb new file mode 100644 index 00000000000..e5baa7522e4 --- /dev/null +++ b/spec/graphql/types/packages/nuget/metadatum_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['NugetMetadata'] do + it 'includes nuget metadatum fields' do + expected_fields = %w[ + id license_url project_url icon_url + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/packages/package_status_enum_spec.rb b/spec/graphql/types/packages/package_status_enum_spec.rb new file mode 100644 index 00000000000..71d05da35ea --- /dev/null +++ b/spec/graphql/types/packages/package_status_enum_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PackageStatus'] do + it 'exposes all package statuses' do + expect(described_class.values.keys).to contain_exactly(*%w[DEFAULT HIDDEN PROCESSING ERROR]) + end +end diff --git a/spec/graphql/types/packages/package_type_enum_spec.rb b/spec/graphql/types/packages/package_type_enum_spec.rb index ccd91485e4b..9d5a7716a61 100644 --- a/spec/graphql/types/packages/package_type_enum_spec.rb +++ b/spec/graphql/types/packages/package_type_enum_spec.rb @@ -4,6 +4,6 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['PackageTypeEnum'] do it 'exposes all package types' do - expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS]) + expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM TERRAFORM_MODULE]) end end diff --git a/spec/graphql/types/packages/package_type_spec.rb b/spec/graphql/types/packages/package_type_spec.rb index 544d6ddc3af..07573044abb 100644 --- a/spec/graphql/types/packages/package_type_spec.rb +++ b/spec/graphql/types/packages/package_type_spec.rb @@ -9,6 +9,7 @@ RSpec.describe GitlabSchema.types['Package'] do created_at updated_at project tags pipelines metadata versions + status ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/permission_types/ci/job_spec.rb b/spec/graphql/types/permission_types/ci/job_spec.rb new file mode 100644 index 00000000000..e4bc5419070 --- /dev/null +++ b/spec/graphql/types/permission_types/ci/job_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::PermissionTypes::Ci::Job do + it 'has expected permission fields' do + expected_permissions = [ + :read_job_artifacts, :read_build, :update_build + ] + + expect(described_class).to have_graphql_fields(expected_permissions).only + end +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index f2c4068f048..0f7cadbd4a7 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -14,7 +14,7 @@ RSpec.describe GitlabSchema.types['Project'] do it 'has the expected fields' do expected_fields = %w[ user_permissions id full_path path name_with_namespace - name description description_html tag_list ssh_url_to_repo + name description description_html tag_list topics ssh_url_to_repo http_url_to_repo web_url star_count forks_count created_at last_activity_at archived visibility container_registry_enabled shared_runners_enabled @@ -32,6 +32,7 @@ RSpec.describe GitlabSchema.types['Project'] do issue_status_counts terraform_states alert_management_integrations container_repositories container_repositories_count pipeline_analytics squash_read_only sast_ci_configuration + ci_template ] expect(described_class).to include_graphql_fields(*expected_fields) @@ -379,4 +380,11 @@ RSpec.describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_type(Types::Ci::JobType.connection_type) } it { is_expected.to have_graphql_arguments(:statuses) } end + + describe 'ci_template field' do + subject { described_class.fields['ciTemplate'] } + + it { is_expected.to have_graphql_type(Types::Ci::TemplateType) } + it { is_expected.to have_graphql_arguments(:name) } + end end diff --git a/spec/graphql/types/projects/services_enum_spec.rb b/spec/graphql/types/projects/services_enum_spec.rb index b8da9305de4..c23c652a378 100644 --- a/spec/graphql/types/projects/services_enum_spec.rb +++ b/spec/graphql/types/projects/services_enum_spec.rb @@ -11,5 +11,5 @@ RSpec.describe GitlabSchema.types['ServiceType'] do end def available_services_enum - ::Service.available_services_types(include_dev: false).map(&:underscore).map(&:upcase) + ::Integration.available_services_types(include_dev: false).map(&:underscore).map(&:upcase) end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index d3dcdd260b0..9a8f2090cc1 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -21,8 +21,11 @@ RSpec.describe GitlabSchema.types['Query'] do user users issue + merge_request usage_trends_measurements runner_platforms + runner + runners ] expect(described_class).to have_graphql_fields(*expected_fields).at_least @@ -60,11 +63,21 @@ RSpec.describe GitlabSchema.types['Query'] do describe 'issue field' do subject { described_class.fields['issue'] } - it 'returns issue' do + it "finds an issue by it's gid" do + is_expected.to have_graphql_arguments(:id) is_expected.to have_graphql_type(Types::IssueType) end end + describe 'merge_request field' do + subject { described_class.fields['mergeRequest'] } + + it "finds a merge_request by it's gid" do + is_expected.to have_graphql_arguments(:id) + is_expected.to have_graphql_type(Types::MergeRequestType) + end + end + describe 'usage_trends_measurements field' do subject { described_class.fields['usageTrendsMeasurements'] } @@ -73,6 +86,18 @@ RSpec.describe GitlabSchema.types['Query'] do end end + describe 'runner field' do + subject { described_class.fields['runner'] } + + it { is_expected.to have_graphql_type(Types::Ci::RunnerType) } + end + + describe 'runners field' do + subject { described_class.fields['runners'] } + + it { is_expected.to have_graphql_type(Types::Ci::RunnerType.connection_type) } + end + describe 'runner_platforms field' do subject { described_class.fields['runnerPlatforms'] } diff --git a/spec/graphql/types/repository/blob_type_spec.rb b/spec/graphql/types/repository/blob_type_spec.rb index f8647e4e964..beab4dcebc2 100644 --- a/spec/graphql/types/repository/blob_type_spec.rb +++ b/spec/graphql/types/repository/blob_type_spec.rb @@ -5,5 +5,32 @@ require 'spec_helper' RSpec.describe Types::Repository::BlobType do specify { expect(described_class.graphql_name).to eq('RepositoryBlob') } - specify { expect(described_class).to have_graphql_fields(:id, :oid, :name, :path, :web_path, :lfs_oid, :mode) } + specify do + expect(described_class).to have_graphql_fields( + :id, + :oid, + :name, + :path, + :web_path, + :lfs_oid, + :mode, + :size, + :raw_size, + :raw_blob, + :raw_text_blob, + :file_type, + :edit_blob_path, + :stored_externally, + :raw_path, + :replace_path, + :simple_viewer, + :rich_viewer, + :plain_data, + :can_modify_blob, + :ide_edit_path, + :external_storage_url, + :fork_and_edit_path, + :ide_fork_and_edit_path + ) + end end diff --git a/spec/graphql/types/repository_type_spec.rb b/spec/graphql/types/repository_type_spec.rb index fa1e54dfcfa..ee0cc4361da 100644 --- a/spec/graphql/types/repository_type_spec.rb +++ b/spec/graphql/types/repository_type_spec.rb @@ -16,4 +16,6 @@ RSpec.describe GitlabSchema.types['Repository'] do specify { expect(described_class).to have_graphql_field(:blobs) } specify { expect(described_class).to have_graphql_field(:branch_names, calls_gitaly?: true, complexity: 170) } + + specify { expect(described_class).to have_graphql_field(:disk_path) } end diff --git a/spec/graphql/types/subscription_type_spec.rb b/spec/graphql/types/subscription_type_spec.rb new file mode 100644 index 00000000000..b99df374bb3 --- /dev/null +++ b/spec/graphql/types/subscription_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['Subscription'] do + it 'has the expected fields' do + expected_fields = %i[ + issuable_assignees_updated + ] + + expect(described_class).to have_graphql_fields(*expected_fields).only + end +end diff --git a/spec/graphql/types/timelog_type_spec.rb b/spec/graphql/types/timelog_type_spec.rb index 38bd70d5097..791c2fdb046 100644 --- a/spec/graphql/types/timelog_type_spec.rb +++ b/spec/graphql/types/timelog_type_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Timelog'] do - let(:fields) { %i[spent_at time_spent user issue note] } + let(:fields) { %i[spent_at time_spent user issue merge_request note] } it { expect(described_class.graphql_name).to eq('Timelog') } it { expect(described_class).to have_graphql_fields(fields) } @@ -25,6 +25,14 @@ RSpec.describe GitlabSchema.types['Timelog'] do end end + describe 'merge_request field' do + subject { described_class.fields['mergeRequest'] } + + it 'returns merge_request' do + is_expected.to have_graphql_type(Types::MergeRequestType) + end + end + describe 'note field' do subject { described_class.fields['note'] } diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb index d9e67ff348b..7d73727b041 100644 --- a/spec/graphql/types/user_type_spec.rb +++ b/spec/graphql/types/user_type_spec.rb @@ -5,7 +5,11 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['User'] do specify { expect(described_class.graphql_name).to eq('User') } - specify { expect(described_class).to require_graphql_authorizations(:read_user) } + specify do + runtime_type = described_class.resolve_type(build(:user), {}) + + expect(runtime_type).to require_graphql_authorizations(:read_user) + end it 'has the expected fields' do expected_fields = %w[ |