@apollo/federation
Version:
Apollo Federation Utilities
687 lines (581 loc) • 23.2 kB
text/typescript
import {
fixtures,
fixturesWithoutTag,
} from 'apollo-federation-integration-testsuite';
import { parse, GraphQLError, visit, StringValueNode } from 'graphql';
import { composeAndValidate, compositionHasErrors } from '../../composition';
describe('printSupergraphSdl', () => {
let supergraphSdl: string, errors: GraphQLError[];
beforeAll(() => {
// composeAndValidate calls `printSupergraphSdl` to return `supergraphSdl`
const compositionResult = composeAndValidate(fixtures);
if (compositionHasErrors(compositionResult)) {
errors = compositionResult.errors;
} else {
supergraphSdl = compositionResult.supergraphSdl;
}
});
it('composes without errors', () => {
expect(errors).toBeUndefined();
});
it('produces a parseable output', () => {
expect(() => parse(supergraphSdl!)).not.toThrow();
});
it('prints a fully composed schema correctly', () => {
expect(supergraphSdl).toMatchInlineSnapshot(`
"schema
@core(feature: \\"https://specs.apollo.dev/core/v0.2\\"),
@core(feature: \\"https://specs.apollo.dev/join/v0.1\\", for: EXECUTION),
@core(feature: \\"https://specs.apollo.dev/tag/v0.1\\")
{
query: Query
mutation: Mutation
}
directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA
directive @join__field(graph: join__Graph, provides: join__FieldSet, requires: join__FieldSet) on FIELD_DEFINITION
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
directive @join__owner(graph: join__Graph!) on INTERFACE | OBJECT
directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on INTERFACE | OBJECT
directive @stream on FIELD
directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
directive @transform(from: String!) on FIELD
union AccountType
@tag(name: \\"from-accounts\\")
= PasswordAccount | SMSAccount
type Amazon {
referrer: String
}
union Body = Image | Text
type Book implements Product
@join__owner(graph: BOOKS)
@join__type(graph: BOOKS, key: \\"isbn\\")
@join__type(graph: INVENTORY, key: \\"isbn\\")
@join__type(graph: PRODUCT, key: \\"isbn\\")
@join__type(graph: REVIEWS, key: \\"isbn\\")
{
details: ProductDetailsBook @join__field(graph: PRODUCT)
inStock: Boolean @join__field(graph: INVENTORY)
isCheckedOut: Boolean @join__field(graph: INVENTORY)
isbn: String! @join__field(graph: BOOKS)
metadata: [MetadataOrError] @join__field(graph: BOOKS)
name(delimeter: String = \\" \\"): String @join__field(graph: PRODUCT, requires: \\"title year\\")
price: String @join__field(graph: PRODUCT)
relatedReviews: [Review!]! @join__field(graph: REVIEWS, requires: \\"similarBooks{isbn}\\")
reviews: [Review] @join__field(graph: REVIEWS)
similarBooks: [Book]! @join__field(graph: BOOKS)
sku: String! @join__field(graph: PRODUCT)
title: String @join__field(graph: BOOKS)
upc: String! @join__field(graph: PRODUCT)
year: Int @join__field(graph: BOOKS)
}
union Brand = Amazon | Ikea
enum CacheControlScope {
PRIVATE
PUBLIC
}
type Car implements Vehicle
@join__owner(graph: PRODUCT)
@join__type(graph: PRODUCT, key: \\"id\\")
@join__type(graph: REVIEWS, key: \\"id\\")
{
description: String @join__field(graph: PRODUCT)
id: String! @join__field(graph: PRODUCT)
price: String @join__field(graph: PRODUCT)
retailPrice: String @join__field(graph: REVIEWS, requires: \\"price\\")
}
type Error {
code: Int
message: String
}
type Furniture implements Product
@join__owner(graph: PRODUCT)
@join__type(graph: PRODUCT, key: \\"upc\\")
@join__type(graph: PRODUCT, key: \\"sku\\")
@join__type(graph: INVENTORY, key: \\"sku\\")
@join__type(graph: REVIEWS, key: \\"upc\\")
{
brand: Brand @join__field(graph: PRODUCT)
details: ProductDetailsFurniture @join__field(graph: PRODUCT)
inStock: Boolean @join__field(graph: INVENTORY)
isHeavy: Boolean @join__field(graph: INVENTORY)
metadata: [MetadataOrError] @join__field(graph: PRODUCT)
name: String @join__field(graph: PRODUCT)
price: String @join__field(graph: PRODUCT)
reviews: [Review] @join__field(graph: REVIEWS)
sku: String! @join__field(graph: PRODUCT)
upc: String! @join__field(graph: PRODUCT)
}
type Ikea {
asile: Int
}
type Image implements NamedObject {
attributes: ImageAttributes!
name: String!
}
type ImageAttributes {
url: String!
}
scalar JSON @specifiedBy(url: \\"https://json-spec.dev\\")
type KeyValue {
key: String!
value: String!
}
type Library
@join__owner(graph: BOOKS)
@join__type(graph: BOOKS, key: \\"id\\")
@join__type(graph: ACCOUNTS, key: \\"id\\")
{
id: ID! @join__field(graph: BOOKS)
name: String @join__field(graph: BOOKS)
userAccount(id: ID! = 1): User @join__field(graph: ACCOUNTS, requires: \\"name\\")
}
type LibraryAccount
@join__owner(graph: ACCOUNTS)
@join__type(graph: ACCOUNTS, key: \\"library{id}\\")
{
description: String! @join__field(graph: ACCOUNTS)
library: Library! @join__field(graph: ACCOUNTS, provides: \\"name\\")
}
union MetadataOrError = Error | KeyValue
type Mutation {
deleteReview(id: ID!): Boolean @join__field(graph: REVIEWS)
login(password: String!, userId: String @deprecated(reason: \\"Use username instead\\"), username: String!): User @join__field(graph: ACCOUNTS)
reviewProduct(input: ReviewProduct!): Product @join__field(graph: REVIEWS)
updateReview(review: UpdateReviewInput!): Review @join__field(graph: REVIEWS)
}
type Name {
first: String
last: String
}
interface NamedObject {
name: String!
}
type PasswordAccount
@join__owner(graph: ACCOUNTS)
@join__type(graph: ACCOUNTS, key: \\"email\\")
{
email: String! @join__field(graph: ACCOUNTS)
}
interface Product
@tag(name: \\"from-reviews\\")
{
details: ProductDetails
inStock: Boolean
name: String
price: String
reviews: [Review]
sku: String!
upc: String!
}
interface ProductDetails {
country: String
}
type ProductDetailsBook implements ProductDetails {
country: String
pages: Int
}
type ProductDetailsFurniture implements ProductDetails {
color: String
country: String
}
type Query {
body: Body! @join__field(graph: DOCUMENTS)
book(isbn: String!): Book @join__field(graph: BOOKS)
books: [Book] @join__field(graph: BOOKS)
library(id: ID!): Library @join__field(graph: BOOKS)
libraryAccount: LibraryAccount @join__field(graph: ACCOUNTS)
me: User @join__field(graph: ACCOUNTS)
product(upc: String!): Product @join__field(graph: PRODUCT)
topCars(first: Int = 5): [Car] @join__field(graph: PRODUCT)
topProducts(first: Int = 5): [Product] @join__field(graph: PRODUCT)
topReviews(first: Int = 5): [Review] @join__field(graph: REVIEWS)
user(id: ID!): User @join__field(graph: ACCOUNTS)
vehicle(id: String!): Vehicle @join__field(graph: PRODUCT)
}
type Review
@join__owner(graph: REVIEWS)
@join__type(graph: REVIEWS, key: \\"id\\")
{
author: User @join__field(graph: REVIEWS, provides: \\"username\\")
body(format: Boolean = false): String @join__field(graph: REVIEWS)
id: ID! @join__field(graph: REVIEWS)
metadata: [MetadataOrError] @join__field(graph: REVIEWS)
product: Product @join__field(graph: REVIEWS)
}
input ReviewProduct {
body: String!
stars: Int @deprecated(reason: \\"Stars are no longer in use\\")
upc: String!
}
type SMSAccount
@join__owner(graph: ACCOUNTS)
@join__type(graph: ACCOUNTS, key: \\"number\\")
{
number: String @join__field(graph: ACCOUNTS)
}
type Text implements NamedObject {
attributes: TextAttributes!
name: String!
}
type TextAttributes {
bold: Boolean
text: String
}
union Thing = Car | Ikea
input UpdateReviewInput {
body: String
id: ID!
}
type User
@join__owner(graph: ACCOUNTS)
@join__type(graph: ACCOUNTS, key: \\"id\\")
@join__type(graph: ACCOUNTS, key: \\"username name{first last}\\")
@join__type(graph: INVENTORY, key: \\"id\\")
@join__type(graph: PRODUCT, key: \\"id\\")
@join__type(graph: REVIEWS, key: \\"id\\")
@tag(name: \\"from-accounts\\")
@tag(name: \\"from-reviews\\")
{
account: AccountType @join__field(graph: ACCOUNTS)
birthDate(locale: String): String @join__field(graph: ACCOUNTS) @tag(name: \\"admin\\") @tag(name: \\"dev\\")
goodAddress: Boolean @join__field(graph: REVIEWS, requires: \\"metadata{address}\\")
goodDescription: Boolean @join__field(graph: INVENTORY, requires: \\"metadata{description}\\")
id: ID! @join__field(graph: ACCOUNTS) @tag(name: \\"accounts\\") @tag(name: \\"on-external\\")
metadata: [UserMetadata] @join__field(graph: ACCOUNTS)
name: Name @join__field(graph: ACCOUNTS)
numberOfReviews: Int! @join__field(graph: REVIEWS)
reviews: [Review] @join__field(graph: REVIEWS)
ssn: String @join__field(graph: ACCOUNTS)
thing: Thing @join__field(graph: PRODUCT)
username: String @join__field(graph: ACCOUNTS)
vehicle: Vehicle @join__field(graph: PRODUCT)
}
type UserMetadata {
address: String
description: String
name: String
}
type Van implements Vehicle
@join__owner(graph: PRODUCT)
@join__type(graph: PRODUCT, key: \\"id\\")
@join__type(graph: REVIEWS, key: \\"id\\")
{
description: String @join__field(graph: PRODUCT)
id: String! @join__field(graph: PRODUCT)
price: String @join__field(graph: PRODUCT)
retailPrice: String @join__field(graph: REVIEWS, requires: \\"price\\")
}
interface Vehicle {
description: String
id: String!
price: String
retailPrice: String
}
enum core__Purpose {
\\"\\"\\"
\`EXECUTION\` features provide metadata necessary to for operation execution.
\\"\\"\\"
EXECUTION
\\"\\"\\"
\`SECURITY\` features provide metadata necessary to securely resolve fields.
\\"\\"\\"
SECURITY
}
scalar join__FieldSet
enum join__Graph {
ACCOUNTS @join__graph(name: \\"accounts\\" url: \\"https://accounts.api.com\\")
BOOKS @join__graph(name: \\"books\\" url: \\"https://books.api.com\\")
DOCUMENTS @join__graph(name: \\"documents\\" url: \\"https://documents.api.com\\")
INVENTORY @join__graph(name: \\"inventory\\" url: \\"https://inventory.api.com\\")
PRODUCT @join__graph(name: \\"product\\" url: \\"https://product.api.com\\")
REVIEWS @join__graph(name: \\"reviews\\" url: \\"https://reviews.api.com\\")
}
"
`);
});
it('prints a fully composed schema without @tag correctly', () => {
// composeAndValidate calls `printSupergraphSdl` to return `supergraphSdl`
const compositionResult = composeAndValidate(fixturesWithoutTag);
if (compositionHasErrors(compositionResult)) {
errors = compositionResult.errors;
} else {
supergraphSdl = compositionResult.supergraphSdl;
}
expect(supergraphSdl).toMatchInlineSnapshot(`
"schema
@core(feature: \\"https://specs.apollo.dev/core/v0.2\\"),
@core(feature: \\"https://specs.apollo.dev/join/v0.1\\", for: EXECUTION)
{
query: Query
mutation: Mutation
}
directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA
directive @join__field(graph: join__Graph, provides: join__FieldSet, requires: join__FieldSet) on FIELD_DEFINITION
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
directive @join__owner(graph: join__Graph!) on INTERFACE | OBJECT
directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on INTERFACE | OBJECT
directive @stream on FIELD
directive @transform(from: String!) on FIELD
union AccountType = PasswordAccount | SMSAccount
type Amazon {
referrer: String
}
union Body = Image | Text
type Book implements Product
@join__owner(graph: BOOKS)
@join__type(graph: BOOKS, key: \\"isbn\\")
@join__type(graph: INVENTORY, key: \\"isbn\\")
@join__type(graph: PRODUCT, key: \\"isbn\\")
@join__type(graph: REVIEWS, key: \\"isbn\\")
{
details: ProductDetailsBook @join__field(graph: PRODUCT)
inStock: Boolean @join__field(graph: INVENTORY)
isCheckedOut: Boolean @join__field(graph: INVENTORY)
isbn: String! @join__field(graph: BOOKS)
metadata: [MetadataOrError] @join__field(graph: BOOKS)
name(delimeter: String = \\" \\"): String @join__field(graph: PRODUCT, requires: \\"title year\\")
price: String @join__field(graph: PRODUCT)
relatedReviews: [Review!]! @join__field(graph: REVIEWS, requires: \\"similarBooks{isbn}\\")
reviews: [Review] @join__field(graph: REVIEWS)
similarBooks: [Book]! @join__field(graph: BOOKS)
sku: String! @join__field(graph: PRODUCT)
title: String @join__field(graph: BOOKS)
upc: String! @join__field(graph: PRODUCT)
year: Int @join__field(graph: BOOKS)
}
union Brand = Amazon | Ikea
enum CacheControlScope {
PRIVATE
PUBLIC
}
type Car implements Vehicle
@join__owner(graph: PRODUCT)
@join__type(graph: PRODUCT, key: \\"id\\")
@join__type(graph: REVIEWS, key: \\"id\\")
{
description: String @join__field(graph: PRODUCT)
id: String! @join__field(graph: PRODUCT)
price: String @join__field(graph: PRODUCT)
retailPrice: String @join__field(graph: REVIEWS, requires: \\"price\\")
}
type Error {
code: Int
message: String
}
type Furniture implements Product
@join__owner(graph: PRODUCT)
@join__type(graph: PRODUCT, key: \\"upc\\")
@join__type(graph: PRODUCT, key: \\"sku\\")
@join__type(graph: INVENTORY, key: \\"sku\\")
@join__type(graph: REVIEWS, key: \\"upc\\")
{
brand: Brand @join__field(graph: PRODUCT)
details: ProductDetailsFurniture @join__field(graph: PRODUCT)
inStock: Boolean @join__field(graph: INVENTORY)
isHeavy: Boolean @join__field(graph: INVENTORY)
metadata: [MetadataOrError] @join__field(graph: PRODUCT)
name: String @join__field(graph: PRODUCT)
price: String @join__field(graph: PRODUCT)
reviews: [Review] @join__field(graph: REVIEWS)
sku: String! @join__field(graph: PRODUCT)
upc: String! @join__field(graph: PRODUCT)
}
type Ikea {
asile: Int
}
type Image implements NamedObject {
attributes: ImageAttributes!
name: String!
}
type ImageAttributes {
url: String!
}
type KeyValue {
key: String!
value: String!
}
type Library
@join__owner(graph: BOOKS)
@join__type(graph: BOOKS, key: \\"id\\")
@join__type(graph: ACCOUNTS, key: \\"id\\")
{
id: ID! @join__field(graph: BOOKS)
name: String @join__field(graph: BOOKS)
userAccount(id: ID! = 1): User @join__field(graph: ACCOUNTS, requires: \\"name\\")
}
union MetadataOrError = Error | KeyValue
type Mutation {
deleteReview(id: ID!): Boolean @join__field(graph: REVIEWS)
login(password: String!, username: String!): User @join__field(graph: ACCOUNTS)
reviewProduct(input: ReviewProduct): Product @join__field(graph: REVIEWS)
updateReview(review: UpdateReviewInput!): Review @join__field(graph: REVIEWS)
}
type Name {
first: String
last: String
}
interface NamedObject {
name: String!
}
type PasswordAccount
@join__owner(graph: ACCOUNTS)
@join__type(graph: ACCOUNTS, key: \\"email\\")
{
email: String! @join__field(graph: ACCOUNTS)
}
interface Product {
details: ProductDetails
inStock: Boolean
name: String
price: String
reviews: [Review]
sku: String!
upc: String!
}
interface ProductDetails {
country: String
}
type ProductDetailsBook implements ProductDetails {
country: String
pages: Int
}
type ProductDetailsFurniture implements ProductDetails {
color: String
country: String
}
type Query {
body: Body! @join__field(graph: DOCUMENTS)
book(isbn: String!): Book @join__field(graph: BOOKS)
books: [Book] @join__field(graph: BOOKS)
library(id: ID!): Library @join__field(graph: BOOKS)
me: User @join__field(graph: ACCOUNTS)
product(upc: String!): Product @join__field(graph: PRODUCT)
topCars(first: Int = 5): [Car] @join__field(graph: PRODUCT)
topProducts(first: Int = 5): [Product] @join__field(graph: PRODUCT)
topReviews(first: Int = 5): [Review] @join__field(graph: REVIEWS)
user(id: ID!): User @join__field(graph: ACCOUNTS)
vehicle(id: String!): Vehicle @join__field(graph: PRODUCT)
}
type Review
@join__owner(graph: REVIEWS)
@join__type(graph: REVIEWS, key: \\"id\\")
{
author: User @join__field(graph: REVIEWS, provides: \\"username\\")
body(format: Boolean = false): String @join__field(graph: REVIEWS)
id: ID! @join__field(graph: REVIEWS)
metadata: [MetadataOrError] @join__field(graph: REVIEWS)
product: Product @join__field(graph: REVIEWS)
}
input ReviewProduct {
body: String!
stars: Int @deprecated(reason: \\"Stars are no longer in use\\")
upc: String!
}
type SMSAccount
@join__owner(graph: ACCOUNTS)
@join__type(graph: ACCOUNTS, key: \\"number\\")
{
number: String @join__field(graph: ACCOUNTS)
}
type Text implements NamedObject {
attributes: TextAttributes!
name: String!
}
type TextAttributes {
bold: Boolean
text: String
}
union Thing = Car | Ikea
input UpdateReviewInput {
body: String
id: ID!
}
type User
@join__owner(graph: ACCOUNTS)
@join__type(graph: ACCOUNTS, key: \\"id\\")
@join__type(graph: ACCOUNTS, key: \\"username name{first last}\\")
@join__type(graph: INVENTORY, key: \\"id\\")
@join__type(graph: PRODUCT, key: \\"id\\")
@join__type(graph: REVIEWS, key: \\"id\\")
{
account: AccountType @join__field(graph: ACCOUNTS)
birthDate(locale: String): String @join__field(graph: ACCOUNTS)
goodAddress: Boolean @join__field(graph: REVIEWS, requires: \\"metadata{address}\\")
goodDescription: Boolean @join__field(graph: INVENTORY, requires: \\"metadata{description}\\")
id: ID! @join__field(graph: ACCOUNTS)
metadata: [UserMetadata] @join__field(graph: ACCOUNTS)
name: Name @join__field(graph: ACCOUNTS)
numberOfReviews: Int! @join__field(graph: REVIEWS)
reviews: [Review] @join__field(graph: REVIEWS)
ssn: String @join__field(graph: ACCOUNTS)
thing: Thing @join__field(graph: PRODUCT)
username: String @join__field(graph: ACCOUNTS)
vehicle: Vehicle @join__field(graph: PRODUCT)
}
type UserMetadata {
address: String
description: String
name: String
}
type Van implements Vehicle
@join__owner(graph: PRODUCT)
@join__type(graph: PRODUCT, key: \\"id\\")
@join__type(graph: REVIEWS, key: \\"id\\")
{
description: String @join__field(graph: PRODUCT)
id: String! @join__field(graph: PRODUCT)
price: String @join__field(graph: PRODUCT)
retailPrice: String @join__field(graph: REVIEWS, requires: \\"price\\")
}
interface Vehicle {
description: String
id: String!
price: String
retailPrice: String
}
enum core__Purpose {
\\"\\"\\"
\`EXECUTION\` features provide metadata necessary to for operation execution.
\\"\\"\\"
EXECUTION
\\"\\"\\"
\`SECURITY\` features provide metadata necessary to securely resolve fields.
\\"\\"\\"
SECURITY
}
scalar join__FieldSet
enum join__Graph {
ACCOUNTS @join__graph(name: \\"accounts\\" url: \\"https://accounts.api.com\\")
BOOKS @join__graph(name: \\"books\\" url: \\"https://books.api.com\\")
DOCUMENTS @join__graph(name: \\"documents\\" url: \\"https://documents.api.com\\")
INVENTORY @join__graph(name: \\"inventory\\" url: \\"https://inventory.api.com\\")
PRODUCT @join__graph(name: \\"product\\" url: \\"https://product.api.com\\")
REVIEWS @join__graph(name: \\"reviews\\" url: \\"https://reviews.api.com\\")
}
"
`);
});
it('fieldsets are parseable', () => {
const parsedSupergraphSdl = parse(supergraphSdl!);
const fieldSets: string[] = [];
// Collect all args with the `key`, `provides`, and `requires` fields
// Note: if our testing schema ever begins to include a directive with args
// that use any of these names, this test will likely fail and will need to
// be a bit less heavy-handed by searching for the specific directives
// instead of by argument name.
const argNames = ['key', 'requires', 'provides'];
visit(parsedSupergraphSdl, {
Argument(node) {
if (argNames.includes(node.name.value)) {
fieldSets.push((node.value as StringValueNode).value);
}
},
});
// Ensure we're actually finding fieldSets, else this will fail quietly
expect(fieldSets).not.toHaveLength(0);
// Ensure each fieldSet arg is graphql parseable (when wrapped in curlies, as we do elsewhere)
fieldSets.forEach((unparsed) => {
expect(() => parse('{' + unparsed + '}')).not.toThrow();
});
});
});