diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 11:27:35 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 11:27:35 +0300 |
commit | 7e9c479f7de77702622631cff2628a9c8dcbc627 (patch) | |
tree | c8f718a08e110ad7e1894510980d2155a6549197 /spec/graphql/mutations | |
parent | e852b0ae16db4052c1c567d9efa4facc81146e88 (diff) |
Add latest changes from gitlab-org/gitlab@13-6-stable-eev13.6.0-rc42
Diffstat (limited to 'spec/graphql/mutations')
32 files changed, 1086 insertions, 45 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') |