diff --git a/v2/pkg/variablesvalidation/variablesvalidation.go b/v2/pkg/variablesvalidation/variablesvalidation.go index b054f6f62..56cb4731d 100644 --- a/v2/pkg/variablesvalidation/variablesvalidation.go +++ b/v2/pkg/variablesvalidation/variablesvalidation.go @@ -3,6 +3,7 @@ package variablesvalidation import ( "bytes" "fmt" + "github.com/wundergraph/graphql-go-tools/v2/pkg/apollocompatibility" "github.com/wundergraph/graphql-go-tools/v2/pkg/errorcodes" "github.com/wundergraph/graphql-go-tools/v2/pkg/federation" @@ -48,6 +49,7 @@ func NewVariablesValidator(options VariablesValidatorOptions) *VariablesValidato variables: &astjson.JSON{}, walker: &walker, apolloCompatibilityFlags: options.ApolloCompatibilityFlags, + dev_mode: false, } walker.RegisterEnterVariableDefinitionVisitor(visitor) return &VariablesValidator{ @@ -56,8 +58,11 @@ func NewVariablesValidator(options VariablesValidatorOptions) *VariablesValidato } } -func (v *VariablesValidator) Validate(operation, definition *ast.Document, variables []byte) error { +func (v *VariablesValidator) Validate(operation, definition *ast.Document, variables []byte, devMode bool) error { v.visitor.err = nil + if devMode { + v.visitor.dev_mode = devMode + } v.visitor.definition = definition v.visitor.operation = operation err := v.visitor.variables.ParseObject(variables) @@ -81,6 +86,7 @@ type variablesVisitor struct { currentVariableName []byte currentVariableJsonNodeRef int path []pathItem + dev_mode bool apolloCompatibilityFlags apollocompatibility.Flags } @@ -203,10 +209,17 @@ func (v *variablesVisitor) renderVariableInvalidObjectTypeError(typeName []byte, return } variableContent := out.String() - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s; Expected type "%s" to be an object.`, string(v.currentVariableName), variableContent, string(typeName)), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s; Expected type "%s" to be an object.`, string(v.currentVariableName), variableContent, string(typeName)), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value; Expected type "%s" to be an object.`, string(v.currentVariableName), string(typeName)), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } } func (v *variablesVisitor) renderVariableRequiredNotProvidedError(fieldName []byte, typeRef int) { @@ -223,10 +236,17 @@ func (v *variablesVisitor) renderVariableRequiredNotProvidedError(fieldName []by v.err = err return } - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s; Field "%s" of required type "%s" was not provided.`, string(v.currentVariableName), variableContent, string(fieldName), out.String()), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s; Field "%s" of required type "%s" was not provided.`, string(v.currentVariableName), variableContent, string(fieldName), out.String()), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value; Field "%s" of required type "%s" was not provided.`, string(v.currentVariableName), string(fieldName), out.String()), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } } func (v *variablesVisitor) renderVariableInvalidNestedTypeError(actualJsonNodeRef int, expectedType ast.NodeKind, expectedTypeName []byte, expectedList bool) { @@ -247,53 +267,116 @@ func (v *variablesVisitor) renderVariableInvalidNestedTypeError(actualJsonNodeRe case ast.NodeKindScalarTypeDefinition: switch typeName { case "String": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; String cannot represent a non string value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; String cannot represent a non string value: %s`, variableName, invalidValue, path, invalidValue), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value%s; String cannot represent a non string value`, variableName, path), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } case "Int": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Int cannot represent non-integer value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Int cannot represent non-integer value: %s`, variableName, invalidValue, path, invalidValue), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value%s; Int cannot represent non-integer value`, variableName, path), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } case "Float": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Float cannot represent non numeric value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Float cannot represent non numeric value: %s`, variableName, invalidValue, path, invalidValue), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value%s; Float cannot represent non numeric value`, variableName, path), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } case "Boolean": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Boolean cannot represent a non boolean value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Boolean cannot represent a non boolean value: %s`, variableName, invalidValue, path, invalidValue), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value%s; Boolean cannot represent a non boolean value`, variableName, path), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } case "ID": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; ID cannot represent value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; ID cannot represent value: %s`, variableName, invalidValue, path, invalidValue), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value%s; ID cannot represent value`, variableName, path), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } default: - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Expected type "%s" to be a scalar.`, variableName, invalidValue, path, typeName), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Expected type "%s" to be a scalar.`, variableName, invalidValue, path, typeName), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value%s; Expected type "%s" to be a scalar.`, variableName, path, typeName), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } } case ast.NodeKindInputObjectTypeDefinition: if expectedList { + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Got input type "%s", want: "[%s]"`, variableName, invalidValue, path, typeName, typeName), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value%s; Got input type "%s", want: "[%s]"`, variableName, path, typeName, typeName), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } + } else { + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Expected type "%s" to be an input object.`, variableName, invalidValue, path, typeName), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value%s; Expected type "%s" to be an input object.`, variableName, path, typeName), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } + } + case ast.NodeKindEnumTypeDefinition: + if v.dev_mode { v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Got input type "%s", want: "[%s]"`, variableName, invalidValue, path, typeName, typeName), + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Enum "%s" cannot represent non-string value: %s.`, variableName, invalidValue, path, typeName, invalidValue), v.apolloCompatibilityFlags.ReplaceInvalidVarError, ) } else { v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Expected type "%s" to be an input object.`, variableName, invalidValue, path, typeName), + fmt.Sprintf(`Variable "$%s" got invalid value%s; Enum "%s" cannot represent non-string value.`, variableName, path, typeName), v.apolloCompatibilityFlags.ReplaceInvalidVarError, ) } - case ast.NodeKindEnumTypeDefinition: - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Enum "%s" cannot represent non-string value: %s.`, variableName, invalidValue, path, typeName, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) } } @@ -307,10 +390,17 @@ func (v *variablesVisitor) renderVariableFieldNotDefinedError(fieldName []byte, } invalidValue := buf.String() path := v.renderPath() - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s at "%s"; Field "%s" is not defined by type "%s".`, variableName, invalidValue, path, string(fieldName), string(typeName)), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s at "%s"; Field "%s" is not defined by type "%s".`, variableName, invalidValue, path, string(fieldName), string(typeName)), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value at "%s"; Field "%s" is not defined by type "%s".`, variableName, path, string(fieldName), string(typeName)), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } } func (v *variablesVisitor) renderVariableEnumValueDoesNotExistError(typeName []byte, enumValue []byte) { @@ -326,10 +416,17 @@ func (v *variablesVisitor) renderVariableEnumValueDoesNotExistError(typeName []b if len(v.path) > 1 { path = fmt.Sprintf(` at "%s"`, v.renderPath()) } - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Value "%s" does not exist in "%s" enum.`, variableName, invalidValue, path, string(enumValue), string(typeName)), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + if v.dev_mode { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Value "%s" does not exist in "%s" enum.`, variableName, invalidValue, path, string(enumValue), string(typeName)), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } else { + v.err = newInvalidVariableError( + fmt.Sprintf(`Variable "$%s" got invalid value%s; Value does not exist in "%s" enum.`, variableName, path, string(typeName)), + v.apolloCompatibilityFlags.ReplaceInvalidVarError, + ) + } } func (v *variablesVisitor) renderVariableInvalidNullError(variableName []byte, typeRef int) { diff --git a/v2/pkg/variablesvalidation/variablesvalidation_test.go b/v2/pkg/variablesvalidation/variablesvalidation_test.go index de562c05e..c98ce633a 100644 --- a/v2/pkg/variablesvalidation/variablesvalidation_test.go +++ b/v2/pkg/variablesvalidation/variablesvalidation_test.go @@ -1,9 +1,10 @@ package variablesvalidation import ( + "testing" + "github.com/wundergraph/graphql-go-tools/v2/pkg/apollocompatibility" "github.com/wundergraph/graphql-go-tools/v2/pkg/errorcodes" - "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,769 +18,1277 @@ import ( func TestVariablesValidation(t *testing.T) { t.Run("required field argument not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: String!): String }`, operation: `query Foo($bar: String!) { hello }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" of required type "String!" was not provided.`, err.Error()) }) t.Run("a missing required input produces an error", func(t *testing.T) { + devMode := false tc := testCase{ schema: inputSchema, operation: `query Foo($input: SelfSatisfiedInput!) { satisfied }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" of required type "SelfSatisfiedInput!" was not provided.`, err.Error()) }) t.Run("provided required input fields with default values do not produce validation errors", func(t *testing.T) { + devMode := false tc := testCase{ schema: inputSchema, operation: `query Foo($input: SelfSatisfiedInput!) { satisfied(input: $input) }`, variables: `{ "input": { } }`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) - t.Run("unprovided required input fields without default values produce validation errors #1", func(t *testing.T) { + t.Run("dev_mode unprovided required input fields without default values produce validation errors #1", func(t *testing.T) { + devMode := true tc := testCase{ schema: inputSchema, operation: `query Foo($input: SelfUnsatisfiedInput!) { unsatisfied(input: $input) }`, variables: `{ "input": { } }`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {}; Field "nested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) }) - t.Run("unprovided required input fields without default values produce validation errors #2", func(t *testing.T) { + t.Run("unprovided required input fields without default values produce validation errors #1", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: inputSchema, + operation: `query Foo($input: SelfUnsatisfiedInput!) { unsatisfied(input: $input) }`, + variables: `{ "input": { } }`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value; Field "nested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) + }) + + + t.Run("dev_mode unprovided required input fields without default values produce validation errors #2", func(t *testing.T) { + devMode := true tc := testCase{ schema: inputSchema, operation: `query Foo($input: SelfUnsatisfiedInput!) { unsatisfied(input: $input) }`, variables: `{ "input": { "nested": { }, "value": "string" } }`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {"nested":{},"value":"string"}; Field "secondNested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) }) + t.Run("unprovided required input fields without default values produce validation errors #2", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: inputSchema, + operation: `query Foo($input: SelfUnsatisfiedInput!) { unsatisfied(input: $input) }`, + variables: `{ "input": { "nested": { }, "value": "string" } }`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value; Field "secondNested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) + }) + t.Run("provided but empty nested required inputs with default values do not produce validation errors", func(t *testing.T) { + devMode := false tc := testCase{ schema: inputSchema, operation: `query Foo($input: SelfUnsatisfiedInput!) { unsatisfied(input: $input) }`, variables: `{ "input": { "nested": { }, "secondNested": { } } }`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("not required field argument not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: String): String }`, operation: `query Foo($bar: String) { hello }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("required field argument provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: String!): String }`, operation: `query Foo($bar: String!) { hello(arg: $bar) }`, variables: `{"bar": "world"}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) - t.Run("nested argument is value instead of list", func(t *testing.T) { + t.Run("dev_mode nested argument is value instead of list", func(t *testing.T) { + devMode := true tc := testCase{ schema: `type Query { hello(input: Input): String } input Input { bar: [String]! }`, operation: `query Foo($input: Input) { hello(input: $input) }`, variables: `{"input":{"bar":"world"}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NotNil(t, err) assert.Equal(t, `Variable "$input" got invalid value "world" at "input.bar"; Got input type "String", want: "[String]"`, err.Error()) }) - t.Run("nested enum argument is value instead of list", func(t *testing.T) { + t.Run("nested argument is value instead of list", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `type Query { hello(input: Input): String } input Input { bar: [String]! }`, + operation: `query Foo($input: Input) { hello(input: $input) }`, + variables: `{"input":{"bar":"world"}}`, + } + err := runTest(t, tc, devMode) + require.NotNil(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; Got input type "String", want: "[String]"`, err.Error()) + }) + + t.Run("dev_mode nested enum argument is value instead of list", func(t *testing.T) { + devMode := true tc := testCase{ schema: `type Query { hello(input: Input): String } input Input { bar: [MyNum]! } enum MyNum { ONE TWO }`, operation: `query Foo($input: Input) { hello(input: $input) }`, variables: `{"input":{"bar":"ONE"}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NotNil(t, err) assert.Equal(t, `Variable "$input" got invalid value "ONE" at "input.bar"; Got input type "MyNum", want: "[MyNum]"`, err.Error()) }) + t.Run("nested enum argument is value instead of list", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `type Query { hello(input: Input): String } input Input { bar: [MyNum]! } enum MyNum { ONE TWO }`, + operation: `query Foo($input: Input) { hello(input: $input) }`, + variables: `{"input":{"bar":"ONE"}}`, + } + err := runTest(t, tc, devMode) + require.NotNil(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; Got input type "MyNum", want: "[MyNum]"`, err.Error()) + }) + t.Run("required field argument of custom scalar type not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: Baz!): String } scalar Baz`, operation: `query Foo($bar: Baz!) { hello(arg: $bar) }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) assert.NotNil(t, err) assert.Equal(t, `Variable "$bar" of required type "Baz!" was not provided.`, err.Error()) }) t.Run("required field argument of custom scalar type was null", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: Baz!): String } scalar Baz`, operation: `query Foo($bar: Baz!) { hello(arg: $bar) }`, variables: `{"bar":null}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) assert.NotNil(t, err) assert.Equal(t, `Variable "$bar" got invalid value null; Expected non-nullable type "Baz!" not to be null.`, err.Error()) }) - t.Run("required nested field field argument of custom scalar not provided", func(t *testing.T) { + t.Run("dev_mode required nested field field argument of custom scalar not provided", func(t *testing.T) { + devMode := true tc := testCase{ schema: `type Query { hello(arg: Foo!): String } input Foo { bar: Baz! } scalar Baz`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":{}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) assert.NotNil(t, err) assert.Equal(t, `Variable "$bar" got invalid value {}; Field "bar" of required type "Baz!" was not provided.`, err.Error()) }) - t.Run("required nested field field argument of custom scalar was null", func(t *testing.T) { + t.Run("required nested field field argument of custom scalar not provided", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `type Query { hello(arg: Foo!): String } input Foo { bar: Baz! } scalar Baz`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":{}}`, + } + err := runTest(t, tc, devMode) + assert.NotNil(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Field "bar" of required type "Baz!" was not provided.`, err.Error()) + }) + + t.Run("dev_mode required nested field field argument of custom scalar was null", func(t *testing.T) { + devMode := true tc := testCase{ schema: `type Query { hello(arg: Foo!): String } input Foo { bar: Baz! } scalar Baz`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":{"bar":null}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) assert.NotNil(t, err) assert.Equal(t, `Variable "$bar" got invalid value {"bar":null}; Field "bar" of required type "Baz!" was not provided.`, err.Error()) }) + t.Run("required nested field field argument of custom scalar was null", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `type Query { hello(arg: Foo!): String } input Foo { bar: Baz! } scalar Baz`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":{"bar":null}}`, + } + err := runTest(t, tc, devMode) + assert.NotNil(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Field "bar" of required type "Baz!" was not provided.`, err.Error()) + }) + t.Run("required field argument provided with default value", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: String!): String }`, operation: `query Foo($bar: String! = "world") { hello(arg: $bar) }`, variables: `{}`, withNormalization: true, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("required Int field argument not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: Int!): String }`, operation: `query Foo($bar: Int!) { hello }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" of required type "Int!" was not provided.`, err.Error()) }) t.Run("required Float field argument not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: Float!): String }`, operation: `query Foo($bar: Float!) { hello }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" of required type "Float!" was not provided.`, err.Error()) }) t.Run("required Boolean field argument not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: Boolean!): String }`, operation: `query Foo($bar: Boolean!) { hello }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" of required type "Boolean!" was not provided.`, err.Error()) }) t.Run("required ID field argument not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: ID!): String }`, operation: `query Foo($bar: ID!) { hello }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" of required type "ID!" was not provided.`, err.Error()) }) t.Run("required ID field argument provided with Int", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: ID!): String }`, operation: `query Foo($bar: ID!) { hello(arg: $bar) }`, variables: `{"bar":123}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("required ID field argument provided with String", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: ID!): String }`, operation: `query Foo($bar: ID!) { hello(arg: $bar) }`, variables: `{"bar":"hello"}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("required Enum field argument not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" of required type "Foo!" was not provided.`, err.Error()) }) t.Run("required input object field argument not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" of required type "Foo!" was not provided.`, err.Error()) }) t.Run("required string list field argument not provided", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: [String]!): String }`, operation: `query Foo($bar: [String]!) { hello }`, variables: `{}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" of required type "[String]!" was not provided.`, err.Error()) }) - t.Run("wrong Boolean value for input object field", func(t *testing.T) { + t.Run("dev_mode wrong Boolean value for input object field", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":true}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value true; Expected type "Foo" to be an object.`, err.Error()) }) - t.Run("wrong Integer value for input object field", func(t *testing.T) { + t.Run("wrong Boolean value for input object field", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":true}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Expected type "Foo" to be an object.`, err.Error()) + }) + + t.Run("dev_mode wrong Integer value for input object field", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":123}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value 123; Expected type "Foo" to be an object.`, err.Error()) }) - t.Run("required field on present input object not provided", func(t *testing.T) { + t.Run("wrong Integer value for input object field", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Expected type "Foo" to be an object.`, err.Error()) + }) + + t.Run("dev_mode required field on present input object not provided", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":{}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value {}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) + t.Run("required field on present input object not provided", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":{}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) + }) + t.Run("required field on present input object provided with correct type", func(t *testing.T) { + devMode := false tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":{"bar":"hello"}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) - t.Run("required field on present input object provided with wrong type", func(t *testing.T) { + t.Run("dev_mode required field on present input object provided with wrong type", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($input: Foo!) { hello(arg: $input) }`, variables: `{"input":{"bar":123}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value 123 at "input.bar"; String cannot represent a non string value: 123`, err.Error()) }) - t.Run("required field on present input object not provided", func(t *testing.T) { + t.Run("required field on present input object provided with wrong type", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":123}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("dev_mode required field on present input object not provided", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($input: Foo!) { hello(arg: $input) }`, variables: `{"input":{}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) - t.Run("required string field on input object provided with null", func(t *testing.T) { + t.Run("required field on present input object not provided", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) + }) + + t.Run("dev_mode required string field on input object provided with null", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($input: Foo!) { hello(arg: $input) }`, variables: `{"input":{"bar":null}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {"bar":null}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) + + t.Run("required string field on input object provided with null", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":null}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) + }) - t.Run("required string field on input object provided with Int", func(t *testing.T) { + t.Run("dev_mode required string field on input object provided with Int", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($input: Foo!) { hello(arg: $input) }`, variables: `{"input":{"bar":123}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value 123 at "input.bar"; String cannot represent a non string value: 123`, err.Error()) }) - t.Run("required string field on input object provided with Float", func(t *testing.T) { + t.Run("required string field on input object provided with Int", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":123}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("dev_mode required string field on input object provided with Float", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($input: Foo!) { hello(arg: $input) }`, variables: `{"input":{"bar":123.456}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value 123.456 at "input.bar"; String cannot represent a non string value: 123.456`, err.Error()) }) - t.Run("required string field on input object provided with Boolean", func(t *testing.T) { + t.Run("required string field on input object provided with Float", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":123.456}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("dev_mode required string field on input object provided with Boolean", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($input: Foo!) { hello(arg: $input) }`, variables: `{"input":{"bar":true}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value true at "input.bar"; String cannot represent a non string value: true`, err.Error()) }) - t.Run("required string field on nested input object not provided", func(t *testing.T) { + t.Run("required string field on input object provided with Boolean", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":true}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("dev_mode required string field on nested input object not provided", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, operation: `query Foo($input: Bar!) { hello(arg: $input) }`, variables: `{"input":{"foo":{}}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {"foo":{}}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) - t.Run("required string field on nested input object provided with null", func(t *testing.T) { + t.Run("required string field on nested input object not provided", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":{}}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) + }) + + t.Run("dev_mode required string field on nested input object provided with null", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, operation: `query Foo($input: Bar!) { hello(arg: $input) }`, variables: `{"input":{"foo":{"bar":null}}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {"foo":{"bar":null}}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) - t.Run("required string field on nested input object provided with Int", func(t *testing.T) { + t.Run("required string field on nested input object provided with null", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":{"bar":null}}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) + }) + + t.Run("dev_mode required string field on nested input object provided with Int", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, operation: `query Foo($input: Bar!) { hello(arg: $input) }`, variables: `{"input":{"foo":{"bar":123}}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value 123 at "input.foo.bar"; String cannot represent a non string value: 123`, err.Error()) }) - t.Run("required string field on nested input object array provided with Int", func(t *testing.T) { + t.Run("required string field on nested input object provided with Int", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":{"bar":123}}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("dev_mode required string field on nested input object array provided with Int", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } input Bar { foo: [Foo!]! } type Query { hello(arg: Bar!): String }`, operation: `query Foo($input: Bar!) { hello(arg: $input) }`, variables: `{"input":{"foo":[{"bar":123}]}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value 123 at "input.foo.[0].bar"; String cannot represent a non string value: 123`, err.Error()) }) - t.Run("required string field on nested input object array index 1 provided with Int", func(t *testing.T) { + t.Run("required string field on nested input object array provided with Int", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: [Foo!]! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":[{"bar":123}]}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo.[0].bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("dev_mode required string field on nested input object array index 1 provided with Int", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } input Bar { foo: [Foo!]! } type Query { hello(arg: Bar!): String }`, operation: `query Foo($input: Bar!) { hello(arg: $input) }`, variables: `{"input":{"foo":[{"bar":"hello"},{"bar":123}]}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value 123 at "input.foo.[1].bar"; String cannot represent a non string value: 123`, err.Error()) }) - t.Run("non existing field on nested input object", func(t *testing.T) { + t.Run("required string field on nested input object array index 1 provided with Int", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: [Foo!]! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":[{"bar":"hello"},{"bar":123}]}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo.[1].bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("dev_mode non existing field on nested input object", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, operation: `query Foo($input: Bar!) { hello(arg: $input) }`, variables: `{"input":{"foo":{"bar":"hello","baz":"world"}}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {"foo":{"bar":"hello","baz":"world"}} at "input.foo"; Field "baz" is not defined by type "Foo".`, err.Error()) }) + t.Run("non existing field on nested input object", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":{"bar":"hello","baz":"world"}}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo"; Field "baz" is not defined by type "Foo".`, err.Error()) + }) + t.Run("required enum argument provided with correct value", func(t *testing.T) { + devMode := false tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":"BAR"}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) - t.Run("required enum argument provided with wrong value", func(t *testing.T) { + t.Run("dev_mode required enum argument provided with wrong value", func(t *testing.T) { + devMode := true tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":"BAZ"}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value "BAZ"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) }) - t.Run("required enum argument provided with Int value", func(t *testing.T) { + t.Run("required enum argument provided with wrong value", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":"BAZ"}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Value does not exist in "Foo" enum.`, err.Error()) + }) + + t.Run("dev_mode required enum argument provided with Int value", func(t *testing.T) { + devMode := true tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":123}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value 123; Enum "Foo" cannot represent non-string value: 123.`, err.Error()) }) + t.Run("required enum argument provided with Int value", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Enum "Foo" cannot represent non-string value.`, err.Error()) + }) + t.Run("required enum argument provided with null", func(t *testing.T) { + devMode := false tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":null}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value null; Expected non-nullable type "Foo!" not to be null.`, err.Error()) }) - t.Run("required nested enum argument provided with null", func(t *testing.T) { + t.Run("dev_mode required nested enum argument provided with null", func(t *testing.T) { + devMode := true tc := testCase{ schema: `enum Foo { BAR } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, operation: `query Foo($input: Bar!) { hello(arg: $input) }`, variables: `{"input":{"foo":null}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {"foo":null}; Field "foo" of required type "Foo!" was not provided.`, err.Error()) }) + t.Run("required nested enum argument provided with null", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `enum Foo { BAR } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":null}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value; Field "foo" of required type "Foo!" was not provided.`, err.Error()) + }) + t.Run("required nested enum argument provided with correct value", func(t *testing.T) { + devMode := false tc := testCase{ schema: `enum Foo { BAR } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, operation: `query Foo($input: Bar!) { hello(arg: $input) }`, variables: `{"input":{"foo":"BAR"}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) - t.Run("required nested enum argument provided with wrong value", func(t *testing.T) { + t.Run("dev_mode required nested enum argument provided with wrong value", func(t *testing.T) { + devMode := true tc := testCase{ schema: `enum Foo { BAR } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, operation: `query Foo($input: Bar!) { hello(arg: $input) }`, variables: `{"input":{"foo":"BAZ"}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {"foo":"BAZ"} at "input.foo"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) }) + t.Run("required nested enum argument provided with wrong value", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `enum Foo { BAR } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":"BAZ"}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo"; Value does not exist in "Foo" enum.`, err.Error()) + }) + t.Run("optional enum argument provided with null", func(t *testing.T) { + devMode := false tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo): String }`, operation: `query Foo($bar: Foo) { hello(arg: $bar) }`, variables: `{"bar":null}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("optional nested enum argument provided with null", func(t *testing.T) { + devMode := false tc := testCase{ schema: `enum Foo { BAR } input Bar { foo: Foo } type Query { hello(arg: Bar): String }`, operation: `query Foo($input: Bar) { hello(arg: $input) }`, variables: `{"input":{"foo":null}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) - t.Run("optional nested enum argument provided with incorrect value", func(t *testing.T) { + t.Run("dev_mode optional nested enum argument provided with incorrect value", func(t *testing.T) { + devMode := true tc := testCase{ schema: `enum Foo { BAR } input Bar { foo: Foo } type Query { hello(arg: Bar): String }`, operation: `query Foo($input: Bar) { hello(arg: $input) }`, variables: `{"input":{"foo":"BAZ"}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {"foo":"BAZ"} at "input.foo"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) }) + t.Run("optional nested enum argument provided with incorrect value", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `enum Foo { BAR } input Bar { foo: Foo } type Query { hello(arg: Bar): String }`, + operation: `query Foo($input: Bar) { hello(arg: $input) }`, + variables: `{"input":{"foo":"BAZ"}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo"; Value does not exist in "Foo" enum.`, err.Error()) + }) + t.Run("optional enum argument provided with correct value", func(t *testing.T) { + devMode := false tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo): String }`, operation: `query Foo($bar: Foo) { hello(arg: $bar) }`, variables: `{"bar":"BAR"}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) - t.Run("optional enum argument provided with wrong value", func(t *testing.T) { + t.Run("dev_mode optional enum argument provided with wrong value", func(t *testing.T) { + devMode := true tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo): String }`, operation: `query Foo($bar: Foo) { hello(arg: $bar) }`, variables: `{"bar":"BAZ"}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value "BAZ"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) }) + t.Run("optional enum argument provided with wrong value", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `enum Foo { BAR } type Query { hello(arg: Foo): String }`, + operation: `query Foo($bar: Foo) { hello(arg: $bar) }`, + variables: `{"bar":"BAZ"}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Value does not exist in "Foo" enum.`, err.Error()) + }) + t.Run("required string list field argument provided with null", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: [String]!): String }`, operation: `query Foo($bar: [String]!) { hello(arg: $bar) }`, variables: `{"bar":null}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value null; Expected non-nullable type "[String]!" not to be null.`, err.Error()) }) - t.Run("required string list field argument provided with non list Int value", func(t *testing.T) { + t.Run("dev_mode required string list field argument provided with non list Int value", func(t *testing.T) { + devMode := true tc := testCase{ schema: `type Query { hello(arg: [String]!): String }`, operation: `query Foo($bar: [String]!) { hello(arg: $bar) }`, variables: `{"bar":123}`, withNormalization: true, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0]"; String cannot represent a non string value: 123`, err.Error()) }) + t.Run("required string list field argument provided with non list Int value", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `type Query { hello(arg: [String]!): String }`, + operation: `query Foo($bar: [String]!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + withNormalization: true, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value at "bar.[0]"; String cannot represent a non string value`, err.Error()) + }) + t.Run("required string argument on input object list provided with correct value", func(t *testing.T) { + devMode := false tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: [Foo!]!): String }`, operation: `query Foo($bar: [Foo!]!) { hello(arg: $bar) }`, variables: `{"bar":[{"bar":"hello"}]}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("required string argument on input object list provided with wrong value", func(t *testing.T) { + devMode := false tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: [Foo!]!): String }`, operation: `query Foo($bar: [Foo!]!) { hello(arg: $bar) }`, variables: `{"bar":[{"bar":123}]}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value at "bar.[0].bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("dev_mode required string argument on input object list provided with wrong value", func(t *testing.T) { + devMode := true + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: [Foo!]!): String }`, + operation: `query Foo($bar: [Foo!]!) { hello(arg: $bar) }`, + variables: `{"bar":[{"bar":123}]}`, + } + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0].bar"; String cannot represent a non string value: 123`, err.Error()) }) t.Run("required string argument provided with string list", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(arg: String!): String }`, operation: `query Foo($bar: String!) { hello(arg: $bar) }`, variables: `{"bar":["hello"]}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value ["hello"]; String cannot represent a non string value: ["hello"]`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; String cannot represent a non string value`, err.Error()) }) - t.Run("required input object list field argument provided with non list Int value", func(t *testing.T) { + t.Run("dev_mode required input object list field argument provided with non list Int value", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: [Foo!]!): String }`, operation: `query Foo($bar: [Foo!]!) { hello(arg: $bar) }`, variables: `{"bar":123}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value 123; Expected type "Foo" to be an object.`, err.Error()) }) - t.Run("required input object field argument provided with list input object value", func(t *testing.T) { + t.Run("required input object list field argument provided with non list Int value", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: [Foo!]!): String }`, + operation: `query Foo($bar: [Foo!]!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Expected type "Foo" to be an object.`, err.Error()) + }) + + t.Run("dev_mode required input object field argument provided with list input object value", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, variables: `{"bar":[{"bar":"hello"}]}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value [{"bar":"hello"}]; Expected type "Foo" to be an object.`, err.Error()) }) - t.Run("required enum list argument provided with non list Int value", func(t *testing.T) { + t.Run("required input object field argument provided with list input object value", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":[{"bar":"hello"}]}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value; Expected type "Foo" to be an object.`, err.Error()) + }) + + t.Run("dev_mode required enum list argument provided with non list Int value", func(t *testing.T) { + devMode := true tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: [Foo]!): String }`, operation: `query Foo($bar: [Foo]!) { hello(arg: $bar) }`, variables: `{"bar":123}`, withNormalization: true, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0]"; Enum "Foo" cannot represent non-string value: 123.`, err.Error()) }) - t.Run("required string list field argument provided with Int", func(t *testing.T) { + t.Run("required enum list argument provided with non list Int value", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `enum Foo { BAR } type Query { hello(arg: [Foo]!): String }`, + operation: `query Foo($bar: [Foo]!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + withNormalization: true, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value at "bar.[0]"; Enum "Foo" cannot represent non-string value.`, err.Error()) + }) + + t.Run("dev_mode required string list field argument provided with Int", func(t *testing.T) { + devMode := true tc := testCase{ schema: `type Query { hello(arg: [String]!): String }`, operation: `query Foo($bar: [String]!) { hello(arg: $bar) }`, variables: `{"bar":123}`, withNormalization: true, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0]"; String cannot represent a non string value: 123`, err.Error()) }) + t.Run("required string list field argument provided with Int", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `type Query { hello(arg: [String]!): String }`, + operation: `query Foo($bar: [String]!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + withNormalization: true, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value at "bar.[0]"; String cannot represent a non string value`, err.Error()) + }) + t.Run("optional nested list argument provided with null", func(t *testing.T) { + devMode := false tc := testCase{ schema: `input Foo { bars : [String] bat: Int! } type Query { hello(arg: Foo): String }`, operation: `query Foo($input: Foo) { hello(arg: $input) }`, variables: `{"input":{"bars":null,"bat":1}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("optional nested list argument provided with empty list", func(t *testing.T) { + devMode := false tc := testCase{ schema: `input Foo { bars : [String] bat: Int! } type Query { hello(arg: Foo): String }`, operation: `query Foo($input: Foo) { hello(arg: $input) }`, variables: `{"input":{"bars":[],"bat":1}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) - t.Run("optional nested list argument provided with empty list and missing Int", func(t *testing.T) { + t.Run("dev_mode optional nested list argument provided with empty list and missing Int", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bars : [String] bat: Int! } type Query { hello(arg: Foo): String }`, operation: `query Foo($input: Foo) { hello(arg: $input) }`, variables: `{"input":{"bars":[]}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value {"bars":[]}; Field "bat" of required type "Int!" was not provided.`, err.Error()) }) - t.Run("optional nested field is null followed by required nested field of wrong type", func(t *testing.T) { + t.Run("optional nested list argument provided with empty list and missing Int", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bars : [String] bat: Int! } type Query { hello(arg: Foo): String }`, + operation: `query Foo($input: Foo) { hello(arg: $input) }`, + variables: `{"input":{"bars":[]}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value; Field "bat" of required type "Int!" was not provided.`, err.Error()) + }) + + t.Run("dev_mode optional nested field is null followed by required nested field of wrong type", func(t *testing.T) { + devMode := true tc := testCase{ schema: `input Foo { bar: String! } input Bar { foo: Foo bat: Int! } type Query { hello(arg: Bar): String }`, operation: `query Foo($input: Bar) { hello(arg: $input) }`, variables: `{"input":{"foo":null,"bat":"hello"}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value "hello" at "input.bat"; Int cannot represent non-integer value: "hello"`, err.Error()) }) + t.Run("optional nested field is null followed by required nested field of wrong type", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo bat: Int! } type Query { hello(arg: Bar): String }`, + operation: `query Foo($input: Bar) { hello(arg: $input) }`, + variables: `{"input":{"foo":null,"bat":"hello"}}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.bat"; Int cannot represent non-integer value`, err.Error()) + }) + t.Run("input field is a double nested list", func(t *testing.T) { + devMode := false tc := testCase{ schema: `input Filter { option: String! } input FilterWrapper { filters: [[Filter!]!] } type Query { hello(filter: FilterWrapper): String }`, operation: `query Foo($input: FilterWrapper) { hello(filter: $input) }`, variables: `{"input":{"filters":[[{"option": "a"}]]}}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("variable of double nested list type", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(filter: [[String]]): String }`, operation: `query Foo($input: [[String]]) { hello(filter: $input) }`, variables: `{"input":[["value"]]}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) - t.Run("triple nested value into variable of double nested list type", func(t *testing.T) { + t.Run("dev_mode triple nested value into variable of double nested list type", func(t *testing.T) { + devMode := true tc := testCase{ schema: `type Query { hello(filter: [[String]]): String }`, operation: `query Foo($input: [[String]]) { hello(filter: $input) }`, variables: `{"input":[[["value"]]]}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.Error(t, err) assert.Equal(t, `Variable "$input" got invalid value ["value"] at "input.[0].[0]"; String cannot represent a non string value: ["value"]`, err.Error()) }) + t.Run("triple nested value into variable of double nested list type", func(t *testing.T) { + devMode := false + tc := testCase{ + schema: `type Query { hello(filter: [[String]]): String }`, + operation: `query Foo($input: [[String]]) { hello(filter: $input) }`, + variables: `{"input":[[["value"]]]}`, + } + err := runTest(t, tc, devMode) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value at "input.[0].[0]"; String cannot represent a non string value`, err.Error()) + }) + t.Run("null into non required list value", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(filter: [String]): String }`, operation: `query Foo($input: [String]) { hello(filter: $input) }`, variables: `{"input":[null]}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("value and null into non required list value", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(filter: [String]): String }`, operation: `query Foo($input: [String]) { hello(filter: $input) }`, variables: `{"input":["ok", null]}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("null into non required value", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(filter: String): String }`, operation: `query Foo($input: String) { hello(filter: $input) }`, variables: `{"input":null}`, } - err := runTest(t, tc) + err := runTest(t, tc, devMode) require.NoError(t, err) }) t.Run("extension code is propagated with apollo compatibility flag", func(t *testing.T) { + devMode := false tc := testCase{ schema: `type Query { hello(filter: String!): String }`, operation: `query Foo($input: String!) { hello(filter: $input) }`, @@ -789,7 +1298,7 @@ func TestVariablesValidation(t *testing.T) { ApolloCompatibilityFlags: apollocompatibility.Flags{ ReplaceInvalidVarError: true, }, - }) + }, devMode) assert.Equal(t, &InvalidVariableError{ ExtensionCode: errorcodes.BadUserInput, Message: `Variable "$input" got invalid value null; Expected non-nullable type "String!" not to be null.`, @@ -802,11 +1311,11 @@ type testCase struct { withNormalization bool } -func runTest(t *testing.T, tc testCase) error { - return runTestWithOptions(t, tc, VariablesValidatorOptions{}) +func runTest(t *testing.T, tc testCase, devMode bool) error { + return runTestWithOptions(t, tc, VariablesValidatorOptions{}, devMode) } -func runTestWithOptions(t *testing.T, tc testCase, options VariablesValidatorOptions) error { +func runTestWithOptions(t *testing.T, tc testCase, options VariablesValidatorOptions, devMode bool) error { t.Helper() def := unsafeparser.ParseGraphqlDocumentString(tc.schema) op := unsafeparser.ParseGraphqlDocumentString(tc.operation) @@ -824,7 +1333,7 @@ func runTestWithOptions(t *testing.T, tc testCase, options VariablesValidatorOpt } } validator := NewVariablesValidator(options) - return validator.Validate(&op, &def, op.Input.Variables) + return validator.Validate(&op, &def, op.Input.Variables, devMode) } var inputSchema = `