From 6adf8883c9bd2715bbc762b6aa42c7f9df3e5693 Mon Sep 17 00:00:00 2001 From: dariuszkuc <9501705+dariuszkuc@users.noreply.github.com> Date: Wed, 3 Aug 2022 12:14:48 -0500 Subject: [PATCH] update @requires test to verify the functionality Updating `users` and `products` subgraph schemas to allow for testing the support of `@requires` functionality in the implementing subgraph. This is a breaking change as we modify the schema so all the existing implementations will be impacted. Changes: * `users` subgraph ```graphql type User @key(fields:"email") { email:ID! name: String totalProductsCreated: Int yearsOfEmployment: Int! # <-- new field } ``` * `products` subgraph ```graphql extend type User @key(fields: "email") { averageProductsCreatedPerYear: Int @requires(fields: "yearsOfEmployment") # <-- test `@requires` functionality email: ID! @external name: String @override(from: "users") totalProductsCreated: Int @external yearsOfEmployment: Int! @external # <-- field from the users subgraph used by @requires } ``` Updated implementations: - [x] apollo-server - [x] federation-jvm - [x] graphql-kotlin Related Issues: * resolves https://github.com/apollographql/apollo-federation-subgraph-compatibility/issues/128 --- README.md | 11 +++++++---- implementations/_template_hosted_/products.graphql | 6 ++++-- implementations/_template_library_/products.graphql | 6 ++++-- implementations/apollo-server/index.js | 1 + router.yaml | 2 +- src/tests/override.test.ts | 4 ++-- src/tests/requires.test.ts | 12 ++++++------ src/tests/shareable.test.ts | 4 ++-- src/utils/client.ts | 11 +++++++++++ subgraphs/users/index.js | 1 + subgraphs/users/users.graphql | 1 + supergraph.graphql | 2 ++ 12 files changed, 42 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 5ee92cebf..eda329e90 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ type User @key(fields: "email") { email: ID! name: String totalProductsCreated: Int + yearsOfEmployment: Int! } ``` @@ -244,9 +245,11 @@ extend type Query { } extend type User @key(fields: "email") { + averageProductsCreatedPerYear: Int @requires(fields: "yearsOfEmployment") email: ID! @external name: String @override(from: "users") totalProductsCreated: Int @external + yearsOfEmployment: Int! @external } ``` @@ -279,14 +282,14 @@ query ($representations: [_Any!]!) { - `@key` and `_entities` - multiple `@key` definitions, multiple-fields `@key` and a complex fields `@key`. - `@requires` - directive used to provide additional non-key information from one subgraph to the computed fields in another subgraph, should support defining complex fields - - This will be tested through a query covering [Product.delivery](http://product.delivery) where the library implementors `dimensions { size weight }` will need to be an expected `{ size: "1", weight: 1 }` to pass. Example query that will be sent directly to `products` subgraph. +- - This will be covered by the subgraph implementors at `Product.createdBy` where they will be expected to provide the `User.averageProductsCreatedPerYear` using `yearsOfEmployment` value provided by the `user` graph and the `totalProductsCreated` value from the implementing `products` subgraph. Example query that will be sent directly to `products` subgraph. ```graphql query ($id: ID!) { product(id: $id) { - dimensions { - size - weight + createdBy { + averageProductsCreatedPerYear + email } } } diff --git a/implementations/_template_hosted_/products.graphql b/implementations/_template_hosted_/products.graphql index da7f790c9..f2b806394 100644 --- a/implementations/_template_hosted_/products.graphql +++ b/implementations/_template_hosted_/products.graphql @@ -1,6 +1,6 @@ extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", - import: ["@key", "@shareable", "@provides", "@external", "@tag", "@extends", "@override", "@inaccessible"]) + import: ["@key", "@shareable", "@provides", "@external", "@tag", "@extends", "@override", "@inaccessible", "@requires"]) directive @override(from: String!) on FIELD_DEFINITION @@ -29,7 +29,9 @@ extend type Query { } extend type User @key(fields: "email") { + averageProductsCreatedPerYear: Int @requires(fields: "yearsOfEmployment") email: ID! @external - name: String @shareable @override(from: "users") + name: String @override(from: "users") totalProductsCreated: Int @external + yearsOfEmployment: Int! @external } diff --git a/implementations/_template_library_/products.graphql b/implementations/_template_library_/products.graphql index da7f790c9..f2b806394 100644 --- a/implementations/_template_library_/products.graphql +++ b/implementations/_template_library_/products.graphql @@ -1,6 +1,6 @@ extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", - import: ["@key", "@shareable", "@provides", "@external", "@tag", "@extends", "@override", "@inaccessible"]) + import: ["@key", "@shareable", "@provides", "@external", "@tag", "@extends", "@override", "@inaccessible", "@requires"]) directive @override(from: String!) on FIELD_DEFINITION @@ -29,7 +29,9 @@ extend type Query { } extend type User @key(fields: "email") { + averageProductsCreatedPerYear: Int @requires(fields: "yearsOfEmployment") email: ID! @external - name: String @shareable @override(from: "users") + name: String @override(from: "users") totalProductsCreated: Int @external + yearsOfEmployment: Int! @external } diff --git a/implementations/apollo-server/index.js b/implementations/apollo-server/index.js index b129e8c40..3d00447d2 100644 --- a/implementations/apollo-server/index.js +++ b/implementations/apollo-server/index.js @@ -68,3 +68,4 @@ const server = new ApolloServer({ server .listen({ port }) .then(({ url }) => console.log(`Products subgraph ready at ${url}`)); + \ No newline at end of file diff --git a/router.yaml b/router.yaml index b355c3c17..f45627b2a 100644 --- a/router.yaml +++ b/router.yaml @@ -1,4 +1,4 @@ server: listen: 0.0.0.0:4000 cors: - allow_credentials: true + allow_credentials: true \ No newline at end of file diff --git a/src/tests/override.test.ts b/src/tests/override.test.ts index 65cbc56a2..eeb84f4a5 100644 --- a/src/tests/override.test.ts +++ b/src/tests/override.test.ts @@ -1,4 +1,4 @@ -import { productsRequest, graphqlRequest, ROUTER_URL } from "../utils/client"; +import { productsRequest, routerRequest } from "../utils/client"; import { compareSchemas } from "../utils/schemaComparison"; import { stripIgnoredCharacters } from "graphql"; @@ -15,7 +15,7 @@ describe("@override", () => { }); it("should return overridden user name", async () => { - const resp = await graphqlRequest(ROUTER_URL, { + const resp = await routerRequest({ query: ` query GetProduct($id: ID!) { product(id: $id) { diff --git a/src/tests/requires.test.ts b/src/tests/requires.test.ts index b9d45e55e..95d5cbd8e 100644 --- a/src/tests/requires.test.ts +++ b/src/tests/requires.test.ts @@ -1,10 +1,10 @@ -import { productsRequest } from "../utils/client"; +import { routerRequest } from "../utils/client"; test("@requires", async () => { - const resp = await productsRequest({ + const resp = await routerRequest({ query: `#graphql query ($id: ID!) { - product(id: $id) { dimensions { size weight } } + product(id: $id) { createdBy { averageProductsCreatedPerYear email } } }`, variables: { id: "apollo-federation" }, }); @@ -12,9 +12,9 @@ test("@requires", async () => { expect(resp).toMatchObject({ data: { product: { - dimensions: { - size: "small", - weight: 1, + createdBy: { + averageProductsCreatedPerYear: expect.any(Number), + email: "support@apollographql.com", }, }, }, diff --git a/src/tests/shareable.test.ts b/src/tests/shareable.test.ts index f5e042009..3199c31ec 100644 --- a/src/tests/shareable.test.ts +++ b/src/tests/shareable.test.ts @@ -1,4 +1,4 @@ -import { productsRequest, graphqlRequest, ROUTER_URL } from "../utils/client"; +import { productsRequest, routerRequest } from "../utils/client"; import { compareSchemas } from "../utils/schemaComparison"; import { stripIgnoredCharacters } from "graphql"; @@ -17,7 +17,7 @@ describe("@shareable", () => { }); it("should be able to resolve @shareable ProductDimension types", async () => { - const resp = await graphqlRequest(ROUTER_URL, { + const resp = await routerRequest({ query: ` query GetProduct($id: ID!) { product(id: $id) { diff --git a/src/utils/client.ts b/src/utils/client.ts index 14c42b584..fb2806876 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -32,6 +32,17 @@ export async function graphqlRequest( return resp.text(); } +export function routerRequest( + req: { + query: string; + variables?: { [key: string]: any }; + operationName?: string; + }, + headers?: { [key: string]: any } +) { + return graphqlRequest(ROUTER_URL, req, headers); +} + export function productsRequest( req: { query: string; diff --git a/subgraphs/users/index.js b/subgraphs/users/index.js index 7840dc119..a6ff85729 100644 --- a/subgraphs/users/index.js +++ b/subgraphs/users/index.js @@ -8,6 +8,7 @@ const users = [ email: "support@apollographql.com", name: "Apollo Studio Support", totalProductsCreated: 4, + yearsOfEmployment: 4 }, ]; diff --git a/subgraphs/users/users.graphql b/subgraphs/users/users.graphql index d918620e2..ca94efb70 100644 --- a/subgraphs/users/users.graphql +++ b/subgraphs/users/users.graphql @@ -2,4 +2,5 @@ type User @key(fields:"email") { email:ID! name: String totalProductsCreated: Int + yearsOfEmployment: Int! } diff --git a/supergraph.graphql b/supergraph.graphql index 9c87407c6..71243285b 100644 --- a/supergraph.graphql +++ b/supergraph.graphql @@ -94,7 +94,9 @@ type User @join__type(graph: PRODUCTS, key: "email", extension: true) @join__type(graph: USERS, key: "email") { + averageProductsCreatedPerYear: Int @join__field(graph: PRODUCTS, requires: "yearsOfEmployment") email: ID! name: String @join__field(graph: PRODUCTS, override: "users") totalProductsCreated: Int @join__field(graph: PRODUCTS, external: true) @join__field(graph: USERS) + yearsOfEmployment: Int! @join__field(graph: PRODUCTS, external: true) @join__field(graph: USERS) } \ No newline at end of file