UNPKG

@apollo/federation

Version:
687 lines (581 loc) 23.2 kB
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(); }); }); });