2017-03-30 20:26:59 -05:00
|
|
|
/* tslint:disable:no-unused-expression */
|
2016-09-12 15:02:41 -07:00
|
|
|
import { expect } from 'chai';
|
|
|
|
import { stub } from 'sinon';
|
2016-10-03 23:13:08 +03:00
|
|
|
import 'mocha';
|
2016-06-10 17:05:39 -07:00
|
|
|
|
|
|
|
import {
|
2018-01-09 00:08:01 +01:00
|
|
|
GraphQLSchema,
|
|
|
|
GraphQLObjectType,
|
|
|
|
GraphQLString,
|
|
|
|
GraphQLInt,
|
|
|
|
GraphQLNonNull,
|
|
|
|
parse,
|
2016-06-10 17:05:39 -07:00
|
|
|
} from 'graphql';
|
|
|
|
|
2018-05-02 16:27:33 -07:00
|
|
|
import { runQuery } from './runQuery';
|
|
|
|
import { LogAction, LogStep } from './logging';
|
2016-06-10 17:05:39 -07:00
|
|
|
|
2016-08-14 03:19:31 -04:00
|
|
|
// Make the global Promise constructor Fiber-aware to simulate a Meteor
|
|
|
|
// environment.
|
|
|
|
import { makeCompatible } from 'meteor-promise';
|
|
|
|
import Fiber = require('fibers');
|
|
|
|
makeCompatible(Promise, Fiber);
|
|
|
|
|
2017-01-23 13:10:42 +02:00
|
|
|
const queryType = new GraphQLObjectType({
|
2018-01-09 00:08:01 +01:00
|
|
|
name: 'QueryType',
|
|
|
|
fields: {
|
|
|
|
testString: {
|
|
|
|
type: GraphQLString,
|
|
|
|
resolve() {
|
|
|
|
return 'it works';
|
|
|
|
},
|
|
|
|
},
|
|
|
|
testObject: {
|
|
|
|
type: new GraphQLObjectType({
|
|
|
|
name: 'TestObject',
|
|
|
|
fields: {
|
|
|
|
testString: {
|
2016-06-10 17:05:39 -07:00
|
|
|
type: GraphQLString,
|
2017-08-02 13:20:10 +03:00
|
|
|
},
|
|
|
|
},
|
2018-01-09 00:08:01 +01:00
|
|
|
}),
|
|
|
|
resolve() {
|
|
|
|
return {
|
|
|
|
testString: 'a very test string',
|
|
|
|
};
|
|
|
|
},
|
|
|
|
},
|
|
|
|
testRootValue: {
|
|
|
|
type: GraphQLString,
|
|
|
|
resolve(root) {
|
|
|
|
return root + ' works';
|
|
|
|
},
|
|
|
|
},
|
|
|
|
testContextValue: {
|
|
|
|
type: GraphQLString,
|
|
|
|
resolve(root, args, context) {
|
|
|
|
return context + ' works';
|
|
|
|
},
|
|
|
|
},
|
|
|
|
testArgumentValue: {
|
|
|
|
type: GraphQLInt,
|
|
|
|
resolve(root, args, context) {
|
|
|
|
return args['base'] + 5;
|
|
|
|
},
|
|
|
|
args: {
|
|
|
|
base: { type: new GraphQLNonNull(GraphQLInt) },
|
|
|
|
},
|
2016-06-10 17:05:39 -07:00
|
|
|
},
|
2018-01-09 00:08:01 +01:00
|
|
|
testAwaitedValue: {
|
|
|
|
type: GraphQLString,
|
|
|
|
resolve(root) {
|
|
|
|
// Calling Promise.await is legal here even though this is
|
|
|
|
// not an async function, because we are guaranteed to be
|
|
|
|
// running in a Fiber.
|
|
|
|
return 'it ' + (<any>Promise).await('works');
|
|
|
|
},
|
|
|
|
},
|
|
|
|
testError: {
|
|
|
|
type: GraphQLString,
|
|
|
|
resolve() {
|
|
|
|
throw new Error('Secret error message');
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2016-06-10 17:05:39 -07:00
|
|
|
});
|
|
|
|
|
2017-01-23 13:10:42 +02:00
|
|
|
const schema = new GraphQLSchema({
|
2018-01-09 00:08:01 +01:00
|
|
|
query: queryType,
|
2016-06-10 17:05:39 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('runQuery', () => {
|
2016-06-13 16:20:21 -07:00
|
|
|
it('returns the right result when query is a string', () => {
|
2018-01-09 00:08:01 +01:00
|
|
|
const query = `{ testString }`;
|
|
|
|
const expected = { testString: 'it works' };
|
|
|
|
return runQuery({ schema, query: query }).then(res => {
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res.data).to.deep.equal(expected);
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-06-13 16:20:21 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('returns the right result when query is a document', () => {
|
2018-01-09 00:08:01 +01:00
|
|
|
const query = parse(`{ testString }`);
|
|
|
|
const expected = { testString: 'it works' };
|
|
|
|
return runQuery({ schema, query: query }).then(res => {
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res.data).to.deep.equal(expected);
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-06-13 16:20:21 -07:00
|
|
|
});
|
|
|
|
|
2016-09-12 15:02:41 -07:00
|
|
|
it('returns a syntax error if the query string contains one', () => {
|
|
|
|
const query = `query { test `;
|
2018-03-13 08:10:37 -07:00
|
|
|
const expected = /Syntax Error/;
|
2016-09-12 15:02:41 -07:00
|
|
|
return runQuery({
|
2018-01-09 00:08:01 +01:00
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
variables: { base: 1 },
|
|
|
|
}).then(res => {
|
|
|
|
expect(res.data).to.be.undefined;
|
|
|
|
expect(res.errors.length).to.equal(1);
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res.errors[0].message).to.match(expected);
|
2016-09-12 15:02:41 -07:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-05-01 16:34:01 -07:00
|
|
|
it('does not call console.error if in an error occurs and debug mode is set', () => {
|
2016-09-12 15:02:41 -07:00
|
|
|
const query = `query { testError }`;
|
|
|
|
const logStub = stub(console, 'error');
|
|
|
|
return runQuery({
|
2018-01-09 00:08:01 +01:00
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
debug: true,
|
|
|
|
}).then(res => {
|
|
|
|
logStub.restore();
|
2018-05-01 16:34:01 -07:00
|
|
|
expect(logStub.callCount).to.equal(0);
|
2016-09-12 15:02:41 -07:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-05-01 16:34:01 -07:00
|
|
|
it('does not call console.error if in an error occurs and not in debug mode', () => {
|
2016-09-12 15:02:41 -07:00
|
|
|
const query = `query { testError }`;
|
|
|
|
const logStub = stub(console, 'error');
|
|
|
|
return runQuery({
|
2018-01-09 00:08:01 +01:00
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
debug: false,
|
|
|
|
}).then(res => {
|
|
|
|
logStub.restore();
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(logStub.callCount).to.equal(0);
|
2016-09-12 15:02:41 -07:00
|
|
|
});
|
2016-06-13 17:16:42 -07:00
|
|
|
});
|
|
|
|
|
2016-06-13 16:20:21 -07:00
|
|
|
it('returns a validation error if the query string does not pass validation', () => {
|
2018-01-09 00:08:01 +01:00
|
|
|
const query = `query TestVar($base: String){ testArgumentValue(base: $base) }`;
|
|
|
|
const expected =
|
|
|
|
'Variable "$base" of type "String" used in position expecting type "Int!".';
|
|
|
|
return runQuery({
|
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
variables: { base: 1 },
|
|
|
|
}).then(res => {
|
|
|
|
expect(res.data).to.be.undefined;
|
|
|
|
expect(res.errors.length).to.equal(1);
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res.errors[0].message).to.deep.equal(expected);
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-06-13 16:20:21 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('correctly passes in the rootValue', () => {
|
2018-01-09 00:08:01 +01:00
|
|
|
const query = `{ testRootValue }`;
|
|
|
|
const expected = { testRootValue: 'it also works' };
|
|
|
|
return runQuery({ schema, query: query, rootValue: 'it also' }).then(
|
|
|
|
res => {
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res.data).to.deep.equal(expected);
|
2018-01-09 00:08:01 +01:00
|
|
|
},
|
|
|
|
);
|
2016-06-13 16:20:21 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('correctly passes in the context', () => {
|
2018-01-09 00:08:01 +01:00
|
|
|
const query = `{ testContextValue }`;
|
|
|
|
const expected = { testContextValue: 'it still works' };
|
|
|
|
return runQuery({ schema, query: query, context: 'it still' }).then(res => {
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res.data).to.deep.equal(expected);
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-06-13 16:20:21 -07:00
|
|
|
});
|
|
|
|
|
2016-08-05 11:42:46 -07:00
|
|
|
it('passes the options to formatResponse', () => {
|
2018-01-09 00:08:01 +01:00
|
|
|
const query = `{ testContextValue }`;
|
|
|
|
const expected = { testContextValue: 'it still works' };
|
|
|
|
return runQuery({
|
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
context: 'it still',
|
|
|
|
formatResponse: (response, { context }) => {
|
|
|
|
response['extensions'] = context;
|
|
|
|
return response;
|
|
|
|
},
|
|
|
|
}).then(res => {
|
|
|
|
expect(res.data).to.deep.equal(expected);
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res['extensions']).to.equal('it still');
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-08-05 11:42:46 -07:00
|
|
|
});
|
|
|
|
|
2016-06-13 16:20:21 -07:00
|
|
|
it('correctly passes in variables (and arguments)', () => {
|
2018-01-09 00:08:01 +01:00
|
|
|
const query = `query TestVar($base: Int!){ testArgumentValue(base: $base) }`;
|
|
|
|
const expected = { testArgumentValue: 6 };
|
|
|
|
return runQuery({
|
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
variables: { base: 1 },
|
|
|
|
}).then(res => {
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res.data).to.deep.equal(expected);
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-06-13 16:20:21 -07:00
|
|
|
});
|
|
|
|
|
2016-06-27 19:20:06 -04:00
|
|
|
it('throws an error if there are missing variables', () => {
|
2018-01-09 00:08:01 +01:00
|
|
|
const query = `query TestVar($base: Int!){ testArgumentValue(base: $base) }`;
|
|
|
|
const expected =
|
|
|
|
'Variable "$base" of required type "Int!" was not provided.';
|
|
|
|
return runQuery({
|
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
}).then(res => {
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res.errors[0].message).to.deep.equal(expected);
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-06-26 21:06:37 -04:00
|
|
|
});
|
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
it('supports yielding resolver functions', () => {
|
|
|
|
return runQuery({
|
|
|
|
schema,
|
|
|
|
query: `{ testAwaitedValue }`,
|
|
|
|
}).then(res => {
|
|
|
|
expect(res.data).to.deep.equal({
|
|
|
|
testAwaitedValue: 'it works',
|
|
|
|
});
|
2016-08-14 03:19:31 -04:00
|
|
|
});
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-06-26 21:06:37 -04:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
it('runs the correct operation when operationName is specified', () => {
|
|
|
|
const query = `
|
2016-06-13 16:20:21 -07:00
|
|
|
query Q1 {
|
|
|
|
testString
|
|
|
|
}
|
|
|
|
query Q2 {
|
|
|
|
testRootValue
|
|
|
|
}`;
|
2018-01-09 00:08:01 +01:00
|
|
|
const expected = {
|
|
|
|
testString: 'it works',
|
|
|
|
};
|
|
|
|
return runQuery({ schema, query: query, operationName: 'Q1' }).then(res => {
|
2018-04-18 15:37:47 +03:00
|
|
|
expect(res.data).to.deep.equal(expected);
|
2016-06-26 21:06:37 -04:00
|
|
|
});
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-08-05 11:14:56 -07:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
it('calls logFunction', () => {
|
|
|
|
const query = `
|
2016-08-05 11:14:56 -07:00
|
|
|
query Q1 {
|
|
|
|
testString
|
|
|
|
}`;
|
2018-01-09 00:08:01 +01:00
|
|
|
const logs = [];
|
|
|
|
const logFn = obj => logs.push(obj);
|
|
|
|
const expected = {
|
|
|
|
testString: 'it works',
|
|
|
|
};
|
|
|
|
return runQuery({
|
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
operationName: 'Q1',
|
|
|
|
variables: { test: 123 },
|
|
|
|
logFunction: logFn,
|
|
|
|
}).then(res => {
|
|
|
|
expect(res.data).to.deep.equal(expected);
|
|
|
|
expect(logs.length).to.equals(11);
|
|
|
|
expect(logs[0]).to.deep.equals({
|
|
|
|
action: LogAction.request,
|
|
|
|
step: LogStep.start,
|
|
|
|
});
|
|
|
|
expect(logs[1]).to.deep.equals({
|
|
|
|
action: LogAction.request,
|
|
|
|
step: LogStep.status,
|
|
|
|
key: 'query',
|
|
|
|
data: query,
|
|
|
|
});
|
|
|
|
expect(logs[2]).to.deep.equals({
|
|
|
|
action: LogAction.request,
|
|
|
|
step: LogStep.status,
|
|
|
|
key: 'variables',
|
|
|
|
data: { test: 123 },
|
|
|
|
});
|
|
|
|
expect(logs[3]).to.deep.equals({
|
|
|
|
action: LogAction.request,
|
|
|
|
step: LogStep.status,
|
|
|
|
key: 'operationName',
|
|
|
|
data: 'Q1',
|
|
|
|
});
|
|
|
|
expect(logs[10]).to.deep.equals({
|
|
|
|
action: LogAction.request,
|
|
|
|
step: LogStep.end,
|
2018-05-01 16:34:01 -07:00
|
|
|
key: 'response',
|
|
|
|
data: {
|
|
|
|
data: expected,
|
|
|
|
},
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-08-05 11:14:56 -07:00
|
|
|
});
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2017-08-02 13:20:10 +03:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
it('uses custom field resolver', async () => {
|
|
|
|
const query = `
|
2017-08-02 13:20:10 +03:00
|
|
|
query Q1 {
|
|
|
|
testObject {
|
|
|
|
testString
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
const result1 = await runQuery({
|
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
operationName: 'Q1',
|
|
|
|
});
|
2017-08-02 13:20:10 +03:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
expect(result1.data).to.deep.equal({
|
|
|
|
testObject: {
|
|
|
|
testString: 'a very test string',
|
|
|
|
},
|
|
|
|
});
|
2017-08-02 13:20:10 +03:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
const result2 = await runQuery({
|
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
operationName: 'Q1',
|
|
|
|
fieldResolver: () => 'a very testful field resolver string',
|
|
|
|
});
|
2017-08-02 13:20:10 +03:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
expect(result2.data).to.deep.equal({
|
|
|
|
testObject: {
|
|
|
|
testString: 'a very testful field resolver string',
|
|
|
|
},
|
2017-08-02 13:20:10 +03:00
|
|
|
});
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2017-12-22 04:46:02 +00:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
describe('async_hooks', () => {
|
|
|
|
let asyncHooks;
|
|
|
|
let asyncHook;
|
|
|
|
const ids: number[] = [];
|
2017-12-22 04:46:02 +00:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
try {
|
|
|
|
asyncHooks = require('async_hooks');
|
|
|
|
} catch (err) {
|
|
|
|
return; // async_hooks not present, give up
|
|
|
|
}
|
2017-12-22 04:46:02 +00:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
before(() => {
|
|
|
|
asyncHook = asyncHooks.createHook({ init: asyncId => ids.push(asyncId) });
|
|
|
|
asyncHook.enable();
|
|
|
|
});
|
2017-12-22 04:46:02 +00:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
after(() => {
|
|
|
|
asyncHook.disable();
|
|
|
|
});
|
2017-12-22 04:46:02 +00:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
it('does not break async_hook call stack', async () => {
|
|
|
|
const query = `
|
|
|
|
query Q1 {
|
|
|
|
testObject {
|
|
|
|
testString
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|
2017-12-22 04:46:02 +00:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
await runQuery({
|
|
|
|
schema,
|
|
|
|
query: query,
|
|
|
|
operationName: 'Q1',
|
|
|
|
});
|
2017-12-22 04:46:02 +00:00
|
|
|
|
2018-01-09 00:08:01 +01:00
|
|
|
// this is the only async process so we expect the async ids to be a sequence
|
|
|
|
ids.forEach((id, i) => {
|
|
|
|
if (i > 0) {
|
|
|
|
expect(id).to.equal(ids[i - 1] + 1);
|
|
|
|
}
|
|
|
|
});
|
2017-12-22 04:46:02 +00:00
|
|
|
});
|
2018-01-09 00:08:01 +01:00
|
|
|
});
|
2016-06-10 20:48:21 -04:00
|
|
|
});
|