diff options
Diffstat (limited to 'spec/support/shared_examples/graphql')
11 files changed, 183 insertions, 68 deletions
diff --git a/spec/support/shared_examples/graphql/connection_redaction_shared_examples.rb b/spec/support/shared_examples/graphql/connection_redaction_shared_examples.rb new file mode 100644 index 00000000000..12a7b3fe414 --- /dev/null +++ b/spec/support/shared_examples/graphql/connection_redaction_shared_examples.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# requires: +# - `connection` (no-empty, containing `unwanted` and at least one more item) +# - `unwanted` (single item in collection) +RSpec.shared_examples 'a redactable connection' do + context 'no redactor set' do + it 'contains the unwanted item' do + expect(connection.nodes).to include(unwanted) + end + + it 'does not redact more than once' do + connection.nodes + r_state = connection.send(:redaction_state) + + expect(r_state.redacted { raise 'Should not be called!' }).to be_present + end + end + + let_it_be(:constant_redactor) do + Class.new do + def initialize(remove) + @remove = remove + end + + def redact(items) + items - @remove + end + end + end + + context 'redactor is set' do + let(:redactor) do + constant_redactor.new([unwanted]) + end + + before do + connection.redactor = redactor + end + + it 'does not contain the unwanted item' do + expect(connection.nodes).not_to include(unwanted) + expect(connection.nodes).not_to be_empty + end + + it 'does not redact more than once' do + expect(redactor).to receive(:redact).once.and_call_original + + connection.nodes + connection.nodes + connection.nodes + end + end +end diff --git a/spec/support/shared_examples/graphql/connection_shared_examples.rb b/spec/support/shared_examples/graphql/connection_shared_examples.rb new file mode 100644 index 00000000000..4cba5b5a69d --- /dev/null +++ b/spec/support/shared_examples/graphql/connection_shared_examples.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a connection with collection methods' do + %i[to_a size include? empty?].each do |method_name| + it "responds to #{method_name}" do + expect(connection).to respond_to(method_name) + end + end +end diff --git a/spec/support/shared_examples/graphql/design_fields_shared_examples.rb b/spec/support/shared_examples/graphql/design_fields_shared_examples.rb index ef7086234c4..9c2eb3e5a5c 100644 --- a/spec/support/shared_examples/graphql/design_fields_shared_examples.rb +++ b/spec/support/shared_examples/graphql/design_fields_shared_examples.rb @@ -26,27 +26,35 @@ RSpec.shared_examples 'a GraphQL type with design fields' do end describe '#image' do + let_it_be(:current_user) { create(:user) } let(:schema) { GitlabSchema } let(:query) { GraphQL::Query.new(schema) } - let(:context) { double('Context', schema: schema, query: query, parent: nil) } + let(:context) { query.context } let(:field) { described_class.fields['image'] } let(:args) { GraphQL::Query::Arguments::NO_ARGS } - let(:instance) do + let(:instance) { instantiate(object_id) } + let(:instance_b) { instantiate(object_id_b) } + + def instantiate(object_id) object = GitlabSchema.sync_lazy(GitlabSchema.object_from_id(object_id)) object_type.authorized_new(object, query.context) end - let(:instance_b) do - object_b = GitlabSchema.sync_lazy(GitlabSchema.object_from_id(object_id_b)) - object_type.authorized_new(object_b, query.context) + def resolve_image(instance) + field.resolve_field(instance, args, context) + end + + before do + context[:current_user] = current_user + allow(Ability).to receive(:allowed?).with(current_user, :read_design, anything).and_return(true) + allow(context).to receive(:parent).and_return(nil) end it 'resolves to the design image URL' do - image = field.resolve_field(instance, args, context) sha = design.versions.first.sha url = ::Gitlab::Routing.url_helpers.project_design_management_designs_raw_image_url(design.project, design, sha) - expect(image).to eq(url) + expect(resolve_image(instance)).to eq(url) end it 'has better than O(N) peformance', :request_store do @@ -68,10 +76,10 @@ RSpec.shared_examples 'a GraphQL type with design fields' do # = 10 expect(instance).not_to eq(instance_b) # preload designs themselves. expect do - image_a = field.resolve_field(instance, args, context) - image_b = field.resolve_field(instance, args, context) - image_c = field.resolve_field(instance_b, args, context) - image_d = field.resolve_field(instance_b, args, context) + image_a = resolve_image(instance) + image_b = resolve_image(instance) + image_c = resolve_image(instance_b) + image_d = resolve_image(instance_b) expect(image_a).to eq(image_b) expect(image_c).not_to eq(image_b) expect(image_c).to eq(image_d) diff --git a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb deleted file mode 100644 index b2047f1d32c..00000000000 --- a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'no Jira import data present' do - it 'returns none' do - expect(resolve_imports).to eq JiraImportState.none - end -end - -RSpec.shared_examples 'no Jira import access' do - it 'raises error' do - expect do - resolve_imports - end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end -end diff --git a/spec/support/shared_examples/graphql/members_shared_examples.rb b/spec/support/shared_examples/graphql/members_shared_examples.rb index 3a046c3feec..b0bdd27a95f 100644 --- a/spec/support/shared_examples/graphql/members_shared_examples.rb +++ b/spec/support/shared_examples/graphql/members_shared_examples.rb @@ -36,9 +36,10 @@ RSpec.shared_examples 'querying members with a group' do let_it_be(:group_2_member) { create(:group_member, user: user_3, group: group_2) } let(:args) { {} } + let(:base_args) { { relations: described_class.arguments['relations'].default_value } } subject do - resolve(described_class, obj: resource, args: args, ctx: { current_user: user_4 }) + resolve(described_class, obj: resource, args: base_args.merge(args), ctx: { current_user: user_4 }) end describe '#resolve' do @@ -72,7 +73,7 @@ RSpec.shared_examples 'querying members with a group' do let_it_be(:other_user) { create(:user) } subject do - resolve(described_class, obj: resource, args: args, ctx: { current_user: other_user }) + resolve(described_class, obj: resource, args: base_args.merge(args), ctx: { current_user: other_user }) end it 'raises an error' do diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb index b67cac94547..84ebd4852b9 100644 --- a/spec/support/shared_examples/graphql/mutation_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb @@ -13,6 +13,8 @@ RSpec.shared_examples 'a mutation that returns top-level errors' do |errors: []| it do post_graphql_mutation(mutation, current_user: current_user) + expect(graphql_errors).to be_present + error_messages = graphql_errors.map { |e| e['message'] } expect(error_messages).to match_errors diff --git a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb index 9c0b398a5c1..2b93d174653 100644 --- a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb @@ -40,6 +40,30 @@ RSpec.shared_examples 'boards create mutation' do end end + context 'when hide_backlog_list parameter is true' do + before do + params[:hide_backlog_list] = true + end + + it 'returns the board with correct hide_backlog_list field' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['board']['hideBacklogList']).to eq(true) + end + end + + context 'when hide_closed_list parameter is true' do + before do + params[:hide_closed_list] = true + end + + it 'returns the board with correct hide_closed_list field' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['board']['hideClosedList']).to eq(true) + end + end + context 'when the Boards::CreateService returns an error response' do before do allow_next_instance_of(Boards::CreateService) do |service| diff --git a/spec/support/shared_examples/graphql/mutations/spammable_mutation_fields_examples.rb b/spec/support/shared_examples/graphql/mutations/spammable_mutation_fields_examples.rb index 54b3f84a6e6..8678b23ad31 100644 --- a/spec/support/shared_examples/graphql/mutations/spammable_mutation_fields_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/spammable_mutation_fields_examples.rb @@ -13,7 +13,8 @@ end RSpec.shared_examples 'can raise spam flag' do it 'spam parameters are passed to the service' do - expect(service).to receive(:new).with(anything, anything, hash_including(api: true, request: instance_of(ActionDispatch::Request))) + args = [anything, anything, hash_including(api: true, request: instance_of(ActionDispatch::Request))] + expect(service).to receive(:new).with(*args).and_call_original subject end @@ -39,7 +40,9 @@ RSpec.shared_examples 'can raise spam flag' do end it 'request parameter is not passed to the service' do - expect(service).to receive(:new).with(anything, anything, hash_not_including(request: instance_of(ActionDispatch::Request))) + expect(service).to receive(:new) + .with(anything, anything, hash_not_including(request: instance_of(ActionDispatch::Request))) + .and_call_original subject end diff --git a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb index 94b7ed1618d..16c2ab07f3a 100644 --- a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb +++ b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb @@ -2,14 +2,12 @@ RSpec.shared_examples 'no project services' do it 'returns empty collection' do - expect(resolve_services).to eq [] + expect(resolve_services).to be_empty end end RSpec.shared_examples 'cannot access project services' do it 'raises error' do - expect do - resolve_services - end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + expect(resolve_services).to be_nil end end diff --git a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb index 7627a7b4d59..f78ea364147 100644 --- a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb +++ b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb @@ -16,80 +16,111 @@ # # Example: # describe 'sorting and pagination' do -# let(:sort_project) { create(:project, :public) } +# let_it_be(:sort_project) { create(:project, :public) } # let(:data_path) { [:project, :issues] } # -# def pagination_query(params, page_info) -# graphql_query_for( -# 'project', -# { 'fullPath' => sort_project.full_path }, -# query_graphql_field('issues', params, "#{page_info} edges { node { id } }") +# def pagination_query(arguments) +# graphql_query_for(:project, { full_path: sort_project.full_path }, +# query_nodes(:issues, :iid, include_pagination_info: true, args: arguments) # ) # end # -# def pagination_results_data(data) -# data.map { |issue| issue.dig('node', 'iid').to_i } +# # A method transforming nodes to data to match against +# # default: the identity function +# def pagination_results_data(issues) +# issues.map { |issue| issue['iid].to_i } # end # # context 'when sorting by weight' do -# ... +# let_it_be(:issues) { make_some_issues_with_weights } +# # context 'when ascending' do +# let(:ordered_issues) { issues.sort_by(&:weight) } +# # it_behaves_like 'sorted paginated query' do -# let(:sort_param) { 'WEIGHT_ASC' } +# let(:sort_param) { :WEIGHT_ASC } # let(:first_param) { 2 } -# let(:expected_results) { [weight_issue3.iid, weight_issue5.iid, weight_issue1.iid, weight_issue4.iid, weight_issue2.iid] } +# let(:expected_results) { ordered_issues.map(&:iid) } # end # end # RSpec.shared_examples 'sorted paginated query' do + # Provided as a convenience when constructing queries using string concatenation + let(:page_info) { 'pageInfo { startCursor endCursor }' } + # Convenience for using default implementation of pagination_results_data + let(:node_path) { ['id'] } + it_behaves_like 'requires variables' do let(:required_variables) { [:sort_param, :first_param, :expected_results, :data_path, :current_user] } end describe do - let(:sort_argument) { "sort: #{sort_param}" if sort_param.present? } - let(:first_argument) { "first: #{first_param}" if first_param.present? } + let(:sort_argument) { graphql_args(sort: sort_param) } let(:params) { sort_argument } - let(:start_cursor) { graphql_data_at(*data_path, :pageInfo, :startCursor) } - let(:end_cursor) { graphql_data_at(*data_path, :pageInfo, :endCursor) } - let(:sorted_edges) { graphql_data_at(*data_path, :edges) } - let(:page_info) { "pageInfo { startCursor endCursor }" } - def pagination_query(params, page_info) - raise('pagination_query(params, page_info) must be defined in the test, see example in comment') unless defined?(super) + # Convenience helper for the large number of queries defined as a projection + # from some root value indexed by full_path to a collection of objects with IID + def nested_internal_id_query(root_field, parent, field, args, selection: :iid) + graphql_query_for(root_field, { full_path: parent.full_path }, + query_nodes(field, selection, args: args, include_pagination_info: true) + ) + end + + def pagination_query(params) + raise('pagination_query(params) must be defined in the test, see example in comment') unless defined?(super) super end - def pagination_results_data(data) - raise('pagination_results_data(data) must be defined in the test, see example in comment') unless defined?(super) + def pagination_results_data(nodes) + if defined?(super) + super(nodes) + else + nodes.map { |n| n.dig(*node_path) } + end + end + + def results + nodes = graphql_dig_at(graphql_data(fresh_response_data), *data_path, :nodes) + pagination_results_data(nodes) + end + + def end_cursor + graphql_dig_at(graphql_data(fresh_response_data), *data_path, :page_info, :end_cursor) + end - super(data) + def start_cursor + graphql_dig_at(graphql_data(fresh_response_data), *data_path, :page_info, :start_cursor) end + let(:query) { pagination_query(params) } + before do - post_graphql(pagination_query(params, page_info), current_user: current_user) + post_graphql(query, current_user: current_user) end context 'when sorting' do it 'sorts correctly' do - expect(pagination_results_data(sorted_edges)).to eq expected_results + expect(results).to eq expected_results end context 'when paginating' do - let(:params) { [sort_argument, first_argument].compact.join(',') } + let(:params) { sort_argument.merge(first: first_param) } + let(:first_page) { expected_results.first(first_param) } + let(:rest) { expected_results.drop(first_param) } it 'paginates correctly' do - expect(pagination_results_data(sorted_edges)).to eq expected_results.first(first_param) + expect(results).to eq first_page - cursored_query = pagination_query([sort_argument, "after: \"#{end_cursor}\""].compact.join(','), page_info) - post_graphql(cursored_query, current_user: current_user) + fwds = pagination_query(sort_argument.merge(after: end_cursor)) + post_graphql(fwds, current_user: current_user) - expect(response).to have_gitlab_http_status(:ok) + expect(results).to eq rest - response_data = graphql_dig_at(Gitlab::Json.parse(response.body), :data, *data_path, :edges) + bwds = pagination_query(sort_argument.merge(before: start_cursor)) + post_graphql(bwds, current_user: current_user) - expect(pagination_results_data(response_data)).to eq expected_results.drop(first_param) + expect(results).to eq first_page end end end diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb index ed139e638bf..269e9170906 100644 --- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb +++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb @@ -32,16 +32,16 @@ RSpec.shared_examples 'Gitlab-style deprecations' do it 'adds a formatted `deprecated_reason` to the subject' do deprecable = subject(deprecated: { milestone: '1.10', reason: 'Deprecation reason' }) - expect(deprecable.deprecation_reason).to eq('Deprecation reason. Deprecated in 1.10') + expect(deprecable.deprecation_reason).to eq('Deprecation reason. Deprecated in 1.10.') end it 'appends to the description if given' do deprecable = subject( deprecated: { milestone: '1.10', reason: 'Deprecation reason' }, - description: 'Deprecable description' + description: 'Deprecable description.' ) - expect(deprecable.description).to eq('Deprecable description. Deprecated in 1.10: Deprecation reason') + expect(deprecable.description).to eq('Deprecable description. Deprecated in 1.10: Deprecation reason.') end it 'does not append to the description if it is absent' do |