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 33
def self.build_suite(execution_strategy)
  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 = %Q|
      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 = %Q|
        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 = %Q|
      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 = %Q|
        {
          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 = %Q|
        {
          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 = %Q|
      {
        node(id: "2001") {
          __typename
        }
      }|

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

      query_string = %Q|
      {
        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 = %Q|
      {
        __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 = %Q|
      {
        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 = %Q|
      {
        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 = %Q|
      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 = %Q|
      {
        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 = %Q|
      {
        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 = %Q|
      {
        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 = %Q|
      {
        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 = %Q|
      {
        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 42
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 213
def test_it_applies_masking
  no_org = ->(member, ctx) { member.name == "Organization" }
  query_string = %Q|
  {
    node(id: "2001") {
      __typename
    }
  }|

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

  query_string = %Q|
  {
    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 = %Q|
  {
    __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 290
def test_it_doesnt_add_errors_for_invalid_nulls_from_execution_errors
  query_string = %Q|
  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 156
def test_it_exposes_raised_and_returned_user_execution_errors
  query_string = %Q|
    {
      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 47
def test_it_fetches_data
  query_string = %Q|
  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 85
def test_it_iterates_over_each
  query_string = %Q|
    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 306
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 270
def test_it_propagates_deeply_nested_nulls
  query_string = %Q|
  {
    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 113
def test_it_propagates_nulls_to_field
  query_string = %Q|
  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 139
def test_it_propages_nulls_to_operation
  query_string = %Q|
    {
      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 252
def test_it_provides_nodes_to_resolve
  query_string = %Q|
  {
    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 342
def test_it_runs_middleware
  log = []
  query_string = %Q|
  {
    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 266
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 421
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 100
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 391
def test_it_treats_failed_type_resolution_like_nil
  log = []
  ctx = { type_errors: log, gobble: true }
  query_string = %Q|
  {
    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 = %Q|
  {
    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 375
def test_it_uses_type_error_hooks_for_failed_type_resolution
  log = []
  query_string = %Q|
  {
    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 354
def test_it_uses_type_error_hooks_for_invalid_nulls
  log = []
  query_string = %Q|
  {
    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