From 859c99cde30ccb13375dd631604ff9dc4f4c0c49 Mon Sep 17 00:00:00 2001 From: VectorTetra Date: Thu, 2 Jan 2025 20:00:44 +0200 Subject: [PATCH] Added tests for input records list and fixed missing `ExecuteInput` on input objects (#509) * Added tests for input records list * Fixed crash of input objects list creation from variables --------- Co-authored-by: Andrii Chebukin --- src/FSharp.Data.GraphQL.Server/Values.fs | 7 + .../FSharp.Data.GraphQL.Tests.fsproj | 1 + .../InputRecordListTests.fs | 191 ++++++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordListTests.fs diff --git a/src/FSharp.Data.GraphQL.Server/Values.fs b/src/FSharp.Data.GraphQL.Server/Values.fs index 4a4af46a..8f93e8bf 100644 --- a/src/FSharp.Data.GraphQL.Server/Values.fs +++ b/src/FSharp.Data.GraphQL.Server/Values.fs @@ -317,6 +317,13 @@ let rec internal compileByType | _ -> Ok null | List (Input innerDef) -> + match innerDef with + | InputObject inputObjDef + | Nullable (InputObject inputObjDef) -> + let inner = compileByType inputObjectPath inputSource (inputDef, innerDef) + inputObjDef.ExecuteInput <- inner + | _ -> () + let isArray = inputDef.Type.IsArray // TODO: Improve creation of inner let inner index = compileByType ((box index) :: inputObjectPath) inputSource (innerDef, innerDef) diff --git a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj index c5613e69..10ef9d6c 100644 --- a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj +++ b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj @@ -55,6 +55,7 @@ + diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordListTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordListTests.fs new file mode 100644 index 00000000..149005f0 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordListTests.fs @@ -0,0 +1,191 @@ +// The MIT License (MIT) + +module FSharp.Data.GraphQL.Tests.InputRecordListTests + +#nowarn "25" + +open Xunit +open System.Collections.Immutable +open System.Text.Json +open System.Text.Json.Serialization + +open FSharp.Data.GraphQL +open FSharp.Data.GraphQL.Types +open FSharp.Data.GraphQL.Parser +open FSharp.Data.GraphQL.Tests.InputRecordTests + +let schema verify = + let schema = + Schema ( + query = + Define.Object ( + "Query", + [ Define.Field ( + "recordInputs", + StringType, + [ Define.Input ("records", ListOf InputRecordType) + Define.Input ("recordsOptional", Nullable (ListOf (Nullable InputRecordOptionalType))) + Define.Input ("recordsNested", ListOf (Nullable InputRecordNestedType)) ], + (fun ctx name -> + let _ = ctx.Arg "records" + let _ = ctx.TryArg> "recordsOptional" + let recordNested = + ctx.Arg> "recordsNested" + |> List.tryHead + |> Option.flatten + match verify, recordNested with + | Nothing, _ -> () + | AllInclude, Some recordNested -> recordNested.s |> ValueOption.iter _.VerifyAllInclude + | AllSkip, Some recordNested -> recordNested.s |> ValueOption.iter _.VerifyAllSkip + | SkipAndIncludeNull, Some recordNested -> recordNested.s |> ValueOption.iter _.VerifySkipAndIncludeNull + | _ -> () + stringifyInput ctx name + ) + ) // TODO: add all args stringificaiton + Define.Field ( + "objectInputs", + StringType, + [ Define.Input ("object", InputObjectType) + Define.Input ("objectOptional", Nullable InputObjectOptionalType) ], + stringifyInput + ) ] // TODO: add all args stringificaiton + ) + ) + + Executor schema + +[] +let ``Execute handles creation of inline empty input records list`` () = + let query = + """{ + recordInputs( + records: [], + recordsOptional: [], + recordsNested: [] + ) + }""" + let result = sync <| (schema AllInclude).AsyncExecute(parse query) + ensureDirect result <| fun data errors -> empty errors + +[] +let ``Execute handles creation of inline input records list with all fields`` () = + let query = + """{ + recordInputs( + records: [{ a: "a", b: "b", c: "c" }], + recordsOptional: [{ a: "a", b: "b", c: "c" }], + recordsNested: [{ + a: { a: "a", b: "b", c: "c" }, + b: { a: "a", b: "b", c: "c" }, + c: { a: "a", b: "b", c: "c" }, + s: { a: "a", b: "b", c: "c" }, + l: [{ a: "a", b: "b", c: "c" }] + }] + ) + }""" + let result = sync <| (schema AllInclude).AsyncExecute(parse query) + ensureDirect result <| fun data errors -> empty errors + +[] +let ``Execute handles creation of inline input records list with optional null fields`` () = + let query = + """{ + recordInputs( + records: [{ a: "a", b: "b", c: "c" }], + recordsOptional: [null], + recordsNested: [{ a: { a: "a", b: "b", c: "c" }, b: null, c: null, s: null, l: [] }] + ) + }""" + let result = sync <| (schema Nothing).AsyncExecute(parse query) + ensureDirect result <| fun data errors -> empty errors + +[] +let ``Execute handles creation of inline input records list with mandatory only fields`` () = + let query = + """{ + recordInputs( + records: [{ a: "a", b: "b", c: "c" }], + recordsNested: [{ a: { a: "a", b: "b", c: "c" }, l: [{ a: "a", b: "b", c: "c" }] }] + ) + }""" + let result = sync <| (schema Nothing).AsyncExecute(parse query) + ensureDirect result <| fun data errors -> empty errors + +let variablesWithAllInputs (record, optRecord, skippable) = + $""" + {{ + "records":[%s{record}], + "optRecords":[%s{optRecord}], + "nestedRecords":[ {{ "a": {record}, "b": {optRecord}, "c": {optRecord}, "s": {skippable}, "l": [{record}] }}] + }} +""" + +let paramsWithValues variables = + JsonDocument + .Parse(variables : string) + .RootElement.Deserialize> (serializerOptions) + +[] +let ``Execute handles creation of input records list from variables with all fields`` () = + let query = + """query ( + $records: [InputRecord!]!, + $optRecords: [InputRecordOptional], + $nestedRecords: [InputRecordNested]! + ) { + recordInputs( + records: $records, + recordsOptional: $optRecords, + recordsNested: $nestedRecords + ) + }""" + let testInputObject = """{"a":"a","b":"b","c":"c"}""" + let params' = + variablesWithAllInputs(testInputObject, testInputObject, testInputObject) |> paramsWithValues + let result = sync <| (schema AllInclude).AsyncExecute(parse query, variables = params') + //let expected = NameValueLookup.ofList [ "recordInputs", upcast testInputObject ] + ensureDirect result <| fun data errors -> + empty errors + //data |> equals (upcast expected) + +[] +let ``Execute handles creation of input records list from variables with optional null fields`` () = + let query = + """query ( + $records: [InputRecord!]!, + $optRecords: [InputRecordOptional], + $nestedRecords: [InputRecordNested]! + ) { + recordInputs( + records: $records, + recordsOptional: $optRecords, + recordsNested: $nestedRecords + ) + }""" + let testInputObject = """{"a":"a","b":"b","c":"c"}""" + let testInputSkippable = """{ "a": null, "b": null, "c": null }""" + let params' = variablesWithAllInputs(testInputObject, "null", testInputSkippable) |> paramsWithValues + let result = sync <| (schema SkipAndIncludeNull).AsyncExecute(parse query, variables = params') + ensureDirect result <| fun data errors -> empty errors + +[] +let ``Execute handles creation of input records from variables with mandatory only fields`` () = + let variablesWithAllInputs (record) = + $""" + {{ + "record":%s{record}, + "list":[%s{record}] + }} + """ + + let query = + """query ($record: InputRecord!, $list: [InputRecord!]!){ + recordInputs( + records: [$record], + recordsNested: [{ a: $record, l: $list }] + ) + }""" + let testInputObject = """{"a":"a","b":"b","c":"c"}""" + let params' = variablesWithAllInputs testInputObject |> paramsWithValues + let result = sync <| (schema AllSkip).AsyncExecute(parse query, variables = params') + ensureDirect result <| fun data errors -> empty errors