spec/graphql/schema/resolver_spec.rb in graphql-1.8.6 vs spec/graphql/schema/resolver_spec.rb in graphql-1.8.7

- old
+ new

@@ -74,11 +74,11 @@ class Resolver8 < Resolver7 end class PrepResolver1 < BaseResolver argument :int, Integer, required: true - + undef_method :load_int def load_int(i) i * 10 end type Integer, null: false @@ -125,41 +125,126 @@ end class PrepResolver5 < PrepResolver1 type Integer, null: true - def before_prepare(int:) + def ready?(int:) check_for_magic_number(int) end end class PrepResolver6 < PrepResolver5 - def before_prepare(**args) + def ready?(**args) LazyBlock.new { super } end end - class PrepResolver7 < PrepResolver1 + class PrepResolver7 < GraphQL::Schema::Mutation + argument :int, Integer, required: true + field :errors, [String], null: true + field :int, Integer, null: true + + def ready?(int:) + if int == 13 + return false, { errors: ["Bad number!"] } + else + true + end + end + + def resolve(int:) + { int: int } + end + end + + module HasValue + include GraphQL::Schema::Interface + field :value, Integer, null: false + def self.resolve_type(obj, ctx) + if obj.is_a?(Integer) + IntegerWrapper + else + raise "Unexpected: #{obj.inspect}" + end + end + end + + class IntegerWrapper < GraphQL::Schema::Object + implements HasValue + field :value, Integer, null: false, method: :object + end + + class PrepResolver9 < BaseResolver + argument :int_id, ID, required: true, loads: HasValue + # Make sure the lazy object is resolved properly: + type HasValue, null: false + def object_from_id(type, id, ctx) + # Make sure a lazy object is handled appropriately + LazyBlock.new { + # Make sure that the right type ends up here + id.to_i + type.graphql_name.length + } + end + + def resolve(int:) + int * 3 + end + end + + class PrepResolver10 < BaseResolver + argument :int1, Integer, required: true + argument :int2, Integer, required: true type Integer, null: true + def authorized?(int1:, int2:) + if int1 + int2 > context[:max_int] + raise GraphQL::ExecutionError, "Inputs too big" + elsif context[:min_int] && (int1 + int2 < context[:min_int]) + false + else + true + end + end - def load_int(int) - int + def resolve(int1:, int2:) + int1 + int2 end + end - def validate_int(int) - check_for_magic_number(int) + class PrepResolver11 < PrepResolver10 + def authorized?(int1:, int2:) + LazyBlock.new { super(int1: int1 * 2, int2: int2) } end end - class PrepResolver8 < PrepResolver7 - def validate_int(int) - LazyBlock.new { super } + class PrepResolver12 < GraphQL::Schema::Mutation + argument :int1, Integer, required: true + argument :int2, Integer, required: true + field :error_messages, [String], null: true + field :value, Integer, null: true + def authorized?(int1:, int2:) + if int1 + int2 > context[:max_int] + return false, { error_messages: ["Inputs must be less than #{context[:max_int]} (but you provided #{int1 + int2})"] } + else + true + end end + + def resolve(int1:, int2:) + { value: int1 + int2 } + end end + class PrepResolver13 < PrepResolver12 + def authorized?(int1:, int2:) + # Increment the numbers so we can be sure they're passing through here + LazyBlock.new { super(int1: int1 + 1, int2: int2 + 1) } + end + end + + class Query < GraphQL::Schema::Object class CustomField < GraphQL::Schema::Field def resolve_field(*args) value = super if @name == "resolver3" @@ -186,16 +271,21 @@ field :prep_resolver_3, resolver: PrepResolver3 field :prep_resolver_4, resolver: PrepResolver4 field :prep_resolver_5, resolver: PrepResolver5 field :prep_resolver_6, resolver: PrepResolver6 field :prep_resolver_7, resolver: PrepResolver7 - field :prep_resolver_8, resolver: PrepResolver8 + field :prep_resolver_9, resolver: PrepResolver9 + field :prep_resolver_10, resolver: PrepResolver10 + field :prep_resolver_11, resolver: PrepResolver11 + field :prep_resolver_12, resolver: PrepResolver12 + field :prep_resolver_13, resolver: PrepResolver13 end class Schema < GraphQL::Schema query(Query) lazy_resolve LazyBlock, :value + orphan_types IntegerWrapper end end def exec_query(*args) ResolverTest::Schema.execute(*args) @@ -278,22 +368,30 @@ res = exec_query("{ int: #{field_name}(int: 200) }") assert_nil res["data"].fetch("int"), "#{description}: No result for authorization error" refute res.key?("errors"), "#{description}: silent auth failure (no top-level error)" end - describe "before_prepare" do + describe "ready?" do it "can raise errors" do res = exec_query("{ int: prepResolver5(int: 5) }") assert_equal 50, res["data"]["int"] - add_error_assertions("prepResolver5", "before_prepare") + add_error_assertions("prepResolver5", "ready?") end it "can raise errors in lazy sync" do res = exec_query("{ int: prepResolver6(int: 5) }") assert_equal 50, res["data"]["int"] - add_error_assertions("prepResolver6", "lazy before_prepare") + add_error_assertions("prepResolver6", "lazy ready?") end + + it "can return false and data" do + res = exec_query("{ int: prepResolver7(int: 13) { errors int } }") + assert_equal ["Bad number!"], res["data"]["int"]["errors"] + + res = exec_query("{ int: prepResolver7(int: 213) { errors int } }") + assert_equal 213, res["data"]["int"]["int"] + end end describe "loading arguments" do it "calls load methods and injects the return value" do res = exec_query("{ prepResolver1(int: 5) }") @@ -317,20 +415,62 @@ add_error_assertions("prepResolver4", "lazy load_ hook") end end describe "validating arguments" do - test_cases = { - "eager" => "prepResolver7", - "lazy" => "prepResolver8", - } + describe ".authorized?" do + it "can raise an error to halt" do + res = exec_query("{ prepResolver10(int1: 5, int2: 6) }", context: { max_int: 9 }) + assert_equal ["Inputs too big"], res["errors"].map { |e| e["message"] } - test_cases.each do |mode, field_name| - it "supports raising #{mode} errors" do - res = exec_query("{ validatedInt: #{field_name}(int: 5) }") - assert_equal 5, res["data"]["validatedInt"] - add_error_assertions(field_name, "#{mode} validation") + res = exec_query("{ prepResolver10(int1: 5, int2: 6) }", context: { max_int: 90 }) + assert_equal 11, res["data"]["prepResolver10"] end + + it "can return a lazy object" do + # This is too big because it's modified in the overridden authorized? hook: + res = exec_query("{ prepResolver11(int1: 3, int2: 5) }", context: { max_int: 9 }) + assert_equal ["Inputs too big"], res["errors"].map { |e| e["message"] } + + res = exec_query("{ prepResolver11(int1: 3, int2: 5) }", context: { max_int: 90 }) + assert_equal 8, res["data"]["prepResolver11"] + end + + it "can return data early" do + res = exec_query("{ prepResolver12(int1: 9, int2: 5) { errorMessages } }", context: { max_int: 9 }) + assert_equal ["Inputs must be less than 9 (but you provided 14)"], res["data"]["prepResolver12"]["errorMessages"] + # This works + res = exec_query("{ prepResolver12(int1: 2, int2: 5) { value } }", context: { max_int: 9 }) + assert_equal 7, res["data"]["prepResolver12"]["value"] + end + + it "can return data early in a promise" do + # This is too big because it's modified in the overridden authorized? hook: + res = exec_query("{ prepResolver13(int1: 4, int2: 4) { errorMessages } }", context: { max_int: 9 }) + assert_equal ["Inputs must be less than 9 (but you provided 10)"], res["data"]["prepResolver13"]["errorMessages"] + # This works + res = exec_query("{ prepResolver13(int1: 2, int2: 5) { value } }", context: { max_int: 9 }) + assert_equal 7, res["data"]["prepResolver13"]["value"] + end + + it "can return false to halt" do + str = <<-GRAPHQL + { + prepResolver10(int1: 5, int2: 10) + prepResolver11(int1: 3, int2: 5) + } + GRAPHQL + res = exec_query(str, context: { max_int: 100, min_int: 20 }) + assert_equal({ "prepResolver10" => nil, "prepResolver11" => nil }, res["data"]) + end + end + end + + describe "Loading inputs" do + it "calls object_from_id" do + res = exec_query('{ prepResolver9(intId: "5") { value } }') + # (5 + 8) * 3 + assert_equal 39, res["data"]["prepResolver9"]["value"] end end end end