From f5aa4bc0f2b8dcc2dcfd711c255c522d6dc72ffa Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Wed, 7 Sep 2022 11:30:47 -0400 Subject: [PATCH] Validate behavior during execution --- .../static_validation/literal_validator.rb | 4 + ...able_default_values_are_correctly_typed.rb | 2 +- spec/graphql/schema/directive/one_of_spec.rb | 86 ++++++++++++++++++- spec/graphql/schema/warden_spec.rb | 2 +- ...default_values_are_correctly_typed_spec.rb | 4 +- 5 files changed, 92 insertions(+), 6 deletions(-) diff --git a/lib/graphql/static_validation/literal_validator.rb b/lib/graphql/static_validation/literal_validator.rb index 8a0c9e0029..4a651b234d 100644 --- a/lib/graphql/static_validation/literal_validator.rb +++ b/lib/graphql/static_validation/literal_validator.rb @@ -108,6 +108,10 @@ def required_input_fields_are_present(type, ast_node) arg_type = @warden.get_argument(type, name).type recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type) end + + if type.one_of? && ast_node.arguments.size != 1 + results << Query::InputValidationResult.from_problem("`#{type.graphql_name}` is a OneOf type, so only one argument may be given (instead of #{ast_node.arguments.size})") + end merge_results(results) end end diff --git a/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb b/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb index 26bdda93e8..d173630936 100644 --- a/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +++ b/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb @@ -23,7 +23,7 @@ def on_variable_definition(node, parent) problems = validation_result.problems first_problem = problems && problems.first if first_problem - error_message = first_problem["message"] + error_message = first_problem["explanation"] end error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}" diff --git a/spec/graphql/schema/directive/one_of_spec.rb b/spec/graphql/schema/directive/one_of_spec.rb index 1e4b828bee..ecf979a23f 100644 --- a/spec/graphql/schema/directive/one_of_spec.rb +++ b/spec/graphql/schema/directive/one_of_spec.rb @@ -33,8 +33,8 @@ def one_of_field(one_of_arg:) graphql_name "OneOfInputObject" directive GraphQL::Schema::Directive::OneOf - argument :int, GraphQL::Types::Int - argument :string, GraphQL::Types::String + argument :int, GraphQL::Types::Int, required: false + argument :string, GraphQL::Types::String, required: false end end @@ -75,4 +75,86 @@ def one_of_field(one_of_arg:) end end end + + describe "execution" do + let(:query) do + <<~GRAPHQL + query TestQuery($oneOfInputObject: OneOfInputObject!) { + oneOfField(oneOfArg: $oneOfInputObject) { + string + int + } + } + GRAPHQL + end + + it "accepts a default value with exactly one non-null key" do + query = <<~GRAPHQL + query TestQuery($oneOfInputObject: OneOfInputObject = { string: "default" }) { + oneOfField(oneOfArg: $oneOfInputObject) { + string + int + } + } + GRAPHQL + + result = schema.execute(query) + + assert_equal({ "string" => "default", "int" => nil }, result["data"]["oneOfField"]) + end + + it "rejects a default value with multiple non-null keys" do + query = <<~GRAPHQL + query TestQuery($oneOfInputObject: OneOfInputObject = { string: "default", int: 2 }) { + oneOfField(oneOfArg: $oneOfInputObject) { + string + int + } + } + GRAPHQL + + result = schema.execute(query) + + expected_errors = ["`OneOfInputObject` is a OneOf type, so only one argument may be given (instead of 2)"] + assert_equal(expected_errors, result["errors"].map { |e| e["message"] }) + end + + it "rejects a default value with multiple nullable keys" do + query = <<~GRAPHQL + query TestQuery($oneOfInputObject: OneOfInputObject = { string: "default", int: null }) { + oneOfField(oneOfArg: $oneOfInputObject) { + string + int + } + } + GRAPHQL + + result = schema.execute(query) + + expected_errors = ["`OneOfInputObject` is a OneOf type, so only one argument may be given (instead of 2)"] + assert_equal(expected_errors, result["errors"].map { |e| e["message"] }) + end + + it "accepts a variable with exactly one non-null key" do + string_result = schema.execute(query, variables: { oneOfInputObject: { string: "abc" } }) + int_result = schema.execute(query, variables: { oneOfInputObject: { int: 2 } }) + + assert_equal({ "string" => "abc", "int" => nil }, string_result["data"]["oneOfField"]) + assert_equal({ "string" => nil, "int" => 2 }, int_result["data"]["oneOfField"]) + end + + it "rejects a variable with exactly one null key" do + result = schema.execute(query, variables: { oneOfInputObject: { string: nil } }) + + expected_errors = ["'OneOfInputObject' requires exactly one argument, but 'string' was `null`."] + assert_equal(expected_errors, result["errors"].map { |e| e["extensions"]["problems"].map { |pr| pr["explanation"] } }.flatten) + end + + it "rejects a variable with multiple non-null keys" do + result = schema.execute(query, variables: { oneOfInputObject: { string: "abc", int: 2 } }) + + expected_errors = ["'OneOfInputObject' requires exactly one argument, but 2 were provided."] + assert_equal(expected_errors, result["errors"].map { |e| e["extensions"]["problems"].map { |pr| pr["explanation"] } }.flatten) + end + end end diff --git a/spec/graphql/schema/warden_spec.rb b/spec/graphql/schema/warden_spec.rb index e7895d1e19..50e3a342ac 100644 --- a/spec/graphql/schema/warden_spec.rb +++ b/spec/graphql/schema/warden_spec.rb @@ -825,7 +825,7 @@ class Query < GraphQL::Schema::Object query getPhonemes($manners: [Manner!] = [STOP, TRILL]){ phonemes(manners: $manners) { symbol } } | res = MaskHelpers.query_with_mask(query_string, mask) - expected_errors = ["Default value for $manners doesn't match type [Manner!]"] + expected_errors = ["Expected \"TRILL\" to be one of: STOP, AFFRICATE, FRICATIVE, APPROXIMANT, VOWEL"] assert_equal expected_errors, error_messages(res) end diff --git a/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb b/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb index dc822aaca9..07adc9a7d8 100644 --- a/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +++ b/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb @@ -25,13 +25,13 @@ it "finds default values that don't match their types" do expected = [ { - "message"=>"Default value for $badInt doesn't match type Int", + "message"=>"Could not coerce value \"abc\" to Int", "locations"=>[{"line"=>5, "column"=>7}], "path"=>["query getCheese"], "extensions"=>{"code"=>"defaultValueInvalidType", "variableName"=>"badInt", "typeName"=>"Int"} }, { - "message"=>"Default value for $badInput doesn't match type DairyProductInput", + "message"=>"Could not coerce value true to Float", "locations"=>[{"line"=>7, "column"=>7}], "path"=>["query getCheese"], "extensions"=>{"code"=>"defaultValueInvalidType", "variableName"=>"badInput", "typeName"=>"DairyProductInput"}