@apollo/federation
Version:
Apollo Federation Utilities
325 lines (299 loc) • 8.19 kB
text/typescript
import {
GraphQLSchema,
specifiedDirectives,
DocumentNode,
Kind,
extendSchema,
GraphQLError,
} from 'graphql';
import { validateSDL } from 'graphql/validation/validate';
import gql from 'graphql-tag';
import { typeSerializer } from 'apollo-federation-integration-testsuite';
import { buildMapsFromServiceList } from '../../../compose';
import { knownSubgraphDirectives } from '@apollo/subgraph/dist/directives';
import { UniqueFieldDefinitionNames } from '..';
import { ServiceDefinition } from '../../../types';
expect.addSnapshotSerializer(typeSerializer);
// simulate the first half of the composition process
function createDocumentsForServices(
serviceList: ServiceDefinition[],
): DocumentNode[] {
const { typeDefinitionsMap, typeExtensionsMap } = buildMapsFromServiceList(
serviceList,
);
return [
{
kind: Kind.DOCUMENT,
definitions: Object.values(typeDefinitionsMap).flat(),
},
{
kind: Kind.DOCUMENT,
definitions: Object.values(typeExtensionsMap).flat(),
},
];
}
describe('UniqueFieldDefinitionNames', () => {
let schema: GraphQLSchema;
// create a blank schema for each test
beforeEach(() => {
schema = new GraphQLSchema({
query: undefined,
directives: [...specifiedDirectives, ...knownSubgraphDirectives],
});
});
describe('enforces unique field names for', () => {
it('object type definitions', () => {
const [definitions, extensions] = createDocumentsForServices([
{
typeDefs: gql`
type Product {
sku: ID!
}
`,
name: 'serviceA',
},
{
typeDefs: gql`
extend type Product {
sku: Int!
}
`,
name: 'serviceB',
},
]);
const errors = validateSDL(definitions, schema, [
UniqueFieldDefinitionNames,
]) as GraphQLError[];
schema = extendSchema(schema, definitions, {
assumeValidSDL: true,
});
errors.push(
...validateSDL(extensions, schema, [UniqueFieldDefinitionNames]),
);
expect(errors).toHaveLength(1);
expect(errors[0].message).toMatch(
'Field "Product.sku" already exists in the schema.',
);
});
it('interface definitions', () => {
const [definitions, extensions] = createDocumentsForServices([
{
typeDefs: gql`
interface Product {
sku: ID!
}
`,
name: 'serviceA',
},
{
typeDefs: gql`
extend interface Product {
sku: String!
}
`,
name: 'serviceB',
},
]);
const errors = validateSDL(definitions, schema, [
UniqueFieldDefinitionNames,
]) as GraphQLError[];
schema = extendSchema(schema, definitions, { assumeValidSDL: true });
errors.push(
...validateSDL(extensions, schema, [UniqueFieldDefinitionNames]),
);
expect(errors).toHaveLength(1);
expect(errors[0].message).toMatch(
'Field "Product.sku" already exists in the schema.',
);
});
it('input object definitions', () => {
const [definitions, extensions] = createDocumentsForServices([
{
typeDefs: gql`
input Product {
sku: ID
}
`,
name: 'serviceA',
},
{
typeDefs: gql`
extend input Product {
sku: String!
}
`,
name: 'serviceB',
},
]);
const errors = validateSDL(definitions, schema, [
UniqueFieldDefinitionNames,
]) as GraphQLError[];
schema = extendSchema(schema, definitions, { assumeValidSDL: true });
errors.push(
...validateSDL(extensions, schema, [UniqueFieldDefinitionNames]),
);
expect(errors).toHaveLength(1);
expect(errors[0].message).toMatch(
'Field "Product.sku" already exists in the schema.',
);
});
});
describe('permits duplicate field names for', () => {
it('value types (identical object types)', () => {
const [definitions] = createDocumentsForServices([
{
typeDefs: gql`
type Product {
sku: ID!
color: String
}
`,
name: 'serviceA',
},
{
typeDefs: gql`
type Product {
sku: ID!
color: String
}
`,
name: 'serviceB',
},
]);
const errors = validateSDL(definitions, schema, [
UniqueFieldDefinitionNames,
]);
expect(errors).toHaveLength(0);
});
it('value types (identical interface types)', () => {
const [definitions] = createDocumentsForServices([
{
typeDefs: gql`
interface Product {
sku: ID!
color: String
}
`,
name: 'serviceA',
},
{
typeDefs: gql`
interface Product {
sku: ID!
color: String
}
`,
name: 'serviceB',
},
]);
const errors = validateSDL(definitions, schema, [
UniqueFieldDefinitionNames,
]);
expect(errors).toHaveLength(0);
});
it('value types (identical input types)', () => {
const [definitions] = createDocumentsForServices([
{
typeDefs: gql`
input Product {
sku: ID!
color: String
}
`,
name: 'serviceA',
},
{
typeDefs: gql`
input Product {
sku: ID!
color: String
}
`,
name: 'serviceB',
},
]);
const errors = validateSDL(definitions, schema, [
UniqueFieldDefinitionNames,
]);
expect(errors).toHaveLength(0);
});
it('object type definitions (non-identical, value types with type mismatch)', () => {
const [definitions] = createDocumentsForServices([
{
typeDefs: gql`
type Product {
sku: ID!
color: String
quantity: Int
}
`,
name: 'serviceA',
},
{
typeDefs: gql`
type Product {
sku: String!
color: String
quantity: Int!
}
`,
name: 'serviceB',
},
]);
const errors = validateSDL(definitions, schema, [
UniqueFieldDefinitionNames,
]);
expect(errors).toHaveLength(0);
});
it('object type definitions field and arguments with different type', () => {
const typeDefs = gql`
type Campaign implements Node {
identifier: String
event(identifier: String!): Event
}
`;
const [definitions] = createDocumentsForServices([
{
typeDefs,
name: 'serviceA',
},
{
typeDefs,
name: 'serviceB',
},
]);
const errors = validateSDL(definitions, schema, [
UniqueFieldDefinitionNames,
]);
expect(errors).toHaveLength(0);
});
});
it('object type definitions order does not impact diffing', () => {
const [definitions] = createDocumentsForServices([
{
typeDefs: gql`
type Order implements Node {
questions(answers: OrderAnswersInput): Questions
fees(answers: OrderAnswersInput): Fees
answers: [Answer]
}
`,
name: 'serviceA',
},
{
typeDefs: gql`
type Order implements Node {
questions(answers: OrderAnswersInput): Questions
answers: [Answer]
fees(answers: OrderAnswersInput): Fees
}
`,
name: 'serviceB',
},
]);
const errors = validateSDL(definitions, schema, [
UniqueFieldDefinitionNames,
]);
expect(errors).toHaveLength(0);
});
});