Skip to content

Commit

Permalink
Test: Uncaptured errors in source event stream.
Browse files Browse the repository at this point in the history
Added a test to illustrate a broken issue and potentially underspecified in spec:

If a source event stream emits an *error* instead of an *event*, then that error is passing up through the whole stack and throwing at the consumer of the response event stream. That's very likely not what we want. I have a proposal in this test case for what should happen in that case, similar to what would happen if an error occurred during during the second step of executing an event from the source stream.
  • Loading branch information
leebyron committed May 26, 2017
1 parent da8fe62 commit 3acc234
Show file tree
Hide file tree
Showing 7 changed files with 439 additions and 58 deletions.
41 changes: 41 additions & 0 deletions src/subscription/__tests__/asyncIteratorReject-test.js
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

import { expect } from 'chai';
import { describe, it } from 'mocha';

import asyncIteratorReject from '../asyncIteratorReject';

describe('asyncIteratorReject', () => {

it('creates a failing async iterator', async () => {
const error = new Error('Oh no, Mr. Bill!');
const iter = asyncIteratorReject(error);

let caughtError;
try {
await iter.next();
} catch (thrownError) {
caughtError = thrownError;
}
expect(caughtError).to.equal(error);

expect(await iter.next()).to.deep.equal({ done: true, value: undefined });
});

it('can be closed before failing', async () => {
const error = new Error('Oh no, Mr. Bill!');
const iter = asyncIteratorReject(error);

// Close iterator
expect(await iter.return()).to.deep.equal({ done: true, value: undefined });

expect(await iter.next()).to.deep.equal({ done: true, value: undefined });
});
});
34 changes: 34 additions & 0 deletions src/subscription/__tests__/asyncIteratorResolve-test.js
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

import { expect } from 'chai';
import { describe, it } from 'mocha';

import asyncIteratorResolve from '../asyncIteratorResolve';

describe('asyncIteratorResolve', () => {

it('creates a failing async iterator', async () => {
const value = 'Hello world!';
const iter = asyncIteratorResolve(value);

expect(await iter.next()).to.deep.equal({ done: false, value });
expect(await iter.next()).to.deep.equal({ done: true, value: undefined });
});

it('can be closed before failing', async () => {
const value = 'Hello world!';
const iter = asyncIteratorResolve(value);

// Close iterator
expect(await iter.return()).to.deep.equal({ done: true, value: undefined });

expect(await iter.next()).to.deep.equal({ done: true, value: undefined });
});
});
240 changes: 202 additions & 38 deletions src/subscription/__tests__/subscribe-test.js
Expand Up @@ -77,7 +77,7 @@ describe('Subscribe', () => {
subscription: SubscriptionType
});

function createSubscription(pubsub, schema = emailSchema) {
function createSubscription(pubsub, schema = emailSchema, ast) {
const data = {
inbox: {
emails: [
Expand Down Expand Up @@ -105,7 +105,7 @@ describe('Subscribe', () => {
});
}

const ast = parse(`
const defaultAst = parse(`
subscription ($priority: Int = 0) {
importantEmail(priority: $priority) {
email {
Expand All @@ -126,7 +126,7 @@ describe('Subscribe', () => {
sendImportantEmail,
subscription: subscribe(
schema,
ast,
ast || defaultAst,
data
),
};
Expand Down Expand Up @@ -545,22 +545,22 @@ describe('Subscribe', () => {
});
});

it('invalid query should result in error', async () => {
const invalidAST = parse(`
it('unknown field should result in closed subscription', async () => {
const ast = parse(`
subscription {
invalidField
unknownField
}
`);

expect(() => {
subscribe(
emailSchema,
invalidAST,
);
}).to.throw('This subscription is not defined by the schema.');
const pubsub = new EventEmitter();

const { subscription } = createSubscription(pubsub, emailSchema, ast);

const payload = await subscription.next();
expect(payload).to.deep.equal({ done: true, value: undefined });
});

it('throws when subscription definition doesnt return iterator', () => {
it('fails when subscription definition doesnt return iterator', async () => {
const invalidEmailSchema = new GraphQLSchema({
query: QueryType,
subscription: new GraphQLObjectType({
Expand All @@ -574,18 +574,19 @@ describe('Subscribe', () => {
})
});

const ast = parse(`
subscription {
importantEmail
}
`);
const pubsub = new EventEmitter();

expect(() => {
subscribe(
invalidEmailSchema,
ast
);
}).to.throw('Subscription must return Async Iterable.');
const { subscription } = createSubscription(pubsub, invalidEmailSchema);

let caughtError;
try {
await subscription.next();
} catch (thrownError) {
caughtError = thrownError;
}
expect(
caughtError && caughtError.message
).to.equal('Subscription must return Async Iterable. Returned: test');
});

it('expects to have subscribe on type definition with iterator', () => {
Expand Down Expand Up @@ -617,33 +618,196 @@ describe('Subscribe', () => {
}).not.to.throw();
});

it('should handle error thrown by subscribe method', () => {
const invalidEmailSchema = new GraphQLSchema({
it('should report error thrown by subscribe function', async () => {
const erroringEmailSchema = emailSchemaWithSubscribeFn(
function importantEmail() {
throw new Error('test error');
}
);

const subscription = subscribe(
erroringEmailSchema,
parse(`
subscription {
importantEmail
}
`)
);

const result = await subscription.next();

expect(result).to.deep.equal({
done: false,
value: {
errors: [
{
message: 'test error',
locations: [ { line: 3, column: 11 } ],
path: [ 'importantEmail' ]
}
]
}
});

expect(
await subscription.next()
).to.deep.equal({ value: undefined, done: true });
});

it('should report error returned by subscribe function', async () => {
const erroringEmailSchema = emailSchemaWithSubscribeFn(
function importantEmail() {
return new Error('test error');
}
);

const subscription = subscribe(
erroringEmailSchema,
parse(`
subscription {
importantEmail
}
`)
);

const result = await subscription.next();

expect(result).to.deep.equal({
done: false,
value: {
errors: [
{
message: 'test error',
locations: [ { line: 3, column: 11 } ],
path: [ 'importantEmail' ]
}
]
}
});

expect(
await subscription.next()
).to.deep.equal({ value: undefined, done: true });
});

it('should handle error during execuction of source event', async () => {
const erroringEmailSchema = new GraphQLSchema({
query: QueryType,
subscription: new GraphQLObjectType({
name: 'Subscription',
fields: {
importantEmail: {
type: GraphQLString,
subscribe: () => {
throw new Error('test error');
resolve(event) {
if (event === 'Goodbye') {
throw new Error('Never leave.');
}
return event;
},
subscribe: async function* importantEmail() {
yield 'Hello';
yield 'Goodbye';
},
},
},
})
});

const ast = parse(`
subscription {
importantEmail
const subscription = subscribe(
erroringEmailSchema,
parse(`
subscription {
importantEmail
}
`)
);

const payload1 = await subscription.next();
expect(payload1).to.jsonEqual({
done: false,
value: {
data: {
importantEmail: 'Hello'
}
}
`);
});

expect(() => {
subscribe(
invalidEmailSchema,
ast
);
}).to.throw('test error');
const payload2 = await subscription.next();
expect(payload2).to.jsonEqual({
done: false,
value: {
errors: [
{
message: 'Never leave.',
locations: [ { line: 3, column: 11 } ],
path: [ 'importantEmail' ],
}
],
data: {
importantEmail: null,
}
}
});
});

function emailSchemaWithSubscribeFn(subscribeFn) {
return new GraphQLSchema({
query: QueryType,
subscription: new GraphQLObjectType({
name: 'Subscription',
fields: {
importantEmail: {
type: GraphQLString,
resolve(event) {
return event;
},
subscribe: subscribeFn,
},
},
})
});
}

it('should pass through error thrown in source event stream', async () => {
const erroringEmailSchema = emailSchemaWithSubscribeFn(
async function* importantEmail() {
yield 'Hello';
throw new Error('test error');
}
);

const subscription = subscribe(
erroringEmailSchema,
parse(`
subscription {
importantEmail
}
`)
);

const payload1 = await subscription.next();
expect(payload1).to.jsonEqual({
done: false,
value: {
data: {
importantEmail: 'Hello'
}
}
});

let expectedError;
try {
await subscription.next();
} catch (error) {
expectedError = error;
}

expect(expectedError).to.deep.equal(new Error('test error'));

const payload2 = await subscription.next();
expect(payload2).to.jsonEqual({
done: true,
value: undefined
});
});
});

0 comments on commit 3acc234

Please sign in to comment.