mirror of
https://github.com/vale981/apollo-server
synced 2025-03-06 02:01:40 -05:00
move ApolloServer tests into integration tests from core
This commit is contained in:
parent
304debf9ee
commit
9d853f4be4
8 changed files with 971 additions and 989 deletions
|
@ -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<string, string>() 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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -2,6 +2,9 @@ import { expect } from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
|
||||||
|
import net from 'net';
|
||||||
|
import http from 'http';
|
||||||
|
|
||||||
import request from 'request';
|
import request from 'request';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
@ -9,7 +12,12 @@ import fetch from 'node-fetch';
|
||||||
import { createApolloFetch } from 'apollo-fetch';
|
import { createApolloFetch } from 'apollo-fetch';
|
||||||
|
|
||||||
import { ApolloServerBase, AuthenticationError } from 'apollo-server-core';
|
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
|
//to remove the circular dependency, we reference it directly
|
||||||
const gql = require('../../apollo-server/dist/index').gql;
|
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<http.Server>(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', () => {
|
describe('apollo-server-express', () => {
|
||||||
//to remove the circular dependency, we reference it directly
|
//to remove the circular dependency, we reference it directly
|
||||||
const ApolloServer = require('../../apollo-server/dist/index').ApolloServer;
|
const ApolloServer = require('../../apollo-server/dist/index').ApolloServer;
|
||||||
let server: ApolloServerBase & {
|
let server: ApolloServerBase | any;
|
||||||
createGraphQLServerOptions: (
|
|
||||||
req: express.Request,
|
|
||||||
res: express.Response,
|
|
||||||
) => any;
|
|
||||||
};
|
|
||||||
let app: express.Application;
|
let app: express.Application;
|
||||||
|
let httpServer: http.Server;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (server) await server.stop();
|
if (server) await server.stop();
|
||||||
|
if (httpServer) await httpServer.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('', () => {
|
describe('constructor', () => {
|
||||||
it('accepts typeDefs and resolvers', () => {
|
it('accepts typeDefs and resolvers', () => {
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = new ApolloServer({ typeDefs, resolvers });
|
const server = new ApolloServer({ typeDefs, resolvers });
|
||||||
|
@ -65,7 +94,9 @@ describe('apollo-server-express', () => {
|
||||||
|
|
||||||
registerServer({ app, server });
|
registerServer({ app, server });
|
||||||
|
|
||||||
const { url: uri } = await server.listen();
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
const apolloFetch = createApolloFetch({ uri });
|
const apolloFetch = createApolloFetch({ uri });
|
||||||
const result = await apolloFetch({ query: '{hello}' });
|
const result = await apolloFetch({ query: '{hello}' });
|
||||||
|
|
||||||
|
@ -96,7 +127,9 @@ describe('apollo-server-express', () => {
|
||||||
|
|
||||||
registerServer({ app, server, gui: true });
|
registerServer({ app, server, gui: true });
|
||||||
|
|
||||||
const { url } = await server.listen();
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
const apolloFetch = createApolloFetch({ uri: url });
|
const apolloFetch = createApolloFetch({ uri: url });
|
||||||
const result = await apolloFetch({ query: INTROSPECTION_QUERY });
|
const result = await apolloFetch({ query: INTROSPECTION_QUERY });
|
||||||
|
|
||||||
|
@ -105,7 +138,7 @@ describe('apollo-server-express', () => {
|
||||||
'GRAPHQL_VALIDATION_FAILED',
|
'GRAPHQL_VALIDATION_FAILED',
|
||||||
);
|
);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<http.Server>((resolve, reject) => {
|
||||||
request(
|
request(
|
||||||
{
|
{
|
||||||
url,
|
url,
|
||||||
|
@ -140,8 +173,10 @@ describe('apollo-server-express', () => {
|
||||||
|
|
||||||
registerServer({ app, server });
|
registerServer({ app, server });
|
||||||
|
|
||||||
const { url } = await server.listen();
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
return new Promise((resolve, reject) => {
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
|
return new Promise<http.Server>((resolve, reject) => {
|
||||||
request(
|
request(
|
||||||
{
|
{
|
||||||
url,
|
url,
|
||||||
|
@ -174,7 +209,9 @@ describe('apollo-server-express', () => {
|
||||||
|
|
||||||
registerServer({ app, server, cors: { origin: 'apollographql.com' } });
|
registerServer({ app, server, cors: { origin: 'apollographql.com' } });
|
||||||
|
|
||||||
const { url: uri } = await server.listen({});
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
|
|
||||||
const apolloFetch = createApolloFetch({ uri }).useAfter(
|
const apolloFetch = createApolloFetch({ uri }).useAfter(
|
||||||
(response, next) => {
|
(response, next) => {
|
||||||
|
@ -196,7 +233,9 @@ describe('apollo-server-express', () => {
|
||||||
|
|
||||||
registerServer({ app, server, bodyParserConfig: { limit: 0 } });
|
registerServer({ app, server, bodyParserConfig: { limit: 0 } });
|
||||||
|
|
||||||
const { url: uri } = await server.listen({});
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
|
|
||||||
const apolloFetch = createApolloFetch({ uri });
|
const apolloFetch = createApolloFetch({ uri });
|
||||||
|
|
||||||
|
@ -226,7 +265,10 @@ describe('apollo-server-express', () => {
|
||||||
|
|
||||||
registerServer({ app, server, bodyParserConfig: { limit: 0 } });
|
registerServer({ app, server, bodyParserConfig: { limit: 0 } });
|
||||||
|
|
||||||
const { port } = await server.listen();
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
|
const { port } = httpServer.address() as net.AddressInfo;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request(
|
request(
|
||||||
|
@ -262,7 +304,10 @@ describe('apollo-server-express', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { port } = await server.listen({});
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
|
const { port } = httpServer.address() as net.AddressInfo;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request(
|
request(
|
||||||
|
@ -295,7 +340,10 @@ describe('apollo-server-express', () => {
|
||||||
disableHealthCheck: true,
|
disableHealthCheck: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { port } = await server.listen({});
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
|
const { port } = httpServer.address() as net.AddressInfo;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request(
|
request(
|
||||||
|
@ -351,7 +399,10 @@ describe('apollo-server-express', () => {
|
||||||
server,
|
server,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { port } = await server.listen({});
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
|
const { port } = httpServer.address() as net.AddressInfo;
|
||||||
|
|
||||||
const body = new FormData();
|
const body = new FormData();
|
||||||
|
|
||||||
|
@ -423,7 +474,9 @@ describe('apollo-server-express', () => {
|
||||||
app = express();
|
app = express();
|
||||||
registerServer({ app, server });
|
registerServer({ app, server });
|
||||||
|
|
||||||
const { url: uri } = await server.listen();
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
const apolloFetch = createApolloFetch({ uri });
|
const apolloFetch = createApolloFetch({ uri });
|
||||||
|
|
||||||
const result = await apolloFetch({ query: '{hello}' });
|
const result = await apolloFetch({ query: '{hello}' });
|
||||||
|
@ -461,7 +514,9 @@ describe('apollo-server-express', () => {
|
||||||
app = express();
|
app = express();
|
||||||
registerServer({ app, server });
|
registerServer({ app, server });
|
||||||
|
|
||||||
const { url: uri } = await server.listen();
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
const apolloFetch = createApolloFetch({ uri });
|
const apolloFetch = createApolloFetch({ uri });
|
||||||
|
|
||||||
const result = await apolloFetch({ query: `{error}` });
|
const result = await apolloFetch({ query: `{error}` });
|
||||||
|
@ -499,7 +554,9 @@ describe('apollo-server-express', () => {
|
||||||
app = express();
|
app = express();
|
||||||
registerServer({ app, server });
|
registerServer({ app, server });
|
||||||
|
|
||||||
const { url: uri } = await server.listen();
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
const apolloFetch = createApolloFetch({ uri });
|
const apolloFetch = createApolloFetch({ uri });
|
||||||
|
|
||||||
const result = await apolloFetch({ query: `{error}` });
|
const result = await apolloFetch({ query: `{error}` });
|
||||||
|
@ -536,7 +593,9 @@ describe('apollo-server-express', () => {
|
||||||
app = express();
|
app = express();
|
||||||
registerServer({ app, server });
|
registerServer({ app, server });
|
||||||
|
|
||||||
const { url: uri } = await server.listen();
|
httpServer = await new Promise<http.Server>(resolve => {
|
||||||
|
const l = app.listen({ port: 4000 }, () => resolve(l));
|
||||||
|
});
|
||||||
const apolloFetch = createApolloFetch({ uri });
|
const apolloFetch = createApolloFetch({ uri });
|
||||||
|
|
||||||
const result = await apolloFetch({ query: `{error}` });
|
const result = await apolloFetch({ query: `{error}` });
|
||||||
|
|
28
packages/apollo-server-hapi/src/ApolloServer.test.ts
Normal file
28
packages/apollo-server-hapi/src/ApolloServer.test.ts
Normal file
|
@ -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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
|
@ -31,8 +31,14 @@
|
||||||
"definition": "dist/index.d.ts"
|
"definition": "dist/index.d.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
846
packages/apollo-server-integration-testsuite/src/ApolloServer.ts
Normal file
846
packages/apollo-server-integration-testsuite/src/ApolloServer.ts
Normal file
|
@ -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<AS extends ApolloServerBase>(
|
||||||
|
server: AS,
|
||||||
|
httpServer: http.Server,
|
||||||
|
): ServerInfo<AS> {
|
||||||
|
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<AS extends ApolloServerBase> {
|
||||||
|
address: string;
|
||||||
|
family: string;
|
||||||
|
url: string;
|
||||||
|
port: number | string;
|
||||||
|
server: AS;
|
||||||
|
httpServer: http.Server;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateServerFunc<AS extends ApolloServerBase> {
|
||||||
|
(config: Config): Promise<ServerInfo<AS>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StopServerFunc {
|
||||||
|
(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function testApolloServer<AS extends ApolloServerBase>(
|
||||||
|
createApolloServer: CreateServerFunc<AS>,
|
||||||
|
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<string, string>() 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ import { GraphQLOptions, Config } from 'apollo-server-core';
|
||||||
import { OperationStore } from 'apollo-server-module-operation-store';
|
import { OperationStore } from 'apollo-server-module-operation-store';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export * from './ApolloServer';
|
||||||
|
|
||||||
const personType = new GraphQLObjectType({
|
const personType = new GraphQLObjectType({
|
||||||
name: 'PersonType',
|
name: 'PersonType',
|
||||||
fields: {
|
fields: {
|
||||||
|
|
|
@ -89,7 +89,7 @@ describe('apollo-server', () => {
|
||||||
resolvers,
|
resolvers,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { url: uri } = await server.listen({});
|
const { url: uri } = await server.listen();
|
||||||
|
|
||||||
const apolloFetch = createApolloFetch({ uri }).useAfter(
|
const apolloFetch = createApolloFetch({ uri }).useAfter(
|
||||||
(response, next) => {
|
(response, next) => {
|
||||||
|
|
|
@ -12,22 +12,20 @@ process.on('unhandledRejection', reason => {
|
||||||
require('../packages/apollo-server-core/dist/runQuery.test.js');
|
require('../packages/apollo-server-core/dist/runQuery.test.js');
|
||||||
require('../packages/apollo-server-core/dist/runHttpQuery.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/errors.test.js');
|
||||||
require('../packages/apollo-server-core/dist/ApolloServer.test.js');
|
|
||||||
|
|
||||||
//Apollo server 2 tests
|
//Apollo server 2 tests
|
||||||
|
|
||||||
//apollo-server
|
//apollo-server
|
||||||
require('../packages/apollo-server/dist/index.test.js');
|
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-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/expressApollo.test');
|
||||||
require('../packages/apollo-server-express/dist/connectApollo.test');
|
require('../packages/apollo-server-express/dist/connectApollo.test');
|
||||||
|
|
||||||
(NODE_MAJOR_VERSION >= 9 ||
|
(NODE_MAJOR_VERSION >= 9 ||
|
||||||
(NODE_MAJOR_VERSION >= 8 && NODE_MAJOR_REVISION >= 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');
|
require('../packages/apollo-server-express/dist/apolloServerHttp.test');
|
||||||
|
|
Loading…
Add table
Reference in a new issue