import { UnauthorizedError, TransportError } from "@nimbus/errors"; import { gql, PubSub } from 'apollo-server-express'; import ApolloClient from "apollo-client"; import { InMemoryCache } from "apollo-cache-inmemory"; import supertest from "supertest"; import { WebSocketLink } from "apollo-link-ws"; import { SubscriptionClient } from "subscriptions-transport-ws"; import { GraphQLServer } from "../src/graphql.server"; import { formatError } from "../src/graphql.error"; import { correlationIdExtension } from "../src/correlation.extension"; describe("GraphQL Server", () => { it("will issue a query to a graphql server", async () => { const graphqlServer = new GraphQLServer({ server: { path: "/graphql" }, typeDefs: gql` type Query { ping: String } `, resolvers: { Query: { ping() { return 'pong' }, } } }).prepare(); const response = await supertest(graphqlServer.express) .post("/graphql") .send({ 'query': `query { ping }` }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') .expect(200); expect(response.body).toMatchObject({ data: { ping: "pong", } }); }); it("will issue a subscription to a graphql server", async () => { const websocketUrl = "ws://localhost:8888/graphql"; const TEST_EVENT = "TEST_EVENT"; const pubsub = new PubSub(); const graphqlServer = new GraphQLServer({ server: { path: "/graphql", }, typeDefs: gql` type Query { ping: String } type Subscription { ping: String } `, subscriptions: {}, resolvers: { Subscription: { ping: { subscribe() { return pubsub.asyncIterator(TEST_EVENT); } } } } }); await graphqlServer.listen(8888); const wsClient = new SubscriptionClient(websocketUrl,{ reconnect: true, }); const link = new WebSocketLink(wsClient); const client = new ApolloClient({ cache: new InMemoryCache(), link, }); const stream = client.subscribe({ query: gql` subscription Ping { ping } ` }); const subscription = new Promise((resolve, reject) => { stream.subscribe({ next: resolve, error: reject, }); }); setTimeout(() => { pubsub.publish(TEST_EVENT, { ping: "pong" }); }, 100); const response = await subscription; expect(response).toMatchObject({ data: { ping: "pong", } }); await graphqlServer.close(); }, 5000); }); describe("GraphQL errors", () => { it("will return a formatted base error", async () => { const graphqlServer = new GraphQLServer({ formatError, server: { path: "/graphql" }, typeDefs: gql` type Query { ping: String } `, resolvers: { Query: { ping() { throw new UnauthorizedError("Unauthorized."); }, } } }).prepare(); const response = await supertest(graphqlServer.express) .post("/graphql") .send({ 'query': `query { ping }` }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') .expect(200); expect(response.body.errors).toEqual([ { "message": "Unauthorized.", "extensions": { "message": "Unauthorized.", "type": "UnauthorizedError", "code": "UNAUTHORIZED", "previous": null }, } ]); }); it("will return a formatted wrapped error", async () => { const graphqlServer = new GraphQLServer({ formatError, server: { path: "/graphql" }, typeDefs: gql` type Query { ping: String } `, resolvers: { Query: { ping() { throw UnauthorizedError.wrap(new TransportError("Not authorized."), "Internal error"); }, } } }).prepare(); const response = await supertest(graphqlServer.express) .post("/graphql") .send({ 'query': `query { ping }` }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') .expect(200); expect(response.body.errors).toEqual([ { "message": "Internal error", "extensions": { "message": "Internal error", "type": "UnauthorizedError", "code": "UNAUTHORIZED", "previous": { code: "REQUEST_ERROR", message: "Not authorized.", type: "TransportError", previous: null }, }, } ]); }); it("will add a correlationId to the error when using the extensions", async () => { const graphqlServer = new GraphQLServer({ formatError, extensions: [correlationIdExtension], context: ({ req }) => ({ req }), server: { path: "/graphql" }, typeDefs: gql` type Query { ping: String } `, resolvers: { Query: { ping() { throw new UnauthorizedError("Unauthorized."); }, } } }).prepare(); const response = await supertest(graphqlServer.express) .post("/graphql") .send({ 'query': `query { ping }` }) .set('Content-Type', 'application/json') .set('X-Correlation-ID', 'test-id') .set('Accept', 'application/json') .expect(200); expect(response.body.errors).toEqual([ { "message": "Unauthorized.", "extensions": { "message": "Unauthorized.", "type": "UnauthorizedError", "code": "UNAUTHORIZED", "correlationId": "test-id", "previous": null, }, } ]); }); it("will return a formatted base error when not a nimbus error", async () => { const graphqlServer = new GraphQLServer({ formatError, server: { path: "/graphql" }, typeDefs: gql` type Query { ping: String } `, resolvers: { Query: { ping() { throw new Error("Not Nimbus."); }, } } }).prepare(); const response = await supertest(graphqlServer.express) .post("/graphql") .send({ 'query': `query { ping }` }) .set('Content-Type', 'application/json') .set('Accept', 'application/json') .expect(200); expect(response.body.errors).toEqual([ { "message": "Not Nimbus.", "extensions": { "message": "Not Nimbus.", "type": "InternalServerError", "code": "INTERNAL_ERROR_CODE", "previous": null }, } ]); }); });