apollo-server/packages/apollo-server-integration-testsuite/src/index.ts

603 lines
19 KiB
TypeScript
Raw Normal View History

import { expect } from 'chai';
import { stub } from 'sinon';
import 'mocha';
import {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
2016-07-29 11:51:25 -07:00
GraphQLError,
introspectionQuery,
2016-07-29 11:51:25 -07:00
BREAK,
} from 'graphql';
// tslint:disable-next-line
const request = require('supertest-as-promised');
import { ApolloOptions } from 'apollo-server-core';
import * as GraphiQL from 'apollo-server-graphiql';
import { OperationStore } from 'apollo-server-operation-store';
const QueryType = new GraphQLObjectType({
name: 'QueryType',
fields: {
testString: {
type: GraphQLString,
2016-07-29 11:36:22 -07:00
resolve() {
return 'it works';
},
},
2016-07-29 11:36:22 -07:00
testContext: {
type: GraphQLString,
resolve(_, args, context) {
return context;
},
},
testRootValue: {
type: GraphQLString,
resolve(rootValue) {
return rootValue;
},
},
testArgument: {
type: GraphQLString,
args: { echo: { type: GraphQLString } },
resolve(root, { echo }) {
return `hello ${echo}`;
},
},
2016-07-29 11:42:05 -07:00
testError: {
type: GraphQLString,
resolve() {
throw new Error('Secret error message');
},
},
},
});
const MutationType = new GraphQLObjectType({
name: 'MutationType',
fields: {
testMutation: {
type: GraphQLString,
args: { echo: { type: GraphQLString } },
resolve(root, { echo }) {
return `not really a mutation, but who cares: ${echo}`;
},
},
},
});
export const Schema = new GraphQLSchema({
query: QueryType,
mutation: MutationType,
});
export interface CreateAppOptions {
excludeParser?: boolean;
apolloOptions?: ApolloOptions | {(): ApolloOptions | Promise<{}>};
graphiqlOptions?: GraphiQL.GraphiQLData;
}
export interface CreateAppFunc {
(options?: CreateAppOptions): void;
}
2016-07-28 20:05:05 +02:00
export interface DestroyAppFunc {
(app: any): void;
}
export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
describe('apolloServer', () => {
let app;
afterEach(() => {
if (app) {
if (destroyApp) {
destroyApp(app);
} else {
app = null;
}
}
});
2016-07-18 21:13:57 -07:00
describe('graphqlHTTP', () => {
it('can be called with an options function', () => {
app = createApp({apolloOptions: (): ApolloOptions => ({schema: Schema})});
2016-07-18 21:13:57 -07:00
const expected = {
testString: 'it works',
};
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testString }',
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected);
});
});
2016-07-18 21:13:57 -07:00
it('can be called with an options function that returns a promise', () => {
app = createApp({ apolloOptions: () => {
2016-07-18 21:13:57 -07:00
return new Promise(resolve => {
resolve({schema: Schema});
});
}});
const expected = {
testString: 'it works',
};
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testString }',
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected);
});
});
2016-07-18 21:13:57 -07:00
it('throws an error if options promise is rejected', () => {
app = createApp({ apolloOptions: () => {
2016-07-18 21:13:57 -07:00
return Promise.reject({}) as any as ApolloOptions;
}});
const expected = 'Invalid options';
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testString }',
});
return req.then((res) => {
expect(res.status).to.equal(500);
return expect(res.error.text).to.contain(expected);
});
});
it('rejects the request if the method is not POST', () => {
app = createApp({excludeParser: true});
const req = request(app)
.get('/graphql')
.send();
return req.then((res) => {
expect(res.status).to.be.oneOf([404, 405]);
// Hapi doesn't return allow header, so we can't test this.
// return expect(res.headers['allow']).to.equal('POST');
});
});
2016-07-18 21:13:57 -07:00
it('throws an error if POST body is missing', () => {
app = createApp({excludeParser: true});
2016-07-18 21:13:57 -07:00
const req = request(app)
.post('/graphql')
2016-07-18 21:17:52 -07:00
.send();
2016-07-18 21:13:57 -07:00
return req.then((res) => {
expect(res.status).to.equal(500);
return expect(res.error.text).to.contain('POST body missing.');
});
});
2016-07-18 21:13:57 -07:00
it('can handle a basic request', () => {
app = createApp();
2016-07-18 21:13:57 -07:00
const expected = {
testString: 'it works',
};
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testString }',
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected);
});
});
2016-07-18 21:13:57 -07:00
it('can handle a request with variables', () => {
app = createApp();
2016-07-18 21:13:57 -07:00
const expected = {
testArgument: 'hello world',
};
const req = request(app)
.post('/graphql')
.send({
query: 'query test($echo: String){ testArgument(echo: $echo) }',
variables: { echo: 'world' },
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected);
});
});
2016-07-18 21:13:57 -07:00
it('can handle a request with variables as string', () => {
app = createApp();
2016-07-18 21:13:57 -07:00
const expected = {
testArgument: 'hello world',
};
const req = request(app)
.post('/graphql')
.send({
query: 'query test($echo: String!){ testArgument(echo: $echo) }',
variables: '{ "echo": "world" }',
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected);
});
});
it('can handle a request with variables as an invalid string', () => {
app = createApp();
const req = request(app)
.post('/graphql')
.send({
query: 'query test($echo: String!){ testArgument(echo: $echo) }',
variables: '{ echo: "world" }',
});
return req.then((res) => {
expect(res.status).to.equal(400);
return expect(res.error.text).to.contain('Variables are invalid JSON.');
});
});
2016-07-18 21:13:57 -07:00
it('can handle a request with operationName', () => {
app = createApp();
2016-07-18 21:13:57 -07:00
const expected = {
testString: 'it works',
};
const req = request(app)
.post('/graphql')
.send({
query: `
query test($echo: String){ testArgument(echo: $echo) }
query test2{ testString }`,
variables: { echo: 'world' },
operationName: 'test2',
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected);
});
});
it('can handle introspection request', () => {
app = createApp();
const req = request(app)
.post('/graphql')
.send({query: introspectionQuery});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data.__schema.types[0].fields[0].name).to.equal('testString');
});
});
2016-07-18 21:13:57 -07:00
it('can handle batch requests', () => {
app = createApp();
2016-07-18 21:13:57 -07:00
const expected = [
{
data: {
testString: 'it works',
},
},
{
data: {
testArgument: 'hello yellow',
},
},
];
const req = request(app)
.post('/graphql')
.send([{
query: `
query test($echo: String){ testArgument(echo: $echo) }
query test2{ testString }`,
variables: { echo: 'world' },
operationName: 'test2',
},
{
query: `
query testX($echo: String){ testArgument(echo: $echo) }`,
variables: { echo: 'yellow' },
operationName: 'testX',
}]);
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body).to.deep.equal(expected);
});
});
it('can handle batch requests', () => {
app = createApp();
const expected = [
{
data: {
testString: 'it works',
},
},
];
const req = request(app)
.post('/graphql')
.send([{
query: `
query test($echo: String){ testArgument(echo: $echo) }
query test2{ testString }`,
variables: { echo: 'world' },
operationName: 'test2',
}]);
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body).to.deep.equal(expected);
});
});
2016-07-18 21:13:57 -07:00
it('can handle a request with a mutation', () => {
app = createApp();
2016-07-18 21:13:57 -07:00
const expected = {
testMutation: 'not really a mutation, but who cares: world',
};
const req = request(app)
.post('/graphql')
.send({
query: 'mutation test($echo: String){ testMutation(echo: $echo) }',
variables: { echo: 'world' },
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected);
});
});
it('applies the formatResponse function', () => {
app = createApp({apolloOptions: {
schema: Schema,
formatResponse(response) {
response['extensions'] = { it: 'works' }; return response;
},
}});
const expected = { it: 'works' };
const req = request(app)
.post('/graphql')
.send({
query: 'mutation test($echo: String){ testMutation(echo: $echo) }',
variables: { echo: 'world' },
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.extensions).to.deep.equal(expected);
});
});
2016-07-17 17:51:41 -07:00
it('passes the context to the resolver', () => {
2016-07-29 11:36:22 -07:00
const expected = 'context works';
app = createApp({apolloOptions: {
2016-07-17 17:51:41 -07:00
schema: Schema,
2016-07-29 11:36:22 -07:00
context: expected,
2016-07-17 17:51:41 -07:00
}});
const req = request(app)
.post('/graphql')
.send({
2016-07-29 11:36:22 -07:00
query: 'query test{ testContext }',
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data.testContext).to.equal(expected);
});
});
it('passes the rootValue to the resolver', () => {
const expected = 'it passes rootValue';
app = createApp({apolloOptions: {
schema: Schema,
rootValue: expected,
}});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testRootValue }',
2016-07-17 17:51:41 -07:00
});
return req.then((res) => {
expect(res.status).to.equal(200);
2016-07-29 11:36:22 -07:00
return expect(res.body.data.testRootValue).to.equal(expected);
2016-07-17 17:51:41 -07:00
});
});
2016-07-29 11:42:05 -07:00
it('returns errors', () => {
const expected = 'Secret error message';
app = createApp({apolloOptions: {
schema: Schema,
}});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testError }',
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.errors[0].message).to.equal(expected);
});
});
it('applies formatError if provided', () => {
const expected = '--blank--';
app = createApp({apolloOptions: {
schema: Schema,
formatError: (err) => ({ message: expected }),
}});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testError }',
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.errors[0].message).to.equal(expected);
});
});
2016-10-17 16:18:45 -03:00
it('sends internal server error when formatError fails', () => {
2016-10-17 13:55:15 -03:00
app = createApp({apolloOptions: {
schema: Schema,
formatError: (err) => {
throw new Error('I should be catched');
},
}});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testError }',
});
return req.then((res) => {
2016-10-17 16:17:23 -03:00
return expect(res.res.body.errors[0].message).to.equal('Internal server error');
2016-10-17 13:55:15 -03:00
});
});
it('sends stack trace to error if debug mode is set', () => {
const expected = /at resolveOrError/;
const stackTrace = [];
const origError = console.error;
console.error = (...args) => stackTrace.push(args);
app = createApp({apolloOptions: {
schema: Schema,
debug: true,
}});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testError }',
});
return req.then((res) => {
console.error = origError;
return expect(stackTrace[0][0]).to.match(expected);
});
});
it('sends stack trace to error log if debug mode is set', () => {
const logStub = stub(console, 'error');
const expected = /at resolveOrError/;
app = createApp({apolloOptions: {
schema: Schema,
debug: true,
}});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testError }',
});
return req.then((res) => {
logStub.restore();
expect(logStub.callCount).to.equal(1);
return expect(logStub.getCall(0).args[0]).to.match(expected);
});
});
2016-07-29 11:51:25 -07:00
it('applies additional validationRules', () => {
const expected = 'AlwaysInvalidRule was really invalid!';
const AlwaysInvalidRule = function (context) {
return {
enter() {
context.reportError(new GraphQLError(
expected
));
return BREAK;
},
};
};
app = createApp({apolloOptions: {
schema: Schema,
validationRules: [AlwaysInvalidRule],
}});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testString }',
});
return req.then((res) => {
expect(res.status).to.equal(400);
return expect(res.body.errors[0].message).to.equal(expected);
});
});
});
2016-07-18 21:13:57 -07:00
describe('renderGraphiQL', () => {
it('presents GraphiQL when accepting HTML', () => {
app = createApp({graphiqlOptions: {
2016-07-18 21:13:57 -07:00
endpointURL: '/graphql',
}});
2016-07-18 21:13:57 -07:00
const req = request(app)
.get('/graphiql?query={test}')
.set('Accept', 'text/html');
return req.then((response) => {
expect(response.status).to.equal(200);
expect(response.type).to.equal('text/html');
expect(response.text).to.include('{test}');
expect(response.text).to.include('/graphql');
expect(response.text).to.include('graphiql.min.js');
});
});
});
2016-07-18 21:13:57 -07:00
describe('stored queries', () => {
it('works with formatParams', () => {
const store = new OperationStore(Schema);
store.put('query testquery{ testString }');
app = createApp({ apolloOptions: {
2016-07-18 21:13:57 -07:00
schema: Schema,
formatParams(params) {
params['query'] = store.get(params.operationName);
return params;
},
}});
const expected = { testString: 'it works' };
const req = request(app)
.post('/graphql')
.send({
operationName: 'testquery',
});
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body.data).to.deep.equal(expected);
});
});
2016-07-18 21:13:57 -07:00
it('can reject non-whitelisted queries', () => {
const store = new OperationStore(Schema);
store.put('query testquery{ testString }');
app = createApp({ apolloOptions: {
2016-07-18 21:13:57 -07:00
schema: Schema,
formatParams(params) {
if (params.query) {
throw new Error('Must not provide query, only operationName');
}
params['query'] = store.get(params.operationName);
return params;
},
}});
const expected = [{
data: {
testString: 'it works',
},
}, {
errors: [{
message: 'Must not provide query, only operationName',
}],
}];
2016-07-18 21:13:57 -07:00
const req = request(app)
.post('/graphql')
.send([{
operationName: 'testquery',
}, {
query: '{ testString }',
}]);
return req.then((res) => {
expect(res.status).to.equal(200);
return expect(res.body).to.deep.equal(expected);
});
});
});
});
};