UNPKG

@apollo/federation

Version:
443 lines (402 loc) 11.4 kB
import { composeServices } from '../../../compose'; import { externalUnused as validateExternalUnused } from '../'; import { gql, graphqlErrorSerializer, } from 'apollo-federation-integration-testsuite'; expect.addSnapshotSerializer(graphqlErrorSerializer); describe('externalUnused', () => { it('warns when there is an unused @external field', () => { const serviceA = { typeDefs: gql` type Product @key(fields: "id") { sku: String! upc: String! id: ID! } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` extend type Product { sku: String! @external id: ID! @external price: Int! @requires(fields: "id") } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const warnings = validateExternalUnused({ schema, serviceList }); expect(warnings).toMatchInlineSnapshot(` Array [ Object { "code": "EXTERNAL_UNUSED", "locations": Array [ Object { "column": 16, "line": 3, }, ], "message": "[serviceB] Product.sku -> is marked as @external but is not used by a @requires, @key, or @provides directive.", }, ] `); }); it('does not warn when @external is selected by a @key', () => { const serviceA = { typeDefs: gql` type Product @key(fields: "sku") { sku: String! } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` extend type Product @key(fields: "sku") { sku: String! @external price: Float! } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const warnings = validateExternalUnused({ schema, serviceList }); expect(warnings).toEqual([]); }); it('does not warn when @external is selected by a @requires', () => { const serviceA = { typeDefs: gql` type Product @key(fields: "sku") { sku: String! } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` extend type Product { sku: String! @external price: Int! @requires(fields: "sku") } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const warnings = validateExternalUnused({ schema, serviceList }); expect(warnings).toEqual([]); }); it('does not warn when @external is selected by a @provides', () => { const serviceA = { typeDefs: gql` type Product @key(fields: "sku") { sku: String! id: String! } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` extend type Product @key(fields: "sku") { sku: String! @external price: Int! @provides(fields: "id") } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const warnings = validateExternalUnused({ schema, serviceList }); expect(warnings).toEqual([]); }); it('does not warn when @external is selected by a @provides used from another type', () => { const serviceA = { typeDefs: gql` type User @key(fields: "id") { id: ID! username: String } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` type Review { author: User @provides(fields: "username") } extend type User @key(fields: "id") { username: String @external } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const warnings = validateExternalUnused({ schema, serviceList }); expect(warnings).toEqual([]); }); it.todo( 'does not error when @provides selects an external field in a subselection', ); it.todo('errors when there is an invalid selection in @requires'); it('does not warn when @external is selected by a @requires used from another type', () => { const serviceA = { typeDefs: gql` type User @key(fields: "id") { id: ID! username: String } type AccountRoles { canRead: Boolean canWrite: Boolean } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` type Review { author: User } extend type User @key(fields: "id") { roles: AccountRoles! isAdmin: Boolean! @requires(fields: "roles { canWrite }") } # Externals -- only referenced by the @requires on User.isAdmin extend type AccountRoles { canWrite: Boolean @external } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const warnings = validateExternalUnused({ schema, serviceList }); expect(warnings).toEqual([]); }); it('does not warn when @external is selected by a @requires in a deep subselection', () => { const serviceA = { typeDefs: gql` type User @key(fields: "id") { id: ID! username: String } type AccountRoles { canRead: Group canWrite: Group } type Group { id: ID! name: String members: [User] } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` type Review { author: User } extend type User @key(fields: "id") { id: ID! @external roles: AccountRoles! username: String @external isAdmin: Boolean! @requires( fields: """ roles { canWrite { members { username } } canRead { members { username } } } """ ) } # Externals -- only referenced by the @requires on User.isAdmin extend type AccountRoles { canWrite: Group @external canRead: Group @external } extend type Group { members: [User] @external } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const warnings = validateExternalUnused({ schema, serviceList }); expect(warnings).toEqual([]); }); it('does not warn when @external is used on type with multiple @key directives', () => { const serviceA = { typeDefs: gql` type Product @key(fields: "upc") @key(fields: "sku") { upc: String sku: String } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` extend type Product @key(fields: "upc") { upc: String @external } `, name: 'serviceB', }; const serviceC = { typeDefs: gql` extend type Product @key(fields: "sku") { sku: String @external } `, name: 'serviceC', }; const serviceList = [serviceA, serviceB, serviceC]; const { schema } = composeServices(serviceList); const warnings = validateExternalUnused({ schema, serviceList }); expect(warnings).toEqual([]); }); it('does not error when @external is used on a field of a concrete type that implements a shared field of an implemented interface', () => { const serviceA = { typeDefs: gql` type Car implements Vehicle @key(fields: "id") { id: ID! speed: Int } interface Vehicle { id: ID! speed: Int } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` extend type Car implements Vehicle @key(fields: "id") { id: ID! @external speed: Int @external } interface Vehicle { id: ID! speed: Int } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const errors = validateExternalUnused({ schema, serviceList }); expect(errors).toHaveLength(0); }); it('does error when @external is used on a field of a concrete type is not shared by its implemented interface', () => { const serviceA = { typeDefs: gql` type Car implements Vehicle @key(fields: "id") { id: ID! speed: Int wheelSize: Int } interface Vehicle { id: ID! speed: Int } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` extend type Car implements Vehicle @key(fields: "id") { id: ID! @external speed: Int @external wheelSize: Int @external } interface Vehicle { id: ID! speed: Int } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const errors = validateExternalUnused({ schema, serviceList }); expect(errors).toMatchInlineSnapshot(` Array [ Object { "code": "EXTERNAL_UNUSED", "locations": Array [ Object { "column": 18, "line": 5, }, ], "message": "[serviceB] Car.wheelSize -> is marked as @external but is not used by a @requires, @key, or @provides directive.", }, ] `); }); it('points to the right location on error when multiple directives are on the field in question', () => { const serviceA = { typeDefs: gql` type Car implements Vehicle @key(fields: "id") { id: ID! speed: Int wheelSize: Int } interface Vehicle { id: ID! speed: Int } `, name: 'serviceA', }; const serviceB = { typeDefs: gql` extend type Car implements Vehicle @key(fields: "id") { id: ID! @external speed: Int @external wheelSize: Int @requires(fields: "id") @external } interface Vehicle { id: ID! speed: Int } `, name: 'serviceB', }; const serviceList = [serviceA, serviceB]; const { schema } = composeServices(serviceList); const errors = validateExternalUnused({ schema, serviceList }); expect(errors).toMatchInlineSnapshot(` Array [ Object { "code": "EXTERNAL_UNUSED", "locations": Array [ Object { "column": 42, "line": 5, }, ], "message": "[serviceB] Car.wheelSize -> is marked as @external but is not used by a @requires, @key, or @provides directive.", }, ] `); }); });