module GraphQL::Compatibility::ExecutionSpecification

Test an execution strategy. This spec is not meant as a development aid. Rather, when the strategy works, run it here to see if it has any differences from the built-in strategy.

Some things are explicitly not tested here, because they're handled by other parts of the system:

Attributes

counter_schema[RW]
specification_schema[RW]

Public Class Methods

build_suite(execution_strategy) click to toggle source

Make a minitest suite for this execution strategy, making sure it fulfills all the requirements of this library. @param execution_strategy [<#new, execute>] An execution strategy class @return [Class<Minitest::Test>] A test suite for this execution strategy

# File lib/graphql/compatibility/execution_specification.rb, line 34
      def self.build_suite(execution_strategy)
        GraphQL::Deprecation.warn "#{self} will be removed from GraphQL-Ruby 2.0. There is no replacement, please open an issue on GitHub if you need support."
        Class.new(Minitest::Test) do
          class << self
            attr_accessor :counter_schema, :specification_schema
          end

          self.specification_schema = SpecificationSchema.build(execution_strategy)
          self.counter_schema = CounterSchema.build(execution_strategy)

          def execute_query(query_string, **kwargs)
            kwargs[:root_value] = SpecificationSchema::DATA
            self.class.specification_schema.execute(query_string, **kwargs)
          end

          def test_it_fetches_data
            query_string = %|
            query getData($nodeId: ID = "1001") {
              flh: node(id: $nodeId) {
                __typename
                ... on Person {
                  name @include(if: true)
                  skippedName: name @skip(if: true)
                  birthdate
                  age(on: 1477660133)
                }

                ... on NamedEntity {
                  ne_tn: __typename
                  ne_n: name
                }

                ... on Organization {
                  org_n: name
                }
              }
            }
            |
            res = execute_query(query_string)

            assert_equal nil, res["errors"], "It doesn't have an errors key"

            flh = res["data"]["flh"]
            assert_equal "Fannie Lou Hamer", flh["name"], "It returns values"
            assert_equal Time.new(1917, 10, 6).to_i, flh["birthdate"], "It returns custom scalars"
            assert_equal 99, flh["age"], "It runs resolve functions"
            assert_equal "Person", flh["__typename"], "It serves __typename"
            assert_equal "Person", flh["ne_tn"], "It serves __typename on interfaces"
            assert_equal "Fannie Lou Hamer", flh["ne_n"], "It serves interface fields"
            assert_equal false, flh.key?("skippedName"), "It obeys @skip"
            assert_equal false, flh.key?("org_n"), "It doesn't apply other type fields"
          end

          def test_it_iterates_over_each
            query_string = %|
              query getData($nodeId: ID = "1002") {
                node(id: $nodeId) {
                  ... on Person {
                    organizations { name }
                  }
                }
              }
            |

            res = execute_query(query_string)
            assert_equal ["SNCC"], res["data"]["node"]["organizations"].map { |o| o["name"] }
          end

          def test_it_skips_skipped_fields
            query_str = <<-GRAPHQL
            {
              o3001: organization(id: "3001")  { name }
              o2001: organization(id: "2001")  { name }
            }
            GRAPHQL

            res = execute_query(query_str)
            assert_equal ["o2001"], res["data"].keys
            assert_equal false, res.key?("errors")
          end

          def test_it_propagates_nulls_to_field
            query_string = %|
            query getOrg($id: ID = "2001"){
              failure: node(id: $id) {
                ... on Organization {
                  name
                  leader { name }
                }
              }
              success: node(id: $id) {
                ... on Organization {
                  name
                }
              }
            }
            |
            res = execute_query(query_string)

            failure = res["data"]["failure"]
            success = res["data"]["success"]

            assert_equal nil, failure, "It propagates nulls to the next nullable field"
            assert_equal({"name" => "SNCC"}, success, "It serves the same object if no invalid null is encountered")
            assert_equal 1, res["errors"].length , "It returns an error for the invalid null"
          end

          def test_it_propages_nulls_to_operation
            query_string = %|
              {
                foundOrg: organization(id: "2001") {
                  name
                }
                organization(id: "2999") {
                  name
                }
              }
            |

            res = execute_query(query_string)
            assert_equal nil, res["data"]
            assert_equal 1, res["errors"].length
          end

          def test_it_exposes_raised_and_returned_user_execution_errors
            query_string = %|
              {
                organization(id: "2001") {
                  name
                  returnedError
                  raisedError
                }
                organizations {
                  returnedError
                  raisedError
                }
              }
            |

            res = execute_query(query_string)

            assert_equal "SNCC", res["data"]["organization"]["name"], "It runs the rest of the query"

            expected_errors = [
              {
                "message"=>"This error was returned",
                "locations"=>[{"line"=>5, "column"=>19}],
                "path"=>["organization", "returnedError"]
              },
              {
                "message"=>"This error was raised",
                "locations"=>[{"line"=>6, "column"=>19}],
                "path"=>["organization", "raisedError"]
              },
              {
                "message"=>"This error was raised",
                "locations"=>[{"line"=>10, "column"=>19}],
                "path"=>["organizations", 0, "raisedError"]
              },
              {
                "message"=>"This error was raised",
                "locations"=>[{"line"=>10, "column"=>19}],
                "path"=>["organizations", 1, "raisedError"]
              },
              {
                "message"=>"This error was returned",
                "locations"=>[{"line"=>9, "column"=>19}],
                "path"=>["organizations", 0, "returnedError"]
              },
              {
                "message"=>"This error was returned",
                "locations"=>[{"line"=>9, "column"=>19}],
                "path"=>["organizations", 1, "returnedError"]
              },
            ]

            expected_errors.each do |expected_err|
              assert_includes res["errors"], expected_err
            end
          end

          def test_it_applies_masking
            no_org = ->(member, ctx) { member.name == "Organization" }
            query_string = %|
            {
              node(id: "2001") {
                __typename
              }
            }|

            err = assert_raises(GraphQL::UnresolvedTypeError) {
              execute_query(query_string, except: no_org)
            }

            query_string = %|
            {
              organization(id: "2001") { name }
            }|

            res = execute_query(query_string, except: no_org)

            assert_equal nil, res["data"]
            assert_equal 1, res["errors"].length
            assert_equal "SNCC", err.value.name
            assert_equal GraphQL::Relay::Node.interface, err.field.type
            assert_equal 1, err.possible_types.length
            assert_equal "Organization", err.resolved_type.name
            assert_equal "Query", err.parent_type.name

            query_string = %|
            {
              __type(name: "Organization") { name }
            }|

            res = execute_query(query_string, except: no_org)

            assert_equal nil, res["data"]["__type"]
            assert_equal nil, res["errors"]
          end

          def test_it_provides_nodes_to_resolve
            query_string = %|
            {
              organization(id: "2001") {
                name
                nodePresence
              }
            }|

            res = execute_query(query_string)
            assert_equal "SNCC", res["data"]["organization"]["name"]
            assert_equal [true, true, false], res["data"]["organization"]["nodePresence"]
          end

          def test_it_runs_the_introspection_query
            execute_query(GraphQL::Introspection::INTROSPECTION_QUERY)
          end

          def test_it_propagates_deeply_nested_nulls
            query_string = %|
            {
              node(id: "1001") {
                ... on Person {
                  name
                  first_organization {
                    leader {
                      name
                    }
                  }
                }
              }
            }
            |
            res = execute_query(query_string)
            assert_equal nil, res["data"]["node"]
            assert_equal 1, res["errors"].length
          end

          def test_it_doesnt_add_errors_for_invalid_nulls_from_execution_errors
            query_string = %|
            query getOrg($id: ID = "2001"){
              failure: node(id: $id) {
                ... on Organization {
                  name
                  leader { name }
                }
              }
            }
            |
            res = execute_query(query_string, context: {return_error: true})
            error_messages = res["errors"].map { |e| e["message"] }
            assert_equal ["Error on Nullable"], error_messages
          end

          def test_it_only_resolves_fields_once_on_typed_fragments
            res = self.class.counter_schema.execute("
            {
              counter { count }
              ... on HasCounter {
                counter { count }
              }
            }
            ")

            expected_data = {
              "counter" => { "count" => 1 }
            }
            assert_equal expected_data, res["data"]
            assert_equal 1, self.class.counter_schema.metadata[:count]

            # Deep typed children are correctly distinguished:
            res = self.class.counter_schema.execute("
            {
              counter {
                ... on Counter {
                  counter { count }
                }
                ... on AltCounter {
                  counter { count, t: __typename }
                }
              }
            }
            ")

            expected_data = {
              "counter" => { "counter" => { "count" => 2 } }
            }
            assert_equal expected_data, res["data"]
          end

          def test_it_runs_middleware
            log = []
            query_string = %|
            {
              node(id: "2001") {
                __typename
              }
            }|
            execute_query(query_string, context: {middleware_log: log})
            assert_equal ["node", "__typename"], log
          end

          def test_it_uses_type_error_hooks_for_invalid_nulls
            log = []
            query_string = %|
            {
              node(id: "1001") {
                ... on Person {
                  name
                  first_organization {
                    leader {
                      name
                    }
                  }
                }
              }
            }|

            res = execute_query(query_string, context: { type_errors: log })
            assert_equal nil, res["data"]["node"]
            assert_equal [nil], log
          end

          def test_it_uses_type_error_hooks_for_failed_type_resolution
            log = []
            query_string = %|
            {
              node(id: "2003") {
                __typename
              }
            }|

            assert_raises(GraphQL::UnresolvedTypeError) {
              execute_query(query_string, context: { type_errors: log })
            }

            assert_equal [SpecificationSchema::BOGUS_NODE], log
          end

          def test_it_treats_failed_type_resolution_like_nil
            log = []
            ctx = { type_errors: log, gobble: true }
            query_string = %|
            {
              node(id: "2003") {
                __typename
              }
            }|

            res = execute_query(query_string, context: ctx)

            assert_equal nil, res["data"]["node"]
            assert_equal false, res.key?("errors")
            assert_equal [SpecificationSchema::BOGUS_NODE], log

            query_string_2 = %|
            {
              requiredNode(id: "2003") {
                __typename
              }
            }|

            res = execute_query(query_string_2, context: ctx)

            assert_equal nil, res["data"]
            assert_equal false, res.key?("errors")
            assert_equal [SpecificationSchema::BOGUS_NODE, SpecificationSchema::BOGUS_NODE], log
          end

          def test_it_skips_connections
            query_type = GraphQL::ObjectType.define do
              name "Query"
              connection :skipped, types[query_type], resolve: ->(o,a,c) { c.skip }
            end
            schema = GraphQL::Schema.define(query: query_type)
            res = schema.execute("{ skipped { __typename } }")
            assert_equal({"data" => nil}, res)
          end
        end
      end

Public Instance Methods

execute_query(query_string, **kwargs) click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 44
def execute_query(query_string, **kwargs)
  kwargs[:root_value] = SpecificationSchema::DATA
  self.class.specification_schema.execute(query_string, **kwargs)
end
test_it_applies_masking() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 215
def test_it_applies_masking
  no_org = ->(member, ctx) { member.name == "Organization" }
  query_string = %|
  {
    node(id: "2001") {
      __typename
    }
  }|

  err = assert_raises(GraphQL::UnresolvedTypeError) {
    execute_query(query_string, except: no_org)
  }

  query_string = %|
  {
    organization(id: "2001") { name }
  }|

  res = execute_query(query_string, except: no_org)

  assert_equal nil, res["data"]
  assert_equal 1, res["errors"].length
  assert_equal "SNCC", err.value.name
  assert_equal GraphQL::Relay::Node.interface, err.field.type
  assert_equal 1, err.possible_types.length
  assert_equal "Organization", err.resolved_type.name
  assert_equal "Query", err.parent_type.name

  query_string = %|
  {
    __type(name: "Organization") { name }
  }|

  res = execute_query(query_string, except: no_org)

  assert_equal nil, res["data"]["__type"]
  assert_equal nil, res["errors"]
end
test_it_doesnt_add_errors_for_invalid_nulls_from_execution_errors() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 292
def test_it_doesnt_add_errors_for_invalid_nulls_from_execution_errors
  query_string = %|
  query getOrg($id: ID = "2001"){
    failure: node(id: $id) {
      ... on Organization {
        name
        leader { name }
      }
    }
  }
  |
  res = execute_query(query_string, context: {return_error: true})
  error_messages = res["errors"].map { |e| e["message"] }
  assert_equal ["Error on Nullable"], error_messages
end
test_it_exposes_raised_and_returned_user_execution_errors() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 158
def test_it_exposes_raised_and_returned_user_execution_errors
  query_string = %|
    {
      organization(id: "2001") {
        name
        returnedError
        raisedError
      }
      organizations {
        returnedError
        raisedError
      }
    }
  |

  res = execute_query(query_string)

  assert_equal "SNCC", res["data"]["organization"]["name"], "It runs the rest of the query"

  expected_errors = [
    {
      "message"=>"This error was returned",
      "locations"=>[{"line"=>5, "column"=>19}],
      "path"=>["organization", "returnedError"]
    },
    {
      "message"=>"This error was raised",
      "locations"=>[{"line"=>6, "column"=>19}],
      "path"=>["organization", "raisedError"]
    },
    {
      "message"=>"This error was raised",
      "locations"=>[{"line"=>10, "column"=>19}],
      "path"=>["organizations", 0, "raisedError"]
    },
    {
      "message"=>"This error was raised",
      "locations"=>[{"line"=>10, "column"=>19}],
      "path"=>["organizations", 1, "raisedError"]
    },
    {
      "message"=>"This error was returned",
      "locations"=>[{"line"=>9, "column"=>19}],
      "path"=>["organizations", 0, "returnedError"]
    },
    {
      "message"=>"This error was returned",
      "locations"=>[{"line"=>9, "column"=>19}],
      "path"=>["organizations", 1, "returnedError"]
    },
  ]

  expected_errors.each do |expected_err|
    assert_includes res["errors"], expected_err
  end
end
test_it_fetches_data() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 49
def test_it_fetches_data
  query_string = %|
  query getData($nodeId: ID = "1001") {
    flh: node(id: $nodeId) {
      __typename
      ... on Person {
        name @include(if: true)
        skippedName: name @skip(if: true)
        birthdate
        age(on: 1477660133)
      }

      ... on NamedEntity {
        ne_tn: __typename
        ne_n: name
      }

      ... on Organization {
        org_n: name
      }
    }
  }
  |
  res = execute_query(query_string)

  assert_equal nil, res["errors"], "It doesn't have an errors key"

  flh = res["data"]["flh"]
  assert_equal "Fannie Lou Hamer", flh["name"], "It returns values"
  assert_equal Time.new(1917, 10, 6).to_i, flh["birthdate"], "It returns custom scalars"
  assert_equal 99, flh["age"], "It runs resolve functions"
  assert_equal "Person", flh["__typename"], "It serves __typename"
  assert_equal "Person", flh["ne_tn"], "It serves __typename on interfaces"
  assert_equal "Fannie Lou Hamer", flh["ne_n"], "It serves interface fields"
  assert_equal false, flh.key?("skippedName"), "It obeys @skip"
  assert_equal false, flh.key?("org_n"), "It doesn't apply other type fields"
end
test_it_iterates_over_each() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 87
def test_it_iterates_over_each
  query_string = %|
    query getData($nodeId: ID = "1002") {
      node(id: $nodeId) {
        ... on Person {
          organizations { name }
        }
      }
    }
  |

  res = execute_query(query_string)
  assert_equal ["SNCC"], res["data"]["node"]["organizations"].map { |o| o["name"] }
end
test_it_only_resolves_fields_once_on_typed_fragments() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 308
def test_it_only_resolves_fields_once_on_typed_fragments
  res = self.class.counter_schema.execute("
  {
    counter { count }
    ... on HasCounter {
      counter { count }
    }
  }
  ")

  expected_data = {
    "counter" => { "count" => 1 }
  }
  assert_equal expected_data, res["data"]
  assert_equal 1, self.class.counter_schema.metadata[:count]

  # Deep typed children are correctly distinguished:
  res = self.class.counter_schema.execute("
  {
    counter {
      ... on Counter {
        counter { count }
      }
      ... on AltCounter {
        counter { count, t: __typename }
      }
    }
  }
  ")

  expected_data = {
    "counter" => { "counter" => { "count" => 2 } }
  }
  assert_equal expected_data, res["data"]
end
test_it_propagates_deeply_nested_nulls() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 272
def test_it_propagates_deeply_nested_nulls
  query_string = %|
  {
    node(id: "1001") {
      ... on Person {
        name
        first_organization {
          leader {
            name
          }
        }
      }
    }
  }
  |
  res = execute_query(query_string)
  assert_equal nil, res["data"]["node"]
  assert_equal 1, res["errors"].length
end
test_it_propagates_nulls_to_field() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 115
def test_it_propagates_nulls_to_field
  query_string = %|
  query getOrg($id: ID = "2001"){
    failure: node(id: $id) {
      ... on Organization {
        name
        leader { name }
      }
    }
    success: node(id: $id) {
      ... on Organization {
        name
      }
    }
  }
  |
  res = execute_query(query_string)

  failure = res["data"]["failure"]
  success = res["data"]["success"]

  assert_equal nil, failure, "It propagates nulls to the next nullable field"
  assert_equal({"name" => "SNCC"}, success, "It serves the same object if no invalid null is encountered")
  assert_equal 1, res["errors"].length , "It returns an error for the invalid null"
end
test_it_propages_nulls_to_operation() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 141
def test_it_propages_nulls_to_operation
  query_string = %|
    {
      foundOrg: organization(id: "2001") {
        name
      }
      organization(id: "2999") {
        name
      }
    }
  |

  res = execute_query(query_string)
  assert_equal nil, res["data"]
  assert_equal 1, res["errors"].length
end
test_it_provides_nodes_to_resolve() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 254
def test_it_provides_nodes_to_resolve
  query_string = %|
  {
    organization(id: "2001") {
      name
      nodePresence
    }
  }|

  res = execute_query(query_string)
  assert_equal "SNCC", res["data"]["organization"]["name"]
  assert_equal [true, true, false], res["data"]["organization"]["nodePresence"]
end
test_it_runs_middleware() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 344
def test_it_runs_middleware
  log = []
  query_string = %|
  {
    node(id: "2001") {
      __typename
    }
  }|
  execute_query(query_string, context: {middleware_log: log})
  assert_equal ["node", "__typename"], log
end
test_it_runs_the_introspection_query() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 268
def test_it_runs_the_introspection_query
  execute_query(GraphQL::Introspection::INTROSPECTION_QUERY)
end
test_it_skips_connections() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 423
def test_it_skips_connections
  query_type = GraphQL::ObjectType.define do
    name "Query"
    connection :skipped, types[query_type], resolve: ->(o,a,c) { c.skip }
  end
  schema = GraphQL::Schema.define(query: query_type)
  res = schema.execute("{ skipped { __typename } }")
  assert_equal({"data" => nil}, res)
end
test_it_skips_skipped_fields() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 102
          def test_it_skips_skipped_fields
            query_str = <<-GRAPHQL
            {
              o3001: organization(id: "3001")  { name }
              o2001: organization(id: "2001")  { name }
            }
            GRAPHQL

            res = execute_query(query_str)
            assert_equal ["o2001"], res["data"].keys
            assert_equal false, res.key?("errors")
          end
test_it_treats_failed_type_resolution_like_nil() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 393
def test_it_treats_failed_type_resolution_like_nil
  log = []
  ctx = { type_errors: log, gobble: true }
  query_string = %|
  {
    node(id: "2003") {
      __typename
    }
  }|

  res = execute_query(query_string, context: ctx)

  assert_equal nil, res["data"]["node"]
  assert_equal false, res.key?("errors")
  assert_equal [SpecificationSchema::BOGUS_NODE], log

  query_string_2 = %|
  {
    requiredNode(id: "2003") {
      __typename
    }
  }|

  res = execute_query(query_string_2, context: ctx)

  assert_equal nil, res["data"]
  assert_equal false, res.key?("errors")
  assert_equal [SpecificationSchema::BOGUS_NODE, SpecificationSchema::BOGUS_NODE], log
end
test_it_uses_type_error_hooks_for_failed_type_resolution() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 377
def test_it_uses_type_error_hooks_for_failed_type_resolution
  log = []
  query_string = %|
  {
    node(id: "2003") {
      __typename
    }
  }|

  assert_raises(GraphQL::UnresolvedTypeError) {
    execute_query(query_string, context: { type_errors: log })
  }

  assert_equal [SpecificationSchema::BOGUS_NODE], log
end
test_it_uses_type_error_hooks_for_invalid_nulls() click to toggle source
# File lib/graphql/compatibility/execution_specification.rb, line 356
def test_it_uses_type_error_hooks_for_invalid_nulls
  log = []
  query_string = %|
  {
    node(id: "1001") {
      ... on Person {
        name
        first_organization {
          leader {
            name
          }
        }
      }
    }
  }|

  res = execute_query(query_string, context: { type_errors: log })
  assert_equal nil, res["data"]["node"]
  assert_equal [nil], log
end