diff options
Diffstat (limited to 'spec/graphql')
105 files changed, 2392 insertions, 136 deletions
diff --git a/spec/graphql/mutations/alert_management/http_integration/create_spec.rb b/spec/graphql/mutations/alert_management/http_integration/create_spec.rb new file mode 100644 index 00000000000..9aa89761aaf --- /dev/null +++ b/spec/graphql/mutations/alert_management/http_integration/create_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +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) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(args) } + + context 'user has access to project' do + before do + project.add_maintainer(current_user) + end + + context 'when HttpIntegrations::CreateService responds with success' do + it 'returns the integration with no errors' do + expect(resolve).to eq( + integration: ::AlertManagement::HttpIntegration.last!, + errors: [] + ) + end + end + + context 'when HttpIntegrations::CreateService responds with an error' do + before do + allow_any_instance_of(::AlertManagement::HttpIntegrations::CreateService) + .to receive(:execute) + .and_return(ServiceResponse.error(payload: { integration: nil }, message: 'An integration already exists')) + end + + it 'returns errors' do + expect(resolve).to eq( + integration: nil, + errors: ['An integration already exists'] + ) + end + end + end + + context 'when resource is not accessible to the user' do + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end diff --git a/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb new file mode 100644 index 00000000000..f74f9186743 --- /dev/null +++ b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +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) } } + + specify { expect(described_class).to require_graphql_authorizations(:admin_operations) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(args) } + + context 'user has access to project' do + before do + project.add_maintainer(current_user) + end + + context 'when HttpIntegrations::DestroyService responds with success' do + it 'returns the integration with no errors' do + expect(resolve).to eq( + integration: integration, + errors: [] + ) + end + end + + context 'when HttpIntegrations::DestroyService responds with an error' do + before do + allow_any_instance_of(::AlertManagement::HttpIntegrations::DestroyService) + .to receive(:execute) + .and_return(ServiceResponse.error(payload: { integration: nil }, message: 'An error has occurred')) + end + + it 'returns errors' do + expect(resolve).to eq( + integration: nil, + errors: ['An error has occurred'] + ) + end + end + end + + context 'when resource is not accessible to the user' do + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end 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 new file mode 100644 index 00000000000..d3ffb2abb47 --- /dev/null +++ b/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +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) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(args) } + + context 'user has sufficient access to project' do + before do + project.add_maintainer(current_user) + end + + context 'when HttpIntegrations::UpdateService responds with success' do + it 'returns the integration with no errors' do + expect(resolve).to eq( + integration: integration, + errors: [] + ) + end + end + + context 'when HttpIntegrations::UpdateService responds with an error' do + before do + allow_any_instance_of(::AlertManagement::HttpIntegrations::UpdateService) + .to receive(:execute) + .and_return(ServiceResponse.error(payload: { integration: nil }, message: 'Token cannot be reset')) + end + + it 'returns errors' do + expect(resolve).to eq( + integration: nil, + errors: ['Token cannot be reset'] + ) + end + end + end + + context 'when resource is not accessible to the user' do + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end diff --git a/spec/graphql/mutations/alert_management/http_integration/update_spec.rb b/spec/graphql/mutations/alert_management/http_integration/update_spec.rb new file mode 100644 index 00000000000..d6318e3161d --- /dev/null +++ b/spec/graphql/mutations/alert_management/http_integration/update_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +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) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(args) } + + context 'user has sufficient access to project' do + before do + project.add_maintainer(current_user) + end + + context 'when HttpIntegrations::UpdateService responds with success' do + it 'returns the integration with no errors' do + expect(resolve).to eq( + integration: integration, + errors: [] + ) + end + end + + context 'when HttpIntegrations::UpdateService responds with an error' do + before do + allow_any_instance_of(::AlertManagement::HttpIntegrations::UpdateService) + .to receive(:execute) + .and_return(ServiceResponse.error(payload: { integration: nil }, message: 'Failed to update')) + end + + it 'returns errors' do + expect(resolve).to eq( + integration: nil, + errors: ['Failed to update'] + ) + end + end + end + + context 'when resource is not accessible to the user' do + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end diff --git a/spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb new file mode 100644 index 00000000000..02a5e2e74e2 --- /dev/null +++ b/spec/graphql/mutations/alert_management/prometheus_integration/create_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +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) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(args) } + + context 'user has access to project' do + before do + project.add_maintainer(current_user) + end + + context 'when Prometheus Integration already exists' do + let_it_be(:existing_integration) { create(:prometheus_service, project: project) } + + it 'returns errors' do + expect(resolve).to eq( + integration: nil, + errors: ['Multiple Prometheus integrations are not supported'] + ) + end + end + + context 'when UpdateService responds with success' do + it 'returns the integration with no errors' do + expect(resolve).to eq( + integration: ::PrometheusService.last!, + errors: [] + ) + end + + it 'creates a corresponding token' do + expect { resolve }.to change(::Alerting::ProjectAlertingSetting, :count).by(1) + end + end + + context 'when UpdateService responds with an error' do + before do + allow_any_instance_of(::Projects::Operations::UpdateService) + .to receive(:execute) + .and_return({ status: :error, message: 'An error occurred' }) + end + + it 'returns errors' do + expect(resolve).to eq( + integration: nil, + errors: ['An error occurred'] + ) + end + end + end + + context 'when resource is not accessible to the user' do + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end 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 new file mode 100644 index 00000000000..45d92695e06 --- /dev/null +++ b/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +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) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(args) } + + context 'user has sufficient access to project' do + before do + project.add_maintainer(current_user) + end + + context 'when ::Projects::Operations::UpdateService responds with success' do + it 'returns the integration with no errors' do + expect(resolve).to eq( + integration: integration, + errors: [] + ) + end + end + + context 'when ::Projects::Operations::UpdateService responds with an error' do + before do + allow_any_instance_of(::Projects::Operations::UpdateService) + .to receive(:execute) + .and_return({ status: :error, message: 'An error occurred' }) + end + + it 'returns errors' do + expect(resolve).to eq( + integration: integration, + errors: ['An error occurred'] + ) + end + end + end + + context 'when resource is not accessible to the user' do + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end diff --git a/spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb new file mode 100644 index 00000000000..eab4474d827 --- /dev/null +++ b/spec/graphql/mutations/alert_management/prometheus_integration/update_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +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) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(args) } + + context 'user has sufficient access to project' do + before do + project.add_maintainer(current_user) + end + + context 'when ::Projects::Operations::UpdateService responds with success' do + it 'returns the integration with no errors' do + expect(resolve).to eq( + integration: integration, + errors: [] + ) + end + end + + context 'when ::Projects::Operations::UpdateService responds with an error' do + before do + allow_any_instance_of(::Projects::Operations::UpdateService) + .to receive(:execute) + .and_return({ status: :error, message: 'An error occurred' }) + end + + it 'returns errors' do + expect(resolve).to eq( + integration: integration, + errors: ['An error occurred'] + ) + end + end + end + + context 'when resource is not accessible to the user' do + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end 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 ab98088ebcd..08761ce64c2 100644 --- a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb +++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb @@ -37,8 +37,8 @@ RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do context 'error occurs when updating' do it 'returns the alert with errors' do # Stub an error on the alert - allow_next_instance_of(Resolvers::AlertManagement::AlertResolver) do |resolver| - allow(resolver).to receive(:resolve).and_return(alert) + allow_next_instance_of(::AlertManagement::AlertsFinder) do |finder| + allow(finder).to receive(:execute).and_return([alert]) end allow(alert).to receive(:save).and_return(false) diff --git a/spec/graphql/mutations/commits/create_spec.rb b/spec/graphql/mutations/commits/create_spec.rb index fb1baafe7bd..82a5e3a62f5 100644 --- a/spec/graphql/mutations/commits/create_spec.rb +++ b/spec/graphql/mutations/commits/create_spec.rb @@ -5,8 +5,9 @@ require 'spec_helper' RSpec.describe Mutations::Commits::Create do subject(:mutation) { described_class.new(object: nil, context: context, field: nil) } - let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository) } + let(:context) do GraphQL::Query::Context.new( query: OpenStruct.new(schema: nil), @@ -18,9 +19,10 @@ RSpec.describe Mutations::Commits::Create do specify { expect(described_class).to require_graphql_authorizations(:push_code) } describe '#resolve' do - subject { mutation.resolve(project_path: project.full_path, branch: branch, message: message, actions: actions) } + subject { mutation.resolve(project_path: project.full_path, branch: branch, start_branch: start_branch, message: message, actions: actions) } let(:branch) { 'master' } + let(:start_branch) { nil } let(:message) { 'Commit message' } let(:actions) do [ @@ -142,6 +144,29 @@ RSpec.describe Mutations::Commits::Create do end end + context 'when branch does not exist and a start branch is provided' do + let(:branch) { 'my-branch' } + let(:start_branch) { 'master' } + let(:actions) do + [ + { + action: 'create', + file_path: 'ANOTHER_FILE.md', + content: 'Bye' + } + ] + 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: 'ANOTHER_FILE.md') + ]) + end + end + context 'when message is not set' do let(:message) { nil } diff --git a/spec/graphql/mutations/container_expiration_policies/update_spec.rb b/spec/graphql/mutations/container_expiration_policies/update_spec.rb index 6aedaab3b53..9c6016e0af4 100644 --- a/spec/graphql/mutations/container_expiration_policies/update_spec.rb +++ b/spec/graphql/mutations/container_expiration_policies/update_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do it_behaves_like 'not creating the container expiration policy' - it "doesn't update the cadence" do + it 'doesn\'t update the cadence' do expect { subject } .not_to change { container_expiration_policy.reload.cadence } end @@ -47,6 +47,24 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do ) end end + + context 'with blank regex' do + let_it_be(:params) { { project_path: project.full_path, name_regex: '', enabled: true } } + + it_behaves_like 'not creating the container expiration policy' + + it "doesn't update the cadence" do + expect { subject } + .not_to change { container_expiration_policy.reload.cadence } + end + + it 'returns an error' do + expect(subject).to eq( + container_expiration_policy: nil, + errors: ['Name regex can\'t be blank'] + ) + end + end end RSpec.shared_examples 'denying access to container expiration policy' do diff --git a/spec/graphql/mutations/container_repositories/destroy_spec.rb b/spec/graphql/mutations/container_repositories/destroy_spec.rb new file mode 100644 index 00000000000..3903196a511 --- /dev/null +++ b/spec/graphql/mutations/container_repositories/destroy_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::ContainerRepositories::Destroy do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:container_repository) { create(:container_repository) } + let_it_be(:user) { create(:user) } + + let(:project) { container_repository.project } + let(:id) { container_repository.to_global_id.to_s } + + specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) } + + describe '#resolve' do + subject do + described_class.new(object: nil, context: { current_user: user }, field: nil) + .resolve(id: id) + end + + shared_examples 'destroying the container repository' do + it 'destroys the container repistory' do + expect(::Packages::CreateEventService) + .to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original + expect(DeleteContainerRepositoryWorker) + .to receive(:perform_async).with(user.id, container_repository.id) + + expect { subject }.to change { ::Packages::Event.count }.by(1) + expect(container_repository.reload.delete_scheduled?).to be true + end + end + + shared_examples 'denying access to container respository' do + it 'raises an error' do + expect(DeleteContainerRepositoryWorker) + .not_to receive(:perform_async).with(user.id, container_repository.id) + + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'with valid id' do + where(:user_role, :shared_examples_name) do + :maintainer | 'destroying the container repository' + :developer | 'destroying the container repository' + :reporter | 'denying access to container respository' + :guest | 'denying access to container respository' + :anonymous | 'denying access to container respository' + end + + with_them do + before do + project.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end + + context 'with invalid id' do + let(:id) { 'gid://gitlab/ContainerRepository/5555' } + + it_behaves_like 'denying access to container respository' + end + end +end diff --git a/spec/graphql/mutations/issues/set_assignees_spec.rb b/spec/graphql/mutations/issues/set_assignees_spec.rb index 77ba511b715..9a27c5acdac 100644 --- a/spec/graphql/mutations/issues/set_assignees_spec.rb +++ b/spec/graphql/mutations/issues/set_assignees_spec.rb @@ -3,6 +3,20 @@ require 'spec_helper' RSpec.describe Mutations::Issues::SetAssignees do + context 'when the user does not have permissions' do + let_it_be(:issue) { create(:issue) } + let_it_be(:user) { create(:user) } + let_it_be(:assignee) { create(:user) } + + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + + describe '#resolve' do + subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, assignee_usernames: [assignee.username]) } + + it_behaves_like 'permission level for issue mutation is correctly verified' + end + end + it_behaves_like 'an assignable resource' do let_it_be(:resource, reload: true) { create(:issue) } end diff --git a/spec/graphql/mutations/issues/set_confidential_spec.rb b/spec/graphql/mutations/issues/set_confidential_spec.rb index 0b2fc0ecb93..c3269e5c0c0 100644 --- a/spec/graphql/mutations/issues/set_confidential_spec.rb +++ b/spec/graphql/mutations/issues/set_confidential_spec.rb @@ -17,9 +17,7 @@ RSpec.describe Mutations::Issues::SetConfidential do subject { mutation.resolve(project_path: project.full_path, iid: issue.iid, confidential: confidential) } - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end + it_behaves_like 'permission level for issue mutation is correctly verified' context 'when the user can update the issue' do before do diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb index a638971d966..9f8d0d6c405 100644 --- a/spec/graphql/mutations/issues/set_due_date_spec.rb +++ b/spec/graphql/mutations/issues/set_due_date_spec.rb @@ -16,9 +16,7 @@ RSpec.describe Mutations::Issues::SetDueDate do subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, due_date: due_date) } - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end + it_behaves_like 'permission level for issue mutation is correctly verified' context 'when the user can update the issue' do before do diff --git a/spec/graphql/mutations/issues/set_locked_spec.rb b/spec/graphql/mutations/issues/set_locked_spec.rb index 10438226c17..1a0af0c6c63 100644 --- a/spec/graphql/mutations/issues/set_locked_spec.rb +++ b/spec/graphql/mutations/issues/set_locked_spec.rb @@ -15,9 +15,7 @@ RSpec.describe Mutations::Issues::SetLocked do subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, locked: locked) } - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end + it_behaves_like 'permission level for issue mutation is correctly verified' context 'when the user can update the issue' do let(:mutated_issue) { subject[:issue] } diff --git a/spec/graphql/mutations/issues/set_severity_spec.rb b/spec/graphql/mutations/issues/set_severity_spec.rb index ed73d3b777e..7698118ae3e 100644 --- a/spec/graphql/mutations/issues/set_severity_spec.rb +++ b/spec/graphql/mutations/issues/set_severity_spec.rb @@ -15,11 +15,7 @@ RSpec.describe Mutations::Issues::SetSeverity do subject(:resolve) { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, severity: severity) } - context 'when the user cannot update the issue' do - it 'raises an error' do - expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end + it_behaves_like 'permission level for issue mutation is correctly verified' context 'when the user can update the issue' do before do diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb index f9f4bdeb6fa..ce1eb874bcf 100644 --- a/spec/graphql/mutations/issues/update_spec.rb +++ b/spec/graphql/mutations/issues/update_spec.rb @@ -35,11 +35,7 @@ RSpec.describe Mutations::Issues::Update do subject { mutation.resolve(mutation_params) } - context 'when the user cannot access the issue' do - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end + it_behaves_like 'permission level for issue mutation is correctly verified' context 'when the user can update the issue' do before do diff --git a/spec/graphql/mutations/labels/create_spec.rb b/spec/graphql/mutations/labels/create_spec.rb new file mode 100644 index 00000000000..8b284816d63 --- /dev/null +++ b/spec/graphql/mutations/labels/create_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Labels::Create do + let_it_be(:user) { create(:user) } + + let(:attributes) do + { + title: 'new title', + description: 'A new label' + } + end + + let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + let(:mutated_label) { subject[:label] } + + shared_examples 'create labels mutation' do + describe '#resolve' do + subject { mutation.resolve(attributes.merge(extra_params)) } + + context 'when the user does not have permission to create a label' do + before do + parent.add_guest(user) + end + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when the user can create a label' do + before do + parent.add_developer(user) + end + + it 'creates label with correct values' do + expect(mutated_label).to have_attributes(attributes) + end + end + end + end + + specify { expect(described_class).to require_graphql_authorizations(:admin_label) } + + context 'when creating a project label' do + let_it_be(:parent) { create(:project) } + let(:extra_params) { { project_path: parent.full_path } } + + it_behaves_like 'create labels mutation' + end + + context 'when creating a group label' do + let_it_be(:parent) { create(:group) } + let(:extra_params) { { group_path: parent.full_path } } + + it_behaves_like 'create labels mutation' + end + + describe '#ready?' do + subject { mutation.ready?(attributes.merge(extra_params)) } + + context 'when passing both project_path and group_path' do + let(:extra_params) { { project_path: 'foo', group_path: 'bar' } } + + it 'raises an argument error' do + expect { subject } + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /Exactly one of/) + end + end + + context 'when passing only project_path or group_path' do + let(:extra_params) { { project_path: 'foo' } } + + it 'does not raise an error' do + expect { subject }.not_to raise_error + end + end + end +end diff --git a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb index 4ac40fc09c6..e2eab591341 100644 --- a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb @@ -3,6 +3,20 @@ require 'spec_helper' RSpec.describe Mutations::MergeRequests::SetAssignees do + context 'when the user does not have permissions' do + let_it_be(:merge_request) { create(:merge_request) } + let_it_be(:user) { create(:user) } + let_it_be(:assignee) { create(:user) } + + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + + describe '#resolve' do + subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, assignee_usernames: [assignee.username]) } + + it_behaves_like 'permission level for merge request mutation is correctly verified' + end + end + it_behaves_like 'an assignable resource' do let_it_be(:resource, reload: true) { create(:merge_request) } end diff --git a/spec/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/graphql/mutations/merge_requests/set_labels_spec.rb index 62a7f650f84..1bb303cf99b 100644 --- a/spec/graphql/mutations/merge_requests/set_labels_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_labels_spec.rb @@ -18,9 +18,7 @@ RSpec.describe Mutations::MergeRequests::SetLabels do subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids) } - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end + it_behaves_like 'permission level for merge request mutation is correctly verified' context 'when the user can update the merge request' do before do diff --git a/spec/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/graphql/mutations/merge_requests/set_locked_spec.rb index aca7df5445f..03c709e9bb3 100644 --- a/spec/graphql/mutations/merge_requests/set_locked_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_locked_spec.rb @@ -16,9 +16,7 @@ RSpec.describe Mutations::MergeRequests::SetLocked do subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, locked: locked) } - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end + it_behaves_like 'permission level for merge request mutation is correctly verified' context 'when the user can update the merge request' do before do diff --git a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb index ccb2d9bd132..4de857f43e3 100644 --- a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb @@ -18,6 +18,8 @@ RSpec.describe Mutations::MergeRequests::SetMilestone do expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) end + it_behaves_like 'permission level for merge request mutation is correctly verified' + context 'when the user can update the merge request' do before do project.add_developer(user) diff --git a/spec/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/graphql/mutations/merge_requests/set_wip_spec.rb index b6cb49724fa..69f6a4328b8 100644 --- a/spec/graphql/mutations/merge_requests/set_wip_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_wip_spec.rb @@ -16,9 +16,7 @@ RSpec.describe Mutations::MergeRequests::SetWip do subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, wip: wip) } - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end + it_behaves_like 'permission level for merge request mutation is correctly verified' context 'when the user can update the merge request' do before do diff --git a/spec/graphql/mutations/merge_requests/update_spec.rb b/spec/graphql/mutations/merge_requests/update_spec.rb index 4a1fdf6e74b..8acd2562ea8 100644 --- a/spec/graphql/mutations/merge_requests/update_spec.rb +++ b/spec/graphql/mutations/merge_requests/update_spec.rb @@ -18,9 +18,7 @@ RSpec.describe Mutations::MergeRequests::Update do mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, **attributes) end - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end + it_behaves_like 'permission level for merge request mutation is correctly verified' context 'when the user can update the merge request' do before do diff --git a/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb b/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb new file mode 100644 index 00000000000..8c22e1a1cb6 --- /dev/null +++ b/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Notes::RepositionImageDiffNote do + include GraphqlHelpers + + describe '#resolve' do + subject do + mutation.resolve({ note: note, position: new_position }) + end + + let_it_be(:noteable) { create(:merge_request) } + let_it_be(:project) { noteable.project } + let(:note) { create(:image_diff_note_on_merge_request, noteable: noteable, project: project) } + + let(:mutation) do + described_class.new(object: nil, context: { current_user: user }, field: nil) + end + + let(:new_position) do + { x: 10, y: 11, width: 12, height: 13 } + end + + context 'when the user does not have permission' do + let(:user) { nil } + + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error( + Gitlab::Graphql::Errors::ResourceNotAvailable, + "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + ) + end + end + + context 'when the user has permission' do + let(:user) { project.creator } + let(:mutated_note) { subject[:note] } + let(:errors) { subject[:errors] } + + it 'mutates the note', :aggregate_failures do + expect { subject }.to change { note.reset.position.to_h }.to(include(new_position)) + + expect(mutated_note).to eq(note) + expect(errors).to be_empty + end + + context 'when the note is a DiffNote, but not on an image' do + let(:note) { create(:diff_note_on_merge_request, noteable: noteable, project: project) } + + it 'raises an error' do + expect { subject }.to raise_error( + Gitlab::Graphql::Errors::ResourceNotAvailable, + 'Resource is not an ImageDiffNote' + ) + end + end + end + end +end diff --git a/spec/graphql/mutations/releases/create_spec.rb b/spec/graphql/mutations/releases/create_spec.rb new file mode 100644 index 00000000000..d6305691dac --- /dev/null +++ b/spec/graphql/mutations/releases/create_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Releases::Create do + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:milestone_12_3) { create(:milestone, project: project, title: '12.3') } + let_it_be(:milestone_12_4) { create(:milestone, project: project, title: '12.4') } + let_it_be(:reporter) { create(:user) } + let_it_be(:developer) { create(:user) } + + let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } + + let(:tag) { 'v1.1.0'} + let(:ref) { 'master'} + let(:name) { 'Version 1.0'} + let(:description) { 'The first release :rocket:' } + let(:released_at) { Time.parse('2018-12-10') } + let(:milestones) { [milestone_12_3.title, milestone_12_4.title] } + let(:assets) do + { + links: [ + { + name: 'An asset link', + url: 'https://gitlab.example.com/link', + filepath: '/permanent/link', + link_type: 'other' + } + ] + } + end + + let(:mutation_arguments) do + { + project_path: project.full_path, + tag: tag, + ref: ref, + name: name, + description: description, + released_at: released_at, + milestones: milestones, + assets: assets + } + end + + around do |example| + freeze_time { example.run } + end + + before do + project.add_reporter(reporter) + project.add_developer(developer) + end + + describe '#resolve' do + subject(:resolve) do + mutation.resolve(**mutation_arguments) + end + + let(:new_release) { subject[:release] } + + context 'when the current user has access to create releases' do + let(:current_user) { developer } + + it 'returns no errors' do + expect(resolve).to include(errors: []) + end + + it 'creates the release with the correct tag' do + expect(new_release.tag).to eq(tag) + end + + it 'creates the release with the correct name' do + expect(new_release.name).to eq(name) + end + + it 'creates the release with the correct description' do + expect(new_release.description).to eq(description) + end + + it 'creates the release with the correct released_at' do + expect(new_release.released_at).to eq(released_at) + end + + it 'creates the release with the correct created_at' do + expect(new_release.created_at).to eq(Time.current) + end + + it 'creates the release with the correct milestone associations' do + expected_milestone_titles = [milestone_12_3.title, milestone_12_4.title] + actual_milestone_titles = new_release.milestones.map { |m| m.title } + + # Right now the milestones are returned in a non-deterministic order. + # `match_array` should be updated to `eq` once + # https://gitlab.com/gitlab-org/gitlab/-/issues/259012 is addressed. + expect(actual_milestone_titles).to match_array(expected_milestone_titles) + end + + describe 'asset links' do + let(:expected_link) { assets[:links].first } + let(:new_link) { new_release.links.first } + + it 'creates a single asset link' do + expect(new_release.links.size).to eq(1) + end + + it 'creates the link with the correct name' do + expect(new_link.name).to eq(expected_link[:name]) + end + + it 'creates the link with the correct url' do + expect(new_link.url).to eq(expected_link[:url]) + end + + it 'creates the link with the correct link type' do + expect(new_link.link_type).to eq(expected_link[:link_type]) + end + + it 'creates the link with the correct direct filepath' do + expect(new_link.filepath).to eq(expected_link[:filepath]) + end + end + end + + context "when the current user doesn't have access to create releases" do + let(:current_user) { reporter } + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end +end diff --git a/spec/graphql/mutations/terraform/state/delete_spec.rb b/spec/graphql/mutations/terraform/state/delete_spec.rb new file mode 100644 index 00000000000..313a85a4bac --- /dev/null +++ b/spec/graphql/mutations/terraform/state/delete_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Terraform::State::Delete do + let_it_be(:user) { create(:user) } + let_it_be(:state) { create(:terraform_state) } + + let(:mutation) do + described_class.new( + object: double, + context: { current_user: user }, + field: double + ) + end + + it { expect(described_class.graphql_name).to eq('TerraformStateDelete') } + it { expect(described_class).to require_graphql_authorizations(:admin_terraform_state) } + + describe '#resolve' do + let(:global_id) { state.to_global_id } + + subject { mutation.resolve(id: global_id) } + + context 'user does not have permission' do + it 'raises an error', :aggregate_failures do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + expect { state.reload }.not_to raise_error + end + end + + context 'user has permission' do + before do + state.project.add_maintainer(user) + end + + it 'deletes the state', :aggregate_failures do + expect do + expect(subject).to eq(errors: []) + end.to change { ::Terraform::State.count }.by(-1) + + expect { state.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with invalid params' do + let(:global_id) { user.to_global_id } + + it 'raises an error', :aggregate_failures do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + expect { state.reload }.not_to raise_error + end + end + end +end diff --git a/spec/graphql/mutations/terraform/state/lock_spec.rb b/spec/graphql/mutations/terraform/state/lock_spec.rb new file mode 100644 index 00000000000..c83563040fd --- /dev/null +++ b/spec/graphql/mutations/terraform/state/lock_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Terraform::State::Lock do + let_it_be(:user) { create(:user) } + let_it_be(:state) { create(:terraform_state) } + + let(:mutation) do + described_class.new( + object: double, + context: { current_user: user }, + field: double + ) + end + + it { expect(described_class.graphql_name).to eq('TerraformStateLock') } + it { expect(described_class).to require_graphql_authorizations(:admin_terraform_state) } + + describe '#resolve' do + let(:global_id) { state.to_global_id } + + subject { mutation.resolve(id: global_id) } + + context 'user does not have permission' do + it 'raises an error', :aggregate_failures do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + expect(state.reload).not_to be_locked + end + end + + context 'user has permission' do + before do + state.project.add_maintainer(user) + end + + it 'locks the state', :aggregate_failures do + expect(subject).to eq(errors: []) + + expect(state.reload).to be_locked + expect(state.locked_by_user).to eq(user) + expect(state.lock_xid).to be_present + expect(state.locked_at).to be_present + end + + context 'state is already locked' do + let(:locked_by_user) { create(:user) } + let(:state) { create(:terraform_state, :locked, locked_by_user: locked_by_user) } + + it 'does not modify the existing lock', :aggregate_failures do + expect(subject).to eq(errors: ['state is already locked']) + + expect(state.reload).to be_locked + expect(state.locked_by_user).to eq(locked_by_user) + end + end + end + + context 'with invalid params' do + let(:global_id) { user.to_global_id } + + it 'raises an error', :aggregate_failures do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + expect(state.reload).not_to be_locked + end + end + end +end diff --git a/spec/graphql/mutations/terraform/state/unlock_spec.rb b/spec/graphql/mutations/terraform/state/unlock_spec.rb new file mode 100644 index 00000000000..4918a1c4abf --- /dev/null +++ b/spec/graphql/mutations/terraform/state/unlock_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Terraform::State::Unlock do + let_it_be(:user) { create(:user) } + let_it_be(:state) { create(:terraform_state, :locked) } + + let(:mutation) do + described_class.new( + object: double, + context: { current_user: user }, + field: double + ) + end + + it { expect(described_class.graphql_name).to eq('TerraformStateUnlock') } + it { expect(described_class).to require_graphql_authorizations(:admin_terraform_state) } + + describe '#resolve' do + let(:global_id) { state.to_global_id } + + subject { mutation.resolve(id: global_id) } + + context 'user does not have permission' do + it 'raises an error', :aggregate_failures do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + expect(state.reload).to be_locked + end + end + + context 'user has permission' do + before do + state.project.add_maintainer(user) + end + + it 'unlocks the state', :aggregate_failures do + expect(subject).to eq(errors: []) + expect(state.reload).not_to be_locked + end + + context 'state is already unlocked' do + let(:state) { create(:terraform_state) } + + it 'does not modify the state' do + expect(subject).to eq(errors: []) + expect(state.reload).not_to be_locked + end + end + end + + context 'with invalid params' do + let(:global_id) { user.to_global_id } + + it 'raises an error', :aggregate_failures do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + expect(state.reload).to be_locked + end + end + end +end diff --git a/spec/graphql/mutations/todos/create_spec.rb b/spec/graphql/mutations/todos/create_spec.rb new file mode 100644 index 00000000000..bbb033e2f33 --- /dev/null +++ b/spec/graphql/mutations/todos/create_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Todos::Create do + include GraphqlHelpers + include DesignManagementTestHelpers + + describe '#resolve' do + context 'when target does not support todos' do + it 'raises error' do + current_user = create(:user) + mutation = described_class.new(object: nil, context: { current_user: current_user }, field: nil) + + target = create(:milestone) + + expect { mutation.resolve(target_id: global_id_of(target)) } + .to raise_error(GraphQL::CoercionError) + end + end + + context 'with issue as target' do + it_behaves_like 'create todo mutation' do + let_it_be(:target) { create(:issue) } + end + end + + context 'with merge request as target' do + it_behaves_like 'create todo mutation' do + let_it_be(:target) { create(:merge_request) } + end + end + + context 'with design as target' do + before do + enable_design_management + end + + it_behaves_like 'create todo mutation' do + let_it_be(:target) { create(:design) } + end + end + end +end diff --git a/spec/graphql/mutations/todos/mark_all_done_spec.rb b/spec/graphql/mutations/todos/mark_all_done_spec.rb index 2f167164050..f3b6bf52ef7 100644 --- a/spec/graphql/mutations/todos/mark_all_done_spec.rb +++ b/spec/graphql/mutations/todos/mark_all_done_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Mutations::Todos::MarkAllDone do expect(todo3.reload.state).to eq('done') expect(other_user_todo.reload.state).to eq('pending') - expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3)) + expect(updated_todo_ids).to contain_exactly(todo1.id, todo3.id) expect(todos).to contain_exactly(todo1, todo3) end diff --git a/spec/graphql/mutations/todos/restore_many_spec.rb b/spec/graphql/mutations/todos/restore_many_spec.rb index 59995e33f2d..dc10355ef22 100644 --- a/spec/graphql/mutations/todos/restore_many_spec.rb +++ b/spec/graphql/mutations/todos/restore_many_spec.rb @@ -24,11 +24,11 @@ RSpec.describe Mutations::Todos::RestoreMany do expect(todo2.reload.state).to eq('pending') expect(other_user_todo.reload.state).to eq('done') - todo_ids = result[:updated_ids] - expect(todo_ids.size).to eq(1) - expect(todo_ids.first).to eq(todo1.to_global_id.to_s) - - expect(result[:todos]).to contain_exactly(todo1) + expect(result).to match( + errors: be_empty, + updated_ids: contain_exactly(todo1.id), + todos: contain_exactly(todo1) + ) end it 'handles a todo which is already pending as expected' do @@ -36,8 +36,11 @@ RSpec.describe Mutations::Todos::RestoreMany do expect_states_were_not_changed - expect(result[:updated_ids]).to eq([]) - expect(result[:todos]).to be_empty + expect(result).to match( + errors: be_empty, + updated_ids: be_empty, + todos: be_empty + ) end it 'ignores requests for todos which do not belong to the current user' do @@ -61,7 +64,7 @@ RSpec.describe Mutations::Todos::RestoreMany do expect(result[:updated_ids].size).to eq(2) returned_todo_ids = result[:updated_ids] - expect(returned_todo_ids).to contain_exactly(todo1.to_global_id.to_s, todo4.to_global_id.to_s) + expect(returned_todo_ids).to contain_exactly(todo1.id, todo4.id) expect(result[:todos]).to contain_exactly(todo1, todo4) expect(todo1.reload.state).to eq('pending') diff --git a/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb b/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb index c5637d43382..578d679ade4 100644 --- a/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb +++ b/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb @@ -14,7 +14,9 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso let_it_be(:project_measurement_new) { create(:instance_statistics_measurement, :project_count, recorded_at: 2.days.ago) } let_it_be(:project_measurement_old) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.days.ago) } - subject { resolve_measurements({ identifier: 'projects' }, { current_user: current_user }) } + let(:arguments) { { identifier: 'projects' } } + + subject { resolve_measurements(arguments, { current_user: current_user }) } context 'when requesting project count measurements' do context 'as an admin user' do @@ -40,6 +42,24 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) end end + + context 'when filtering by recorded_after and recorded_before' do + before do + arguments[:recorded_after] = 4.days.ago + arguments[:recorded_before] = 1.day.ago + end + + it { is_expected.to match_array([project_measurement_new]) } + + context 'when "incorrect" values are passed' do + before do + arguments[:recorded_after] = 1.day.ago + arguments[:recorded_before] = 4.days.ago + end + + it { is_expected.to be_empty } + end + end end context 'when requesting pipeline counts by pipeline status' do diff --git a/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb b/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb new file mode 100644 index 00000000000..36e409e0677 --- /dev/null +++ b/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::AlertManagement::IntegrationsResolver do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:prometheus_integration) { create(:prometheus_service, project: project) } + let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) } + let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) } + let_it_be(:other_proj_integration) { create(:alert_management_http_integration) } + + subject { sync(resolve_http_integrations) } + + specify do + expect(described_class).to have_nullable_graphql_type(Types::AlertManagement::IntegrationType.connection_type) + end + + context 'user does not have permission' do + it { is_expected.to be_empty } + end + + context 'user has permission' do + before do + project.add_maintainer(current_user) + end + + it { is_expected.to contain_exactly(active_http_integration, prometheus_integration) } + end + + private + + def resolve_http_integrations(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: project, ctx: context) + end +end diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb index 40dc2370052..e5b9fb57e42 100644 --- a/spec/graphql/resolvers/base_resolver_spec.rb +++ b/spec/graphql/resolvers/base_resolver_spec.rb @@ -7,10 +7,13 @@ RSpec.describe Resolvers::BaseResolver do let(:resolver) do Class.new(described_class) do - def resolve(**args) + argument :test, ::GraphQL::INT_TYPE, required: false + type [::GraphQL::INT_TYPE], null: true + + def resolve(test: 100) process(object) - [args, args] + [test, test] end def process(obj); end @@ -19,17 +22,75 @@ RSpec.describe Resolvers::BaseResolver do let(:last_resolver) do Class.new(described_class) do + type [::GraphQL::INT_TYPE], null: true + def resolve(**args) [1, 2] end end end + describe '.singular_type' do + subject { resolver.singular_type } + + context 'for a connection of scalars' do + let(:resolver) do + Class.new(described_class) do + type ::GraphQL::INT_TYPE.connection_type, null: true + end + end + + it { is_expected.to eq(::GraphQL::INT_TYPE) } + end + + context 'for a connection of objects' do + let(:object) do + Class.new(::Types::BaseObject) do + graphql_name 'Foo' + end + end + + let(:resolver) do + conn = object.connection_type + + Class.new(described_class) do + type conn, null: true + end + end + + it { is_expected.to eq(object) } + end + + context 'for a list type' do + let(:resolver) do + Class.new(described_class) do + type [::GraphQL::STRING_TYPE], null: true + end + end + + it { is_expected.to eq(::GraphQL::STRING_TYPE) } + end + + context 'for a scalar type' do + let(:resolver) do + Class.new(described_class) do + type ::GraphQL::BOOLEAN_TYPE, null: true + end + end + + it { is_expected.to eq(::GraphQL::BOOLEAN_TYPE) } + end + end + describe '.single' do it 'returns a subclass from the resolver' do expect(resolver.single.superclass).to eq(resolver) end + it 'has the correct (singular) type' do + expect(resolver.single.type).to eq(::GraphQL::INT_TYPE) + end + it 'returns the same subclass every time' do expect(resolver.single.object_id).to eq(resolver.single.object_id) end @@ -37,15 +98,106 @@ RSpec.describe Resolvers::BaseResolver do it 'returns a resolver that gives the first result from the original resolver' do result = resolve(resolver.single, args: { test: 1 }) - expect(result).to eq(test: 1) + expect(result).to eq(1) + end + end + + describe '.when_single' do + let(:resolver) do + Class.new(described_class) do + type [::GraphQL::INT_TYPE], null: true + + when_single do + argument :foo, ::GraphQL::INT_TYPE, required: true + end + + def resolve(foo: 1) + [foo * foo] # rubocop: disable Lint/BinaryOperatorWithIdenticalOperands + end + end + end + + it 'does not apply the block to the resolver' do + expect(resolver.field_options).to include( + arguments: be_empty + ) + result = resolve(resolver) + + expect(result).to eq([1]) + end + + it 'applies the block to the single version of the resolver' do + expect(resolver.single.field_options).to include( + arguments: match('foo' => an_instance_of(::Types::BaseArgument)) + ) + result = resolve(resolver.single, args: { foo: 7 }) + + expect(result).to eq(49) + end + + context 'multiple when_single blocks' do + let(:resolver) do + Class.new(described_class) do + type [::GraphQL::INT_TYPE], null: true + + when_single do + argument :foo, ::GraphQL::INT_TYPE, required: true + end + + when_single do + argument :bar, ::GraphQL::INT_TYPE, required: true + end + + def resolve(foo: 1, bar: 2) + [foo * bar] + end + end + end + + it 'applies both blocks to the single version of the resolver' do + expect(resolver.single.field_options).to include( + arguments: match('foo' => ::Types::BaseArgument, 'bar' => ::Types::BaseArgument) + ) + result = resolve(resolver.single, args: { foo: 7, bar: 5 }) + + expect(result).to eq(35) + end + end + + context 'inheritance' do + let(:subclass) do + Class.new(resolver) do + when_single do + argument :inc, ::GraphQL::INT_TYPE, required: true + end + + def resolve(foo:, inc:) + super(foo: foo + inc) + end + end + end + + it 'applies both blocks to the single version of the resolver' do + expect(resolver.single.field_options).to include( + arguments: match('foo' => ::Types::BaseArgument) + ) + expect(subclass.single.field_options).to include( + arguments: match('foo' => ::Types::BaseArgument, 'inc' => ::Types::BaseArgument) + ) + result = resolve(subclass.single, args: { foo: 7, inc: 1 }) + + expect(result).to eq(64) + end end end context 'when the resolver returns early' do let(:resolver) do Class.new(described_class) do + type [::GraphQL::STRING_TYPE], null: true + def ready?(**args) - [false, %w(early return)] + [false, %w[early return]] end def resolve(**args) @@ -121,28 +273,4 @@ RSpec.describe Resolvers::BaseResolver do end end end - - describe '#synchronized_object' do - let(:object) { double(foo: :the_foo) } - - let(:resolver) do - Class.new(described_class) do - def resolve(**args) - [synchronized_object.foo] - end - end - end - - it 'handles raw objects' do - expect(resolve(resolver, obj: object)).to contain_exactly(:the_foo) - end - - it 'handles lazy objects' do - delayed = BatchLoader::GraphQL.for(1).batch do |_, loader| - loader.call(1, object) - end - - expect(resolve(resolver, obj: delayed)).to contain_exactly(:the_foo) - end - end end diff --git a/spec/graphql/resolvers/ci/jobs_resolver_spec.rb b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb new file mode 100644 index 00000000000..a836c89bd61 --- /dev/null +++ b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::JobsResolver do + include GraphqlHelpers + + let_it_be(:pipeline) { create(:ci_pipeline) } + + before_all do + create(:ci_build, name: 'Normal job', pipeline: pipeline) + create(:ci_build, :sast, name: 'DAST job', pipeline: pipeline) + create(:ci_build, :dast, name: 'SAST job', pipeline: pipeline) + create(:ci_build, :container_scanning, name: 'Container scanning job', pipeline: pipeline) + end + + describe '#resolve' do + context 'when security_report_types is empty' do + it "returns all of the pipeline's jobs" do + jobs = resolve(described_class, obj: pipeline, args: {}, ctx: {}) + + job_names = jobs.map(&:name) + expect(job_names).to contain_exactly('Normal job', 'DAST job', 'SAST job', 'Container scanning job') + end + end + + context 'when security_report_types is present' do + it "returns the pipeline's jobs with the given security report types" do + report_types = [ + ::Types::Security::ReportTypeEnum.values['SAST'].value, + ::Types::Security::ReportTypeEnum.values['DAST'].value + ] + jobs = resolve(described_class, obj: pipeline, args: { security_report_types: report_types }, ctx: {}) + + job_names = jobs.map(&:name) + expect(job_names).to contain_exactly('DAST job', 'SAST job') + end + end + end +end diff --git a/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb b/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb new file mode 100644 index 00000000000..3d004290d9b --- /dev/null +++ b/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::RunnerSetupResolver do + include GraphqlHelpers + + describe '#resolve' do + let(:user) { create(:user) } + + subject(:resolve_subject) { resolve(described_class, ctx: { current_user: user }, args: { platform: platform, architecture: 'amd64' }.merge(target_param)) } + + context 'with container platforms' do + let(:platform) { 'docker' } + let(:project) { create(:project) } + let(:target_param) { { project_id: project.to_global_id } } + + it 'returns install instructions' do + expect(resolve_subject[:install_instructions]).not_to eq(nil) + end + + it 'does not return register instructions' do + expect(resolve_subject[:register_instructions]).to eq(nil) + end + end + + context 'with regular platforms' do + let(:platform) { 'linux' } + + context 'without target parameter' do + let(:target_param) { {} } + + context 'when user is not admin' do + it 'returns access error' do + expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when user is admin' do + before do + user.update!(admin: true) + end + + it 'returns install and register instructions' do + expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions) + expect(resolve_subject.values).not_to include(nil) + end + end + end + + context 'with project target parameter' do + let(:project) { create(:project) } + let(:target_param) { { project_id: project.to_global_id } } + + context 'when user has access to admin builds on project' do + before do + project.add_maintainer(user) + end + + it 'returns install and register instructions' do + expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions) + expect(resolve_subject.values).not_to include(nil) + end + end + + context 'when user does not have access to admin builds on project' do + before do + project.add_developer(user) + end + + it 'returns access error' do + expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + context 'with group target parameter' do + let(:group) { create(:group) } + let(:target_param) { { group_id: group.to_global_id } } + + context 'when user has access to admin builds on group' do + before do + group.add_owner(user) + end + + it 'returns install and register instructions' do + expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions) + expect(resolve_subject.values).not_to include(nil) + end + end + + context 'when user does not have access to admin builds on group' do + before do + group.add_developer(user) + end + + it 'returns access error' do + expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + end + end +end diff --git a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb new file mode 100644 index 00000000000..b6fe94a2312 --- /dev/null +++ b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::CachingArrayResolver do + include GraphqlHelpers + + let_it_be(:non_admins) { create_list(:user, 4, admin: false) } + let(:query_context) { {} } + let(:max_page_size) { 10 } + let(:field) { double('Field', max_page_size: max_page_size) } + let(:schema) { double('Schema', default_max_page_size: 3) } + + let_it_be(:caching_resolver) do + mod = described_class + + Class.new(::Resolvers::BaseResolver) do + include mod + + def query_input(is_admin:) + is_admin + end + + def query_for(is_admin) + if is_admin.nil? + model_class.all + else + model_class.where(admin: is_admin) + end + end + + def model_class + User # Happens to include FromUnion, and is cheap-ish to create + end + end + end + + describe '#resolve' do + context 'there are more than MAX_UNION_SIZE queries' do + let_it_be(:max_union) { 3 } + let_it_be(:resolver) do + mod = described_class + max = max_union + + Class.new(::Resolvers::BaseResolver) do + include mod + + def query_input(username:) + username + end + + def query_for(username) + if username.nil? + model_class.all + else + model_class.where(username: username) + end + end + + def model_class + User # Happens to include FromUnion, and is cheap-ish to create + end + + define_method :max_union_size do + max + end + end + end + + it 'executes the queries in multiple batches' do + users = create_list(:user, (max_union * 2) + 1) + expect(User).to receive(:from_union).twice.and_call_original + + results = users.in_groups_of(2, false).map do |users| + resolve(resolver, args: { username: users.map(&:username) }, field: field, schema: schema) + end + + expect(results.flat_map(&method(:force))).to match_array(users) + end + end + + context 'all queries return results' do + let_it_be(:admins) { create_list(:admin, 3) } + + it 'batches the queries' do + expect do + [resolve_users(true), resolve_users(false)].each(&method(:force)) + end.to issue_same_number_of_queries_as { force(resolve_users(nil)) } + end + + it 'finds the correct values' do + found_admins = resolve_users(true) + found_others = resolve_users(false) + admins_again = resolve_users(true) + found_all = resolve_users(nil) + + expect(force(found_admins)).to match_array(admins) + expect(force(found_others)).to match_array(non_admins) + expect(force(admins_again)).to match_array(admins) + expect(force(found_all)).to match_array(admins + non_admins) + end + end + + it 'does not perform a union of a query with itself' do + expect(User).to receive(:where).once.and_call_original + + [resolve_users(false), resolve_users(false)].each(&method(:force)) + end + + context 'one of the queries returns no results' do + it 'finds the correct values' do + found_admins = resolve_users(true) + found_others = resolve_users(false) + found_all = resolve_users(nil) + + expect(force(found_admins)).to be_empty + expect(force(found_others)).to match_array(non_admins) + expect(force(found_all)).to match_array(non_admins) + end + end + + context 'one of the queries has already been cached' do + before do + force(resolve_users(nil)) + end + + it 'avoids further queries' do + expect do + repeated_find = resolve_users(nil) + + expect(force(repeated_find)).to match_array(non_admins) + end.not_to exceed_query_limit(0) + end + end + + context 'the resolver overrides item_found' do + let_it_be(:admins) { create_list(:admin, 2) } + let(:query_context) do + { + found: { true => [], false => [], nil => [] } + } + end + + let_it_be(:with_item_found) do + Class.new(caching_resolver) do + def item_found(key, item) + context[:found][key] << item + end + end + end + + it 'receives item_found for each key the item mapped to' do + found_admins = resolve_users(true, with_item_found) + found_all = resolve_users(nil, with_item_found) + + [found_admins, found_all].each(&method(:force)) + + expect(query_context[:found]).to match({ + false => be_empty, + true => match_array(admins), + nil => match_array(admins + non_admins) + }) + end + end + + context 'the max_page_size is lower than the total result size' do + let(:max_page_size) { 2 } + + it 'respects the max_page_size, on a per subset basis' do + found_all = resolve_users(nil) + found_others = resolve_users(false) + + expect(force(found_all).size).to eq(2) + expect(force(found_others).size).to eq(2) + end + end + + context 'the field does not declare max_page_size' do + let(:max_page_size) { nil } + + it 'takes the page size from schema.default_max_page_size' do + found_all = resolve_users(nil) + found_others = resolve_users(false) + + expect(force(found_all).size).to eq(schema.default_max_page_size) + expect(force(found_others).size).to eq(schema.default_max_page_size) + end + end + + specify 'force . resolve === to_a . query_for . query_input' do + r = resolver_instance(caching_resolver) + args = { is_admin: false } + + naive = r.query_for(r.query_input(**args)).to_a + + expect(force(r.resolve(**args))).to eq(naive) + end + end + + def resolve_users(is_admin, resolver = caching_resolver) + args = { is_admin: is_admin } + resolve(resolver, args: args, field: field, ctx: query_context, schema: schema) + end + + def force(lazy) + ::Gitlab::Graphql::Lazy.force(lazy) + end +end diff --git a/spec/graphql/resolvers/container_repositories_resolver_spec.rb b/spec/graphql/resolvers/container_repositories_resolver_spec.rb new file mode 100644 index 00000000000..b888d79626e --- /dev/null +++ b/spec/graphql/resolvers/container_repositories_resolver_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::ContainerRepositoriesResolver do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be_with_reload(:project) { create(:project, group: group) } + let_it_be(:container_repositories) { create(:container_repository, project: project) } + + let(:args) { {} } + + describe '#resolve' do + let(:object) { project } + + subject { resolve(described_class, ctx: { current_user: user }, args: args, obj: object) } + + shared_examples 'returning container repositories' do + it { is_expected.to contain_exactly(container_repositories) } + + context 'with a named search' do + let_it_be(:named_container_repository) { create(:container_repository, project: project, name: 'Foobar') } + + let(:args) { { name: 'ooba' } } + + it { is_expected.to contain_exactly(named_container_repository) } + end + end + + context 'with authorized user' do + before do + group.add_user(user, :maintainer) + end + + context 'when the object is a project' do + it_behaves_like 'returning container repositories' + end + + context 'when the object is a group' do + let(:object) { group } + + it_behaves_like 'returning container repositories' + end + + context 'when the object is an invalid type' do + let(:object) { Object.new } + + it { expect { subject }.to raise_exception('invalid subject_type') } + end + end + + context 'with unauthorized user' do + it { is_expected.to be nil } + end + end +end diff --git a/spec/graphql/resolvers/design_management/design_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_resolver_spec.rb index 02d7f94612c..e33eaedf167 100644 --- a/spec/graphql/resolvers/design_management/design_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/design_resolver_spec.rb @@ -6,6 +6,10 @@ RSpec.describe Resolvers::DesignManagement::DesignResolver do include GraphqlHelpers include DesignManagementTestHelpers + specify do + expect(described_class).to have_nullable_graphql_type(::Types::DesignManagement::DesignType) + end + before do enable_design_management end @@ -57,12 +61,21 @@ RSpec.describe Resolvers::DesignManagement::DesignResolver do end context 'the ID belongs to a design on another issue' do - let(:args) { { id: GitlabSchema.id_from_object(design_on_other_issue).to_s } } + let(:args) { { id: global_id_of(design_on_other_issue) } } it 'returns nothing' do expect(resolve_design).to be_nil end end + + context 'the ID does not belong to a design at all' do + let(:args) { { id: global_id_of(issue) } } + let(:msg) { /does not represent an instance of DesignManagement::Design/ } + + it 'complains meaningfully' do + expect { resolve_design }.to raise_error(msg) + end + end end context 'by filename' do diff --git a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb index cfa37d34fd9..28e963c88a9 100644 --- a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb @@ -6,6 +6,10 @@ RSpec.describe Resolvers::DesignManagement::DesignsResolver do include GraphqlHelpers include DesignManagementTestHelpers + specify do + expect(described_class).to have_nullable_graphql_type(::Types::DesignManagement::DesignType.connection_type) + end + before do enable_design_management end @@ -65,8 +69,24 @@ RSpec.describe Resolvers::DesignManagement::DesignsResolver do let(:second_version) { create(:design_version) } let(:second_design) { create(:design, issue: issue, versions: [second_version]) } + context 'ids is provided but null' do + let(:args) { { ids: nil } } + + it 'behaves as if unfiltered' do + expect(resolve_designs).to contain_exactly(first_design, second_design) + end + end + + context 'ids is provided but empty' do + let(:args) { { ids: [] } } + + it 'eliminates all values' do + expect(resolve_designs).to be_empty + end + end + context 'the ID is on the current issue' do - let(:args) { { ids: [GitlabSchema.id_from_object(second_design).to_s] } } + let(:args) { { ids: [GitlabSchema.id_from_object(second_design)] } } it 'resolves to just the relevant design' do expect(resolve_designs).to contain_exactly(second_design) @@ -77,7 +97,7 @@ RSpec.describe Resolvers::DesignManagement::DesignsResolver do let(:third_version) { create(:design_version) } let(:third_design) { create(:design, issue: create(:issue, project: project), versions: [third_version]) } - let(:args) { { ids: [GitlabSchema.id_from_object(third_design).to_s] } } + let(:args) { { ids: [GitlabSchema.id_from_object(third_design)] } } it 'ignores it' do expect(resolve_designs).to be_empty diff --git a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb index 8ad928e9854..403261fc22a 100644 --- a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb @@ -32,7 +32,7 @@ RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do end context 'we pass an id' do - let(:params) { { id: global_id_of(first_version) } } + let(:params) { { version_id: global_id_of(first_version) } } it { is_expected.to eq(first_version) } end @@ -44,13 +44,14 @@ RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do end context 'we pass an inconsistent mixture of sha and version id' do - let(:params) { { sha: first_version.sha, id: global_id_of(create(:design_version)) } } + let(:params) { { sha: first_version.sha, version_id: global_id_of(create(:design_version)) } } it { is_expected.to be_nil } end context 'we pass the id of something that is not a design_version' do - let(:params) { { id: global_id_of(project) } } + let(:params) { { version_id: global_id_of(project) } } + let(:appropriate_error) { ::GraphQL::CoercionError } it 'raises an appropriate error' do expect { result }.to raise_error(appropriate_error) diff --git a/spec/graphql/resolvers/echo_resolver_spec.rb b/spec/graphql/resolvers/echo_resolver_spec.rb index 2182ac221f6..4f48e5e0d7a 100644 --- a/spec/graphql/resolvers/echo_resolver_spec.rb +++ b/spec/graphql/resolvers/echo_resolver_spec.rb @@ -8,6 +8,10 @@ RSpec.describe Resolvers::EchoResolver do let(:current_user) { create(:user) } let(:text) { 'Message test' } + specify do + expect(described_class).to have_non_null_graphql_type(::GraphQL::STRING_TYPE) + end + describe '#resolve' do it 'echoes text and username' do expect(resolve_echo(text)).to eq %Q("#{current_user.username}" says: #{text}) diff --git a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb index 7e531910184..bf8d2139c82 100644 --- a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb +++ b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb @@ -10,6 +10,10 @@ RSpec.describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do let(:issue_details_service) { spy('ErrorTracking::IssueDetailsService') } + specify do + expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryDetailedErrorType) + end + before do project.add_developer(current_user) @@ -61,7 +65,9 @@ RSpec.describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do context 'blank id' do let(:args) { { id: '' } } - it_behaves_like 'it resolves to nil' + it 'responds with an error' do + expect { resolve_error(args) }.to raise_error(::GraphQL::CoercionError) + end end end diff --git a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb index 02e0420be2a..20c2bdcd4e1 100644 --- a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb +++ b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb @@ -10,6 +10,10 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorCollectionResolver do let(:list_issues_service) { spy('ErrorTracking::ListIssuesService') } + specify do + expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryErrorCollectionType) + end + before do project.add_developer(current_user) diff --git a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb index 554873a6e21..edca11f40d7 100644 --- a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb +++ b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb @@ -14,6 +14,10 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do let(:issues) { nil } let(:pagination) { nil } + specify do + expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryErrorType.connection_type) + end + describe '#resolve' do context 'insufficient user permission' do let(:user) { create(:user) } diff --git a/spec/graphql/resolvers/group_members_resolver_spec.rb b/spec/graphql/resolvers/group_members_resolver_spec.rb index bbfea575492..bd0b4870062 100644 --- a/spec/graphql/resolvers/group_members_resolver_spec.rb +++ b/spec/graphql/resolvers/group_members_resolver_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe Resolvers::GroupMembersResolver do include GraphqlHelpers + specify do + expect(described_class).to have_nullable_graphql_type(Types::GroupMemberType.connection_type) + end + it_behaves_like 'querying members with a group' do let_it_be(:resource_member) { create(:group_member, user: user_1, group: group_1) } let_it_be(:resource) { group_1 } diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 3a6507f906c..43cbd4d2bdd 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -20,6 +20,10 @@ RSpec.describe Resolvers::IssuesResolver do let_it_be(:label1) { create(:label, project: project) } let_it_be(:label2) { create(:label, project: project) } + specify do + expect(described_class).to have_nullable_graphql_type(Types::IssueType.connection_type) + end + context "with a project" do before do project.add_developer(current_user) diff --git a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb index ae3097c1d9e..deb5ff584cf 100644 --- a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb @@ -14,6 +14,7 @@ RSpec.describe Resolvers::MergeRequestPipelinesResolver do sha: merge_request.diff_head_sha ) end + 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) } diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb index aecffc487aa..3a3393a185c 100644 --- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb @@ -34,13 +34,13 @@ RSpec.describe Resolvers::MergeRequestsResolver do context 'no arguments' do it 'returns all merge requests' do - result = resolve_mr(project, {}) + result = resolve_mr(project) expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3, merge_request_4, merge_request_5, merge_request_6, merge_request_with_milestone) end it 'returns only merge requests that the current user can see' do - result = resolve_mr(project, {}, user: build(:user)) + result = resolve_mr(project, user: build(:user)) expect(result).to be_empty end @@ -236,10 +236,10 @@ RSpec.describe Resolvers::MergeRequestsResolver do end def resolve_mr_single(project, iid) - resolve_mr(project, { iids: iid }, resolver: described_class.single) + resolve_mr(project, resolver: described_class.single, iids: iid) end - def resolve_mr(project, args, resolver: described_class, user: current_user) + def resolve_mr(project, resolver: described_class, user: current_user, **args) resolve(resolver, obj: project, args: args, ctx: { current_user: user }) end end diff --git a/spec/graphql/resolvers/metadata_resolver_spec.rb b/spec/graphql/resolvers/metadata_resolver_spec.rb index 20556941de4..f8c01f9d531 100644 --- a/spec/graphql/resolvers/metadata_resolver_spec.rb +++ b/spec/graphql/resolvers/metadata_resolver_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Resolvers::MetadataResolver do describe '#resolve' do it 'returns version and revision' do - expect(resolve(described_class)).to eq(version: Gitlab::VERSION, revision: Gitlab.revision) + expect(resolve(described_class)).to have_attributes(version: Gitlab::VERSION, revision: Gitlab.revision) end end end diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb index a6a86c49373..1950c2ca067 100644 --- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb +++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb @@ -10,6 +10,10 @@ RSpec.describe Resolvers::ProjectPipelineResolver do let_it_be(:other_pipeline) { create(:ci_pipeline) } let(:current_user) { create(:user) } + specify do + expect(described_class).to have_nullable_graphql_type(::Types::Ci::PipelineType) + end + def resolve_pipeline(project, args) resolve(described_class, obj: project, args: args, ctx: { current_user: current_user }) end diff --git a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb index 0775c1c31d1..ad59cb6b95e 100644 --- a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb +++ b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe Resolvers::Projects::JiraImportsResolver do include GraphqlHelpers + specify do + expect(described_class).to have_nullable_graphql_type(Types::JiraImportType.connection_type) + end + describe '#resolve' do let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, :public) } diff --git a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb index 840aea8b8c4..c375345250d 100644 --- a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe Resolvers::Projects::JiraProjectsResolver do include GraphqlHelpers + specify do + expect(described_class).to have_nullable_graphql_type(Types::Projects::Services::JiraProjectType.connection_type) + end + describe '#resolve' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/graphql/resolvers/projects/services_resolver_spec.rb b/spec/graphql/resolvers/projects/services_resolver_spec.rb index 8b6eff9e8b6..a1b631113b2 100644 --- a/spec/graphql/resolvers/projects/services_resolver_spec.rb +++ b/spec/graphql/resolvers/projects/services_resolver_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe Resolvers::Projects::ServicesResolver do include GraphqlHelpers + specify do + expect(described_class).to have_nullable_graphql_type(Types::Projects::ServiceType.connection_type) + end + describe '#resolve' do let_it_be(:user) { create(:user) } diff --git a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb index b4a5eb8ddb0..6f7feff8fe5 100644 --- a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb +++ b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb @@ -56,12 +56,6 @@ RSpec.describe Resolvers::Projects::SnippetsResolver do expect(snippets).to contain_exactly(project_snippet, other_project_snippet) end - - it 'returns an error if the gid is invalid' do - expect do - resolve_snippets(args: { ids: 'foo' }) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) - end end context 'when no project is provided' do diff --git a/spec/graphql/resolvers/projects_resolver_spec.rb b/spec/graphql/resolvers/projects_resolver_spec.rb index 83a26062957..3de54c7e410 100644 --- a/spec/graphql/resolvers/projects_resolver_spec.rb +++ b/spec/graphql/resolvers/projects_resolver_spec.rb @@ -134,8 +134,8 @@ RSpec.describe Resolvers::ProjectsResolver do is_expected.to eq([named_project3, named_project1, named_project2]) end - it 'returns projects not in order of similarity to search if flag is off' do - is_expected.not_to eq([named_project3, named_project1, named_project2]) + it 'returns projects in any order if flag is off' do + is_expected.to match_array([named_project3, named_project1, named_project2]) end end end diff --git a/spec/graphql/resolvers/release_resolver_spec.rb b/spec/graphql/resolvers/release_resolver_spec.rb index 666d54fbc3c..04765fc68e9 100644 --- a/spec/graphql/resolvers/release_resolver_spec.rb +++ b/spec/graphql/resolvers/release_resolver_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Resolvers::ReleaseResolver do let(:args) { {} } it 'raises an error' do - expect { resolve_release }.to raise_error(ArgumentError, "missing keyword: tag_name") + expect { resolve_release }.to raise_error(ArgumentError, "missing keyword: :tag_name") end end end diff --git a/spec/graphql/resolvers/releases_resolver_spec.rb b/spec/graphql/resolvers/releases_resolver_spec.rb index ee8b33fc748..b9b90686aa7 100644 --- a/spec/graphql/resolvers/releases_resolver_spec.rb +++ b/spec/graphql/resolvers/releases_resolver_spec.rb @@ -5,12 +5,19 @@ require 'spec_helper' RSpec.describe Resolvers::ReleasesResolver do include GraphqlHelpers + let_it_be(:today) { Time.now } + let_it_be(:yesterday) { today - 1.day } + let_it_be(:tomorrow) { today + 1.day } + let_it_be(:project) { create(:project, :private) } - let_it_be(:release_v1) { create(:release, project: project, tag: 'v1.0.0') } - let_it_be(:release_v2) { create(:release, project: project, tag: 'v2.0.0') } + let_it_be(:release_v1) { create(:release, project: project, tag: 'v1.0.0', released_at: yesterday, created_at: tomorrow) } + let_it_be(:release_v2) { create(:release, project: project, tag: 'v2.0.0', released_at: today, created_at: yesterday) } + let_it_be(:release_v3) { create(:release, project: project, tag: 'v3.0.0', released_at: tomorrow, created_at: today) } let_it_be(:developer) { create(:user) } let_it_be(:public_user) { create(:user) } + let(:args) { { sort: :released_at_desc } } + before do project.add_developer(developer) end @@ -28,7 +35,41 @@ RSpec.describe Resolvers::ReleasesResolver do let(:current_user) { developer } it 'returns all releases associated to the project' do - expect(resolve_releases).to eq([release_v1, release_v2]) + expect(resolve_releases).to eq([release_v3, release_v2, release_v1]) + end + + describe 'sorting behavior' do + context 'with sort: :released_at_desc' do + let(:args) { { sort: :released_at_desc } } + + it 'returns the releases ordered by released_at in descending order' do + expect(resolve_releases).to eq([release_v3, release_v2, release_v1]) + end + end + + context 'with sort: :released_at_asc' do + let(:args) { { sort: :released_at_asc } } + + it 'returns the releases ordered by released_at in ascending order' do + expect(resolve_releases).to eq([release_v1, release_v2, release_v3]) + end + end + + context 'with sort: :created_desc' do + let(:args) { { sort: :created_desc } } + + it 'returns the releases ordered by created_at in descending order' do + expect(resolve_releases).to eq([release_v1, release_v3, release_v2]) + end + end + + context 'with sort: :created_asc' do + let(:args) { { sort: :created_asc } } + + it 'returns the releases ordered by created_at in ascending order' do + expect(resolve_releases).to eq([release_v2, release_v3, release_v1]) + end + end end end end @@ -37,6 +78,6 @@ RSpec.describe Resolvers::ReleasesResolver do def resolve_releases context = { current_user: current_user } - resolve(described_class, obj: project, args: {}, ctx: context) + resolve(described_class, obj: project, args: args, ctx: context) end end diff --git a/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb b/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb index fdbd87c32be..16e69f662c0 100644 --- a/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb +++ b/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe Resolvers::Snippets::BlobsResolver do include GraphqlHelpers + specify do + expect(described_class).to have_nullable_graphql_type(Types::Snippets::BlobType.connection_type) + end + describe '#resolve' do let_it_be(:current_user) { create(:user) } let_it_be(:snippet) { create(:personal_snippet, :private, :repository, author: current_user) } diff --git a/spec/graphql/resolvers/snippets_resolver_spec.rb b/spec/graphql/resolvers/snippets_resolver_spec.rb index 180be8e8624..a58d9c5ac3a 100644 --- a/spec/graphql/resolvers/snippets_resolver_spec.rb +++ b/spec/graphql/resolvers/snippets_resolver_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Resolvers::SnippetsResolver do context 'when using filters' do context 'by author id' do it 'returns the snippets' do - snippets = resolve_snippets(args: { author_id: current_user.to_global_id }) + snippets = resolve_snippets(args: { author_id: global_id_of(current_user) }) expect(snippets).to contain_exactly(personal_snippet, project_snippet) end @@ -44,7 +44,7 @@ RSpec.describe Resolvers::SnippetsResolver do it 'returns an error if the param id is invalid' do expect do resolve_snippets(args: { author_id: 'foo' }) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end.to raise_error(GraphQL::CoercionError) end end @@ -65,7 +65,7 @@ RSpec.describe Resolvers::SnippetsResolver do it 'returns an error if the param id is invalid' do expect do resolve_snippets(args: { project_id: 'foo' }) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end.to raise_error(GraphQL::CoercionError) end end @@ -99,14 +99,14 @@ RSpec.describe Resolvers::SnippetsResolver do expect(snippets).to contain_exactly(personal_snippet, project_snippet) end - it 'returns an error if the gid is invalid' do + it 'returns an error if the id cannot be coerced' do args = { ids: [personal_snippet.to_global_id, 'foo'] } expect do resolve_snippets(args: args) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end.to raise_error(GraphQL::CoercionError, '"foo" is not a valid Global ID') end it 'returns an error if both project and author are provided' do diff --git a/spec/graphql/resolvers/todo_resolver_spec.rb b/spec/graphql/resolvers/todo_resolver_spec.rb index 83e3140b676..c764f389c16 100644 --- a/spec/graphql/resolvers/todo_resolver_spec.rb +++ b/spec/graphql/resolvers/todo_resolver_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe Resolvers::TodoResolver do include GraphqlHelpers + specify do + expect(described_class).to have_nullable_graphql_type(Types::TodoType.connection_type) + end + describe '#resolve' do let_it_be(:current_user) { create(:user) } let_it_be(:author1) { create(:user) } diff --git a/spec/graphql/resolvers/tree_resolver_spec.rb b/spec/graphql/resolvers/tree_resolver_spec.rb index 7818c25fe47..9eafd272771 100644 --- a/spec/graphql/resolvers/tree_resolver_spec.rb +++ b/spec/graphql/resolvers/tree_resolver_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Resolvers::TreeResolver do let(:repository) { create(:project, :repository).repository } + specify do + expect(described_class).to have_nullable_graphql_type(Types::Tree::TreeType) + end + describe '#resolve' do it 'resolves to a tree' do result = resolve_repository({ ref: "master" }) diff --git a/spec/graphql/resolvers/users/group_count_resolver_spec.rb b/spec/graphql/resolvers/users/group_count_resolver_spec.rb new file mode 100644 index 00000000000..47160a33646 --- /dev/null +++ b/spec/graphql/resolvers/users/group_count_resolver_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Users::GroupCountResolver do + include GraphqlHelpers + + describe '#resolve' do + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + let_it_be(:group1) { create(:group) } + let_it_be(:group2) { create(:group) } + let_it_be(:project) { create(:project, group: create(:group)) } + let_it_be(:group_member1) { create(:group_member, source: group1, user_id: user1.id, access_level: Gitlab::Access::OWNER) } + let_it_be(:project_member1) { create(:project_member, source: project, user_id: user1.id, access_level: Gitlab::Access::DEVELOPER) } + let_it_be(:group_member2) { create(:group_member, source: group2, user_id: user2.id, access_level: Gitlab::Access::DEVELOPER) } + + it 'resolves group count for users' do + current_user = user1 + + result = batch_sync do + [user1, user2].map { |user| resolve_group_count(user, current_user) } + end + + expect(result).to eq([2, nil]) + end + + context 'permissions' do + context 'when current_user is an admin', :enable_admin_mode do + let_it_be(:admin) { create(:admin) } + + it do + result = batch_sync do + [user1, user2].map { |user| resolve_group_count(user, admin) } + end + + expect(result).to eq([2, 1]) + end + end + + context 'when current_user does not have access to the requested resource' do + it do + result = batch_sync { resolve_group_count(user1, user2) } + + expect(result).to be nil + end + end + + context 'when current_user does not exist' do + it do + result = batch_sync { resolve_group_count(user1, nil) } + + expect(result).to be nil + end + end + end + end + + def resolve_group_count(user, current_user) + resolve(described_class, obj: user, ctx: { current_user: current_user }) + end +end diff --git a/spec/graphql/resolvers/users/snippets_resolver_spec.rb b/spec/graphql/resolvers/users/snippets_resolver_spec.rb index 497b6b11b46..9ccbebc59e6 100644 --- a/spec/graphql/resolvers/users/snippets_resolver_spec.rb +++ b/spec/graphql/resolvers/users/snippets_resolver_spec.rb @@ -73,7 +73,7 @@ RSpec.describe Resolvers::Users::SnippetsResolver do expect do resolve_snippets(args: args) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end.to raise_error(GraphQL::CoercionError) end end end diff --git a/spec/graphql/resolvers/users_resolver_spec.rb b/spec/graphql/resolvers/users_resolver_spec.rb index e3d595e0790..1aa24055a89 100644 --- a/spec/graphql/resolvers/users_resolver_spec.rb +++ b/spec/graphql/resolvers/users_resolver_spec.rb @@ -5,8 +5,12 @@ require 'spec_helper' RSpec.describe Resolvers::UsersResolver do include GraphqlHelpers - let_it_be(:user1) { create(:user) } - let_it_be(:user2) { create(:user) } + let_it_be(:user1) { create(:user, name: "SomePerson") } + let_it_be(:user2) { create(:user, username: "someone123784") } + + specify do + expect(described_class).to have_nullable_graphql_type(Types::UserType.connection_type) + end describe '#resolve' do it 'raises an error when read_users_list is not authorized' do @@ -43,6 +47,14 @@ RSpec.describe Resolvers::UsersResolver do ).to contain_exactly(user1, user2) end end + + context 'when a search term is passed' do + it 'returns all users who match', :aggregate_failures do + expect(resolve_users(search: "some")).to contain_exactly(user1, user2) + expect(resolve_users(search: "123784")).to contain_exactly(user2) + expect(resolve_users(search: "someperson")).to contain_exactly(user1) + end + end end def resolve_users(args = {}) diff --git a/spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb b/spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb index 625fb17bbf8..8a7408224a2 100644 --- a/spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb +++ b/spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb @@ -6,7 +6,10 @@ RSpec.describe GitlabSchema.types['MeasurementIdentifier'] do specify { expect(described_class.graphql_name).to eq('MeasurementIdentifier') } it 'exposes all the existing identifier values' do - identifiers = Analytics::InstanceStatistics::Measurement.identifiers.keys.map(&:upcase) + ee_only_identifiers = %w[billable_users] + identifiers = Analytics::InstanceStatistics::Measurement.identifiers.keys.reject do |x| + ee_only_identifiers.include?(x) + end.map(&:upcase) expect(described_class.values.keys).to match_array(identifiers) end diff --git a/spec/graphql/types/alert_management/http_integration_type_spec.rb b/spec/graphql/types/alert_management/http_integration_type_spec.rb new file mode 100644 index 00000000000..a4b64e2e37f --- /dev/null +++ b/spec/graphql/types/alert_management/http_integration_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['AlertManagementHttpIntegration'] do + specify { expect(described_class.graphql_name).to eq('AlertManagementHttpIntegration') } + + specify { expect(described_class).to require_graphql_authorizations(:admin_operations) } +end diff --git a/spec/graphql/types/alert_management/integration_type_enum_spec.rb b/spec/graphql/types/alert_management/integration_type_enum_spec.rb new file mode 100644 index 00000000000..0cdd67cb140 --- /dev/null +++ b/spec/graphql/types/alert_management/integration_type_enum_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['AlertManagementIntegrationType'] do + specify { expect(described_class.graphql_name).to eq('AlertManagementIntegrationType') } + + describe 'statuses' do + using RSpec::Parameterized::TableSyntax + + where(:name, :value) do + 'PROMETHEUS' | :prometheus + 'HTTP' | :http + end + + with_them do + it 'exposes a type with the correct value' do + expect(described_class.values[name].value).to eq(value) + end + end + end +end diff --git a/spec/graphql/types/alert_management/integration_type_spec.rb b/spec/graphql/types/alert_management/integration_type_spec.rb new file mode 100644 index 00000000000..5d149e6da6e --- /dev/null +++ b/spec/graphql/types/alert_management/integration_type_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['AlertManagementIntegration'] do + specify { expect(described_class.graphql_name).to eq('AlertManagementIntegration') } + + it 'exposes the expected fields' do + expected_fields = %i[ + id + type + name + active + token + url + api_url + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb b/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb new file mode 100644 index 00000000000..0e9994035d8 --- /dev/null +++ b/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['AlertManagementPrometheusIntegration'] do + include GraphqlHelpers + + specify { expect(described_class.graphql_name).to eq('AlertManagementPrometheusIntegration') } + specify { expect(described_class).to require_graphql_authorizations(:admin_project) } + + describe 'resolvers' do + shared_examples_for 'has field with value' do |field_name| + it 'correctly renders the field' do + expect(resolve_field(field_name, integration)).to eq(value) + end + end + + let_it_be_with_reload(:integration) { create(:prometheus_service) } + + it_behaves_like 'has field with value', 'name' do + let(:value) { integration.title } + end + + it_behaves_like 'has field with value', 'type' do + let(:value) { :prometheus } + end + + it_behaves_like 'has field with value', 'token' do + let(:value) { nil } + end + + it_behaves_like 'has field with value', 'url' do + let(:value) { "http://localhost/#{integration.project.full_path}/prometheus/alerts/notify.json" } + end + + it_behaves_like 'has field with value', 'active' do + let(:value) { integration.manual_configuration? } + end + + context 'with alerting setting' do + let_it_be(:alerting_setting) { create(:project_alerting_setting, project: integration.project) } + + it_behaves_like 'has field with value', 'token' do + let(:value) { alerting_setting.token } + end + end + + context 'without project' do + let_it_be(:integration) { create(:prometheus_service, project: nil, group: create(:group)) } + + it_behaves_like 'has field with value', 'token' do + let(:value) { nil } + end + + it_behaves_like 'has field with value', 'url' do + let(:value) { nil } + end + end + end +end diff --git a/spec/graphql/types/availability_enum_spec.rb b/spec/graphql/types/availability_enum_spec.rb new file mode 100644 index 00000000000..a9bdf5e4da6 --- /dev/null +++ b/spec/graphql/types/availability_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['AvailabilityEnum'] do + specify { expect(described_class.graphql_name).to eq('AvailabilityEnum') } + + it 'exposes all the existing access levels' do + expect(described_class.values.keys).to match_array(%w[NOT_SET BUSY]) + end +end diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb index ddb3a1450df..9fa3280657a 100644 --- a/spec/graphql/types/ci/detailed_status_type_spec.rb +++ b/spec/graphql/types/ci/detailed_status_type_spec.rb @@ -3,11 +3,30 @@ require 'spec_helper' RSpec.describe Types::Ci::DetailedStatusType do + include GraphqlHelpers + specify { expect(described_class.graphql_name).to eq('DetailedStatus') } - it "has all fields" do + it 'has all fields' do expect(described_class).to have_graphql_fields(:group, :icon, :favicon, :details_path, :has_details, :label, :text, :tooltip, :action) end + + describe 'action field' do + it 'correctly renders the field' do + stage = create(:ci_stage_entity, status: :skipped) + status = stage.detailed_status(stage.pipeline.user) + + expected_status = { + button_title: status.action_button_title, + icon: status.action_icon, + method: status.action_method, + path: status.action_path, + title: status.action_title + } + + expect(resolve_field('action', status)).to eq(expected_status) + end + end end diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb index 3a54ed2efed..3dcb81eefbf 100644 --- a/spec/graphql/types/ci/job_type_spec.rb +++ b/spec/graphql/types/ci/job_type_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Types::Ci::JobType do it 'exposes the expected fields' do expected_fields = %i[ + pipeline name needs detailedStatus diff --git a/spec/graphql/types/ci/runner_setup_type_spec.rb b/spec/graphql/types/ci/runner_setup_type_spec.rb new file mode 100644 index 00000000000..197e717e964 --- /dev/null +++ b/spec/graphql/types/ci/runner_setup_type_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::RunnerSetupType do + specify { expect(described_class.graphql_name).to eq('RunnerSetup') } + + it 'exposes the expected fields' do + expected_fields = %i[ + install_instructions + register_instructions + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb index d222287270d..e9bc7f6bb94 100644 --- a/spec/graphql/types/commit_type_spec.rb +++ b/spec/graphql/types/commit_type_spec.rb @@ -10,7 +10,7 @@ RSpec.describe GitlabSchema.types['Commit'] do it 'contains attributes related to commit' do expect(described_class).to have_graphql_fields( :id, :sha, :title, :description, :description_html, :message, :title_html, :authored_date, - :author_name, :author_gravatar, :author, :web_url, :web_path, :latest_pipeline, + :author_name, :author_gravatar, :author, :web_url, :web_path, :pipelines, :signature_html ) end diff --git a/spec/graphql/types/container_repository_cleanup_status_enum_spec.rb b/spec/graphql/types/container_repository_cleanup_status_enum_spec.rb new file mode 100644 index 00000000000..36cfc789ee9 --- /dev/null +++ b/spec/graphql/types/container_repository_cleanup_status_enum_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ContainerRepositoryCleanupStatus'] do + it 'exposes all statuses' do + expected_keys = ContainerRepository.expiration_policy_cleanup_statuses + .keys + .map { |k| k.gsub('cleanup_', '') } + .map(&:upcase) + expect(described_class.values.keys).to contain_exactly(*expected_keys) + end +end diff --git a/spec/graphql/types/container_repository_details_type_spec.rb b/spec/graphql/types/container_repository_details_type_spec.rb new file mode 100644 index 00000000000..b5ff460fcf7 --- /dev/null +++ b/spec/graphql/types/container_repository_details_type_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do + fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status tags] + + it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') } + + it { expect(described_class.description).to eq('Details of a container repository') } + + it { expect(described_class).to require_graphql_authorizations(:read_container_image) } + + it { expect(described_class).to have_graphql_fields(fields) } + + describe 'tags field' do + subject { described_class.fields['tags'] } + + it 'returns tags connection type' do + is_expected.to have_graphql_type(Types::ContainerRepositoryTagType.connection_type) + end + end +end diff --git a/spec/graphql/types/container_repository_status_enum_spec.rb b/spec/graphql/types/container_repository_status_enum_spec.rb new file mode 100644 index 00000000000..9598879779a --- /dev/null +++ b/spec/graphql/types/container_repository_status_enum_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ContainerRepositoryStatus'] do + it 'exposes all statuses' do + expect(described_class.values.keys).to contain_exactly(*ContainerRepository.statuses.keys.map(&:upcase)) + end +end diff --git a/spec/graphql/types/container_repository_tag_type_spec.rb b/spec/graphql/types/container_repository_tag_type_spec.rb new file mode 100644 index 00000000000..1d1a76d6916 --- /dev/null +++ b/spec/graphql/types/container_repository_tag_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ContainerRepositoryTag'] do + fields = %i[name path location digest revision short_revision total_size created_at can_delete] + + it { expect(described_class.graphql_name).to eq('ContainerRepositoryTag') } + + it { expect(described_class.description).to eq('A tag from a container repository') } + + it { expect(described_class).to require_graphql_authorizations(:read_container_image) } + + it { expect(described_class).to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/container_repository_type_spec.rb b/spec/graphql/types/container_repository_type_spec.rb new file mode 100644 index 00000000000..3d3445ba5c3 --- /dev/null +++ b/spec/graphql/types/container_repository_type_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ContainerRepository'] do + fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status] + + it { expect(described_class.graphql_name).to eq('ContainerRepository') } + + it { expect(described_class.description).to eq('A container repository') } + + it { expect(described_class).to require_graphql_authorizations(:read_container_image) } + + it { expect(described_class).to have_graphql_fields(fields) } + + describe 'status field' do + subject { described_class.fields['status'] } + + it 'returns status enum' do + is_expected.to have_graphql_type(Types::ContainerRepositoryStatusEnum) + end + end + + describe 'expiration_policy_cleanup_status field' do + subject { described_class.fields['expirationPolicyCleanupStatus'] } + + it 'returns cleanup status enum' do + is_expected.to have_graphql_type(Types::ContainerRepositoryCleanupStatusEnum) + end + end +end diff --git a/spec/graphql/types/countable_connection_type_spec.rb b/spec/graphql/types/countable_connection_type_spec.rb index af34611ecfe..3b3c02baa5d 100644 --- a/spec/graphql/types/countable_connection_type_spec.rb +++ b/spec/graphql/types/countable_connection_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['IssueConnection'] do +RSpec.describe GitlabSchema.types['MergeRequestConnection'] do it 'has the expected fields' do expected_fields = %i[count page_info edges nodes] diff --git a/spec/graphql/types/custom_emoji_type_spec.rb b/spec/graphql/types/custom_emoji_type_spec.rb new file mode 100644 index 00000000000..7f3c99e4b63 --- /dev/null +++ b/spec/graphql/types/custom_emoji_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CustomEmoji'] do + specify { expect(described_class.graphql_name).to eq('CustomEmoji') } + + specify { expect(described_class).to require_graphql_authorizations(:read_custom_emoji) } + + specify { expect(described_class).to have_graphql_fields(:id, :name, :url, :external) } +end diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb index 2220f847e4e..3671d35e8a5 100644 --- a/spec/graphql/types/environment_type_spec.rb +++ b/spec/graphql/types/environment_type_spec.rb @@ -44,18 +44,12 @@ RSpec.describe GitlabSchema.types['Environment'] do expect(subject['data']['project']['environment']['name']).to eq(environment.name) end - it 'returns the path when the feature is enabled' do + it 'returns the path to the environment' do expect(subject['data']['project']['environment']['path']).to eq( Gitlab::Routing.url_helpers.project_environment_path(project, environment) ) end - it 'does not return the path when the feature is disabled' do - stub_feature_flags(expose_environment_path_in_alert_details: false) - - expect(subject['data']['project']['environment']['path']).to be_nil - end - context 'when query alert data for the environment' do let_it_be(:query) do %( diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb index 7589b0e285e..cb129868f7e 100644 --- a/spec/graphql/types/global_id_type_spec.rb +++ b/spec/graphql/types/global_id_type_spec.rb @@ -45,8 +45,7 @@ RSpec.describe Types::GlobalIDType do end it 'rejects nil' do - expect { described_class.coerce_isolated_input(nil) } - .to raise_error(GraphQL::CoercionError) + expect(described_class.coerce_isolated_input(nil)).to be_nil end it 'rejects gids from different apps' do diff --git a/spec/graphql/types/grafana_integration_type_spec.rb b/spec/graphql/types/grafana_integration_type_spec.rb index b4658db08d7..816264c36c8 100644 --- a/spec/graphql/types/grafana_integration_type_spec.rb +++ b/spec/graphql/types/grafana_integration_type_spec.rb @@ -7,7 +7,6 @@ RSpec.describe GitlabSchema.types['GrafanaIntegration'] do %i[ id grafana_url - token enabled created_at updated_at diff --git a/spec/graphql/types/group_invitation_type_spec.rb b/spec/graphql/types/group_invitation_type_spec.rb new file mode 100644 index 00000000000..dab2d43fc90 --- /dev/null +++ b/spec/graphql/types/group_invitation_type_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::GroupInvitationType do + specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) } + + specify { expect(described_class.graphql_name).to eq('GroupInvitation') } + + specify { expect(described_class).to require_graphql_authorizations(:read_group) } + + it 'has the expected fields' do + expected_fields = %w[ + email access_level created_by created_at updated_at expires_at group + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/invitation_interface_spec.rb b/spec/graphql/types/invitation_interface_spec.rb new file mode 100644 index 00000000000..8f345c58ca3 --- /dev/null +++ b/spec/graphql/types/invitation_interface_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::InvitationInterface do + it 'exposes the expected fields' do + expected_fields = %i[ + email + access_level + created_by + created_at + updated_at + expires_at + user + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end + + describe '.resolve_type' do + subject { described_class.resolve_type(object, {}) } + + context 'for project member' do + let(:object) { build(:project_member) } + + it { is_expected.to be Types::ProjectInvitationType } + end + + context 'for group member' do + let(:object) { build(:group_member) } + + it { is_expected.to be Types::GroupInvitationType } + end + + context 'for an unknown type' do + let(:object) { build(:user) } + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::BaseError) + end + end + end +end diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb index c55e624dd11..558fc479af1 100644 --- a/spec/graphql/types/issue_type_spec.rb +++ b/spec/graphql/types/issue_type_spec.rb @@ -14,10 +14,10 @@ RSpec.describe GitlabSchema.types['Issue'] do specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) } it 'has specific fields' do - fields = %i[id iid title description state reference author assignees participants labels milestone due_date - confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position - subscribed time_estimate total_time_spent closed_at created_at updated_at task_completion_status - designs design_collection alert_management_alert severity current_user_todos] + fields = %i[id iid title description state reference author assignees updated_by participants labels milestone due_date + confidential discussion_locked upvotes downvotes user_notes_count user_discussions_count web_path web_url relative_position + emails_disabled subscribed time_estimate total_time_spent human_time_estimate human_total_time_spent closed_at created_at updated_at task_completion_status + design_collection alert_management_alert severity current_user_todos moved moved_to] fields.each do |field_name| expect(described_class).to have_graphql_field(field_name) diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb index 9d901655b7b..8800250b103 100644 --- a/spec/graphql/types/merge_request_type_spec.rb +++ b/spec/graphql/types/merge_request_type_spec.rb @@ -17,11 +17,11 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do 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 - merge_commit_sha user_notes_count should_remove_source_branch + 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 merge_error allow_collaboration should_be_rebased rebase_commit_sha - rebase_in_progress merge_commit_message default_merge_commit_message + rebase_in_progress default_merge_commit_message merge_ongoing mergeable_discussions_state web_url source_branch_exists target_branch_exists upvotes downvotes head_pipeline pipelines task_completion_status diff --git a/spec/graphql/types/permission_types/note_spec.rb b/spec/graphql/types/permission_types/note_spec.rb index 9769c7b3aa3..d75284951c7 100644 --- a/spec/graphql/types/permission_types/note_spec.rb +++ b/spec/graphql/types/permission_types/note_spec.rb @@ -5,9 +5,9 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['NotePermissions'] do it 'has the expected fields' do expected_permissions = [ - :read_note, :create_note, :admin_note, :resolve_note, :award_emoji + :read_note, :create_note, :admin_note, :resolve_note, :reposition_note, :award_emoji ] - expect(described_class).to have_graphql_fields(expected_permissions) + expect(described_class).to have_graphql_fields(expected_permissions).only end end diff --git a/spec/graphql/types/project_invitation_type_spec.rb b/spec/graphql/types/project_invitation_type_spec.rb new file mode 100644 index 00000000000..148a763a5fa --- /dev/null +++ b/spec/graphql/types/project_invitation_type_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::ProjectInvitationType do + specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) } + + specify { expect(described_class.graphql_name).to eq('ProjectInvitation') } + + specify { expect(described_class).to require_graphql_authorizations(:read_project) } + + it 'has the expected fields' do + expected_fields = %w[ + access_level created_by created_at updated_at expires_at project user + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/project_statistics_type_spec.rb b/spec/graphql/types/project_statistics_type_spec.rb index e6cffd407de..407ce82e73a 100644 --- a/spec/graphql/types/project_statistics_type_spec.rb +++ b/spec/graphql/types/project_statistics_type_spec.rb @@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['ProjectStatistics'] do it 'has all the required fields' do expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size, :build_artifacts_size, :packages_size, :commit_count, - :wiki_size, :snippets_size) + :wiki_size, :snippets_size, :uploads_size) end end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 8aa9e1138cc..be579e92fb3 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -27,7 +27,8 @@ RSpec.describe GitlabSchema.types['Project'] do environment boards jira_import_status jira_imports services releases release alert_management_alerts alert_management_alert alert_management_alert_status_counts container_expiration_policy service_desk_enabled service_desk_address - issue_status_counts terraform_states + issue_status_counts terraform_states alert_management_integrations + ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/projects/services_enum_spec.rb b/spec/graphql/types/projects/services_enum_spec.rb index dac1213daf3..b8da9305de4 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.services_types.map(&:underscore).map(&:upcase) + ::Service.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 1d9ca8323f8..7a0b3035607 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -80,4 +80,18 @@ RSpec.describe GitlabSchema.types['Query'] do is_expected.to have_graphql_type(Types::Ci::RunnerPlatformType.connection_type) end end + + describe 'runner_setup field' do + subject { described_class.fields['runnerSetup'] } + + it 'returns runner setup instructions' do + is_expected.to have_graphql_type(Types::Ci::RunnerSetupType) + end + end + + describe 'container_repository field' do + subject { described_class.fields['containerRepository'] } + + it { is_expected.to have_graphql_type(Types::ContainerRepositoryDetailsType) } + end end diff --git a/spec/graphql/types/release_asset_link_input_type_spec.rb b/spec/graphql/types/release_asset_link_input_type_spec.rb new file mode 100644 index 00000000000..d97a91b609a --- /dev/null +++ b/spec/graphql/types/release_asset_link_input_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::ReleaseAssetLinkInputType do + specify { expect(described_class.graphql_name).to eq('ReleaseAssetLinkInput') } + + it 'has the correct arguments' do + expect(described_class.arguments.keys).to match_array(%w[name url directAssetPath linkType]) + end + + it 'sets the type of link_type argument to ReleaseAssetLinkTypeEnum' do + expect(described_class.arguments['linkType'].type).to eq(Types::ReleaseAssetLinkTypeEnum) + end +end diff --git a/spec/graphql/types/release_assets_input_type_spec.rb b/spec/graphql/types/release_assets_input_type_spec.rb new file mode 100644 index 00000000000..c44abe1e171 --- /dev/null +++ b/spec/graphql/types/release_assets_input_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::ReleaseAssetsInputType do + specify { expect(described_class.graphql_name).to eq('ReleaseAssetsInput') } + + it 'has the correct arguments' do + expect(described_class.arguments.keys).to match_array(%w[links]) + end + + it 'sets the type of links argument to ReleaseAssetLinkInputType' do + expect(described_class.arguments['links'].type.of_type.of_type).to eq(Types::ReleaseAssetLinkInputType) + end +end diff --git a/spec/graphql/types/release_links_type_spec.rb b/spec/graphql/types/release_links_type_spec.rb index d505f0a4b5c..38c38d58baa 100644 --- a/spec/graphql/types/release_links_type_spec.rb +++ b/spec/graphql/types/release_links_type_spec.rb @@ -8,8 +8,11 @@ RSpec.describe GitlabSchema.types['ReleaseLinks'] do it 'has the expected fields' do expected_fields = %w[ selfUrl - mergeRequestsUrl - issuesUrl + openedMergeRequestsUrl + mergedMergeRequestsUrl + closedMergeRequestsUrl + openedIssuesUrl + closedIssuesUrl editUrl ] diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb index 79d474f13ad..4fef8f6eafd 100644 --- a/spec/graphql/types/root_storage_statistics_type_spec.rb +++ b/spec/graphql/types/root_storage_statistics_type_spec.rb @@ -8,7 +8,7 @@ RSpec.describe GitlabSchema.types['RootStorageStatistics'] do it 'has all the required fields' do expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size, :build_artifacts_size, :packages_size, :wiki_size, :snippets_size, - :pipeline_artifacts_size) + :pipeline_artifacts_size, :uploads_size) end specify { expect(described_class).to require_graphql_authorizations(:read_statistics) } diff --git a/spec/graphql/types/security/report_types_enum_spec.rb b/spec/graphql/types/security/report_types_enum_spec.rb new file mode 100644 index 00000000000..2da852606c7 --- /dev/null +++ b/spec/graphql/types/security/report_types_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SecurityReportTypeEnum'] do + it 'exposes all security report types' do + expect(described_class.values.keys).to contain_exactly( + *::Security::SecurityJobsFinder.allowed_job_types.map(&:to_s).map(&:upcase) + ) + end +end diff --git a/spec/graphql/types/terraform/state_type_spec.rb b/spec/graphql/types/terraform/state_type_spec.rb index 51508208046..9f65bb926d7 100644 --- a/spec/graphql/types/terraform/state_type_spec.rb +++ b/spec/graphql/types/terraform/state_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['TerraformState'] do it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) } describe 'fields' do - let(:fields) { %i[id name locked_by_user locked_at created_at updated_at] } + let(:fields) { %i[id name locked_by_user locked_at latest_version created_at updated_at] } it { expect(described_class).to have_graphql_fields(fields) } @@ -17,5 +17,8 @@ RSpec.describe GitlabSchema.types['TerraformState'] do it { expect(described_class.fields['lockedAt'].type).not_to be_non_null } it { expect(described_class.fields['createdAt'].type).to be_non_null } it { expect(described_class.fields['updatedAt'].type).to be_non_null } + + it { expect(described_class.fields['latestVersion'].type).not_to be_non_null } + it { expect(described_class.fields['latestVersion'].complexity).to eq(3) } end end diff --git a/spec/graphql/types/terraform/state_version_type_spec.rb b/spec/graphql/types/terraform/state_version_type_spec.rb new file mode 100644 index 00000000000..1c1e95039dc --- /dev/null +++ b/spec/graphql/types/terraform/state_version_type_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['TerraformStateVersion'] do + it { expect(described_class.graphql_name).to eq('TerraformStateVersion') } + it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) } + + describe 'fields' do + let(:fields) { %i[id created_by_user job created_at updated_at] } + + it { expect(described_class).to have_graphql_fields(fields) } + + it { expect(described_class.fields['id'].type).to be_non_null } + it { expect(described_class.fields['createdByUser'].type).not_to be_non_null } + it { expect(described_class.fields['job'].type).not_to be_non_null } + it { expect(described_class.fields['createdAt'].type).to be_non_null } + it { expect(described_class.fields['updatedAt'].type).to be_non_null } + end +end diff --git a/spec/graphql/types/user_status_type_spec.rb b/spec/graphql/types/user_status_type_spec.rb index c4421a9cc10..ced9c40d552 100644 --- a/spec/graphql/types/user_status_type_spec.rb +++ b/spec/graphql/types/user_status_type_spec.rb @@ -10,6 +10,7 @@ RSpec.describe Types::UserStatusType do emoji message message_html + availability ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb index 1d5af24b3d9..c8953d9ccb7 100644 --- a/spec/graphql/types/user_type_spec.rb +++ b/spec/graphql/types/user_type_spec.rb @@ -24,6 +24,7 @@ RSpec.describe GitlabSchema.types['User'] do authoredMergeRequests assignedMergeRequests groupMemberships + groupCount projectMemberships starredProjects ] |