diff options
Diffstat (limited to 'spec/graphql/mutations')
16 files changed, 491 insertions, 0 deletions
diff --git a/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb b/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb new file mode 100644 index 00000000000..1e51767cf0e --- /dev/null +++ b/spec/graphql/mutations/alert_management/create_alert_issue_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mutations::AlertManagement::CreateAlertIssue do + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered') } + let(:args) { { project_path: project.full_path, iid: alert.iid } } + + specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(args) } + + context 'user has access to project' do + before do + project.add_developer(current_user) + end + + context 'when CreateAlertIssueService responds with success' do + it 'returns the issue with no errors' do + expect(resolve).to eq( + alert: alert.reload, + issue: Issue.last!, + errors: [] + ) + end + end + + context 'when CreateAlertIssue responds with an error' do + before do + allow_any_instance_of(::AlertManagement::CreateAlertIssueService) + .to receive(:execute) + .and_return(ServiceResponse.error(payload: { issue: nil }, message: 'An issue already exists')) + end + + it 'returns errors' do + expect(resolve).to eq( + alert: alert, + issue: nil, + errors: ['An issue 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/update_alert_status_spec.rb b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb new file mode 100644 index 00000000000..8b9abd9497d --- /dev/null +++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mutations::AlertManagement::UpdateAlertStatus do + let_it_be(:current_user) { create(:user) } + let_it_be(:alert) { create(:alert_management_alert, :triggered) } + let_it_be(:project) { alert.project } + let(:new_status) { Types::AlertManagement::StatusEnum.values['ACKNOWLEDGED'].value } + let(:args) { { status: new_status, project_path: project.full_path, iid: alert.iid } } + + specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(args) } + + context 'user has access to project' do + before do + project.add_developer(current_user) + end + + it 'changes the status' do + expect { resolve }.to change { alert.reload.acknowledged? }.to(true) + end + + it 'returns the alert with no errors' do + expect(resolve).to eq( + alert: alert, + errors: [] + ) + end + + context 'error occurs when updating' do + it 'returns the alert with errors' do + # Stub an error on the alert + allow_next_instance_of(Resolvers::AlertManagementAlertResolver) do |resolver| + allow(resolver).to receive(:resolve).and_return(alert) + end + + allow(alert).to receive(:save).and_return(false) + allow(alert).to receive(:errors).and_return( + double(full_messages: %w(foo bar)) + ) + expect(resolve).to eq( + alert: alert, + errors: ['foo and bar'] + ) + end + + context 'invalid status given' do + let(:new_status) { 'invalid_status' } + + it 'returns the alert with errors' do + expect(resolve).to eq( + alert: alert, + errors: [_('Invalid status')] + ) + end + end + end + 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 + 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/branches/create_spec.rb b/spec/graphql/mutations/branches/create_spec.rb new file mode 100644 index 00000000000..744f8f1f2bc --- /dev/null +++ b/spec/graphql/mutations/branches/create_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mutations::Branches::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(:context) do + GraphQL::Query::Context.new( + query: OpenStruct.new(schema: nil), + values: { current_user: user }, + object: nil + ) + end + + describe '#resolve' do + subject { mutation.resolve(project_path: project.full_path, name: branch, ref: ref) } + + let(:branch) { 'new_branch' } + let(:ref) { 'master' } + let(:mutated_branch) { subject[:branch] } + + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + + context 'when the user can create a branch' do + before do + project.add_developer(user) + + allow_next_instance_of(::Branches::CreateService, project, user) do |create_service| + allow(create_service).to receive(:execute).with(branch, ref) { service_result } + end + end + + context 'when service successfully creates a new branch' do + let(:service_result) { { status: :success, branch: double(name: branch) } } + + it 'returns a new branch' do + expect(mutated_branch.name).to eq(branch) + expect(subject[:errors]).to be_empty + end + end + + context 'when service fails to create a new branch' do + let(:service_result) { { status: :error, message: 'Branch already exists' } } + + it { expect(mutated_branch).to be_nil } + it { expect(subject[:errors]).to eq(['Branch already exists']) } + end + end + end +end diff --git a/spec/graphql/mutations/design_management/delete_spec.rb b/spec/graphql/mutations/design_management/delete_spec.rb new file mode 100644 index 00000000000..60be6dad62a --- /dev/null +++ b/spec/graphql/mutations/design_management/delete_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mutations::DesignManagement::Delete do + include DesignManagementTestHelpers + + let(:issue) { create(:issue) } + let(:current_designs) { issue.designs.current } + let(:user) { issue.author } + let(:project) { issue.project } + let(:design_a) { create(:design, :with_file, issue: issue) } + let(:design_b) { create(:design, :with_file, issue: issue) } + let(:design_c) { create(:design, :with_file, issue: issue) } + let(:filenames) { [design_a, design_b, design_c].map(&:filename) } + + let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + + before do + stub_const('Errors', Gitlab::Graphql::Errors, transfer_nested_constants: true) + end + + def run_mutation + mutation = described_class.new(object: nil, context: { current_user: user }, field: nil) + mutation.resolve(project_path: project.full_path, iid: issue.iid, filenames: filenames) + end + + describe '#resolve' do + let(:expected_response) do + { errors: [], version: DesignManagement::Version.for_issue(issue).ordered.first } + end + + shared_examples "failures" do |error: Gitlab::Graphql::Errors::ResourceNotAvailable| + it "raises #{error.name}" do + expect { run_mutation }.to raise_error(error) + end + end + + shared_examples "resource not available" do + it_behaves_like "failures" + end + + context "when the feature is not available" do + before do + enable_design_management(false) + end + + it_behaves_like "resource not available" + end + + context "when the feature is available" do + before do + enable_design_management(true) + end + + context "when the user is not allowed to delete designs" do + let(:user) { create(:user) } + + it_behaves_like "resource not available" + end + + context 'deleting an already deleted file' do + before do + run_mutation + end + + it 'fails with an argument error' do + expect { run_mutation }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end + end + + context "when deleting all the designs" do + let(:response) { run_mutation } + + it "returns a new version, and no errors" do + expect(response).to include(expected_response) + end + + describe 'the current designs' do + before do + run_mutation + end + + it 'is empty' do + expect(current_designs).to be_empty + end + end + + it 'runs no more than 28 queries' do + filenames.each(&:present?) # ignore setup + # Queries: as of 2019-08-28 + # ------------- + # 01. routing query + # 02. find project by id + # 03. project.project_features + # 04. find namespace by id and type + # 05,06. project.authorizations for user (same query twice) + # 07. find issue by iid + # 08. find project by id + # 09. find namespace by id + # 10. find group namespace by id + # 11. project.authorizations for user (same query as 5) + # 12. project.project_features (same query as 3) + # 13. project.authorizations for user (same query as 5) + # 14. current designs by filename and issue + # 15, 16 project.authorizations for user (same query as 5) + # 17. find route by id and source_type + # ------------- our queries are below: + # 18. start transaction 1 + # 19. start transaction 2 + # 20. find version by sha and issue + # 21. exists version with sha and issue? + # 22. leave transaction 2 + # 23. create version with sha and issue + # 24. create design-version links + # 25. validate version.actions.present? + # 26. validate version.issue.present? + # 27. validate version.sha is unique + # 28. leave transaction 1 + # + expect { run_mutation }.not_to exceed_query_limit(28) + end + end + + context "when deleting a design" do + let(:filenames) { [design_a.filename] } + let(:response) { run_mutation } + + it "returns the expected response" do + expect(response).to include(expected_response) + end + + describe 'the current designs' do + before do + run_mutation + end + + it 'does contain designs b and c' do + expect(current_designs).to contain_exactly(design_b, design_c) + end + end + end + end + end +end diff --git a/spec/graphql/mutations/design_management/upload_spec.rb b/spec/graphql/mutations/design_management/upload_spec.rb new file mode 100644 index 00000000000..783af70448c --- /dev/null +++ b/spec/graphql/mutations/design_management/upload_spec.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Mutations::DesignManagement::Upload do + include DesignManagementTestHelpers + include ConcurrentHelpers + + let(:issue) { create(:issue) } + let(:user) { issue.author } + let(:project) { issue.project } + + subject(:mutation) do + described_class.new(object: nil, context: { current_user: user }, field: nil) + end + + def run_mutation(files_to_upload = files, project_path = project.full_path, iid = issue.iid) + mutation = described_class.new(object: nil, context: { current_user: user }, field: nil) + mutation.resolve(project_path: project_path, iid: iid, files: files_to_upload) + end + + describe "#resolve" do + let(:files) { [fixture_file_upload('spec/fixtures/dk.png')] } + + subject(:resolve) do + mutation.resolve(project_path: project.full_path, iid: issue.iid, files: files) + end + + shared_examples "resource not available" do + it "raises an error" do + expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context "when the feature is not available" do + it_behaves_like "resource not available" + end + + context "when the feature is available" do + before do + enable_design_management + end + + describe 'contention in the design repo' do + before do + issue.design_collection.repository.create_if_not_exists + end + + let(:files) do + ['dk.png', 'rails_sample.jpg', 'banana_sample.gif'] + .cycle + .take(Concurrent.processor_count * 2) + .map { |f| RenameableUpload.unique_file(f) } + end + + def creates_designs + prior_count = DesignManagement::Design.count + + expect { yield }.not_to raise_error + + expect(DesignManagement::Design.count).to eq(prior_count + files.size) + end + + describe 'running requests in parallel' do + it 'does not cause errors' do + creates_designs do + run_parallel(files.map { |f| -> { run_mutation([f]) } }) + end + end + end + + describe 'running requests in parallel on different issues' do + it 'does not cause errors' do + creates_designs do + issues = create_list(:issue, files.size, author: user) + issues.each { |i| i.project.add_developer(user) } + blocks = files.zip(issues).map do |(f, i)| + -> { run_mutation([f], i.project.full_path, i.iid) } + end + + run_parallel(blocks) + end + end + end + + describe 'running requests in serial' do + it 'does not cause errors' do + creates_designs do + files.each do |f| + run_mutation([f]) + end + end + end + end + end + + context "when the user is not allowed to upload designs" do + let(:user) { create(:user) } + + it_behaves_like "resource not available" + end + + context "a valid design" do + it "returns the updated designs" do + expect(resolve[:errors]).to eq [] + expect(resolve[:designs].map(&:filename)).to contain_exactly("dk.png") + end + end + + context "context when passing an invalid project" do + let(:project) { build(:project) } + + it_behaves_like "resource not available" + end + + context "context when passing an invalid issue" do + let(:issue) { build(:issue) } + + it_behaves_like "resource not available" + end + + context "when creating designs causes errors" do + before do + fake_service = double(::DesignManagement::SaveDesignsService) + + allow(fake_service).to receive(:execute).and_return(status: :error, message: "Something failed") + allow(::DesignManagement::SaveDesignsService).to receive(:new).and_return(fake_service) + end + + it "wraps the errors" do + expect(resolve[:errors]).to eq(["Something failed"]) + expect(resolve[:designs]).to eq([]) + end + end + end + end +end diff --git a/spec/graphql/mutations/issues/set_confidential_spec.rb b/spec/graphql/mutations/issues/set_confidential_spec.rb index 6031953c869..c90ce2658d6 100644 --- a/spec/graphql/mutations/issues/set_confidential_spec.rb +++ b/spec/graphql/mutations/issues/set_confidential_spec.rb @@ -8,6 +8,8 @@ describe Mutations::Issues::SetConfidential do subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + specify { expect(described_class).to require_graphql_authorizations(:update_issue) } + describe '#resolve' do let(:confidential) { true } let(:mutated_issue) { subject[:issue] } diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb index 73ba11fc551..84df6fce7c7 100644 --- a/spec/graphql/mutations/issues/set_due_date_spec.rb +++ b/spec/graphql/mutations/issues/set_due_date_spec.rb @@ -8,6 +8,8 @@ describe Mutations::Issues::SetDueDate do subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + specify { expect(described_class).to require_graphql_authorizations(:update_issue) } + describe '#resolve' do let(:due_date) { 2.days.since } let(:mutated_issue) { subject[:issue] } diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb index da286bb4092..8c3d01918fd 100644 --- a/spec/graphql/mutations/issues/update_spec.rb +++ b/spec/graphql/mutations/issues/update_spec.rb @@ -16,6 +16,8 @@ describe Mutations::Issues::Update do let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } let(:mutated_issue) { subject[:issue] } + specify { expect(described_class).to require_graphql_authorizations(:update_issue) } + describe '#resolve' do let(:mutation_params) do { diff --git a/spec/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/graphql/mutations/merge_requests/set_labels_spec.rb index f58f35eb6f3..0fd2c20a5c8 100644 --- a/spec/graphql/mutations/merge_requests/set_labels_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_labels_spec.rb @@ -8,6 +8,8 @@ describe Mutations::MergeRequests::SetLabels do subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) } + describe '#resolve' do let(:label) { create(:label, project: merge_request.project) } let(:label2) { create(:label, project: merge_request.project) } diff --git a/spec/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/graphql/mutations/merge_requests/set_locked_spec.rb index 12ae1314f22..d5219c781fd 100644 --- a/spec/graphql/mutations/merge_requests/set_locked_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_locked_spec.rb @@ -8,6 +8,8 @@ describe Mutations::MergeRequests::SetLocked do subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) } + describe '#resolve' do let(:locked) { true } let(:mutated_merge_request) { subject[:merge_request] } diff --git a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb index ad7f2df0842..d77ec4de4d0 100644 --- a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb @@ -8,6 +8,8 @@ describe Mutations::MergeRequests::SetMilestone do subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) } + describe '#resolve' do let(:milestone) { create(:milestone, project: merge_request.project) } let(:mutated_merge_request) { subject[:merge_request] } diff --git a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb index a28bab363f3..cf569a74aa9 100644 --- a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb @@ -9,6 +9,8 @@ describe Mutations::MergeRequests::SetSubscription do subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) } + describe '#resolve' do let(:subscribe) { true } let(:mutated_merge_request) { subject[:merge_request] } diff --git a/spec/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/graphql/mutations/merge_requests/set_wip_spec.rb index 9f0adcf117a..7255d0fe7d7 100644 --- a/spec/graphql/mutations/merge_requests/set_wip_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_wip_spec.rb @@ -8,6 +8,8 @@ describe Mutations::MergeRequests::SetWip do subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) } + describe '#resolve' do let(:wip) { true } let(:mutated_merge_request) { subject[:merge_request] } diff --git a/spec/graphql/mutations/todos/mark_all_done_spec.rb b/spec/graphql/mutations/todos/mark_all_done_spec.rb index 98b22a3e761..4af00307969 100644 --- a/spec/graphql/mutations/todos/mark_all_done_spec.rb +++ b/spec/graphql/mutations/todos/mark_all_done_spec.rb @@ -17,6 +17,8 @@ describe Mutations::Todos::MarkAllDone do let_it_be(:user3) { create(:user) } + specify { expect(described_class).to require_graphql_authorizations(:update_user) } + describe '#resolve' do it 'marks all pending todos as done' do updated_todo_ids = mutation_for(current_user).resolve.dig(:updated_ids) diff --git a/spec/graphql/mutations/todos/mark_done_spec.rb b/spec/graphql/mutations/todos/mark_done_spec.rb index 059ef3c8eee..44065f83f74 100644 --- a/spec/graphql/mutations/todos/mark_done_spec.rb +++ b/spec/graphql/mutations/todos/mark_done_spec.rb @@ -16,6 +16,8 @@ describe Mutations::Todos::MarkDone do let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } + specify { expect(described_class).to require_graphql_authorizations(:update_todo) } + describe '#resolve' do it 'marks a single todo as done' do result = mark_done_mutation(todo1) diff --git a/spec/graphql/mutations/todos/restore_spec.rb b/spec/graphql/mutations/todos/restore_spec.rb index 1637acc2fb5..949ab6a164b 100644 --- a/spec/graphql/mutations/todos/restore_spec.rb +++ b/spec/graphql/mutations/todos/restore_spec.rb @@ -14,6 +14,8 @@ describe Mutations::Todos::Restore do let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } + specify { expect(described_class).to require_graphql_authorizations(:update_todo) } + describe '#resolve' do it 'restores a single todo' do result = restore_mutation(todo1) |