diff --git a/src/HotChocolate/Fusion/test/Core.Tests/UnionTests.cs b/src/HotChocolate/Fusion/test/Core.Tests/UnionTests.cs index 313d4f162b4..8d03ebabc05 100644 --- a/src/HotChocolate/Fusion/test/Core.Tests/UnionTests.cs +++ b/src/HotChocolate/Fusion/test/Core.Tests/UnionTests.cs @@ -408,6 +408,81 @@ ... on Item3 { Assert.False(subgraphB.HasReceivedRequest); } + [Fact] + public async Task Union_List_With_Differing_Union_Item_Dependencies_SameSelections() + { + // arrange + var subgraphA = await TestSubgraph.CreateAsync( + configure: builder => + { + builder + .AddQueryType() + .AddType() + .AddType() + .AddType() + .AddType() + .AddType() + .AddType() + .ModifyOptions(o => o.EnsureAllNodesCanBeResolved = false) + .AddGlobalObjectIdentification(); + }); + + var subgraphB = await TestSubgraph.CreateAsync( + configure: builder => + { + builder + .AddQueryType() + .AddType() + .AddType() + .AddGlobalObjectIdentification(); + }); + + using var subgraphs = new TestSubgraphCollection(output, [subgraphA, subgraphB]); + var executor = await subgraphs.GetExecutorAsync(); + var request = Parse(""" + query { + listOfUnion { + __typename + ... on Item1 { + something + product { + id + name + } + } + ... on Item2 { + other + product { + id + name + } + } + ... on Item3 { + another + review { + id + score + } + } + } + } + """); + + // act + var result = await executor.ExecuteAsync( + OperationRequestBuilder + .New() + .SetDocument(request) + .Build()); + + // assert + var snapshot = new Snapshot(); + CollectSnapshotData(snapshot, request, result); + await snapshot.MatchMarkdownAsync(); + // Ideally it would just be one request, but that's for another day... + Assert.Equal(3, subgraphB.NumberOfReceivedRequests); + } + [Fact] public async Task Union_List_With_Differing_Union_Item_Dependencies() { @@ -453,7 +528,6 @@ ... on Item1 { ... on Item2 { other product { - id name } } @@ -479,6 +553,8 @@ ... on Item3 { var snapshot = new Snapshot(); CollectSnapshotData(snapshot, request, result); await snapshot.MatchMarkdownAsync(); + // Ideally it would just be one request, but that's for another day... + Assert.Equal(3, subgraphB.NumberOfReceivedRequests); } [ObjectType("Query")] @@ -488,8 +564,8 @@ public ISomeUnion GetUnion(int item) { return item switch { - 1 => new SubgraphA_Item1("Something", new SubgraphA_Product(2)), - _ => new SubgraphA_Item3(true, new SubgraphA_Review(1)) + 1 => new SubgraphA_Item1("Something", new SubgraphA_Product(1)), + _ => new SubgraphA_Item3(true, new SubgraphA_Review(2)) }; } @@ -497,9 +573,12 @@ public List GetListOfUnion() { return [ - new SubgraphA_Item1("Something", new SubgraphA_Product(2)), - new SubgraphA_Item2(123, new SubgraphA_Product(4)), - new SubgraphA_Item3(true, new SubgraphA_Review(1)) + new SubgraphA_Item1("Something", new SubgraphA_Product(1)), + new SubgraphA_Item2(123, new SubgraphA_Product(2)), + new SubgraphA_Item3(true, new SubgraphA_Review(3)), + new SubgraphA_Item1("Something", new SubgraphA_Product(4)), + new SubgraphA_Item2(123, new SubgraphA_Product(5)), + new SubgraphA_Item3(true, new SubgraphA_Review(6)) ]; } } diff --git a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/DemoIntegrationTests.Test.md b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/DemoIntegrationTests.Test.md deleted file mode 100644 index 14a4c1b379e..00000000000 --- a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/DemoIntegrationTests.Test.md +++ /dev/null @@ -1,48 +0,0 @@ -# Test - -## Result - -```json -{ - "errors": [ - { - "message": "Unexpected Execution Error" - } - ] -} -``` - -## Request - -```graphql -{ - wrapper { - id - items { - __typename - ... on Item1 { - id - product { - id - name - } - } - ... on Item2 { - id - product { - id - name - } - } - ... on Item3 { - id - product { - id - name - } - } - } - } -} -``` - diff --git a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_List_With_Differing_Union_Item_Dependencies.md b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_List_With_Differing_Union_Item_Dependencies.md index cac74e6c808..182019a3c69 100644 --- a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_List_With_Differing_Union_Item_Dependencies.md +++ b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_List_With_Differing_Union_Item_Dependencies.md @@ -4,45 +4,56 @@ ```json { - "errors": [ - { - "message": "Cannot return null for non-nullable field.", - "locations": [ - { - "line": 15, - "column": 9 + "data": { + "listOfUnion": [ + { + "__typename": "Item1", + "something": "Something", + "product": { + "id": "UHJvZHVjdDox", + "name": "Product_1" } - ], - "path": [ - "listOfUnion", - 1, - "product", - "name" - ], - "extensions": { - "code": "HC0018" - } - }, - { - "message": "Cannot return null for non-nullable field.", - "locations": [ - { - "line": 15, - "column": 9 + }, + { + "__typename": "Item2", + "other": 123, + "product": { + "name": "Product_2" + } + }, + { + "__typename": "Item3", + "another": true, + "review": { + "id": "UmV2aWV3OjM=", + "score": 3 + } + }, + { + "__typename": "Item1", + "something": "Something", + "product": { + "id": "UHJvZHVjdDo0", + "name": "Product_4" + } + }, + { + "__typename": "Item2", + "other": 123, + "product": { + "name": "Product_5" + } + }, + { + "__typename": "Item3", + "another": true, + "review": { + "id": "UmV2aWV3OjY=", + "score": 1 } - ], - "path": [ - "listOfUnion", - 0, - "product", - "name" - ], - "extensions": { - "code": "HC0018" } - } - ], - "data": null + ] + } } ``` @@ -62,7 +73,6 @@ ... on Item2 { other product { - id name } } @@ -80,21 +90,21 @@ ## QueryPlan Hash ```text -51CA519135EDC9C49C71D22D7EF0562D417753EA +6F3D15770F165F5A7166C5598F4B1A7D6910A88D ``` ## QueryPlan ```json { - "document": "{ listOfUnion { __typename ... on Item1 { something product { id name } } ... on Item2 { other product { id name } } ... on Item3 { another review { id score } } } }", + "document": "{ listOfUnion { __typename ... on Item1 { something product { id name } } ... on Item2 { other product { name } } ... on Item3 { another review { id score } } } }", "rootNode": { "type": "Sequence", "nodes": [ { "type": "Resolve", "subgraph": "Subgraph_1", - "document": "query fetch_listOfUnion_1 { listOfUnion { __typename ... on Item3 { __typename another review { id __fusion_exports__1: id } } ... on Item2 { __typename other product { id __fusion_exports__2: id } } ... on Item1 { __typename something product { id __fusion_exports__2: id } } } }", + "document": "query fetch_listOfUnion_1 { listOfUnion { __typename ... on Item3 { __typename another review { id __fusion_exports__1: id } } ... on Item2 { __typename other product { __fusion_exports__2: id } } ... on Item1 { __typename something product { id __fusion_exports__3: id } } } }", "selectionSetId": 0, "provides": [ { @@ -102,6 +112,9 @@ }, { "variable": "__fusion_exports__2" + }, + { + "variable": "__fusion_exports__3" } ] }, @@ -145,14 +158,14 @@ { "type": "ResolveByKeyBatch", "subgraph": "Subgraph_2", - "document": "query fetch_listOfUnion_4($__fusion_exports__2: [ID!]!) { nodes(ids: $__fusion_exports__2) { ... on Product { name __fusion_exports__2: id } } }", - "selectionSetId": 5, + "document": "query fetch_listOfUnion_4($__fusion_exports__3: [ID!]!) { nodes(ids: $__fusion_exports__3) { ... on Product { name __fusion_exports__3: id } } }", + "selectionSetId": 6, "path": [ "nodes" ], "requires": [ { - "variable": "__fusion_exports__2" + "variable": "__fusion_exports__3" } ] } @@ -162,14 +175,16 @@ "type": "Compose", "selectionSetIds": [ 4, - 5 + 5, + 6 ] } ] }, "state": { "__fusion_exports__1": "Review_id", - "__fusion_exports__2": "Product_id" + "__fusion_exports__2": "Product_id", + "__fusion_exports__3": "Product_id" } } ``` diff --git a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_List_With_Differing_Union_Item_Dependencies_SameSelections.md b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_List_With_Differing_Union_Item_Dependencies_SameSelections.md new file mode 100644 index 00000000000..a4f8111a225 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_List_With_Differing_Union_Item_Dependencies_SameSelections.md @@ -0,0 +1,212 @@ +# Union_List_With_Differing_Union_Item_Dependencies_SameSelections + +## Result + +```json +{ + "errors": [ + { + "message": "Cannot return null for non-nullable field.", + "locations": [ + { + "line": 15, + "column": 9 + } + ], + "path": [ + "listOfUnion", + 4, + "product", + "name" + ], + "extensions": { + "code": "HC0018" + } + }, + { + "message": "Cannot return null for non-nullable field.", + "locations": [ + { + "line": 15, + "column": 9 + } + ], + "path": [ + "listOfUnion", + 3, + "product", + "name" + ], + "extensions": { + "code": "HC0018" + } + }, + { + "message": "Cannot return null for non-nullable field.", + "locations": [ + { + "line": 15, + "column": 9 + } + ], + "path": [ + "listOfUnion", + 1, + "product", + "name" + ], + "extensions": { + "code": "HC0018" + } + }, + { + "message": "Cannot return null for non-nullable field.", + "locations": [ + { + "line": 15, + "column": 9 + } + ], + "path": [ + "listOfUnion", + 0, + "product", + "name" + ], + "extensions": { + "code": "HC0018" + } + } + ], + "data": null +} +``` + +## Request + +```graphql +{ + listOfUnion { + __typename + ... on Item1 { + something + product { + id + name + } + } + ... on Item2 { + other + product { + id + name + } + } + ... on Item3 { + another + review { + id + score + } + } + } +} +``` + +## QueryPlan Hash + +```text +51CA519135EDC9C49C71D22D7EF0562D417753EA +``` + +## QueryPlan + +```json +{ + "document": "{ listOfUnion { __typename ... on Item1 { something product { id name } } ... on Item2 { other product { id name } } ... on Item3 { another review { id score } } } }", + "rootNode": { + "type": "Sequence", + "nodes": [ + { + "type": "Resolve", + "subgraph": "Subgraph_1", + "document": "query fetch_listOfUnion_1 { listOfUnion { __typename ... on Item3 { __typename another review { id __fusion_exports__1: id } } ... on Item2 { __typename other product { id __fusion_exports__2: id } } ... on Item1 { __typename something product { id __fusion_exports__2: id } } } }", + "selectionSetId": 0, + "provides": [ + { + "variable": "__fusion_exports__1" + }, + { + "variable": "__fusion_exports__2" + } + ] + }, + { + "type": "Compose", + "selectionSetIds": [ + 0 + ] + }, + { + "type": "Parallel", + "nodes": [ + { + "type": "ResolveByKeyBatch", + "subgraph": "Subgraph_2", + "document": "query fetch_listOfUnion_2($__fusion_exports__1: [ID!]!) { nodes(ids: $__fusion_exports__1) { ... on Review { score __fusion_exports__1: id } } }", + "selectionSetId": 4, + "path": [ + "nodes" + ], + "requires": [ + { + "variable": "__fusion_exports__1" + } + ] + }, + { + "type": "ResolveByKeyBatch", + "subgraph": "Subgraph_2", + "document": "query fetch_listOfUnion_3($__fusion_exports__2: [ID!]!) { nodes(ids: $__fusion_exports__2) { ... on Product { name __fusion_exports__2: id } } }", + "selectionSetId": 5, + "path": [ + "nodes" + ], + "requires": [ + { + "variable": "__fusion_exports__2" + } + ] + }, + { + "type": "ResolveByKeyBatch", + "subgraph": "Subgraph_2", + "document": "query fetch_listOfUnion_4($__fusion_exports__2: [ID!]!) { nodes(ids: $__fusion_exports__2) { ... on Product { name __fusion_exports__2: id } } }", + "selectionSetId": 5, + "path": [ + "nodes" + ], + "requires": [ + { + "variable": "__fusion_exports__2" + } + ] + } + ] + }, + { + "type": "Compose", + "selectionSetIds": [ + 4, + 5 + ] + } + ] + }, + "state": { + "__fusion_exports__1": "Review_id", + "__fusion_exports__2": "Product_id" + } +} +``` + diff --git a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_Two_Branches_With_Differing_Resolve_Nodes_Item1.md b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_Two_Branches_With_Differing_Resolve_Nodes_Item1.md index e3c3709ad37..3202ff88d96 100644 --- a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_Two_Branches_With_Differing_Resolve_Nodes_Item1.md +++ b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_Two_Branches_With_Differing_Resolve_Nodes_Item1.md @@ -8,8 +8,8 @@ "union": { "something": "Something", "product": { - "id": "UHJvZHVjdDoy", - "name": "Product_2" + "id": "UHJvZHVjdDox", + "name": "Product_1" } } } diff --git a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_Two_Branches_With_Differing_Resolve_Nodes_Item2.md b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_Two_Branches_With_Differing_Resolve_Nodes_Item2.md index 0d4bfca4cb7..3c7d274db82 100644 --- a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_Two_Branches_With_Differing_Resolve_Nodes_Item2.md +++ b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/UnionTests.Union_Two_Branches_With_Differing_Resolve_Nodes_Item2.md @@ -8,8 +8,8 @@ "union": { "another": true, "review": { - "id": "UmV2aWV3OjE=", - "score": 1 + "id": "UmV2aWV3OjI=", + "score": 2 } } } diff --git a/src/HotChocolate/Fusion/test/Shared/TestSubgraph.cs b/src/HotChocolate/Fusion/test/Shared/TestSubgraph.cs index 262d57a39cb..060feeddb0c 100644 --- a/src/HotChocolate/Fusion/test/Shared/TestSubgraph.cs +++ b/src/HotChocolate/Fusion/test/Shared/TestSubgraph.cs @@ -45,7 +45,7 @@ public static async Task CreateAsync( { app.Use(next => context => { - testContext.HasReceivedRequest = true; + testContext.NumberOfReceivedRequests++; return next(context); }); @@ -57,10 +57,12 @@ public static async Task CreateAsync( return new TestSubgraph(testServer, schema, testContext, extensions, isOffline); } - public bool HasReceivedRequest => Context.HasReceivedRequest; + public int NumberOfReceivedRequests => Context.NumberOfReceivedRequests; + + public bool HasReceivedRequest => Context.NumberOfReceivedRequests > 0; } public class SubgraphTestContext { - public bool HasReceivedRequest { get; set; } + public int NumberOfReceivedRequests { get; set; } }