loading
Generated 2022-01-03T17:34:54+01:00

All Files ( 98.61% covered at 39.52 hits/line )

19 files in total.
791 relevant lines, 780 lines covered and 11 lines missed. ( 98.61% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
lib/gqli.rb 100.00 % 7 5 5 0 1.00
lib/gqli/base.rb 100.00 % 64 31 31 0 108.81
lib/gqli/client.rb 95.24 % 89 42 40 2 15.55
lib/gqli/clients.rb 100.00 % 4 2 2 0 1.00
lib/gqli/clients/contentful.rb 100.00 % 21 6 6 0 16.00
lib/gqli/clients/github.rb 100.00 % 17 4 4 0 1.75
lib/gqli/dsl.rb 100.00 % 82 27 27 0 2.74
lib/gqli/enum_value.rb 100.00 % 17 7 7 0 2.00
lib/gqli/fragment.rb 100.00 % 25 10 10 0 2.30
lib/gqli/introspection.rb 100.00 % 97 63 63 0 4.06
lib/gqli/mutation.rb 88.89 % 27 9 8 1 2.56
lib/gqli/node.rb 100.00 % 61 31 31 0 563.03
lib/gqli/query.rb 100.00 % 29 10 10 0 10.40
lib/gqli/response.rb 100.00 % 28 13 13 0 12.15
lib/gqli/subscription.rb 88.89 % 27 9 8 1 2.56
lib/gqli/validation.rb 93.27 % 203 104 97 7 80.88
spec/lib/gqli/dsl_spec.rb 100.00 % 468 174 174 0 1.14
spec/lib/gqli/introspection_spec.rb 100.00 % 380 215 215 0 1.55
spec/lib/gqli/response_spec.rb 100.00 % 51 29 29 0 1.69

lib/gqli.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require_relative './gqli/dsl'
  3. 1 require_relative './gqli/client'
  4. 1 require_relative './gqli/introspection'
  5. 1 require_relative './gqli/version'
  6. 1 require_relative './gqli/clients'

lib/gqli/base.rb

100.0% lines covered

31 relevant lines. 31 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require_relative './enum_value'
  3. 1 module GQLi
  4. # Base class for GraphQL type wrappers
  5. 1 class Base
  6. 1 attr_reader :__name, :__depth, :__nodes
  7. 1 def initialize(name = nil, depth = 0, &block)
  8. 401 @__name = name
  9. 401 @__depth = depth
  10. 401 @__nodes = []
  11. 401 instance_eval(&block) unless block.nil?
  12. end
  13. # Inlines fragment nodes into current node
  14. 1 def ___(fragment)
  15. 10 @__nodes += __clone_nodes(fragment)
  16. end
  17. # Adds type match node
  18. 1 def __on(type_name, &block)
  19. 3 __node("... on #{type_name}", {}, &block)
  20. end
  21. # Adds children node into current node
  22. 1 def __node(name, params = {}, &block)
  23. 174 require_relative './node'
  24. 174 @__nodes << Node.new(name, params, __depth + 1, &block)
  25. end
  26. # Creates an EnumType value
  27. 1 def __enum(value)
  28. 2 EnumValue.new(value)
  29. end
  30. 1 protected
  31. 1 def __clone_nodes(node_container)
  32. 182 require_relative './node'
  33. 182 __clone(node_container.__nodes).map do |n|
  34. 172 node = Node.new(n.__name, n.__params, __depth + 1)
  35. 172 node.instance_variable_set(
  36. :@__nodes,
  37. node.send(:__clone_nodes, n)
  38. )
  39. 172 node
  40. end
  41. end
  42. 1 def __clone(obj)
  43. 182 Marshal.load(Marshal.dump(obj))
  44. end
  45. 1 def __params_from_args(args)
  46. 165 args.empty? ? {} : args[0]
  47. end
  48. 1 def method_missing(name, *args, &block)
  49. 165 __node(name.to_s, __params_from_args(args), &block)
  50. end
  51. end
  52. end

lib/gqli/client.rb

95.24% lines covered

42 relevant lines. 40 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'http'
  3. 1 require 'json'
  4. 1 require_relative './response'
  5. 1 require_relative './introspection'
  6. 1 require_relative './version'
  7. 1 module GQLi
  8. # GraphQL HTTP Client
  9. 1 class Client
  10. 1 attr_reader :url, :params, :headers, :validate_query, :schema, :options
  11. 1 def initialize(url, params: {}, headers: {}, validate_query: true, options: {})
  12. 35 @url = url
  13. 35 @params = params
  14. 35 @headers = headers
  15. 35 @validate_query = validate_query
  16. 35 @options = options
  17. 35 @options[:read_timeout] ||= 60
  18. 35 @options[:write_timeout] ||= 60
  19. 35 @options[:connect_timeout] ||= 60
  20. 35 @schema = Introspection.new(self) if validate_query
  21. end
  22. # Executes a query
  23. # If validations are enabled, will perform validation check before request.
  24. 1 def execute(query)
  25. 4 if validate_query
  26. 3 validation = schema.validate(query)
  27. 3 fail validation_error_message(validation) unless validation.valid?
  28. end
  29. 3 execute!(query)
  30. end
  31. # Executres a query
  32. # Ignores validations
  33. 1 def execute!(query)
  34. 33 http_response = request.post(@url, params: @params, json: { query: query.to_gql })
  35. 33 fail "Error: #{http_response.reason}\nBody: #{http_response.body}" if http_response.status >= 300
  36. 31 parsed_response = JSON.parse(http_response.to_s)
  37. 31 data = parsed_response['data']
  38. 31 errors = parsed_response['errors']
  39. 31 Response.new(data, errors, query)
  40. end
  41. # Validates a query against the schema
  42. 1 def valid?(query)
  43. return true unless validate_query
  44. schema.valid?(query)
  45. end
  46. 1 def request
  47. 39 HTTP.headers(request_headers).timeout(timeout_options)
  48. end
  49. 1 protected
  50. 1 def validation_error_message(validation)
  51. 1 <<~ERROR
  52. Validation Error: query is invalid - HTTP Request not sent.
  53. Errors:
  54. - #{validation.errors.join("\n - ")}
  55. ERROR
  56. end
  57. 1 def request_headers
  58. {
  59. 39 accept: 'application/json',
  60. user_agent: "gqli.rb/#{VERSION}; http.rb/#{HTTP::VERSION}"
  61. }.merge(@headers)
  62. end
  63. 1 def timeout_options
  64. {
  65. 39 write: options[:write_timeout],
  66. connect: options[:connect_timeout],
  67. read: options[:read_timeout]
  68. }
  69. end
  70. end
  71. end

lib/gqli/clients.rb

100.0% lines covered

2 relevant lines. 2 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require_relative './clients/contentful'
  3. 1 require_relative './clients/github'

lib/gqli/clients/contentful.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module GQLi
  3. # Module for creating a Contentful GraphQL client
  4. 1 module Contentful
  5. # Creates a Contentful GraphQL client
  6. 1 def self.create(space, access_token, environment: nil, validate_query: true, options: {})
  7. 31 api_url = "https://graphql.contentful.com/content/v1/spaces/#{space}"
  8. 31 api_url += "/environments/#{environment}" unless environment.nil?
  9. 31 GQLi::Client.new(
  10. api_url,
  11. headers: {
  12. 'Authorization' => "Bearer #{access_token}"
  13. },
  14. validate_query: validate_query,
  15. options: options
  16. )
  17. end
  18. end
  19. end

lib/gqli/clients/github.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module GQLi
  3. # Module for creating a Github GraphQL client
  4. 1 module Github
  5. # Creates a Github GraphQL client
  6. 1 def self.create(access_token, validate_query: true)
  7. 4 GQLi::Client.new(
  8. 'https://api.github.com/graphql',
  9. headers: {
  10. 'Authorization' => "Bearer #{access_token}"
  11. },
  12. validate_query: validate_query
  13. )
  14. end
  15. end
  16. end

lib/gqli/dsl.rb

100.0% lines covered

27 relevant lines. 27 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require_relative './query'
  3. 1 require_relative './mutation'
  4. 1 require_relative './subscription'
  5. 1 require_relative './fragment'
  6. 1 require_relative './enum_value'
  7. 1 module GQLi
  8. # GraphQL-like DSL methods
  9. 1 module DSL
  10. # Creates a Query object
  11. #
  12. # Can be used at a class level
  13. 1 def self.query(name = nil, &block)
  14. 34 Query.new(name, &block)
  15. end
  16. # Creates a Subscription object
  17. #
  18. # Can be used at a class level
  19. 1 def self.subscription(name = nil, &block)
  20. 2 Subscription.new(name, &block)
  21. end
  22. # Creates a Mutation object
  23. #
  24. # Can be used at a class level
  25. 1 def self.mutation(name = nil, &block)
  26. 5 Mutation.new(name, &block)
  27. end
  28. # Creates a Fragment object
  29. #
  30. # Can be used at a class level
  31. 1 def self.fragment(name, on, &block)
  32. 3 Fragment.new(name, on, &block)
  33. end
  34. # Creates a EnumValue object
  35. #
  36. # Can be used at a class level
  37. 1 def self.enum(value)
  38. 1 EnumValue.new(value)
  39. end
  40. # Creates a Query object
  41. #
  42. # Can be used at an instance level
  43. 1 def query(name = nil, &block)
  44. 3 Query.new(name, &block)
  45. end
  46. # Creates a Mutation object
  47. #
  48. # Can be used at a instance level
  49. 1 def mutation(name = nil, &block)
  50. 2 Mutation.new(name, &block)
  51. end
  52. # Creates a Subscription object
  53. #
  54. # Can be used at a instance level
  55. 1 def subscription(name = nil, &block)
  56. 2 Subscription.new(name, &block)
  57. end
  58. # Creates a Fragment object
  59. #
  60. # Can be used at an instance level
  61. 1 def fragment(name, on, &block)
  62. 4 Fragment.new(name, on, &block)
  63. end
  64. # Creates a EnumValue object
  65. #
  66. # Can be used at an instance level
  67. 1 def enum(value)
  68. 1 EnumValue.new(value)
  69. end
  70. end
  71. end

lib/gqli/enum_value.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module GQLi
  3. # Wrapper for Enum values
  4. 1 class EnumValue
  5. 1 attr_reader :value
  6. 1 def initialize(value)
  7. 4 @value = value
  8. end
  9. # Serializes the enum value to string
  10. 1 def to_s
  11. 5 value.to_s
  12. end
  13. end
  14. end

lib/gqli/fragment.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require_relative './base'
  3. 1 require_relative './node'
  4. 1 module GQLi
  5. # Fragment wrapper
  6. 1 class Fragment < Base
  7. 1 attr_reader :__on_type
  8. 1 def initialize(name, on, &block)
  9. 7 super(name, 0, &block)
  10. 7 @__on_type = on
  11. end
  12. # Serializes to a GraphQL string
  13. 1 def to_gql
  14. 2 <<~GQL
  15. fragment #{__name} on #{__on_type} {
  16. #{__nodes.map(&:to_gql).join("\n")}
  17. }
  18. GQL
  19. end
  20. end
  21. end

lib/gqli/introspection.rb

100.0% lines covered

63 relevant lines. 63 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require_relative './dsl'
  3. 1 require_relative './validation'
  4. 1 module GQLi
  5. # Introspection schema and validator
  6. 1 class Introspection
  7. 1 extend DSL
  8. # Specific type kind introspection fragment
  9. 1 TypeRef = fragment('TypeRef', '__Type') {
  10. 1 kind
  11. 1 name
  12. 1 ofType {
  13. 1 kind
  14. 1 name
  15. 1 ofType {
  16. 1 kind
  17. 1 name
  18. 1 ofType {
  19. 1 kind
  20. 1 name
  21. }
  22. }
  23. }
  24. }
  25. # Input value introspection fragment
  26. 1 InputValue = fragment('InputValue', '__InputValue') {
  27. 1 name
  28. 1 description
  29. 2 type { ___ TypeRef }
  30. 1 defaultValue
  31. }
  32. # Type introspection fragment
  33. 1 FullType = fragment('FullType', '__Type') {
  34. 1 kind
  35. 1 name
  36. 1 description
  37. 1 fields(includeDeprecated: true) {
  38. 1 name
  39. 1 description
  40. 2 args { ___ InputValue }
  41. 2 type { ___ TypeRef }
  42. 1 isDeprecated
  43. 1 deprecationReason
  44. }
  45. 2 inputFields { ___ InputValue }
  46. 2 interfaces { ___ TypeRef }
  47. 1 enumValues(includeDeprecated: true) {
  48. 1 name
  49. 1 description
  50. 1 isDeprecated
  51. 1 deprecationReason
  52. }
  53. 2 possibleTypes { ___ TypeRef }
  54. }
  55. # Query for fetching the complete schema
  56. 1 IntrospectionQuery = query {
  57. 1 __schema {
  58. 2 queryType { name }
  59. 2 mutationType { name }
  60. 2 subscriptionType { name }
  61. 2 types { ___ FullType }
  62. 1 directives {
  63. 1 name
  64. 1 description
  65. 2 args { ___ InputValue }
  66. 1 locations
  67. }
  68. }
  69. }
  70. 1 attr_reader :schema, :query_type, :mutation_type, :subscription_type, :types
  71. 1 def initialize(client)
  72. 29 @schema = client.execute!(IntrospectionQuery).data.__schema
  73. 29 @query_type = schema.queryType
  74. 29 @mutation_type = schema.mutationType
  75. 29 @subscription_type = schema.subscriptionType
  76. 29 @types = schema.types
  77. end
  78. # Returns the evaluated validation for a query
  79. 1 def validate(query)
  80. 34 Validation.new(self, query)
  81. end
  82. # Returns if the query is valid
  83. 1 def valid?(query)
  84. 10 validate(query).valid?
  85. end
  86. end
  87. end

lib/gqli/mutation.rb

88.89% lines covered

9 relevant lines. 8 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module GQLi
  3. # Mutation node
  4. 1 class Mutation < Base
  5. # Serializes to a GraphQL string
  6. 1 def to_gql
  7. 8 result = <<~GQL
  8. mutation #{__name ? __name + ' ' : ''}{
  9. #{__nodes.map(&:to_gql).join("\n")}
  10. }
  11. GQL
  12. 8 result.lstrip
  13. end
  14. # Delegates itself to the client to be executed
  15. 1 def __execute(client)
  16. client.execute(self)
  17. end
  18. # Serializes to a GraphQL string
  19. 1 def to_s
  20. 2 to_gql
  21. end
  22. end
  23. end

lib/gqli/node.rb

100.0% lines covered

31 relevant lines. 31 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require_relative './base'
  3. 1 module GQLi
  4. # Node wrapper
  5. 1 class Node < Base
  6. 1 attr_reader :__params
  7. 1 def initialize(name, params = {}, depth = 1, &block)
  8. 346 super(name, depth, &block)
  9. 346 @__params = params
  10. end
  11. # Serializes to a GraphQL string
  12. 1 def to_gql
  13. 3238 result = ' ' * __depth + __name
  14. 3238 result += __params_to_s(__params, true) unless __params.empty?
  15. 3238 unless __nodes.empty?
  16. 1046 result += " {\n"
  17. 1046 result += __nodes.map(&:to_gql).join("\n")
  18. 1046 result += "\n#{' ' * __depth}}"
  19. end
  20. 3238 result
  21. end
  22. 1 private
  23. 1 def __directive?(params)
  24. 83 params.size == 1 && params.keys.first.to_s.start_with?('@')
  25. end
  26. 1 def __directive(params)
  27. 2 params.first.tap do |directive, directive_params|
  28. 2 return " #{directive}#{__params_to_s(directive_params, true)}"
  29. end
  30. end
  31. 1 def __params_to_s(params, initial = false)
  32. 162 case params
  33. when ::Hash
  34. 83 return __directive(params) if __directive?(params)
  35. 81 result = params.map do |k, v|
  36. 82 "#{k}: #{__params_to_s(v)}"
  37. end.join(', ')
  38. 81 return "(#{result})" if initial
  39. 5 "{#{result}}"
  40. when ::Array
  41. 3 "[#{params.map { |p| __params_to_s(p) }.join(', ')}]"
  42. when ::String
  43. 12 "\"#{params}\""
  44. else
  45. 66 params.to_s
  46. end
  47. end
  48. end
  49. end

lib/gqli/query.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require_relative './base'
  3. 1 module GQLi
  4. # Query node
  5. 1 class Query < Base
  6. # Serializes to a GraphQL string
  7. 1 def to_gql
  8. 48 result = <<~GQL
  9. query #{__name ? __name + ' ' : ''}{
  10. #{__nodes.map(&:to_gql).join("\n")}
  11. }
  12. GQL
  13. 48 result.lstrip
  14. end
  15. # Delegates itself to the client to be executed
  16. 1 def __execute(client)
  17. 1 client.execute(self)
  18. end
  19. # Serializes to a GraphQL string
  20. 1 def to_s
  21. 1 to_gql
  22. end
  23. end
  24. end

lib/gqli/response.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'hashie/mash'
  3. 1 module GQLi
  4. # Response object wrapper
  5. 1 class Response
  6. 1 attr_reader :data, :errors, :query
  7. 1 def initialize(data, errors, query)
  8. 36 @data = Hashie::Mash.new(data)
  9. 36 @errors = parse_errors(errors)
  10. 36 @query = query
  11. end
  12. 1 private
  13. # Accepts Hash or Array of errors and converts them to Hashie::Mash instances.
  14. # Recursively calls #parse_errors with items if array.
  15. # Returns nil if nil is passed.
  16. 1 def parse_errors(errors)
  17. 37 return unless errors
  18. 4 return errors.map { |e| parse_errors(e) } if errors.is_a?(Array)
  19. 2 Hashie::Mash.new(errors)
  20. end
  21. end
  22. end

lib/gqli/subscription.rb

88.89% lines covered

9 relevant lines. 8 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module GQLi
  3. # Subscription node
  4. 1 class Subscription < Base
  5. # Serializes to a GraphQL string
  6. 1 def to_gql
  7. 8 result = <<~GQL
  8. subscription #{__name ? __name + ' ' : ''}{
  9. #{__nodes.map(&:to_gql).join("\n")}
  10. }
  11. GQL
  12. 8 result.lstrip
  13. end
  14. # Delegates itself to the client to be executed
  15. 1 def __execute(client)
  16. client.execute(self)
  17. end
  18. # Serializes to a GraphQL string
  19. 1 def to_s
  20. 2 to_gql
  21. end
  22. end
  23. end

lib/gqli/validation.rb

93.27% lines covered

104 relevant lines. 97 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module GQLi
  3. # Validations
  4. 1 class Validation
  5. 1 attr_reader :schema, :root, :errors
  6. 1 def initialize(schema, root)
  7. 34 @schema = schema
  8. 34 @root = root
  9. 34 @errors = []
  10. 34 validate
  11. end
  12. # Returns wether the query is valid or not
  13. 1 def valid?
  14. 68 errors.empty?
  15. end
  16. 1 protected
  17. 1 def validate
  18. 34 @errors = []
  19. 34 type_name = root.class.name.split('::').last
  20. 34 validate_type(type_name)
  21. end
  22. 1 private
  23. 1 def validate_type(type)
  24. 1295 root_type = types.find { |t| t.name.casecmp(type).zero? }
  25. 34 root.__nodes.each do |node|
  26. begin
  27. 35 validate_node(root_type, node)
  28. rescue StandardError => e
  29. 13 errors << e
  30. end
  31. end
  32. 34 valid?
  33. end
  34. 1 def remove_alias(name)
  35. 94 return name unless name.include?(':')
  36. 2 name.split(':')[1].strip
  37. end
  38. 1 def types
  39. 164 schema.types
  40. end
  41. 1 def validate_node(parent_type, node)
  42. 101 validate_directives(node)
  43. 98 return valid_match_node?(parent_type, node) if node.__name.start_with?('... on')
  44. 94 node_name = remove_alias(node.__name)
  45. 556 node_type = parent_type.fetch('fields', []).find { |f| f.name == node_name }
  46. 94 fail "Node type not found for '#{node_name}'" if node_type.nil?
  47. 90 validate_params(node_type, node)
  48. 90 resolved_node_type = type_for(node_type)
  49. 90 fail "Node type not found for '#{node_name}'" if resolved_node_type.nil?
  50. 90 validate_nesting_node(resolved_node_type, node)
  51. 152 node.__nodes.each { |n| validate_node(resolved_node_type, n) }
  52. end
  53. 1 def valid_match_node?(parent_type, node)
  54. 14 return if parent_type.fetch('possibleTypes', []).find { |t| t.name == node.__name.gsub('... on ', '') }
  55. 2 fail "Match type '#{node.__name.gsub('... on ', '')}' invalid"
  56. end
  57. 1 def validate_directives(node)
  58. 101 return unless node.__params.size >= 1
  59. 24 node.__params.first.tap do |k, v|
  60. 24 break unless k.to_s.start_with?('@')
  61. 7 fail "Directive unknown '#{k}'" unless %i[@include @skip].include?(k)
  62. 6 fail "Missing arguments for directive '#{k}'" if v.nil? || !v.is_a?(::Hash) || v.empty?
  63. 4 v.each do |arg, value|
  64. begin
  65. 5 fail "Invalid argument '#{arg}' for directive '#{k}'" if arg.to_s != 'if'
  66. 3 fail "Invalid value for 'if`, must be a boolean" if value != !!value
  67. rescue StandardError => e
  68. 3 errors << e
  69. end
  70. end
  71. end
  72. end
  73. 1 def validate_params(node_type, node)
  74. 115 node.__params.reject { |p, _| p.to_s.start_with?('@') }.each do |param, value|
  75. begin
  76. 109 arg = node_type.fetch('args', []).find { |a| a.name == param.to_s }
  77. 21 fail "Invalid argument '#{param}'" if arg.nil?
  78. 18 arg_type = type_for(arg)
  79. 18 fail "Argument type not found for '#{param}'" if arg_type.nil?
  80. 18 validate_value_for_type(arg_type, value, param)
  81. rescue StandardError => e
  82. 9 errors << e
  83. end
  84. end
  85. end
  86. 1 def validate_nesting_node(node_type, node)
  87. 90 fail "Invalid object for node '#{node.__name}'" unless valid_object_node?(node_type, node)
  88. end
  89. 1 def valid_object_node?(node_type, node)
  90. 90 return false if %w[OBJECT INTERFACE].include?(node_type.kind) && node.__nodes.empty?
  91. 86 true
  92. end
  93. 1 def valid_array_node?(node_type, node)
  94. return false if %w[OBJECT INTERFACE].include?(node_type.kind) && node.__nodes.empty?
  95. true
  96. end
  97. 1 def value_type_error(is_type, should_be, for_arg)
  98. 6 should_be = should_be.kind == 'ENUM' ? 'Enum' : should_be.name
  99. 6 additional_message = '. Wrap the value with `__enum`.' if should_be == 'Enum'
  100. 6 fail "Value is '#{is_type}', but should be '#{should_be}' for '#{for_arg}'#{additional_message}"
  101. end
  102. 1 def validate_value_for_type(arg_type, value, for_arg)
  103. 31 case value
  104. when EnumValue
  105. 2 if arg_type.kind == 'ENUM' && !arg_type.enumValues.map(&:name).include?(value.to_s)
  106. fail "Invalid value for Enum '#{arg_type.name}' for '#{for_arg}'"
  107. end
  108. when ::String
  109. 15 unless arg_type.name == 'String' || arg_type.name == 'ID'
  110. 4 value_type_error('String or ID', arg_type, for_arg)
  111. end
  112. when ::Integer
  113. 5 value_type_error('Integer', arg_type, for_arg) unless arg_type.name == 'Int'
  114. when ::Float
  115. value_type_error('Float', arg_type, for_arg) unless arg_type.name == 'Float'
  116. when ::Hash
  117. 9 validate_hash_value(arg_type, value, for_arg)
  118. when true, false
  119. value_type_error('Boolean', arg_type, for_arg) unless arg_type.name == 'Boolean'
  120. else
  121. value_type_error(value.class.name, arg_type, for_arg)
  122. end
  123. end
  124. 1 def validate_hash_value(arg_type, value, for_arg)
  125. 9 value_type_error('Object', arg_type.name, for_arg) unless arg_type.kind == 'INPUT_OBJECT'
  126. 671 type = types.find { |f| f.name == arg_type.name }
  127. 9 fail "Type not found for '#{arg_type.name}'" if type.nil?
  128. 9 value.each do |k, v|
  129. begin
  130. 128 input_field = type.fetch('inputFields', []).find { |f| f.name == k.to_s }
  131. 13 fail "Input field definition not found for '#{k}'" if input_field.nil?
  132. 13 input_field_type = type_for(input_field)
  133. 13 fail "Input field type not found for '#{k}'" if input_field_type.nil?
  134. 13 validate_value_for_type(input_field_type, v, k)
  135. rescue StandardError => e
  136. errors << e
  137. end
  138. end
  139. end
  140. 1 def type_for(field_type)
  141. 121 type = case field_type.type.kind
  142. when 'NON_NULL'
  143. 29 non_null_type(field_type.type.ofType)
  144. when 'LIST'
  145. 10 field_type.type.ofType
  146. when 'OBJECT', 'INTERFACE', 'INPUT_OBJECT'
  147. 42 field_type.type
  148. when 'SCALAR'
  149. 40 field_type.type
  150. end
  151. 2928 types.find { |t| t.name == type.name }
  152. end
  153. 1 def non_null_type(non_null)
  154. 29 case non_null.kind
  155. when 'LIST'
  156. 24 non_null.ofType
  157. else
  158. 5 non_null
  159. end
  160. end
  161. end
  162. end

spec/lib/gqli/dsl_spec.rb

100.0% lines covered

174 relevant lines. 174 lines covered and 0 lines missed.
    
  1. 1 require 'spec_helper'
  2. 1 class MockGQLInterface
  3. 1 include GQLi::DSL
  4. end
  5. 1 describe GQLi::DSL do
  6. 18 subject { described_class }
  7. 1 describe 'module level methods' do
  8. 1 describe '::query' do
  9. 1 it 'can create a query without name' do
  10. 1 query = subject.query {
  11. 1 someField
  12. }
  13. 1 expect(query).to be_a GQLi::Query
  14. 1 expect(query.to_gql).to eq <<~GRAPHQL
  15. query {
  16. someField
  17. }
  18. GRAPHQL
  19. 1 expect(query.to_gql).to eq query.to_s
  20. end
  21. 1 it 'can create a query with a name' do
  22. 1 query = subject.query('FooBar') {
  23. 1 someOtherField
  24. }
  25. 1 expect(query).to be_a GQLi::Query
  26. 1 expect(query.to_gql).to eq <<~GRAPHQL
  27. query FooBar {
  28. someOtherField
  29. }
  30. GRAPHQL
  31. end
  32. end
  33. 1 describe '::mutation' do
  34. 1 it 'can create a mutation without name' do
  35. 1 mutation = subject.mutation {
  36. 1 addFoo(name: 'bar') {
  37. 1 name
  38. }
  39. }
  40. 1 expect(mutation).to be_a GQLi::Mutation
  41. 1 expect(mutation.to_gql).to eq <<~GRAPHQL
  42. mutation {
  43. addFoo(name: "bar") {
  44. name
  45. }
  46. }
  47. GRAPHQL
  48. 1 expect(mutation.to_gql).to eq mutation.to_s
  49. end
  50. 1 it 'can create a query with a name' do
  51. 1 mutation = subject.mutation('AddFoo') {
  52. 1 addFoo(name: 'bar') {
  53. 1 name
  54. }
  55. }
  56. 1 expect(mutation).to be_a GQLi::Mutation
  57. 1 expect(mutation.to_gql).to eq <<~GRAPHQL
  58. mutation AddFoo {
  59. addFoo(name: "bar") {
  60. name
  61. }
  62. }
  63. GRAPHQL
  64. end
  65. end
  66. 1 describe '::subscription' do
  67. 1 it 'can create a subscription without name' do
  68. 1 subscription = subject.subscription {
  69. 1 someField
  70. }
  71. 1 expect(subscription).to be_a GQLi::Subscription
  72. 1 expect(subscription.to_gql).to eq <<~GRAPHQL
  73. subscription {
  74. someField
  75. }
  76. GRAPHQL
  77. 1 expect(subscription.to_gql).to eq subscription.to_s
  78. end
  79. 1 it 'can create a subscription with a name' do
  80. 1 subscription = subject.subscription('FooBar') {
  81. 1 someOtherField
  82. }
  83. 1 expect(subscription).to be_a GQLi::Subscription
  84. 1 expect(subscription.to_gql).to eq <<~GRAPHQL
  85. subscription FooBar {
  86. someOtherField
  87. }
  88. GRAPHQL
  89. end
  90. end
  91. 1 describe '::fragment' do
  92. 1 it 'can create a fragment' do
  93. 1 fragment = subject.fragment('FooBar', 'Foo') {
  94. 1 someFooField
  95. }
  96. 1 expect(fragment).to be_a GQLi::Fragment
  97. 1 expect(fragment.to_gql).to eq <<~GRAPHQL
  98. fragment FooBar on Foo {
  99. someFooField
  100. }
  101. GRAPHQL
  102. end
  103. end
  104. 1 describe '::enum' do
  105. 1 it 'can create an enum value' do
  106. 1 enum_value = subject.enum('foo')
  107. 1 expect(enum_value).to be_a GQLi::EnumValue
  108. 1 expect(enum_value.to_s).to eq 'foo'
  109. end
  110. end
  111. end
  112. 1 describe 'instance level methods' do
  113. 9 subject { MockGQLInterface.new }
  114. 1 describe '#query does the same as ::query' do
  115. 1 it 'can create a query without name' do
  116. 1 query = subject.query {
  117. 1 someField
  118. }
  119. 1 expect(query).to be_a GQLi::Query
  120. 1 expect(query.to_gql).to eq <<~GRAPHQL
  121. query {
  122. someField
  123. }
  124. GRAPHQL
  125. end
  126. 1 it 'can create a query with a name' do
  127. 1 query = subject.query('FooBar') {
  128. 1 someOtherField
  129. }
  130. 1 expect(query).to be_a GQLi::Query
  131. 1 expect(query.to_gql).to eq <<~GRAPHQL
  132. query FooBar {
  133. someOtherField
  134. }
  135. GRAPHQL
  136. end
  137. end
  138. 1 describe '#mutation does the same as ::mutation' do
  139. 1 it 'can create a mutation without name' do
  140. 1 mutation = subject.mutation {
  141. 1 addFoo(name: 'bar') {
  142. 1 name
  143. }
  144. }
  145. 1 expect(mutation).to be_a GQLi::Mutation
  146. 1 expect(mutation.to_gql).to eq <<~GRAPHQL
  147. mutation {
  148. addFoo(name: "bar") {
  149. name
  150. }
  151. }
  152. GRAPHQL
  153. 1 expect(mutation.to_gql).to eq mutation.to_s
  154. end
  155. 1 it 'can create a query with a name' do
  156. 1 mutation = subject.mutation('AddFoo') {
  157. 1 addFoo(name: 'bar') {
  158. 1 name
  159. }
  160. }
  161. 1 expect(mutation).to be_a GQLi::Mutation
  162. 1 expect(mutation.to_gql).to eq <<~GRAPHQL
  163. mutation AddFoo {
  164. addFoo(name: "bar") {
  165. name
  166. }
  167. }
  168. GRAPHQL
  169. end
  170. end
  171. 1 describe '#subscription does the same as ::subscription' do
  172. 1 it 'can create a subscription without name' do
  173. 1 subscription = subject.subscription {
  174. 1 someField
  175. }
  176. 1 expect(subscription).to be_a GQLi::Subscription
  177. 1 expect(subscription.to_gql).to eq <<~GRAPHQL
  178. subscription {
  179. someField
  180. }
  181. GRAPHQL
  182. 1 expect(subscription.to_gql).to eq subscription.to_s
  183. end
  184. 1 it 'can create a subscription with a name' do
  185. 1 subscription = subject.subscription('FooBar') {
  186. 1 someOtherField
  187. }
  188. 1 expect(subscription).to be_a GQLi::Subscription
  189. 1 expect(subscription.to_gql).to eq <<~GRAPHQL
  190. subscription FooBar {
  191. someOtherField
  192. }
  193. GRAPHQL
  194. end
  195. end
  196. 1 describe '#fragment does the same as ::fragment' do
  197. 1 it 'can create a fragment' do
  198. 1 fragment = subject.fragment('FooBar', 'Foo') {
  199. 1 someFooField
  200. }
  201. 1 expect(fragment).to be_a GQLi::Fragment
  202. 1 expect(fragment.to_gql).to eq <<~GRAPHQL
  203. fragment FooBar on Foo {
  204. someFooField
  205. }
  206. GRAPHQL
  207. end
  208. end
  209. 1 describe '#enum does the same as ::enum' do
  210. 1 it 'can create an enum value' do
  211. 1 enum_value = subject.enum('foo')
  212. 1 expect(enum_value).to be_a GQLi::EnumValue
  213. 1 expect(enum_value.to_s).to eq 'foo'
  214. end
  215. end
  216. end
  217. 1 describe 'node DSL' do
  218. 1 it 'nodes can have arguments' do
  219. 1 query = subject.query {
  220. 1 someNode(arg1: 100)
  221. 1 otherNode(arg2: 'some_string')
  222. 1 moreNodes(arg3: { nestedArg: [1, 2] })
  223. }
  224. 1 expect(query.to_gql).to eq <<~GRAPHQL
  225. query {
  226. someNode(arg1: 100)
  227. otherNode(arg2: "some_string")
  228. moreNodes(arg3: {nestedArg: [1, 2]})
  229. }
  230. GRAPHQL
  231. end
  232. 1 it 'nodes can have aliases' do
  233. 1 query = subject.query {
  234. 1 __node('pinned: catCollection', where: {
  235. sys: { id_in: 'nyancat' }
  236. }) {
  237. 1 items {
  238. 1 name
  239. }
  240. }
  241. 1 __node('unpinned: catCollection', where: {
  242. sys: { id_not_in: 'nyancat' }
  243. }, limit: 4) {
  244. 1 items {
  245. 1 name
  246. }
  247. }
  248. }
  249. 1 expect(query.to_gql).to eq <<~GRAPHQL
  250. query {
  251. pinned: catCollection(where: {sys: {id_in: "nyancat"}}) {
  252. items {
  253. name
  254. }
  255. }
  256. unpinned: catCollection(where: {sys: {id_not_in: "nyancat"}}, limit: 4) {
  257. items {
  258. name
  259. }
  260. }
  261. }
  262. GRAPHQL
  263. end
  264. 1 it 'nodes can have directives' do
  265. 1 query = subject.query {
  266. 1 someNode(:@include => { if: true })
  267. 1 otherNode(:@skip => { if: false })
  268. }
  269. 1 expect(query.to_gql).to eq <<~GRAPHQL
  270. query {
  271. someNode @include(if: true)
  272. otherNode @skip(if: false)
  273. }
  274. GRAPHQL
  275. end
  276. 1 it 'nodes can have arbitrarily nested nodes' do
  277. 1 query = subject.query {
  278. 1 aNode {
  279. 1 withChild {
  280. 1 moreChildren
  281. 1 andASibiling(someParameterHere: 'just for fun') {
  282. 1 withEvenMore
  283. }
  284. }
  285. 1 alsoASibiling
  286. }
  287. 1 withoutChild
  288. }
  289. 1 expect(query.to_gql).to eq <<~GRAPHQL
  290. query {
  291. aNode {
  292. withChild {
  293. moreChildren
  294. andASibiling(someParameterHere: "just for fun") {
  295. withEvenMore
  296. }
  297. }
  298. alsoASibiling
  299. }
  300. withoutChild
  301. }
  302. GRAPHQL
  303. end
  304. 1 it 'queries can have inlined fragments at any level' do
  305. 1 NameFragment = subject.fragment('NameFragment', 'Foo') {
  306. 1 name
  307. }
  308. 1 AgeFragment = subject.fragment('AgeFragment', 'Foo') {
  309. 1 age
  310. }
  311. 1 query = subject.query {
  312. 1 peopleCollection {
  313. 1 ___ NameFragment
  314. 1 ___ AgeFragment
  315. }
  316. }
  317. 1 expect(query.to_gql).to eq <<~GRAPHQL
  318. query {
  319. peopleCollection {
  320. name
  321. age
  322. }
  323. }
  324. GRAPHQL
  325. end
  326. 1 it 'queries can have type matchers' do
  327. 1 query = subject.query {
  328. 1 catCollection {
  329. 1 items {
  330. 1 name
  331. 1 bestFriend {
  332. 1 __on('Cat') {
  333. 1 name
  334. }
  335. }
  336. }
  337. }
  338. }
  339. 1 expect(query.to_gql).to eq <<~GRAPHQL
  340. query {
  341. catCollection {
  342. items {
  343. name
  344. bestFriend {
  345. ... on Cat {
  346. name
  347. }
  348. }
  349. }
  350. }
  351. }
  352. GRAPHQL
  353. end
  354. 1 it 'can use __node to define nodes that would collide otherwise' do
  355. 1 query = subject.query {
  356. 1 catCollection {
  357. 1 items {
  358. 1 sys {
  359. 1 __node('id')
  360. }
  361. }
  362. }
  363. }
  364. 1 expect(query.to_gql).to eq <<~GRAPHQL
  365. query {
  366. catCollection {
  367. items {
  368. sys {
  369. id
  370. }
  371. }
  372. }
  373. }
  374. GRAPHQL
  375. end
  376. 1 it '__node can receive arguments and children nodes' do
  377. 1 query = subject.query {
  378. 1 __node('catCollection', limit: 1) {
  379. 1 items {
  380. 1 name
  381. }
  382. }
  383. }
  384. 1 expect(query.to_gql).to eq <<~GRAPHQL
  385. query {
  386. catCollection(limit: 1) {
  387. items {
  388. name
  389. }
  390. }
  391. }
  392. GRAPHQL
  393. end
  394. 1 it '__enum can create EnumType values' do
  395. 1 query = subject.query {
  396. 1 catCollection(order: __enum('lives_ASC')) {
  397. 1 items {
  398. 1 name
  399. }
  400. }
  401. }
  402. 1 expect(query.to_gql).to eq <<~GRAPHQL
  403. query {
  404. catCollection(order: lives_ASC) {
  405. items {
  406. name
  407. }
  408. }
  409. }
  410. GRAPHQL
  411. end
  412. end
  413. end

spec/lib/gqli/introspection_spec.rb

100.0% lines covered

215 relevant lines. 215 lines covered and 0 lines missed.
    
  1. 1 require 'spec_helper'
  2. 1 describe GQLi::Introspection do
  3. 22 let(:dsl) { GQLi::DSL }
  4. 1 let(:client) do
  5. 19 vcr('client') {
  6. 19 space_id = 'cfexampleapi'
  7. 19 token = 'b4c0n73n7fu1'
  8. 19 GQLi::Contentful.create(space_id, token)
  9. }
  10. end
  11. 23 subject { client.schema }
  12. 1 describe 'introspection schema' do
  13. 1 it 'queries the API for the schema' do
  14. 1 expect(subject.types).not_to be_empty
  15. 1 expect(subject.types.map(&:name)).to include('Cat', 'CatCollection', 'Human')
  16. end
  17. end
  18. 1 describe 'validations' do
  19. 1 it 'valid query returns true' do
  20. 1 query = dsl.query {
  21. 1 catCollection(
  22. locale:"en-US",
  23. limit: 1,
  24. where: {
  25. name:"Nyan Cat",
  26. OR: {
  27. name:"Happy Cat"
  28. }
  29. }
  30. ) {
  31. 1 items {
  32. 1 name
  33. 1 color
  34. 1 birthday
  35. 1 lives
  36. 1 bestFriend {
  37. 1 __on('Cat') {
  38. 1 name
  39. }
  40. }
  41. 1 image {
  42. 1 url
  43. }
  44. }
  45. }
  46. }
  47. 1 expect(subject.valid?(query)).to be_truthy
  48. 1 validation = subject.validate(query)
  49. 1 expect(validation.valid?).to be_truthy
  50. 1 expect(validation.errors).to be_empty
  51. end
  52. 1 it 'wrong node returns false' do
  53. 1 query = dsl.query {
  54. 1 foo
  55. }
  56. 1 expect(subject.valid?(query)).to be_falsey
  57. 1 validation = subject.validate(query)
  58. 1 expect(validation.valid?).to be_falsey
  59. 1 expect(validation.errors).not_to be_empty
  60. 1 expect(validation.errors.map(&:to_s)).to include("Node type not found for 'foo'")
  61. end
  62. 1 it 'object node that doesnt have proper values returns false' do
  63. 1 query = dsl.query {
  64. 1 catCollection
  65. }
  66. 1 expect(subject.valid?(query)).to be_falsey
  67. 1 validation = subject.validate(query)
  68. 1 expect(validation.valid?).to be_falsey
  69. 1 expect(validation.errors).not_to be_empty
  70. 1 expect(validation.errors.map(&:to_s)).to include("Invalid object for node 'catCollection'")
  71. end
  72. 1 it 'object list node that doesnt have proper values returns false' do
  73. 1 query = dsl.query {
  74. 1 catCollection {
  75. 1 items
  76. }
  77. }
  78. 1 expect(subject.valid?(query)).to be_falsey
  79. 1 validation = subject.validate(query)
  80. 1 expect(validation.valid?).to be_falsey
  81. 1 expect(validation.errors).not_to be_empty
  82. 1 expect(validation.errors.map(&:to_s)).to include("Invalid object for node 'items'")
  83. end
  84. 1 it 'type matching on invalid type returns false' do
  85. 1 query = dsl.query {
  86. 1 catCollection {
  87. 1 items {
  88. 1 bestFriend {
  89. 1 __on('InvalidType') {
  90. 1 foo
  91. }
  92. }
  93. }
  94. }
  95. }
  96. 1 expect(subject.valid?(query)).to be_falsey
  97. 1 validation = subject.validate(query)
  98. 1 expect(validation.valid?).to be_falsey
  99. 1 expect(validation.errors).not_to be_empty
  100. 1 expect(validation.errors.map(&:to_s)).to include("Match type 'InvalidType' invalid")
  101. end
  102. 1 it 'invalid arguments return false' do
  103. 1 query = dsl.query {
  104. 1 catCollection(invalidParam: 1) {
  105. 1 items {
  106. 1 name
  107. }
  108. }
  109. }
  110. 1 expect(subject.valid?(query)).to be_falsey
  111. 1 validation = subject.validate(query)
  112. 1 expect(validation.valid?).to be_falsey
  113. 1 expect(validation.errors).not_to be_empty
  114. 1 expect(validation.errors.map(&:to_s)).to include("Invalid argument 'invalidParam'")
  115. end
  116. 1 it 'invalid argument type returns false' do
  117. 1 query = dsl.query {
  118. 1 catCollection(limit: 'foo') {
  119. 1 items {
  120. 1 name
  121. }
  122. }
  123. }
  124. 1 expect(subject.valid?(query)).to be_falsey
  125. 1 validation = subject.validate(query)
  126. 1 expect(validation.valid?).to be_falsey
  127. 1 expect(validation.errors).not_to be_empty
  128. 1 expect(validation.errors.map(&:to_s)).to include("Value is 'String or ID', but should be 'Int' for 'limit'")
  129. end
  130. 1 describe 'enum values' do
  131. 1 it 'can create a query with an enum as a filter and validations should not fail' do
  132. 1 query = dsl.query {
  133. 1 catCollection(order: __enum('lives_ASC')) {
  134. 1 items {
  135. 1 name
  136. }
  137. }
  138. }
  139. 1 expect(subject.valid?(query)).to be_truthy
  140. 1 validation = subject.validate(query)
  141. 1 expect(validation.valid?).to be_truthy
  142. 1 expect(validation.errors).to be_empty
  143. end
  144. 1 it 'fails when enum value is not provided properly' do
  145. 1 query = dsl.query {
  146. 1 catCollection(order: 'lives_ASC') {
  147. 1 items {
  148. 1 name
  149. }
  150. }
  151. }
  152. 1 expect(subject.valid?(query)).to be_falsey
  153. 1 validation = subject.validate(query)
  154. 1 expect(validation.valid?).to be_falsey
  155. 1 expect(validation.errors).not_to be_empty
  156. 1 expect(validation.errors.map(&:to_s)).to include("Value is 'String or ID', but should be 'Enum' for 'order'. Wrap the value with `__enum`.")
  157. end
  158. 1 it 'fails when enum value is not provided properly and is not a string' do
  159. 1 query = dsl.query {
  160. 1 catCollection(order: 1) {
  161. 1 items {
  162. 1 name
  163. }
  164. }
  165. }
  166. 1 expect(subject.valid?(query)).to be_falsey
  167. 1 validation = subject.validate(query)
  168. 1 expect(validation.valid?).to be_falsey
  169. 1 expect(validation.errors).not_to be_empty
  170. 1 expect(validation.errors.map(&:to_s)).to include("Value is 'Integer', but should be 'Enum' for 'order'. Wrap the value with `__enum`.")
  171. end
  172. end
  173. 1 describe 'aliases' do
  174. 1 it 'can create a query with an alias' do
  175. 1 query = dsl.query {
  176. 1 __node('pinned: catCollection', where: {
  177. sys: { id_in: 'nyancat' }
  178. }) {
  179. 1 items {
  180. 1 name
  181. }
  182. }
  183. 1 __node('unpinned: catCollection', where: {
  184. sys: { id_not_in: 'nyancat' }
  185. }, limit: 4) {
  186. 1 items {
  187. 1 name
  188. }
  189. }
  190. }
  191. 1 validation = subject.validate(query)
  192. 1 expect(validation.valid?).to be_truthy
  193. 1 expect(validation.errors).to be_empty
  194. end
  195. end
  196. 1 describe 'directives' do
  197. 1 it 'can create a query with a directive and validations should not fail' do
  198. 1 query = dsl.query {
  199. 1 catCollection {
  200. 1 items {
  201. 1 name(:@include => { if: true })
  202. }
  203. }
  204. }
  205. 1 validation = subject.validate(query)
  206. 1 expect(validation.valid?).to be_truthy
  207. end
  208. 1 it 'unknown directives will fail' do
  209. 1 query = dsl.query {
  210. 1 catCollection(:@unknownDirective => nil)
  211. }
  212. 1 validation = subject.validate(query)
  213. 1 expect(validation.valid?).to be_falsey
  214. 1 expect(validation.errors).not_to be_empty
  215. 1 expect(validation.errors.map(&:to_s)).to include("Directive unknown '@unknownDirective'")
  216. end
  217. 1 it 'known directive will fail if no arguments are passed' do
  218. 1 query = dsl.query {
  219. 1 catCollection(:@include => nil)
  220. }
  221. 1 validation = subject.validate(query)
  222. 1 expect(validation.valid?).to be_falsey
  223. 1 expect(validation.errors).not_to be_empty
  224. 1 expect(validation.errors.map(&:to_s)).to include("Missing arguments for directive '@include'")
  225. end
  226. 1 it 'known directive will fail if arguments are empty' do
  227. 1 query = dsl.query {
  228. 1 catCollection(:@include => {})
  229. }
  230. 1 validation = subject.validate(query)
  231. 1 expect(validation.valid?).to be_falsey
  232. 1 expect(validation.errors).not_to be_empty
  233. 1 expect(validation.errors.map(&:to_s)).to include("Missing arguments for directive '@include'")
  234. end
  235. 1 it 'known directive will fail if arguments is not if' do
  236. 1 query = dsl.query {
  237. 1 catCollection {
  238. 1 items {
  239. 1 name(:@include => { else: true })
  240. }
  241. }
  242. }
  243. 1 validation = subject.validate(query)
  244. 1 expect(validation.valid?).to be_falsey
  245. 1 expect(validation.errors).not_to be_empty
  246. 1 expect(validation.errors.map(&:to_s)).to include("Invalid argument 'else' for directive '@include'")
  247. end
  248. 1 it 'known directive will fail when if value is not boolean' do
  249. 1 query = dsl.query {
  250. 1 catCollection {
  251. 1 items {
  252. 1 name(:@include => { if: 123 })
  253. }
  254. }
  255. }
  256. 1 validation = subject.validate(query)
  257. 1 expect(validation.valid?).to be_falsey
  258. 1 expect(validation.errors).not_to be_empty
  259. 1 expect(validation.errors.map(&:to_s)).to include("Invalid value for 'if`, must be a boolean")
  260. end
  261. 1 it 'known directive will fail when multiple arguments are passed' do
  262. 1 query = dsl.query {
  263. 1 catCollection {
  264. 1 items {
  265. 1 name(:@include => { if: true, else: false })
  266. }
  267. }
  268. }
  269. 1 validation = subject.validate(query)
  270. 1 expect(validation.valid?).to be_falsey
  271. 1 expect(validation.errors).not_to be_empty
  272. 1 expect(validation.errors.map(&:to_s)).to include("Invalid argument 'else' for directive '@include'")
  273. end
  274. end
  275. 1 describe 'mutations' do
  276. 1 let(:client) {
  277. 3 vcr('mutation_client') {
  278. 3 GQLi::Github.create(ENV.fetch('GITHUB_READ_ONLY', '<ACCESS_TOKEN>'))
  279. }
  280. }
  281. 1 it 'fails for unknown mutation' do
  282. 1 mutation = dsl.mutation {
  283. 1 foo(bar: 'baz') {
  284. 1 bar
  285. }
  286. }
  287. 1 validation = subject.validate(mutation)
  288. 1 expect(validation.valid?).to be_falsey
  289. 1 expect(validation.errors).not_to be_empty
  290. 1 expect(validation.errors.map(&:to_s)).to include("Node type not found for 'foo'")
  291. end
  292. 1 it 'fails for unknown arguments' do
  293. 1 mutation = dsl.mutation {
  294. 1 addComment(foo: 'bar') {
  295. 1 subject {
  296. 1 id
  297. }
  298. }
  299. }
  300. 1 validation = subject.validate(mutation)
  301. 1 expect(validation.valid?).to be_falsey
  302. 1 expect(validation.errors).not_to be_empty
  303. 1 expect(validation.errors.map(&:to_s)).to include("Invalid argument 'foo'")
  304. end
  305. 1 it 'true for valid mutation' do
  306. 1 mutation = dsl.mutation {
  307. 1 addComment(input: {
  308. subjectId: 'some subject id',
  309. body: 'some subject',
  310. clientMutationId: 'some identifier'
  311. }) {
  312. 1 subject {
  313. 1 id
  314. }
  315. }
  316. }
  317. 1 validation = subject.validate(mutation)
  318. 1 expect(validation.valid?).to be_truthy
  319. 1 expect(validation.errors).to be_empty
  320. end
  321. end
  322. end
  323. end

spec/lib/gqli/response_spec.rb

100.0% lines covered

29 relevant lines. 29 lines covered and 0 lines missed.
    
  1. 1 require 'spec_helper'
  2. 1 describe GQLi::Response do
  3. 4 let(:errors) { nil }
  4. 6 let(:data) { {"testkey" => "test val"} }
  5. 6 let(:query) { double(:query) }
  6. 6 subject { described_class.new(data, errors, query) }
  7. 1 describe "#data" do
  8. 1 it "returns correct data Hashie" do
  9. 1 expect(subject.data).to be_kind_of(Hashie::Mash)
  10. 1 expect(subject.data.testkey).to eq(data["testkey"])
  11. end
  12. end
  13. 1 describe "#query" do
  14. 1 it "returns correct query" do
  15. 1 expect(subject.query).to eq(query)
  16. end
  17. end
  18. 1 describe "#errors" do
  19. 1 context "array errors" do
  20. 2 let(:errors) { [{"message" => "this is an error"}] }
  21. 1 it "converts errors to correct Hashie instances" do
  22. 1 expect(subject.errors).to be_kind_of(Array)
  23. 1 expect(subject.errors.length).to eq(1)
  24. 1 expect(subject.errors.first).to be_kind_of(Hashie::Mash)
  25. 1 expect(subject.errors.first.message).to eq(errors.first["message"])
  26. end
  27. end
  28. 1 context "hash errors" do
  29. 2 let(:errors) { {"message" => "this is an error"} }
  30. 1 it "converts errors to correct Hashie instances" do
  31. 1 expect(subject.errors).to be_kind_of(Hashie::Mash)
  32. 1 expect(subject.errors.message).to eq(errors["message"])
  33. end
  34. end
  35. 1 context "nil errors" do
  36. 1 it "does not raise error and returns nil" do
  37. 1 expect(subject.errors).to be(nil)
  38. end
  39. end
  40. end
  41. end