@apollo/federation
Version:
Apollo Federation Utilities
443 lines (402 loc) • 11.4 kB
text/typescript
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 {
sku: String!
upc: String!
id: ID!
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
extend type Product {
sku: String!
id: ID!
price: Int!
}
`,
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 {
sku: String!
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
extend type Product {
sku: String!
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 {
sku: String!
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
extend type Product {
sku: String!
price: Int!
}
`,
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 {
sku: String!
id: String!
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
extend type Product {
sku: String!
price: Int!
}
`,
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 {
id: ID!
username: String
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
type Review {
author: User
}
extend type User {
username: String
}
`,
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 {
id: ID!
username: String
}
type AccountRoles {
canRead: Boolean
canWrite: Boolean
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
type Review {
author: User
}
extend type User {
roles: AccountRoles!
isAdmin: Boolean!
}
# Externals -- only referenced by the on User.isAdmin
extend type AccountRoles {
canWrite: Boolean
}
`,
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 {
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 {
id: ID!
roles: AccountRoles!
username: String
isAdmin: Boolean!
}
# Externals -- only referenced by the on User.isAdmin
extend type AccountRoles {
canWrite: Group
canRead: Group
}
extend type Group {
members: [User]
}
`,
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 {
upc: String
sku: String
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
extend type Product {
upc: String
}
`,
name: 'serviceB',
};
const serviceC = {
typeDefs: gql`
extend type Product {
sku: String
}
`,
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 {
id: ID!
speed: Int
}
interface Vehicle {
id: ID!
speed: Int
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
extend type Car implements Vehicle {
id: ID!
speed: Int
}
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 {
id: ID!
speed: Int
wheelSize: Int
}
interface Vehicle {
id: ID!
speed: Int
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
extend type Car implements Vehicle {
id: ID!
speed: Int
wheelSize: Int
}
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 {
id: ID!
speed: Int
wheelSize: Int
}
interface Vehicle {
id: ID!
speed: Int
}
`,
name: 'serviceA',
};
const serviceB = {
typeDefs: gql`
extend type Car implements Vehicle {
id: ID!
speed: Int
wheelSize: Int
}
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.",
},
]
`);
});
});