diff options
author | Bob Van Landuyt <bob@vanlanduyt.co> | 2018-07-10 16:19:45 +0200 |
---|---|---|
committer | Bob Van Landuyt <bob@vanlanduyt.co> | 2018-07-25 18:37:12 +0200 |
commit | 3bcb04f100f5e982379fbeae37a30a191581d1ef (patch) | |
tree | e01065b8a6728bcc75af16166baafcbddd1a6cf5 /spec | |
parent | 9fe58f5a23f2960f666c4d641b463c744138d29c (diff) | |
download | gitlab-ce-3bcb04f100f5e982379fbeae37a30a191581d1ef.tar.gz |
Add mutation toggling WIP state of merge requests
This is mainly the setup of mutations for GraphQL. Including
authorization and basic return type-structure.
Diffstat (limited to 'spec')
-rw-r--r-- | spec/graphql/gitlab_schema_spec.rb | 2 | ||||
-rw-r--r-- | spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb | 19 | ||||
-rw-r--r-- | spec/graphql/mutations/merge_requests/set_wip_spec.rb | 51 | ||||
-rw-r--r-- | spec/graphql/types/mutation_type_spec.rb | 9 | ||||
-rw-r--r-- | spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb | 103 | ||||
-rw-r--r-- | spec/lib/gitlab/graphql/authorize_spec.rb | 20 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb | 68 | ||||
-rw-r--r-- | spec/support/helpers/graphql_helpers.rb | 61 | ||||
-rw-r--r-- | spec/support/matchers/graphql_matchers.rb | 9 | ||||
-rw-r--r-- | spec/support/shared_examples/requests/graphql_shared_examples.rb | 2 |
10 files changed, 333 insertions, 11 deletions
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index 515bbe78cb7..b9ddb427e85 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -18,8 +18,6 @@ describe GitlabSchema do end it 'has the base mutation' do - pending('Adding an empty mutation breaks the documentation explorer') - expect(described_class.mutation).to eq(::Types::MutationType.to_graphql) end diff --git a/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb new file mode 100644 index 00000000000..19f5a8907a2 --- /dev/null +++ b/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Mutations::ResolvesProject do + let(:mutation_class) do + Class.new(Mutations::BaseMutation) do + include Mutations::ResolvesProject + end + end + + let(:context) { double } + subject(:mutation) { mutation_class.new(object: nil, context: context) } + + it 'uses the ProjectsResolver to resolve projects by path' do + project = create(:project) + + expect(Resolvers::ProjectResolver).to receive(:new).with(object: nil, context: context).and_call_original + expect(mutation.resolve_project(full_path: project.full_path)).to eq(project) + end +end diff --git a/spec/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/graphql/mutations/merge_requests/set_wip_spec.rb new file mode 100644 index 00000000000..e600abf3941 --- /dev/null +++ b/spec/graphql/mutations/merge_requests/set_wip_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Mutations::MergeRequests::SetWip do + let(:merge_request) { create(:merge_request) } + let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } + + describe '#resolve' do + let(:wip) { true } + let(:mutated_merge_request) { subject[:merge_request] } + 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 + + context 'when the user can update the merge request' do + before do + merge_request.project.add_developer(user) + end + + it 'returns the merge request as a wip' do + expect(mutated_merge_request).to eq(merge_request) + expect(mutated_merge_request).to be_work_in_progress + expect(subject[:errors]).to be_empty + end + + it 'returns errors merge request could not be updated' do + # Make the merge request invalid + merge_request.allow_broken = true + merge_request.update!(source_project: nil) + + expect(subject[:errors]).not_to be_empty + end + + context 'when passing wip as false' do + let(:wip) { false } + + it 'removes `wip` from the title' do + merge_request.update(title: "WIP: working on it") + + expect(mutated_merge_request).not_to be_work_in_progress + end + + it 'does not do anything if the title did not start with wip' do + expect(mutated_merge_request).not_to be_work_in_progress + end + end + end + end +end diff --git a/spec/graphql/types/mutation_type_spec.rb b/spec/graphql/types/mutation_type_spec.rb new file mode 100644 index 00000000000..a67d83b1edf --- /dev/null +++ b/spec/graphql/types/mutation_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Types::MutationType do + it 'is expected to have the MergeRequestSetWip' do + expect(described_class).to have_graphql_mutation(Mutations::MergeRequests::SetWip) + end +end diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb new file mode 100644 index 00000000000..95bf7685ade --- /dev/null +++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe Gitlab::Graphql::Authorize::AuthorizeResource do + let(:fake_class) do + Class.new do + include Gitlab::Graphql::Authorize::AuthorizeResource + + attr_reader :user, :found_object + + authorize :read_the_thing + + def initialize(user, found_object) + @user, @found_object = user, found_object + end + + def find_object + found_object + end + + def current_user + user + end + end + end + + let(:user) { build(:user) } + let(:project) { build(:project) } + subject(:loading_resource) { fake_class.new(user, project) } + + context 'when the user is allowed to perform the action' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project, scope: :user) do + true + end + end + + describe '#authorized_find' do + it 'returns the object' do + expect(loading_resource.authorized_find).to eq(project) + end + end + + describe '#authorized_find!' do + it 'returns the object' do + expect(loading_resource.authorized_find!).to eq(project) + end + end + + describe '#authorize!' do + it 'does not raise an error' do + expect { loading_resource.authorize!(project) }.not_to raise_error + end + end + + describe '#authorized?' do + it 'is true' do + expect(loading_resource.authorized?(project)).to be(true) + end + end + end + + context 'when the user is not allowed to perform the action' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project, scope: :user) do + false + end + end + + describe '#authorized_find' do + it 'returns `nil`' do + expect(loading_resource.authorized_find).to be_nil + end + end + + describe '#authorized_find!' do + it 'raises an error' do + expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + describe '#authorize!' do + it 'does not raise an error' do + expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + describe '#authorized?' do + it 'is false' do + expect(loading_resource.authorized?(project)).to be(false) + end + end + end + + context 'when the class does not define #find_object' do + let(:fake_class) do + Class.new { include Gitlab::Graphql::Authorize::AuthorizeResource } + end + + it 'raises a comprehensive error message' do + expect { fake_class.new.find_object }.to raise_error(/Implement #find_object in #{fake_class.name}/) + end + end +end diff --git a/spec/lib/gitlab/graphql/authorize_spec.rb b/spec/lib/gitlab/graphql/authorize_spec.rb new file mode 100644 index 00000000000..9c17a3b0e4b --- /dev/null +++ b/spec/lib/gitlab/graphql/authorize_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Graphql::Authorize do + describe '#authorize' do + it 'adds permissions from subclasses to those of superclasses when used on classes' do + base_class = Class.new do + extend Gitlab::Graphql::Authorize + + authorize :base_authorization + end + sub_class = Class.new(base_class) do + authorize :sub_authorization + end + + expect(base_class.required_permissions).to contain_exactly(:base_authorization) + expect(sub_class.required_permissions) + .to contain_exactly(:base_authorization, :sub_authorization) + end + end +end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb new file mode 100644 index 00000000000..8f427d71a32 --- /dev/null +++ b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +describe 'Setting WIP status of a merge request' do + include GraphqlHelpers + + let(:current_user) { create(:user) } + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } + let(:input) { { wip: true } } + + let(:mutation) do + variables = { + project_path: project.full_path, + iid: merge_request.iid + } + graphql_mutation(:merge_request_set_wip, variables.merge(input)) + end + + def mutation_response + graphql_mutation_response(:merge_request_set_wip) + end + + before do + project.add_developer(current_user) + end + + it 'returns an error if the user is not allowed to update the merge request' do + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).not_to be_empty + end + + it 'marks the merge request as WIP' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['mergeRequest']['title']).to start_with('WIP:') + end + + it 'does not do anything if the merge request was already marked `WIP`' do + merge_request.update!(title: 'wip: hello world') + + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['mergeRequest']['title']).to start_with('wip:') + end + + context 'when passing WIP false as input' do + let(:input) { { wip: false } } + + it 'does not do anything if the merge reqeust was not marked wip' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['mergeRequest']['title']).not_to start_with(/wip\:/) + end + + it 'unmarks the merge request as `WIP`' do + merge_request.update!(title: 'wip: hello world') + + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['mergeRequest']['title']).not_to start_with('/wip\:/') + end + end +end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index b9322975b5a..75827df80dc 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -1,4 +1,6 @@ module GraphqlHelpers + MutationDefinition = Struct.new(:query, :variables) + # makes an underscored string look like a fieldname # "merge_request" => "mergeRequest" def self.fieldnamerize(underscored_field_name) @@ -41,6 +43,37 @@ module GraphqlHelpers QUERY end + def graphql_mutation(name, input, fields = nil) + mutation_name = GraphqlHelpers.fieldnamerize(name) + input_variable_name = "$#{input_variable_name_for_mutation(name)}" + mutation_field = GitlabSchema.mutation.fields[mutation_name] + fields ||= all_graphql_fields_for(mutation_field.type) + + query = <<~MUTATION + mutation(#{input_variable_name}: #{mutation_field.arguments['input'].type}) { + #{mutation_name}(input: #{input_variable_name}) { + #{fields} + } + } + MUTATION + variables = variables_for_mutation(name, input) + + MutationDefinition.new(query, variables) + end + + def variables_for_mutation(name, input) + graphql_input = input.map { |name, value| [GraphqlHelpers.fieldnamerize(name), value] }.to_h + { input_variable_name_for_mutation(name) => graphql_input }.to_json + end + + def input_variable_name_for_mutation(mutation_name) + mutation_name = GraphqlHelpers.fieldnamerize(mutation_name) + mutation_field = GitlabSchema.mutation.fields[mutation_name] + input_type = field_type(mutation_field.arguments['input']) + + GraphqlHelpers.fieldnamerize(input_type) + end + def query_graphql_field(name, attributes = {}, fields = nil) fields ||= all_graphql_fields_for(name.classify) attributes = attributes_to_graphql(attributes) @@ -73,8 +106,12 @@ module GraphqlHelpers end.join(", ") end - def post_graphql(query, current_user: nil) - post api('/', current_user, version: 'graphql'), query: query + def post_graphql(query, current_user: nil, variables: nil) + post api('/', current_user, version: 'graphql'), query: query, variables: variables + end + + def post_graphql_mutation(mutation, current_user: nil) + post_graphql(mutation.query, current_user: current_user, variables: mutation.variables) end def graphql_data @@ -82,7 +119,11 @@ module GraphqlHelpers end def graphql_errors - json_response['data'] + json_response['errors'] + end + + def graphql_mutation_response(mutation_name) + graphql_data[GraphqlHelpers.fieldnamerize(mutation_name)] end def nested_fields?(field) @@ -102,10 +143,14 @@ module GraphqlHelpers end def field_type(field) - if field.type.respond_to?(:of_type) - field.type.of_type - else - field.type - end + field_type = field.type + + # The type could be nested. For example `[GraphQL::STRING_TYPE]`: + # - List + # - String! + # - String + field_type = field_type.of_type while field_type.respond_to?(:of_type) + + field_type end end diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index be6fa4c71a0..7be84838e00 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -34,6 +34,15 @@ RSpec::Matchers.define :have_graphql_field do |field_name| end end +RSpec::Matchers.define :have_graphql_mutation do |mutation_class| + match do |mutation_type| + field = mutation_type.fields[GraphqlHelpers.fieldnamerize(mutation_class.graphql_name)] + + expect(field).to be_present + expect(field.resolver).to eq(mutation_class) + end +end + RSpec::Matchers.define :have_graphql_arguments do |*expected| include GraphqlHelpers diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb index fe7b7bc306f..04140cad3f0 100644 --- a/spec/support/shared_examples/requests/graphql_shared_examples.rb +++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb @@ -5,7 +5,7 @@ shared_examples 'a working graphql query' do it 'returns a successful response', :aggregate_failures do expect(response).to have_gitlab_http_status(:success) - expect(graphql_errors['errors']).to be_nil + expect(graphql_errors).to be_nil expect(json_response.keys).to include('data') end end |