diff options
Diffstat (limited to 'spec/support/helpers/graphql_helpers.rb')
-rw-r--r-- | spec/support/helpers/graphql_helpers.rb | 119 |
1 files changed, 101 insertions, 18 deletions
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index ff8908e531a..db8d45f61ea 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -244,15 +244,16 @@ module GraphqlHelpers def graphql_mutation(name, input, fields = nil, &block) raise ArgumentError, 'Please pass either `fields` parameter or a block to `#graphql_mutation`, but not both.' if fields.present? && block_given? + name = name.graphql_name if name.respond_to?(:graphql_name) mutation_name = GraphqlHelpers.fieldnamerize(name) input_variable_name = "$#{input_variable_name_for_mutation(name)}" mutation_field = GitlabSchema.mutation.fields[mutation_name] fields = yield if block_given? - fields ||= all_graphql_fields_for(mutation_field.type.to_graphql) + fields ||= all_graphql_fields_for(mutation_field.type.to_type_signature) query = <<~MUTATION - mutation(#{input_variable_name}: #{mutation_field.arguments['input'].type.to_graphql}) { + mutation(#{input_variable_name}: #{mutation_field.arguments['input'].type.to_type_signature}) { #{mutation_name}(input: #{input_variable_name}) { #{fields} } @@ -264,7 +265,7 @@ module GraphqlHelpers end def variables_for_mutation(name, input) - graphql_input = prepare_input_for_mutation(input) + graphql_input = prepare_variables(input) { input_variable_name_for_mutation(name) => graphql_input } end @@ -273,25 +274,35 @@ module GraphqlHelpers return unless variables return variables if variables.is_a?(String) - ::Gitlab::Utils::MergeHash.merge(Array.wrap(variables).map(&:to_h)).to_json + # Combine variables into a single hash. + hash = ::Gitlab::Utils::MergeHash.merge(Array.wrap(variables).map(&:to_h)) + + prepare_variables(hash).to_json end - # Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys + # Recursively convert any ruby object we can pass as a variable value + # to an object we can serialize with JSON, using fieldname-style keys # - # prepare_input_for_mutation({ 'my_key' => 1 }) - # => { 'myKey' => 1} - def prepare_input_for_mutation(input) - input.to_h do |name, value| - value = prepare_input_for_mutation(value) if value.is_a?(Hash) + # prepare_variables({ 'my_key' => 1 }) + # => { 'myKey' => 1 } + # prepare_variables({ enums: [:FOO, :BAR], user_id: global_id_of(user) }) + # => { 'enums' => ['FOO', 'BAR'], 'userId' => "gid://User/123" } + # prepare_variables({ nested: { hash_values: { are_supported: true } } }) + # => { 'nested' => { 'hashValues' => { 'areSupported' => true } } } + def prepare_variables(input) + return input.map { prepare_variables(_1) } if input.is_a?(Array) + return input.to_s if input.is_a?(GlobalID) || input.is_a?(Symbol) + return input unless input.is_a?(Hash) - [GraphqlHelpers.fieldnamerize(name), value] + input.to_h do |name, value| + [GraphqlHelpers.fieldnamerize(name), prepare_variables(value)] end 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']) + input_type = mutation_field.arguments['input'].type.unwrap.to_type_signature GraphqlHelpers.fieldnamerize(input_type) end @@ -346,6 +357,10 @@ module GraphqlHelpers end end + def query_double(schema:) + double('query', schema: schema) + end + def wrap_fields(fields) fields = Array.wrap(fields).map do |field| case field @@ -646,11 +661,11 @@ module GraphqlHelpers end end - def global_id_of(model, id: nil, model_name: nil) + def global_id_of(model = nil, id: nil, model_name: nil) if id || model_name - ::Gitlab::GlobalId.build(model, id: id, model_name: model_name).to_s + ::Gitlab::GlobalId.as_global_id(id || model.id, model_name: model_name || model.class.name) else - model.to_global_id.to_s + model.to_global_id end end @@ -683,26 +698,94 @@ module GraphqlHelpers end end - # assumes query_string to be let-bound in the current context - def execute_query(query_type, schema: empty_schema, graphql: query_string) + # assumes query_string and user to be let-bound in the current context + def execute_query(query_type, schema: empty_schema, graphql: query_string, raise_on_error: false) schema.query(query_type) - schema.execute( + r = schema.execute( graphql, context: { current_user: user }, variables: {} ) + + if raise_on_error && r.to_h['errors'].present? + raise NoData, r.to_h['errors'] + end + + r end def empty_schema Class.new(GraphQL::Schema) do use GraphQL::Pagination::Connections use Gitlab::Graphql::Pagination::Connections + use BatchLoader::GraphQL lazy_resolve ::Gitlab::Graphql::Lazy, :force end end + # Wrapper around a_hash_including that supports unpacking with ** + class UnpackableMatcher < SimpleDelegator + include RSpec::Matchers + + attr_reader :to_hash + + def initialize(hash) + @to_hash = hash + super(a_hash_including(hash)) + end + + def to_json(_opts = {}) + to_hash.to_json + end + + def as_json(opts = {}) + to_hash.as_json(opts) + end + end + + # Construct a matcher for GraphQL entity response objects, of the form + # `{ "id" => "some-gid" }`. + # + # Usage: + # + # ```ruby + # expect(graphql_data_at(:path, :to, :entity)).to match a_graphql_entity_for(user) + # ``` + # + # This can be called as: + # + # ```ruby + # a_graphql_entity_for(project, :full_path) # also checks that `entity['fullPath'] == project.full_path + # a_graphql_entity_for(project, full_path: 'some/path') # same as above, with explicit values + # a_graphql_entity_for(user, :username, foo: 'bar') # combinations of the above + # a_graphql_entity_for(foo: 'bar') # if properties are defined, the model is not necessary + # ``` + # + # Note that the model instance must not be nil, unless some properties are + # explicitly passed in. The following are rejected with `ArgumentError`: + # + # ``` + # a_graphql_entity_for(nil, :username) + # a_graphql_entity_for(:username) + # a_graphql_entity_for + # ``` + # + def a_graphql_entity_for(model = nil, *fields, **attrs) + raise ArgumentError, 'model is nil' if model.nil? && fields.any? + + attrs.transform_keys! { GraphqlHelpers.fieldnamerize(_1) } + attrs['id'] = global_id_of(model).to_s if model + fields.each do |name| + attrs[GraphqlHelpers.fieldnamerize(name)] = model.public_send(name) + end + + raise ArgumentError, 'no attributes' if attrs.empty? + + UnpackableMatcher.new(attrs) + end + # A lookahead that selects everything def positive_lookahead double(selects?: true).tap do |selection| |