diff --git a/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/product.ts b/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/product.ts index 87d5bae1700..e1de28eb495 100644 --- a/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/product.ts +++ b/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/product.ts @@ -8,6 +8,7 @@ export const typeDefs = gql` extend type Query { product(upc: String!): Product + vehicle(id: String!): Vehicle topProducts(first: Int = 5): [Product] topCars(first: Int = 5): [Car] } @@ -65,11 +66,32 @@ export const typeDefs = gql` details: ProductDetailsBook } - type Car @key(fields: "id") { + interface Vehicle { id: String! + description: String price: String } + type Car implements Vehicle @key(fields: "id") { + id: String! + description: String + price: String + } + + type Van implements Vehicle @key(fields: "id") { + id: String! + description: String + price: String + } + + union Thing = Car | Ikea + + extend type User @key(fields: "id") { + id: ID! @external + vehicle: Vehicle + thing: Thing + } + # Value type type KeyValue { key: String! @@ -131,17 +153,25 @@ const products = [ { __typename: 'Book', isbn: '0987654321', price: 29 }, ]; -const cars = [ +const vehicles = [ { __typename: 'Car', id: '1', + description: 'Humble Toyota', price: 9990, }, { __typename: 'Car', id: '2', + description: 'Awesome Tesla', price: 12990, }, + { + __typename: 'Van', + id: '3', + description: 'Just a van...', + price: 15990, + }, ]; export const resolvers: GraphQLResolverMap = { @@ -176,13 +206,34 @@ export const resolvers: GraphQLResolverMap = { }, Car: { __resolveReference(object) { - return cars.find(car => car.id === object.id); + return vehicles.find(vehicles => vehicles.id === object.id); + }, + }, + Van: { + __resolveReference(object) { + return vehicles.find(vehicles => vehicles.id === object.id); + }, + }, + Thing: { + __resolveType(object) { + return 'id' in object ? 'Car' : 'Ikea'; + }, + }, + User: { + vehicle(user) { + return vehicles.find(vehicles => vehicles.id === user.id); + }, + thing(user) { + return vehicles.find(vehicles => vehicles.id === user.id); }, }, Query: { product(_, args) { return products.find(product => product.upc === args.upc); }, + vehicle(_, args) { + return vehicles.find(vehicles => vehicles.id === args.id); + }, topProducts(_, args) { return products.slice(0, args.first); }, diff --git a/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/reviews.ts b/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/reviews.ts index 53a399bd0d3..69b8f58ef90 100644 --- a/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/reviews.ts +++ b/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/reviews.ts @@ -52,7 +52,17 @@ export const typeDefs = gql` relatedReviews: [Review!]! @requires(fields: "similarBooks { isbn }") } - extend type Car @key(fields: "id") { + extend interface Vehicle { + retailPrice: String + } + + extend type Car implements Vehicle @key(fields: "id") { + id: String! @external + price: String @external + retailPrice: String @requires(fields: "price") + } + + extend type Van implements Vehicle @key(fields: "id") { id: String! @external price: String @external retailPrice: String @requires(fields: "price") @@ -217,6 +227,11 @@ export const resolvers: GraphQLResolverMap = { return car.price; }, }, + Van: { + retailPrice(van) { + return van.price; + }, + }, MetadataOrError: { __resolveType(object) { return 'key' in object ? 'KeyValue' : 'Error'; diff --git a/packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts b/packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts index 6d0d872247e..2069f88df90 100644 --- a/packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts +++ b/packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts @@ -15,6 +15,10 @@ import { buildQueryPlan, buildOperationContext } from '../buildQueryPlan'; import { executeQueryPlan } from '../executeQueryPlan'; import { LocalGraphQLDataSource } from '../datasources/LocalGraphQLDataSource'; +import { astSerializer, queryPlanSerializer } from '../snapshotSerializers'; +expect.addSnapshotSerializer(astSerializer); +expect.addSnapshotSerializer(queryPlanSerializer); + function buildLocalService(modules: GraphQLSchemaModule[]) { const schema = buildFederatedSchema(modules); return new LocalGraphQLDataSource(schema); @@ -434,6 +438,119 @@ describe('executeQueryPlan', () => { expect(response.errors).toBeUndefined(); }); + it(`can execute queries on interface types`, async () => { + const query = gql` + query { + vehicle(id: "1") { + description + price + retailPrice + } + } + `; + + const operationContext = buildOperationContext(schema, query); + const queryPlan = buildQueryPlan(operationContext); + + const response = await executeQueryPlan( + queryPlan, + serviceMap, + buildRequestContext(), + operationContext, + ); + + expect(response.data).toMatchInlineSnapshot(` + Object { + "vehicle": Object { + "description": "Humble Toyota", + "price": "9990", + "retailPrice": "9990", + }, + } + `); + }); + + it(`can execute queries whose fields are interface types`, async () => { + const query = gql` + query { + user(id: "1") { + name + vehicle { + description + price + retailPrice + } + } + } + `; + + const operationContext = buildOperationContext(schema, query); + const queryPlan = buildQueryPlan(operationContext); + + const response = await executeQueryPlan( + queryPlan, + serviceMap, + buildRequestContext(), + operationContext, + ); + + expect(response.data).toMatchInlineSnapshot(` + Object { + "user": Object { + "name": "Ada Lovelace", + "vehicle": Object { + "description": "Humble Toyota", + "price": "9990", + "retailPrice": "9990", + }, + }, + } + `); + }); + + it(`can execute queries whose fields are union types`, async () => { + const query = gql` + query { + user(id: "1") { + name + thing { + ... on Vehicle { + description + price + retailPrice + } + ... on Ikea { + asile + } + } + } + } + `; + + const operationContext = buildOperationContext(schema, query); + const queryPlan = buildQueryPlan(operationContext); + + const response = await executeQueryPlan( + queryPlan, + serviceMap, + buildRequestContext(), + operationContext, + ); + + expect(response.data).toMatchInlineSnapshot(` + Object { + "user": Object { + "name": "Ada Lovelace", + "thing": Object { + "description": "Humble Toyota", + "price": "9990", + "retailPrice": "9990", + }, + }, + } + `); + }); + it('can execute queries with falsey @requires (except undefined)', async () => { const query = gql` query {