From 9d853f4be43746c35399cd17fdfc193c1bc1e9f9 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Thu, 14 Jun 2018 01:12:50 -0700 Subject: [PATCH] move ApolloServer tests into integration tests from core --- .../src/ApolloServer.test.ts | 957 ------------------ .../src/ApolloServer.test.ts | 105 +- .../src/ApolloServer.test.ts | 28 + .../package.json | 10 +- .../src/ApolloServer.ts | 846 ++++++++++++++++ .../src/index.ts | 2 + packages/apollo-server/src/index.test.ts | 2 +- test/tests.js | 10 +- 8 files changed, 971 insertions(+), 989 deletions(-) delete mode 100644 packages/apollo-server-core/src/ApolloServer.test.ts create mode 100644 packages/apollo-server-hapi/src/ApolloServer.test.ts create mode 100644 packages/apollo-server-integration-testsuite/src/ApolloServer.ts diff --git a/packages/apollo-server-core/src/ApolloServer.test.ts b/packages/apollo-server-core/src/ApolloServer.test.ts deleted file mode 100644 index a85f4dd0..00000000 --- a/packages/apollo-server-core/src/ApolloServer.test.ts +++ /dev/null @@ -1,957 +0,0 @@ -/* tslint:disable:no-unused-expression */ -import { expect } from 'chai'; -import { stub } from 'sinon'; -import http from 'http'; -import url from 'url'; -import 'mocha'; -import { sha256 } from 'js-sha256'; - -import { - GraphQLSchema, - GraphQLObjectType, - GraphQLString, - GraphQLError, - ValidationContext, - FieldDefinitionNode, -} from 'graphql'; - -import { PubSub } from 'graphql-subscriptions'; -import { SubscriptionClient } from 'subscriptions-transport-ws'; -import WebSocket from 'ws'; - -import { execute } from 'apollo-link'; -import { createHttpLink } from 'apollo-link-http'; -import { - createPersistedQueryLink as createPersistedQuery, - VERSION, -} from 'apollo-link-persisted-queries'; - -import { createApolloFetch } from 'apollo-fetch'; -import { ApolloServerBase } from './ApolloServer'; -import { AuthenticationError } from './errors'; -import { gql } from './index'; -import { convertNodeHttpToRequest } from './nodeHttpToRequest'; -import { runHttpQuery } from './runHttpQuery'; - -const INTROSPECTION_QUERY = ` - { - __schema { - directives { - name - } - } - } -`; - -const TEST_STRING_QUERY = ` - { - testString - } -`; - -const queryType = new GraphQLObjectType({ - name: 'QueryType', - fields: { - testString: { - type: GraphQLString, - resolve() { - return 'test string'; - }, - }, - }, -}); - -const schema = new GraphQLSchema({ - query: queryType, -}); - -function createHttpServer(server: ApolloServerBase) { - return http.createServer(async (req, res) => { - let body: any = []; - req - .on('data', chunk => { - body.push(chunk); - }) - .on('end', () => { - body = Buffer.concat(body).toString(); - // At this point, we have the headers, method, url and body, and can now - // do whatever we need to in order to respond to this request. - runHttpQuery([req, res], { - method: req.method, - options: (server as any).graphQLServerOptions({ req, res }), - query: - req.method.toUpperCase() === 'GET' - ? url.parse(req.url, true) - : JSON.parse(body), - request: convertNodeHttpToRequest(req), - }) - .then(gqlResponse => { - res.setHeader('Content-Type', 'application/json'); - res.setHeader( - 'Content-Length', - Buffer.byteLength(gqlResponse, 'utf8').toString(), - ); - res.write(gqlResponse); - res.end(); - }) - .catch(error => { - if (error.headers) { - Object.keys(error.headers).forEach(header => { - res.setHeader(header, error.headers[header]); - }); - } - - res.statusCode = error.statusCode; - res.write(error.message); - res.end(); - }); - }); - }); -} - -describe('ApolloServerBase', () => { - describe('constructor', () => { - describe('validation rules', () => { - it('accepts additional rules', async () => { - const NoTestString = (context: ValidationContext) => ({ - Field(node: FieldDefinitionNode) { - if (node.name.value === 'testString') { - context.reportError( - new GraphQLError('Not allowed to use', [node]), - ); - } - }, - }); - - const server = new ApolloServerBase({ - schema, - validationRules: [NoTestString], - introspection: false, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - - const introspectionResult = await apolloFetch({ - query: INTROSPECTION_QUERY, - }); - expect(introspectionResult.data, 'data should not exist').not.to.exist; - expect(introspectionResult.errors, 'errors should exist').to.exist; - - const result = await apolloFetch({ query: TEST_STRING_QUERY }); - expect(result.data, 'data should not exist').not.to.exist; - expect(result.errors, 'errors should exist').to.exist; - - await server.stop(); - }); - - it('allows introspection by default', async () => { - const nodeEnv = process.env.NODE_ENV; - delete process.env.NODE_ENV; - - const server = new ApolloServerBase({ - schema, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - - const result = await apolloFetch({ query: INTROSPECTION_QUERY }); - expect(result.data, 'data should not exist').to.exist; - expect(result.errors, 'errors should exist').not.to.exist; - - process.env.NODE_ENV = nodeEnv; - await server.stop(); - }); - - it('prevents introspection by default during production', async () => { - const nodeEnv = process.env.NODE_ENV; - process.env.NODE_ENV = 'production'; - - const server = new ApolloServerBase({ - schema, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - - const result = await apolloFetch({ query: INTROSPECTION_QUERY }); - expect(result.data, 'data should not exist').not.to.exist; - expect(result.errors, 'errors should exist').to.exist; - expect(result.errors.length).to.equal(1); - expect(result.errors[0].extensions.code).to.equal( - 'GRAPHQL_VALIDATION_FAILED', - ); - - process.env.NODE_ENV = nodeEnv; - await server.stop(); - }); - - it('allows introspection to be enabled explicitly', async () => { - const nodeEnv = process.env.NODE_ENV; - process.env.NODE_ENV = 'production'; - - const server = new ApolloServerBase({ - schema, - introspection: true, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - - const result = await apolloFetch({ query: INTROSPECTION_QUERY }); - expect(result.data, 'data should not exist').to.exist; - expect(result.errors, 'errors should exist').not.to.exist; - - process.env.NODE_ENV = nodeEnv; - await server.stop(); - }); - }); - - describe('schema creation', () => { - it('accepts typeDefs and resolvers', async () => { - const typeDefs = gql` - type Query { - hello: String - } - `; - const resolvers = { Query: { hello: () => 'hi' } }; - const server = new ApolloServerBase({ - typeDefs, - resolvers, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/', - }); - - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - const result = await apolloFetch({ query: '{hello}' }); - - expect(result.data).to.deep.equal({ hello: 'hi' }); - expect(result.errors, 'errors should exist').not.to.exist; - await server.stop(); - }); - it('throws if typeDefs are a string', async () => { - const typeDefs: any = ` - type Query { - hello: String - } - `; - const resolvers = { Query: { hello: () => 'hi' } }; - - expect( - () => - new ApolloServerBase({ - typeDefs, - resolvers, - }), - ).to.throw(/apollo-server/); - }); - it('uses schema over resolvers + typeDefs', async () => { - const typeDefs = gql` - type Query { - hello: String - } - `; - const resolvers = { Query: { hello: () => 'hi' } }; - const server = new ApolloServerBase({ - typeDefs, - resolvers, - schema, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/', - }); - - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - const typeDefResult = await apolloFetch({ query: '{hello}' }); - - expect(typeDefResult.data, 'data should not exist').not.to.exist; - expect(typeDefResult.errors, 'errors should exist').to.exist; - - const result = await apolloFetch({ query: '{testString}' }); - expect(result.data).to.deep.equal({ testString: 'test string' }); - expect(result.errors, 'errors should exist').not.to.exist; - await server.stop(); - }); - it('allows mocks as boolean', async () => { - const typeDefs = gql` - type Query { - hello: String - } - `; - const server = new ApolloServerBase({ - typeDefs, - mocks: true, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/', - }); - - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - const result = await apolloFetch({ query: '{hello}' }); - expect(result.data).to.deep.equal({ hello: 'Hello World' }); - expect(result.errors, 'errors should exist').not.to.exist; - await server.stop(); - }); - - it('allows mocks as an object', async () => { - const typeDefs = gql` - type Query { - hello: String - } - `; - const server = new ApolloServerBase({ - typeDefs, - mocks: { String: () => 'mock city' }, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/', - }); - - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - const result = await apolloFetch({ query: '{hello}' }); - - expect(result.data).to.deep.equal({ hello: 'mock city' }); - expect(result.errors, 'errors should exist').not.to.exist; - await server.stop(); - }); - }); - }); - - describe('lifecycle', () => { - it('defers context eval with thunk until after options creation', async () => { - const uniqueContext = { key: 'major' }; - const typeDefs = gql` - type Query { - hello: String - } - `; - const resolvers = { - Query: { - hello: (_parent, _args, context) => { - expect(context).to.equal(Promise.resolve(uniqueContext)); - return 'hi'; - }, - }, - }; - const spy = stub().returns({}); - const server = new ApolloServerBase({ - typeDefs, - resolvers, - context: spy, - }); - const httpServer = createHttpServer(server); - server.use({ - getHttp: () => httpServer, - path: '/', - }); - - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - - expect(spy.notCalled).true; - - await apolloFetch({ query: '{hello}' }); - expect(spy.calledOnce).true; - await apolloFetch({ query: '{hello}' }); - expect(spy.calledTwice).true; - await server.stop(); - }); - - it('allows context to be async function', async () => { - const uniqueContext = { key: 'major' }; - const spy = stub().returns('hi'); - const typeDefs = gql` - type Query { - hello: String - } - `; - const resolvers = { - Query: { - hello: (_parent, _args, context) => { - expect(context).to.equal(uniqueContext); - return spy(); - }, - }, - }; - const server = new ApolloServerBase({ - typeDefs, - resolvers, - context: async () => uniqueContext, - }); - const httpServer = createHttpServer(server); - server.use({ - getHttp: () => httpServer, - path: '/', - }); - - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - - expect(spy.notCalled).true; - await apolloFetch({ query: '{hello}' }); - expect(spy.calledOnce).true; - await server.stop(); - }); - - it('returns thrown context error as a valid graphql result', async () => { - const nodeEnv = process.env.NODE_ENV; - delete process.env.NODE_ENV; - const typeDefs = gql` - type Query { - hello: String - } - `; - const resolvers = { - Query: { - hello: () => { - throw Error('never get here'); - }, - }, - }; - const server = new ApolloServerBase({ - typeDefs, - resolvers, - context: () => { - throw new AuthenticationError('valid result'); - }, - }); - const httpServer = createHttpServer(server); - server.use({ - getHttp: () => httpServer, - path: '/', - }); - - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - - const result = await apolloFetch({ query: '{hello}' }); - expect(result.errors.length).to.equal(1); - expect(result.data).not.to.exist; - - const e = result.errors[0]; - expect(e.message).to.contain('valid result'); - expect(e.extensions).to.exist; - expect(e.extensions.code).to.equal('UNAUTHENTICATED'); - expect(e.extensions.exception.stacktrace).to.exist; - - process.env.NODE_ENV = nodeEnv; - await server.stop(); - }); - - it('propogates error codes in production', async () => { - const nodeEnv = process.env.NODE_ENV; - process.env.NODE_ENV = 'production'; - - const server = new ApolloServerBase({ - typeDefs: gql` - type Query { - error: String - } - `, - resolvers: { - Query: { - error: () => { - throw new AuthenticationError('we the best music'); - }, - }, - }, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - - const result = await apolloFetch({ query: `{error}` }); - expect(result.data).to.exist; - expect(result.data).to.deep.equal({ error: null }); - - expect(result.errors, 'errors should exist').to.exist; - expect(result.errors.length).to.equal(1); - expect(result.errors[0].extensions.code).to.equal('UNAUTHENTICATED'); - expect(result.errors[0].extensions.exception).not.to.exist; - - process.env.NODE_ENV = nodeEnv; - await server.stop(); - }); - - it('propogates error codes with null response in production', async () => { - const nodeEnv = process.env.NODE_ENV; - process.env.NODE_ENV = 'production'; - - const server = new ApolloServerBase({ - typeDefs: gql` - type Query { - error: String! - } - `, - resolvers: { - Query: { - error: () => { - throw new AuthenticationError('we the best music'); - }, - }, - }, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - const { url: uri } = await server.listen(); - const apolloFetch = createApolloFetch({ uri }); - - const result = await apolloFetch({ query: `{error}` }); - expect(result.data).null; - - expect(result.errors, 'errors should exist').to.exist; - expect(result.errors.length).to.equal(1); - expect(result.errors[0].extensions.code).to.equal('UNAUTHENTICATED'); - expect(result.errors[0].extensions.exception).not.to.exist; - - process.env.NODE_ENV = nodeEnv; - await server.stop(); - }); - }); - - describe('subscriptions', () => { - const SOMETHING_CHANGED_TOPIC = 'something_changed'; - const pubsub = new PubSub(); - let server: ApolloServerBase; - let subscription; - - function createEvent(num) { - return setTimeout( - () => - pubsub.publish(SOMETHING_CHANGED_TOPIC, { - num, - }), - num + 10, - ); - } - - afterEach(async () => { - if (server) { - try { - await server.stop(); - } catch (e) {} - server = null; - } - if (subscription) { - try { - await subscription.unsubscribe(); - } catch (e) {} - subscription = null; - } - }); - - it('enables subscriptions by default', done => { - const typeDefs = gql` - type Query { - hi: String - } - - type Subscription { - num: Int - } - `; - - const query = ` - subscription { - num - } - `; - - const resolvers = { - Query: { - hi: () => 'here to placate graphql-js', - }, - Subscription: { - num: { - subscribe: () => { - createEvent(1); - createEvent(2); - createEvent(3); - return pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC); - }, - }, - }, - }; - - server = new ApolloServerBase({ - typeDefs, - resolvers, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - server.listen({}).then(({ port }) => { - const client = new SubscriptionClient( - `ws://localhost:${port}${server.subscriptionsPath}`, - {}, - WebSocket, - ); - - const observable = client.request({ query }); - - let i = 1; - subscription = observable.subscribe({ - next: ({ data }) => { - try { - expect(data.num).to.equal(i); - if (i === 3) { - done(); - } - i++; - } catch (e) { - done(e); - } - }, - error: done, - complete: () => { - done(new Error('should not complete')); - }, - }); - }); - }); - it('disables subscritpions when option set to false', done => { - const typeDefs = gql` - type Query { - "graphql-js forces there to be a query type" - hi: String - } - - type Subscription { - num: Int - } - `; - - const query = ` - subscription { - num - } - `; - - const resolvers = { - Query: { - hi: () => 'here to placate graphql-js', - }, - Subscription: { - num: { - subscribe: () => { - createEvent(1); - return pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC); - }, - }, - }, - }; - - server = new ApolloServerBase({ - typeDefs, - resolvers, - }); - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - server - .listen({ - subscriptions: false, - }) - .then(({ port }) => { - const client = new SubscriptionClient( - `ws://localhost:${port}${server.subscriptionsPath}`, - {}, - WebSocket, - ); - - const observable = client.request({ query }); - - subscription = observable.subscribe({ - next: () => { - done(new Error('should not call next')); - }, - error: () => { - done(new Error('should not notify of error')); - }, - complete: () => { - done(new Error('should not complete')); - }, - }); - - //Unfortunately the error connection is not propagated to the - //observable. What should happen is we provide a default onError - //function that notifies the returned observable and can cursomize - //the behavior with an option in the client constructor. If you're - //available to make a PR to the following please do! - //https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts - client.onError((_: Error) => { - done(); - }); - }); - }); - it('accepts subscriptions configuration', done => { - const onConnect = stub().callsFake(connectionParams => ({ - ...connectionParams, - })); - const typeDefs = gql` - type Query { - hi: String - } - - type Subscription { - num: Int - } - `; - - const query = ` - subscription { - num - } - `; - - const resolvers = { - Query: { - hi: () => 'here to placate graphql-js', - }, - Subscription: { - num: { - subscribe: () => { - createEvent(1); - createEvent(2); - createEvent(3); - return pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC); - }, - }, - }, - }; - - server = new ApolloServerBase({ - typeDefs, - resolvers, - }); - const httpServer = createHttpServer(server); - const path = '/sub'; - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - server - .listen({ - subscriptions: { onConnect, path }, - }) - .then(({ port }) => { - expect(onConnect.notCalled).true; - - expect(server.subscriptionsPath).to.equal(path); - const client = new SubscriptionClient( - `ws://localhost:${port}${server.subscriptionsPath}`, - {}, - WebSocket, - ); - - const observable = client.request({ query }); - - let i = 1; - subscription = observable.subscribe({ - next: ({ data }) => { - try { - expect(onConnect.calledOnce).true; - expect(data.num).to.equal(i); - if (i === 3) { - done(); - } - i++; - } catch (e) { - done(e); - } - }, - error: done, - complete: () => { - done(new Error('should not complete')); - }, - }); - }) - .catch(done); - }); - }); - describe('Persisted Queries', () => { - let server; - const query = gql` - ${TEST_STRING_QUERY} - `; - const hash = sha256 - .create() - .update(TEST_STRING_QUERY) - .hex(); - const extensions = { - persistedQuery: { - version: VERSION, - sha256Hash: hash, - }, - }; - let uri: string; - - beforeEach(async () => { - server = new ApolloServerBase({ - schema, - introspection: false, - persistedQueries: { - cache: new Map() as any, - }, - }); - - const httpServer = createHttpServer(server); - - server.use({ - getHttp: () => httpServer, - path: '/graphql', - }); - uri = (await server.listen()).url; - }); - - afterEach(async () => { - await server.stop(); - }); - - it('returns PersistedQueryNotFound on the first try', async () => { - const apolloFetch = createApolloFetch({ uri }); - - const result = await apolloFetch({ - extensions, - } as any); - - expect(result.data).not.to.exist; - expect(result.errors.length).to.equal(1); - expect(result.errors[0].message).to.equal('PersistedQueryNotFound'); - expect(result.errors[0].extensions.code).to.equal( - 'PERSISTED_QUERY_NOT_FOUND', - ); - }); - it('returns result on the second try', async () => { - const apolloFetch = createApolloFetch({ uri }); - - await apolloFetch({ - extensions, - } as any); - const result = await apolloFetch({ - extensions, - query: TEST_STRING_QUERY, - } as any); - - expect(result.data).to.deep.equal({ testString: 'test string' }); - expect(result.errors).not.to.exist; - }); - - it('returns result on the persisted query', async () => { - const apolloFetch = createApolloFetch({ uri }); - - await apolloFetch({ - extensions, - } as any); - await apolloFetch({ - extensions, - query: TEST_STRING_QUERY, - } as any); - const result = await apolloFetch({ - extensions, - } as any); - - expect(result.data).to.deep.equal({ testString: 'test string' }); - expect(result.errors).not.to.exist; - }); - - it('returns error when hash does not match', async () => { - const apolloFetch = createApolloFetch({ uri }); - - try { - await apolloFetch({ - extensions: { - persistedQuery: { - version: VERSION, - sha: - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - }, - }, - query: TEST_STRING_QUERY, - } as any); - expect.fail(); - } catch (e) { - expect(e.response.status).to.equal(400); - expect(e.response.raw).to.match(/does not match query/); - } - }); - - it('returns correct result for persisted query link', done => { - const variables = { id: 1 }; - const link = createPersistedQuery().concat( - createHttpLink({ uri, fetch } as any), - ); - - execute(link, { query, variables } as any).subscribe(result => { - expect(result.data).to.deep.equal({ testString: 'test string' }); - done(); - }, done); - }); - - it('returns correct result for persisted query link using get request', done => { - const variables = { id: 1 }; - const link = createPersistedQuery({ - useGETForHashedQueries: true, - }).concat(createHttpLink({ uri, fetch } as any)); - - execute(link, { query, variables } as any).subscribe(result => { - expect(result.data).to.deep.equal({ testString: 'test string' }); - done(); - }, done); - }); - }); -}); diff --git a/packages/apollo-server-express/src/ApolloServer.test.ts b/packages/apollo-server-express/src/ApolloServer.test.ts index 94475c8f..cd0605a7 100644 --- a/packages/apollo-server-express/src/ApolloServer.test.ts +++ b/packages/apollo-server-express/src/ApolloServer.test.ts @@ -2,6 +2,9 @@ import { expect } from 'chai'; import 'mocha'; import express from 'express'; +import net from 'net'; +import http from 'http'; + import request from 'request'; import FormData from 'form-data'; import fs from 'fs'; @@ -9,7 +12,12 @@ import fetch from 'node-fetch'; import { createApolloFetch } from 'apollo-fetch'; import { ApolloServerBase, AuthenticationError } from 'apollo-server-core'; -import { registerServer } from './ApolloServer'; +import { registerServer, ApolloServer } from './ApolloServer'; + +import { + testApolloServer, + createServerInfo, +} from 'apollo-server-integration-testsuite'; //to remove the circular dependency, we reference it directly const gql = require('../../apollo-server/dist/index').gql; @@ -26,22 +34,43 @@ const resolvers = { }, }; +const url = 'http://localhost:4000/graphql'; +const uri = url; + +describe('apollo-server-express', () => { + let server; + let httpServer; + testApolloServer( + async options => { + server = new ApolloServer(options); + const app = express(); + registerServer({ app, server }); + httpServer = await new Promise(resolve => { + const s = app.listen({ port: 4000 }, () => resolve(s)); + }); + return createServerInfo(server, httpServer); + }, + async () => { + if (server) await server.stop(); + if (httpServer && httpServer.listening) await httpServer.close(); + }, + ); +}); + describe('apollo-server-express', () => { //to remove the circular dependency, we reference it directly const ApolloServer = require('../../apollo-server/dist/index').ApolloServer; - let server: ApolloServerBase & { - createGraphQLServerOptions: ( - req: express.Request, - res: express.Response, - ) => any; - }; + let server: ApolloServerBase | any; + let app: express.Application; + let httpServer: http.Server; afterEach(async () => { if (server) await server.stop(); + if (httpServer) await httpServer.close(); }); - describe('', () => { + describe('constructor', () => { it('accepts typeDefs and resolvers', () => { const app = express(); const server = new ApolloServer({ typeDefs, resolvers }); @@ -65,7 +94,9 @@ describe('apollo-server-express', () => { registerServer({ app, server }); - const { url: uri } = await server.listen(); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); const apolloFetch = createApolloFetch({ uri }); const result = await apolloFetch({ query: '{hello}' }); @@ -96,7 +127,9 @@ describe('apollo-server-express', () => { registerServer({ app, server, gui: true }); - const { url } = await server.listen(); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); const apolloFetch = createApolloFetch({ uri: url }); const result = await apolloFetch({ query: INTROSPECTION_QUERY }); @@ -105,7 +138,7 @@ describe('apollo-server-express', () => { 'GRAPHQL_VALIDATION_FAILED', ); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { request( { url, @@ -140,8 +173,10 @@ describe('apollo-server-express', () => { registerServer({ app, server }); - const { url } = await server.listen(); - return new Promise((resolve, reject) => { + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); + return new Promise((resolve, reject) => { request( { url, @@ -174,7 +209,9 @@ describe('apollo-server-express', () => { registerServer({ app, server, cors: { origin: 'apollographql.com' } }); - const { url: uri } = await server.listen({}); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); const apolloFetch = createApolloFetch({ uri }).useAfter( (response, next) => { @@ -196,7 +233,9 @@ describe('apollo-server-express', () => { registerServer({ app, server, bodyParserConfig: { limit: 0 } }); - const { url: uri } = await server.listen({}); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); const apolloFetch = createApolloFetch({ uri }); @@ -226,7 +265,10 @@ describe('apollo-server-express', () => { registerServer({ app, server, bodyParserConfig: { limit: 0 } }); - const { port } = await server.listen(); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); + const { port } = httpServer.address() as net.AddressInfo; return new Promise((resolve, reject) => { request( @@ -262,7 +304,10 @@ describe('apollo-server-express', () => { }, }); - const { port } = await server.listen({}); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); + const { port } = httpServer.address() as net.AddressInfo; return new Promise((resolve, reject) => { request( @@ -295,7 +340,10 @@ describe('apollo-server-express', () => { disableHealthCheck: true, }); - const { port } = await server.listen({}); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); + const { port } = httpServer.address() as net.AddressInfo; return new Promise((resolve, reject) => { request( @@ -351,7 +399,10 @@ describe('apollo-server-express', () => { server, }); - const { port } = await server.listen({}); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); + const { port } = httpServer.address() as net.AddressInfo; const body = new FormData(); @@ -423,7 +474,9 @@ describe('apollo-server-express', () => { app = express(); registerServer({ app, server }); - const { url: uri } = await server.listen(); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); const apolloFetch = createApolloFetch({ uri }); const result = await apolloFetch({ query: '{hello}' }); @@ -461,7 +514,9 @@ describe('apollo-server-express', () => { app = express(); registerServer({ app, server }); - const { url: uri } = await server.listen(); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); const apolloFetch = createApolloFetch({ uri }); const result = await apolloFetch({ query: `{error}` }); @@ -499,7 +554,9 @@ describe('apollo-server-express', () => { app = express(); registerServer({ app, server }); - const { url: uri } = await server.listen(); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); const apolloFetch = createApolloFetch({ uri }); const result = await apolloFetch({ query: `{error}` }); @@ -536,7 +593,9 @@ describe('apollo-server-express', () => { app = express(); registerServer({ app, server }); - const { url: uri } = await server.listen(); + httpServer = await new Promise(resolve => { + const l = app.listen({ port: 4000 }, () => resolve(l)); + }); const apolloFetch = createApolloFetch({ uri }); const result = await apolloFetch({ query: `{error}` }); diff --git a/packages/apollo-server-hapi/src/ApolloServer.test.ts b/packages/apollo-server-hapi/src/ApolloServer.test.ts new file mode 100644 index 00000000..570ae083 --- /dev/null +++ b/packages/apollo-server-hapi/src/ApolloServer.test.ts @@ -0,0 +1,28 @@ +import { Server } from 'hapi'; +import { + testApolloServer, + createServerInfo, +} from 'apollo-server-integration-testsuite'; + +import { registerServer, ApolloServer } from './ApolloServer'; + +describe('apollo-server-hapi', () => { + let server; + let app; + let httpServer; + testApolloServer( + async options => { + server = new ApolloServer(options); + app = new Server({ host: 'localhost', port: 4000 }); + registerServer({ app, server }); + await app.start(); + const httpServer = app.listener; + return createServerInfo(server, httpServer); + }, + async () => { + if (server) await server.stop(); + if (app) await app.stop(); + if (httpServer && httpServer.listening) await httpServer.close(); + }, + ); +}); diff --git a/packages/apollo-server-integration-testsuite/package.json b/packages/apollo-server-integration-testsuite/package.json index b5eac3fe..80237805 100644 --- a/packages/apollo-server-integration-testsuite/package.json +++ b/packages/apollo-server-integration-testsuite/package.json @@ -31,8 +31,14 @@ "definition": "dist/index.d.ts" }, "devDependencies": { - "apollo-link-persisted-queries": "^0.2.0", + "apollo-fetch": "^0.7.0", + "apollo-link": "^1.2.2", + "apollo-link-http": "^1.5.4", + "apollo-link-persisted-queries": "^0.2.1", + "graphql-subscriptions": "^0.5.8", "graphql-tag": "^2.9.2", - "js-sha256": "^0.9.0" + "js-sha256": "^0.9.0", + "subscriptions-transport-ws": "^0.9.11", + "ws": "^5.2.0" } } diff --git a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts new file mode 100644 index 00000000..67ee429b --- /dev/null +++ b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts @@ -0,0 +1,846 @@ +/* tslint:disable:no-unused-expression */ +import { expect } from 'chai'; +import { stub } from 'sinon'; +import http from 'http'; +import net from 'net'; +import 'mocha'; +import { sha256 } from 'js-sha256'; + +import { + GraphQLSchema, + GraphQLObjectType, + GraphQLString, + GraphQLError, + ValidationContext, + FieldDefinitionNode, +} from 'graphql'; + +import { PubSub } from 'graphql-subscriptions'; +import { SubscriptionClient } from 'subscriptions-transport-ws'; +import WebSocket from 'ws'; + +import { execute } from 'apollo-link'; +import { createHttpLink } from 'apollo-link-http'; +import { + createPersistedQueryLink as createPersistedQuery, + VERSION, +} from 'apollo-link-persisted-queries'; + +import { createApolloFetch } from 'apollo-fetch'; +import { + AuthenticationError, + gql, + Config, + ApolloServerBase, +} from 'apollo-server-core'; + +export function createServerInfo( + server: AS, + httpServer: http.Server, +): ServerInfo { + const serverInfo: any = { + ...(httpServer.address() as net.AddressInfo), + server, + httpServer, + }; + + // Convert IPs which mean "any address" (IPv4 or IPv6) into localhost + // corresponding loopback ip. Note that the url field we're setting is + // primarily for consumption by our test suite. If this heuristic is + // wrong for your use case, explicitly specify a frontend host (in the + // `frontends.host` field in your engine config, or in the `host` + // option to ApolloServer.listen). + let hostForUrl = serverInfo.address; + if (serverInfo.address === '' || serverInfo.address === '::') + hostForUrl = 'localhost'; + + serverInfo.url = require('url').format({ + protocol: 'http', + hostname: hostForUrl, + port: serverInfo.port, + pathname: server.graphqlPath, + }); + + return serverInfo; +} + +const INTROSPECTION_QUERY = ` + { + __schema { + directives { + name + } + } + } +`; + +const TEST_STRING_QUERY = ` + { + testString + } +`; + +const queryType = new GraphQLObjectType({ + name: 'QueryType', + fields: { + testString: { + type: GraphQLString, + resolve() { + return 'test string'; + }, + }, + }, +}); + +const schema = new GraphQLSchema({ + query: queryType, +}); + +export interface ServerInfo { + address: string; + family: string; + url: string; + port: number | string; + server: AS; + httpServer: http.Server; +} + +export interface CreateServerFunc { + (config: Config): Promise>; +} + +export interface StopServerFunc { + (): Promise; +} + +export function testApolloServer( + createApolloServer: CreateServerFunc, + stopServer: StopServerFunc, +) { + describe('ApolloServer', () => { + afterEach(stopServer); + + describe('constructor', () => { + describe('validation rules', () => { + it('accepts additional rules', async () => { + const NoTestString = (context: ValidationContext) => ({ + Field(node: FieldDefinitionNode) { + if (node.name.value === 'testString') { + context.reportError( + new GraphQLError('Not allowed to use', [node]), + ); + } + }, + }); + + const { url: uri } = await createApolloServer({ + schema, + validationRules: [NoTestString], + introspection: false, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const introspectionResult = await apolloFetch({ + query: INTROSPECTION_QUERY, + }); + expect(introspectionResult.data, 'data should not exist').not.to + .exist; + expect(introspectionResult.errors, 'errors should exist').to.exist; + + const result = await apolloFetch({ query: TEST_STRING_QUERY }); + expect(result.data, 'data should not exist').not.to.exist; + expect(result.errors, 'errors should exist').to.exist; + }); + + it('allows introspection by default', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + + const { url: uri } = await createApolloServer({ + schema, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: INTROSPECTION_QUERY }); + expect(result.data, 'data should not exist').to.exist; + expect(result.errors, 'errors should exist').not.to.exist; + + process.env.NODE_ENV = nodeEnv; + }); + + it('prevents introspection by default during production', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + + const { url: uri } = await createApolloServer({ + schema, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: INTROSPECTION_QUERY }); + expect(result.data, 'data should not exist').not.to.exist; + expect(result.errors, 'errors should exist').to.exist; + expect(result.errors.length).to.equal(1); + expect(result.errors[0].extensions.code).to.equal( + 'GRAPHQL_VALIDATION_FAILED', + ); + + process.env.NODE_ENV = nodeEnv; + }); + + it('allows introspection to be enabled explicitly', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + + const { url: uri } = await createApolloServer({ + schema, + introspection: true, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: INTROSPECTION_QUERY }); + expect(result.data, 'data should not exist').to.exist; + expect(result.errors, 'errors should exist').not.to.exist; + + process.env.NODE_ENV = nodeEnv; + }); + }); + + describe('schema creation', () => { + it('accepts typeDefs and resolvers', async () => { + const typeDefs = gql` + type Query { + hello: String + } + `; + const resolvers = { Query: { hello: () => 'hi' } }; + const { url: uri } = await createApolloServer({ + typeDefs, + resolvers, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ query: '{hello}' }); + + expect(result.data).to.deep.equal({ hello: 'hi' }); + expect(result.errors, 'errors should exist').not.to.exist; + }); + it('throws if typeDefs are a string', done => { + const typeDefs: any = ` + type Query { + hello: String + } + `; + const resolvers = { Query: { hello: () => 'hi' } }; + + createApolloServer({ + typeDefs, + resolvers, + }) + .then(expect.fail) + .catch(e => expect(e.message).to.match(/apollo-server/)) + .then(() => done()); + }); + it('uses schema over resolvers + typeDefs', async () => { + const typeDefs = gql` + type Query { + hello: String + } + `; + const resolvers = { Query: { hello: () => 'hi' } }; + const { url: uri } = await createApolloServer({ + typeDefs, + resolvers, + schema, + }); + + const apolloFetch = createApolloFetch({ uri }); + const typeDefResult = await apolloFetch({ query: '{hello}' }); + + expect(typeDefResult.data, 'data should not exist').not.to.exist; + expect(typeDefResult.errors, 'errors should exist').to.exist; + + const result = await apolloFetch({ query: '{testString}' }); + expect(result.data).to.deep.equal({ testString: 'test string' }); + expect(result.errors, 'errors should exist').not.to.exist; + }); + it('allows mocks as boolean', async () => { + const typeDefs = gql` + type Query { + hello: String + } + `; + const { url: uri } = await createApolloServer({ + typeDefs, + mocks: true, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ query: '{hello}' }); + expect(result.data).to.deep.equal({ hello: 'Hello World' }); + expect(result.errors, 'errors should exist').not.to.exist; + }); + + it('allows mocks as an object', async () => { + const typeDefs = gql` + type Query { + hello: String + } + `; + const { url: uri } = await createApolloServer({ + typeDefs, + mocks: { String: () => 'mock city' }, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ query: '{hello}' }); + + expect(result.data).to.deep.equal({ hello: 'mock city' }); + expect(result.errors, 'errors should exist').not.to.exist; + }); + }); + }); + + describe('lifecycle', () => { + it('defers context eval with thunk until after options creation', async () => { + const uniqueContext = { key: 'major' }; + const typeDefs = gql` + type Query { + hello: String + } + `; + const resolvers = { + Query: { + hello: (_parent, _args, context) => { + expect(context).to.equal(Promise.resolve(uniqueContext)); + return 'hi'; + }, + }, + }; + const spy = stub().returns({}); + const { url: uri } = await createApolloServer({ + typeDefs, + resolvers, + context: spy, + }); + + const apolloFetch = createApolloFetch({ uri }); + + expect(spy.notCalled).true; + + await apolloFetch({ query: '{hello}' }); + expect(spy.calledOnce).true; + await apolloFetch({ query: '{hello}' }); + expect(spy.calledTwice).true; + }); + + it('allows context to be async function', async () => { + const uniqueContext = { key: 'major' }; + const spy = stub().returns('hi'); + const typeDefs = gql` + type Query { + hello: String + } + `; + const resolvers = { + Query: { + hello: (_parent, _args, context) => { + expect(context).to.equal(uniqueContext); + return spy(); + }, + }, + }; + const { url: uri } = await createApolloServer({ + typeDefs, + resolvers, + context: async () => uniqueContext, + }); + + const apolloFetch = createApolloFetch({ uri }); + + expect(spy.notCalled).true; + await apolloFetch({ query: '{hello}' }); + expect(spy.calledOnce).true; + }); + + it('returns thrown context error as a valid graphql result', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + const typeDefs = gql` + type Query { + hello: String + } + `; + const resolvers = { + Query: { + hello: () => { + throw Error('never get here'); + }, + }, + }; + const { url: uri } = await createApolloServer({ + typeDefs, + resolvers, + context: () => { + throw new AuthenticationError('valid result'); + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: '{hello}' }); + expect(result.errors.length).to.equal(1); + expect(result.data).not.to.exist; + + const e = result.errors[0]; + expect(e.message).to.contain('valid result'); + expect(e.extensions).to.exist; + expect(e.extensions.code).to.equal('UNAUTHENTICATED'); + expect(e.extensions.exception.stacktrace).to.exist; + + process.env.NODE_ENV = nodeEnv; + }); + + it('propogates error codes in production', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + + const { url: uri } = await createApolloServer({ + typeDefs: gql` + type Query { + error: String + } + `, + resolvers: { + Query: { + error: () => { + throw new AuthenticationError('we the best music'); + }, + }, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: `{error}` }); + expect(result.data).to.exist; + expect(result.data).to.deep.equal({ error: null }); + + expect(result.errors, 'errors should exist').to.exist; + expect(result.errors.length).to.equal(1); + expect(result.errors[0].extensions.code).to.equal('UNAUTHENTICATED'); + expect(result.errors[0].extensions.exception).not.to.exist; + + process.env.NODE_ENV = nodeEnv; + }); + + it('propogates error codes with null response in production', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + + const { url: uri } = await createApolloServer({ + typeDefs: gql` + type Query { + error: String! + } + `, + resolvers: { + Query: { + error: () => { + throw new AuthenticationError('we the best music'); + }, + }, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: `{error}` }); + expect(result.data).null; + + expect(result.errors, 'errors should exist').to.exist; + expect(result.errors.length).to.equal(1); + expect(result.errors[0].extensions.code).to.equal('UNAUTHENTICATED'); + expect(result.errors[0].extensions.exception).not.to.exist; + + process.env.NODE_ENV = nodeEnv; + }); + }); + + describe('subscriptions', () => { + const SOMETHING_CHANGED_TOPIC = 'something_changed'; + const pubsub = new PubSub(); + let subscription; + + function createEvent(num) { + return setTimeout( + () => + pubsub.publish(SOMETHING_CHANGED_TOPIC, { + num, + }), + num + 10, + ); + } + + afterEach(async () => { + if (subscription) { + try { + await subscription.unsubscribe(); + } catch (e) {} + subscription = null; + } + }); + + it('enables subscriptions after creating subscriptions server', done => { + const typeDefs = gql` + type Query { + hi: String + } + + type Subscription { + num: Int + } + `; + + const query = ` + subscription { + num + } + `; + + const resolvers = { + Query: { + hi: () => 'here to placate graphql-js', + }, + Subscription: { + num: { + subscribe: () => { + createEvent(1); + createEvent(2); + createEvent(3); + return pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC); + }, + }, + }, + }; + + createApolloServer({ + typeDefs, + resolvers, + }).then(({ port, server, httpServer }) => { + server.createSubscriptionServer(httpServer); + + const client = new SubscriptionClient( + `ws://localhost:${port}${server.subscriptionsPath}`, + {}, + WebSocket, + ); + + const observable = client.request({ query }); + + let i = 1; + subscription = observable.subscribe({ + next: ({ data }) => { + try { + expect(data.num).to.equal(i); + if (i === 3) { + done(); + } + i++; + } catch (e) { + done(e); + } + }, + error: done, + complete: () => { + done(new Error('should not complete')); + }, + }); + }); + }); + it('disables subscritpions when option set to false', done => { + const typeDefs = gql` + type Query { + "graphql-js forces there to be a query type" + hi: String + } + + type Subscription { + num: Int + } + `; + + const query = ` + subscription { + num + } + `; + + const resolvers = { + Query: { + hi: () => 'here to placate graphql-js', + }, + Subscription: { + num: { + subscribe: () => { + createEvent(1); + return pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC); + }, + }, + }, + }; + + createApolloServer({ + typeDefs, + resolvers, + subscriptions: false, + }).then(({ port, server, httpServer }) => { + try { + server.createSubscriptionServer(httpServer); + expect.fail(); + } catch (e) { + expect(e.message).to.match(/disabled/); + } + + const client = new SubscriptionClient( + `ws://localhost:${port}${server.subscriptionsPath}`, + {}, + WebSocket, + ); + + const observable = client.request({ query }); + + subscription = observable.subscribe({ + next: () => { + done(new Error('should not call next')); + }, + error: () => { + done(new Error('should not notify of error')); + }, + complete: () => { + done(new Error('should not complete')); + }, + }); + + //Unfortunately the error connection is not propagated to the + //observable. What should happen is we provide a default onError + //function that notifies the returned observable and can cursomize + //the behavior with an option in the client constructor. If you're + //available to make a PR to the following please do! + //https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts + client.onError((_: Error) => { + done(); + }); + }); + }); + it('accepts subscriptions configuration', done => { + const onConnect = stub().callsFake(connectionParams => ({ + ...connectionParams, + })); + const typeDefs = gql` + type Query { + hi: String + } + + type Subscription { + num: Int + } + `; + + const query = ` + subscription { + num + } + `; + + const resolvers = { + Query: { + hi: () => 'here to placate graphql-js', + }, + Subscription: { + num: { + subscribe: () => { + createEvent(1); + createEvent(2); + createEvent(3); + return pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC); + }, + }, + }, + }; + + const path = '/sub'; + createApolloServer({ + typeDefs, + resolvers, + subscriptions: { onConnect, path }, + }) + .then(({ port, server, httpServer }) => { + server.createSubscriptionServer(httpServer); + expect(onConnect.notCalled).true; + + expect(server.subscriptionsPath).to.equal(path); + const client = new SubscriptionClient( + `ws://localhost:${port}${server.subscriptionsPath}`, + {}, + WebSocket, + ); + + const observable = client.request({ query }); + + let i = 1; + subscription = observable.subscribe({ + next: ({ data }) => { + try { + expect(onConnect.calledOnce).true; + expect(data.num).to.equal(i); + if (i === 3) { + done(); + } + i++; + } catch (e) { + done(e); + } + }, + error: done, + complete: () => { + done(new Error('should not complete')); + }, + }); + }) + .catch(done); + }); + }); + + describe('Persisted Queries', () => { + let uri: string; + const query = gql` + ${TEST_STRING_QUERY} + `; + const hash = sha256 + .create() + .update(TEST_STRING_QUERY) + .hex(); + const extensions = { + persistedQuery: { + version: VERSION, + sha256Hash: hash, + }, + }; + + beforeEach(async () => { + const serverInfo = await createApolloServer({ + schema, + introspection: false, + persistedQueries: { + cache: new Map() as any, + }, + }); + uri = serverInfo.url; + }); + + it('returns PersistedQueryNotFound on the first try', async () => { + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ + extensions, + } as any); + + expect(result.data).not.to.exist; + expect(result.errors.length).to.equal(1); + expect(result.errors[0].message).to.equal('PersistedQueryNotFound'); + expect(result.errors[0].extensions.code).to.equal( + 'PERSISTED_QUERY_NOT_FOUND', + ); + }); + it('returns result on the second try', async () => { + const apolloFetch = createApolloFetch({ uri }); + + await apolloFetch({ + extensions, + } as any); + const result = await apolloFetch({ + extensions, + query: TEST_STRING_QUERY, + } as any); + + expect(result.data).to.deep.equal({ testString: 'test string' }); + expect(result.errors).not.to.exist; + }); + + it('returns result on the persisted query', async () => { + const apolloFetch = createApolloFetch({ uri }); + + await apolloFetch({ + extensions, + } as any); + await apolloFetch({ + extensions, + query: TEST_STRING_QUERY, + } as any); + const result = await apolloFetch({ + extensions, + } as any); + + expect(result.data).to.deep.equal({ testString: 'test string' }); + expect(result.errors).not.to.exist; + }); + + //Apollo Fetch's result depends on the server implementation, if the + //statusText of the error is unparsable, then we'll fall into the catch, + //such as with express. If it is parsable, then we'll use the afterware + it('returns error when hash does not match', async () => { + const apolloFetch = createApolloFetch({ uri }).useAfter((res, next) => { + expect(res.response.status).to.equal(400); + expect(res.response.raw).to.match(/does not match query/); + next(); + }); + + try { + await apolloFetch({ + extensions: { + persistedQuery: { + version: VERSION, + sha: + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + }, + }, + query: TEST_STRING_QUERY, + } as any); + } catch (e) { + expect(e.response).to.exist; + expect(e.response.status).to.equal(400); + expect(e.response.raw).to.match(/does not match query/); + } + }); + + it('returns correct result for persisted query link', done => { + const variables = { id: 1 }; + const link = createPersistedQuery().concat( + createHttpLink({ uri, fetch } as any), + ); + + execute(link, { query, variables } as any).subscribe(result => { + expect(result.data).to.deep.equal({ testString: 'test string' }); + done(); + }, done); + }); + + it('returns correct result for persisted query link using get request', done => { + const variables = { id: 1 }; + const link = createPersistedQuery({ + useGETForHashedQueries: true, + }).concat(createHttpLink({ uri, fetch } as any)); + + execute(link, { query, variables } as any).subscribe(result => { + expect(result.data).to.deep.equal({ testString: 'test string' }); + done(); + }, done); + }); + }); + }); +} diff --git a/packages/apollo-server-integration-testsuite/src/index.ts b/packages/apollo-server-integration-testsuite/src/index.ts index 52fc7a84..eeeca716 100644 --- a/packages/apollo-server-integration-testsuite/src/index.ts +++ b/packages/apollo-server-integration-testsuite/src/index.ts @@ -24,6 +24,8 @@ import { GraphQLOptions, Config } from 'apollo-server-core'; import { OperationStore } from 'apollo-server-module-operation-store'; import gql from 'graphql-tag'; +export * from './ApolloServer'; + const personType = new GraphQLObjectType({ name: 'PersonType', fields: { diff --git a/packages/apollo-server/src/index.test.ts b/packages/apollo-server/src/index.test.ts index e913986c..cce84fb1 100644 --- a/packages/apollo-server/src/index.test.ts +++ b/packages/apollo-server/src/index.test.ts @@ -89,7 +89,7 @@ describe('apollo-server', () => { resolvers, }); - const { url: uri } = await server.listen({}); + const { url: uri } = await server.listen(); const apolloFetch = createApolloFetch({ uri }).useAfter( (response, next) => { diff --git a/test/tests.js b/test/tests.js index 2ea68c4d..cda46907 100644 --- a/test/tests.js +++ b/test/tests.js @@ -12,22 +12,20 @@ process.on('unhandledRejection', reason => { require('../packages/apollo-server-core/dist/runQuery.test.js'); require('../packages/apollo-server-core/dist/runHttpQuery.test.js'); require('../packages/apollo-server-core/dist/errors.test.js'); -require('../packages/apollo-server-core/dist/ApolloServer.test.js'); //Apollo server 2 tests //apollo-server require('../packages/apollo-server/dist/index.test.js'); -//apollo-server-express -require('../packages/apollo-server-express/dist/ApolloServer.test.js'); - -//Apollo server 1 tests require('../packages/apollo-server-module-operation-store/dist/operationStore.test'); +require('../packages/apollo-server-express/dist/ApolloServer.test.js'); require('../packages/apollo-server-express/dist/expressApollo.test'); require('../packages/apollo-server-express/dist/connectApollo.test'); + (NODE_MAJOR_VERSION >= 9 || (NODE_MAJOR_VERSION >= 8 && NODE_MAJOR_REVISION >= 9)) && - require('../packages/apollo-server-hapi/dist/hapiApollo.test'); // Hapi 17 is 8.9+ +require('../packages/apollo-server-hapi/dist/hapiApollo.test') && // Hapi 17 is 8.9+ + require('../packages/apollo-server-hapi/dist/ApolloServer.test.js'); require('../packages/apollo-server-express/dist/apolloServerHttp.test');