Skip to content

Commit

Permalink
Add another test
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-tengler committed May 23, 2024
1 parent b95b0ed commit 50bc985
Show file tree
Hide file tree
Showing 2 changed files with 333 additions and 5 deletions.
63 changes: 58 additions & 5 deletions src/HotChocolate/Fusion/test/Core.Tests/DemoIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1683,7 +1683,7 @@ public async Task Require_Data_In_Context_4()
var request = Parse(
"""
query Requires {
# All requested fields are available in the Shipping subgraph
# All requested fields are available in the Shipping subgraph.
productById(id: "UHJvZHVjdDox") {
deliveryEstimate(zip: "12345") {
min
Expand All @@ -1695,10 +1695,63 @@ query Requires {

// act
await using var result = await executor.ExecuteAsync(
QueryRequestBuilder
.New()
.SetQuery(request)
.Create());
OperationRequestBuilder
.Create()
.SetDocument(request)
.Build());

// assert
var snapshot = new Snapshot();
CollectSnapshotData(snapshot, request, result, fusionGraph);
await snapshot.MatchMarkdownAsync();

Assert.Null(result.ExpectQueryResult().Errors);
}

[Fact]
public async Task Require_Data_In_Context_5()
{
// arrange
using var demoProject = await DemoProject.CreateAsync();

// act
var fusionGraph = await new FusionGraphComposer(logFactory: _logFactory).ComposeAsync(
new[]
{
demoProject.Products.ToConfiguration(ProductsExtensionSdl),
demoProject.Shipping.ToConfiguration(ShippingExtensionSdl),
},
new FusionFeatureCollection(FusionFeatures.NodeField));

var executor = await new ServiceCollection()
.AddSingleton(demoProject.HttpClientFactory)
.AddSingleton(demoProject.WebSocketConnectionFactory)
.AddFusionGatewayServer()
.ConfigureFromDocument(SchemaFormatter.FormatAsDocument(fusionGraph))
.BuildRequestExecutorAsync();

var request = Parse(
"""
query Requires {
productById(id: "UHJvZHVjdDox") {
# This field and the requirements for deliveryEstimate
# are coming from the Products subgraph,
# while all other fields are coming from the Shipping subgraph.
name
deliveryEstimate(zip: "12345") {
min
max
}
}
}
""");

// act
await using var result = await executor.ExecuteAsync(
OperationRequestBuilder
.Create()
.SetDocument(request)
.Build());

// assert
var snapshot = new Snapshot();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
# Require_Data_In_Context_5

## User Request

```graphql
query Requires {
productById(id: "UHJvZHVjdDox") {
name
deliveryEstimate(zip: "12345") {
min
max
}
}
}
```

## Result

```json
{
"errors": [
{
"message": "The argument `weight` is required.",
"locations": [
{
"line": 1,
"column": 54
}
],
"path": [
"productById"
],
"extensions": {
"type": "Product",
"field": "deliveryEstimate",
"argument": "weight",
"specifiedBy": "https://spec.graphql.org/October2021/#sec-Required-Arguments"
}
},
{
"message": "The argument `size` is required.",
"locations": [
{
"line": 1,
"column": 54
}
],
"path": [
"productById"
],
"extensions": {
"type": "Product",
"field": "deliveryEstimate",
"argument": "size",
"specifiedBy": "https://spec.graphql.org/October2021/#sec-Required-Arguments"
}
}
],
"data": {
"productById": null
}
}
```

## QueryPlan

```json
{
"document": "query Requires { productById(id: \u0022UHJvZHVjdDox\u0022) { name deliveryEstimate(zip: \u002212345\u0022) { min max } } }",
"operation": "Requires",
"rootNode": {
"type": "Sequence",
"nodes": [
{
"type": "Resolve",
"subgraph": "Shipping",
"document": "query Requires_1 { productById(id: \u0022UHJvZHVjdDox\u0022) { deliveryEstimate(zip: \u002212345\u0022) { min max } __fusion_exports__1: id } }",
"selectionSetId": 0,
"provides": [
{
"variable": "__fusion_exports__1"
}
]
},
{
"type": "Compose",
"selectionSetIds": [
0
]
},
{
"type": "Resolve",
"subgraph": "Products",
"document": "query Requires_2($__fusion_exports__1: ID!) { productById(id: $__fusion_exports__1) { name } }",
"selectionSetId": 1,
"path": [
"productById"
],
"requires": [
{
"variable": "__fusion_exports__1"
}
]
},
{
"type": "Compose",
"selectionSetIds": [
1
]
}
]
},
"state": {
"__fusion_exports__1": "Product_id"
}
}
```

## QueryPlan Hash

```text
64787216D94A2AE96B22A3ACEFEB98C5A9262F0F
```

## Fusion Graph

```graphql
schema
@fusion(version: 1)
@transport(subgraph: "Products", location: "http:\/\/localhost:5000\/graphql", kind: "HTTP")
@transport(subgraph: "Products", location: "ws:\/\/localhost:5000\/graphql", kind: "WebSocket")
@transport(subgraph: "Shipping", location: "http:\/\/localhost:5000\/graphql", kind: "HTTP")
@transport(subgraph: "Shipping", location: "ws:\/\/localhost:5000\/graphql", kind: "WebSocket")
@node(subgraph: "Products", types: [ "Product" ]) {
query: Query
mutation: Mutation
}

type Query {
"Fetches an object given its ID."
node("ID of the object." id: ID!): Node
@variable(subgraph: "Products", name: "id", argument: "id")
@resolver(subgraph: "Products", select: "{ node(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
"Lookup nodes by a list of IDs."
nodes("The list of node IDs." ids: [ID!]!): [Node]!
@variable(subgraph: "Products", name: "ids", argument: "ids")
@resolver(subgraph: "Products", select: "{ nodes(ids: $ids) }", arguments: [ { name: "ids", type: "[ID!]!" } ])
productById(id: ID!): Product
@variable(subgraph: "Products", name: "id", argument: "id")
@resolver(subgraph: "Products", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
@variable(subgraph: "Shipping", name: "id", argument: "id")
@resolver(subgraph: "Shipping", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
topProducts(first: Int!): [Product!]!
@variable(subgraph: "Products", name: "first", argument: "first")
@resolver(subgraph: "Products", select: "{ topProducts(first: $first) }", arguments: [ { name: "first", type: "Int!" } ])
}

type Mutation {
uploadMultipleProductPictures(input: UploadMultipleProductPicturesInput!): UploadMultipleProductPicturesPayload!
@variable(subgraph: "Products", name: "input", argument: "input")
@resolver(subgraph: "Products", select: "{ uploadMultipleProductPictures(input: $input) }", arguments: [ { name: "input", type: "UploadMultipleProductPicturesInput!" } ])
uploadProductPicture(input: UploadProductPictureInput!): UploadProductPicturePayload!
@variable(subgraph: "Products", name: "input", argument: "input")
@resolver(subgraph: "Products", select: "{ uploadProductPicture(input: $input) }", arguments: [ { name: "input", type: "UploadProductPictureInput!" } ])
}

type DeliveryEstimate {
max: Int!
@source(subgraph: "Shipping")
min: Int!
@source(subgraph: "Shipping")
}

type Product implements Node
@variable(subgraph: "Products", name: "Product_id", select: "id")
@variable(subgraph: "Shipping", name: "Product_id", select: "id")
@resolver(subgraph: "Products", select: "{ productById(id: $Product_id) }", arguments: [ { name: "Product_id", type: "ID!" } ])
@resolver(subgraph: "Shipping", select: "{ productById(id: $Product_id) }", arguments: [ { name: "Product_id", type: "ID!" } ])
@resolver(subgraph: "Products", select: "{ nodes(ids: $Product_id) { ... on Product { ... Product } } }", arguments: [ { name: "Product_id", type: "[ID!]!" } ], kind: "BATCH") {
deliveryEstimate(zip: String!): DeliveryEstimate!
@source(subgraph: "Shipping")
@variable(subgraph: "Shipping", name: "zip", argument: "zip")
@variable(subgraph: "Products", name: "Product_dimension_size", select: "dimension { size }")
@variable(subgraph: "Products", name: "Product_dimension_weight", select: "dimension { weight }")
@resolver(subgraph: "Shipping", select: "{ deliveryEstimate(size: $Product_dimension_size, weight: $Product_dimension_weight, zip: $zip) }", arguments: [ { name: "Product_dimension_size", type: "Int!" }, { name: "Product_dimension_weight", type: "Int!" }, { name: "zip", type: "String!" } ])
dimension: ProductDimension!
@source(subgraph: "Products")
id: ID!
@source(subgraph: "Products")
@source(subgraph: "Shipping")
name: String!
@source(subgraph: "Products")
price: Int!
@source(subgraph: "Products")
repeat(num: Int!): Int!
@source(subgraph: "Products")
@variable(subgraph: "Products", name: "num", argument: "num")
repeatData(data: SomeDataInput!): SomeData!
@source(subgraph: "Products")
@variable(subgraph: "Products", name: "data", argument: "data")
weight: Int!
@source(subgraph: "Products")
}

type ProductDimension {
size: Int!
@source(subgraph: "Products")
weight: Int!
@source(subgraph: "Products")
}

type ProductNotFoundError implements Error {
message: String!
@source(subgraph: "Products")
productId: Int!
@source(subgraph: "Products")
}

type SomeData {
data: SomeData
@source(subgraph: "Products")
num: Int
@source(subgraph: "Products")
}

type UploadMultipleProductPicturesPayload {
boolean: Boolean
@source(subgraph: "Products")
errors: [UploadMultipleProductPicturesError!]
@source(subgraph: "Products")
}

type UploadProductPicturePayload {
boolean: Boolean
@source(subgraph: "Products")
errors: [UploadProductPictureError!]
@source(subgraph: "Products")
}

interface Error {
message: String!
}

"The node interface is implemented by entities that have a global unique identifier."
interface Node {
id: ID!
}

union UploadMultipleProductPicturesError = ProductNotFoundError

union UploadProductPictureError = ProductNotFoundError

input ProductIdWithUploadInput {
file: Upload!
productId: Int!
}

input SomeDataInput {
data: SomeDataInput
num: Int
}

input UploadMultipleProductPicturesInput {
products: [ProductIdWithUploadInput!]!
}

input UploadProductPictureInput {
file: Upload!
productId: Int!
}

"The `Upload` scalar type represents a file upload."
scalar Upload
```

0 comments on commit 50bc985

Please sign in to comment.