UNPKG

@graphql-hive/federation-gateway-audit

Version:
382 lines (365 loc) 8.27 kB
import { c as createSubgraph, a as createTest, s as serve } from './testkit-BtZjdl4n.js'; import '@apollo/composition'; import 'graphql'; import 'fets'; import '@apollo/subgraph'; import 'graphql-yoga'; let products = []; let categories = []; const numbers = {}; async function getProducts() { return products; } function getCategories() { return categories; } async function addProduct(name, price) { const newProduct = { id: "p-added-" + products.length, name, price }; products.push(newProduct); return newProduct; } async function addCategory(name, requestId) { if (categories.some((c) => c.id === "c-added-" + requestId)) { throw new Error("Category with this requestId was already added"); } const newCategory = { id: "c-added-" + requestId, name }; categories.push(newCategory); return newCategory; } async function deleteProduct(id) { const after = products.filter((p) => p.id !== id); products = after; } function initProducts() { const product = { id: "p1", name: "p1-name", price: 9.99 }; if (products.some((p) => p.id === product.id)) { return; } products.push(product); } function getNumber(requestId) { const num = numbers[requestId]; return num ?? 0; } function addNumber(num, requestId) { const existingNumber = getNumber(requestId); const sum = existingNumber + num; numbers[requestId] = sum; return sum; } function multiplyNumber(by, requestId) { const existingNumber = getNumber(requestId); const result = existingNumber * by; numbers[requestId] = result; return result; } function deleteNumber(requestId) { const num = getNumber(requestId); if (typeof numbers[requestId] === "number") { delete numbers[requestId]; } return num; } var a = createSubgraph("a", { typeDefs: ( /* GraphQL */ ` extend schema @link( url: "https://specs.apollo.dev/federation/v2.3" import: ["@key", "@shareable"] ) type Mutation { addProduct(input: AddProductInput!): Product! addCategory(name: String!, requestId: String!): Category! @shareable multiply(by: Int!, requestId: String!): Int! } type Query { product(id: ID!): Product! products: [Product!]! } input AddProductInput { name: String! price: Float! } type Product @key(fields: "id") { id: ID! name: String! price: Float! } type Category @key(fields: "id") { id: ID! } ` ), resolvers: { Query: { async product(_, { id }) { initProducts(); const product = (await getProducts()).find((p) => p.id === id); if (!product) { return null; } return { id: product.id, name: product.name, price: product.price }; }, async products() { initProducts(); return getProducts(); } }, Mutation: { async multiply(_, args) { return multiplyNumber(args.by, args.requestId); }, async addProduct(_, { input }) { return addProduct(input.name, input.price); }, async addCategory(_, { name, requestId }) { return addCategory(name, requestId); } }, Product: { async __resolveReference(key) { const products = await getProducts(); const product = products.find((p) => p.id === key.id); if (!product) { return null; } await deleteProduct(product.id); return { id: product.id, name: product.name, price: product.price }; } }, Category: { __resolveReference(key) { const categories = getCategories(); const category = categories.find((p) => p.id === key.id); if (!category) { return null; } return { id: category.id }; } } } }); var b = createSubgraph("b", { typeDefs: ( /* GraphQL */ ` extend schema @link( url: "https://specs.apollo.dev/federation/v2.3" import: ["@key", "@external", "@requires", "@shareable"] ) type Product @key(fields: "id") { id: ID! price: Float! @external isExpensive: Boolean! @requires(fields: "price") isAvailable: Boolean! } type Mutation { delete(requestId: String!): Int! addCategory(name: String!, requestId: String!): Category! @shareable } type Category @key(fields: "id") { id: ID! name: String! } ` ), resolvers: { Mutation: { delete(_, args) { return deleteNumber(args.requestId); }, async addCategory(_, { name, requestId }) { return addCategory(name, requestId); } }, Product: { async __resolveReference(key) { const product = (await getProducts()).find( (product2) => product2.id === key.id ); if (!product) { return null; } await deleteProduct(product.id); if (typeof product.price === "number") { return { id: product.id, isAvailable: true, price: product.price }; } return { id: product.id, isAvailable: false }; }, isExpensive(product) { if (typeof product.price !== "number") { throw new Error("Price is not available"); } return product.price > 100; } }, Category: { __resolveReference(key) { const categories = getCategories(); const category = categories.find((p) => p.id === key.id); if (!category) { return null; } return { id: category.id, name: category.name }; } } } }); var c = createSubgraph("c", { typeDefs: ( /* GraphQL */ ` extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) type Mutation { add(num: Int!, requestId: String!): Int! } ` ), resolvers: { Mutation: { add(_, args) { return addNumber(args.num, args.requestId); } } } }); var test = () => { const randomId = Math.random().toString(16).substr(2); return [ createTest( /* GraphQL */ ` mutation { addProduct(input: { name: "new", price: 599.99 }) { name price isExpensive isAvailable } } `, { data: { addProduct: { name: "new", price: 599.99, isExpensive: true, isAvailable: true } } } ), createTest( /* GraphQL */ ` query { product(id: "p1") { id name price isExpensive isAvailable } } `, { data: { product: { id: "p1", name: "p1-name", price: 9.99, isExpensive: false, isAvailable: true } } } ), // Test correct order of execution // It obviously does not solve a problem with shared state and race conditions, // but at least it reduces the risk a bit createTest( /* GraphQL */ ` mutation { five: add(num: 5, requestId: "${randomId}") ten: multiply(by: 2, requestId: "${randomId}") twelve: add(num: 2, requestId: "${randomId}") final: delete(requestId: "${randomId}") } `, { data: { five: 5, ten: 10, twelve: 12, final: 12 } } ), // shared-root (mutation) createTest( /* GraphQL */ ` mutation { addCategory(name: "new", requestId: "${randomId}") { id name } } `, { data: { addCategory: { id: "c-added-" + randomId, name: "new" } } } ) ]; }; var index = serve("mutations", [a, b, c], test); export { index as default };