Skip to content

Subscriptions support #846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 17, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 292 additions & 12 deletions src/subscription/__tests__/subscribe-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { expect } from 'chai';
import { describe, it } from 'mocha';
import EventEmitter from 'events';
import eventEmitterAsyncIterator from './eventEmitterAsyncIterator';
import {subscribe} from '../subscribe';
import { subscribe } from '../subscribe';
import { parse } from '../../language';
import {
GraphQLSchema,
Expand Down Expand Up @@ -77,10 +77,7 @@ describe('Subscribe', () => {
subscription: SubscriptionType
});

it('produces a payload per subscription event', async () => {

const pubsub = new EventEmitter();

const createSubscription = pubsub => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor style detail: functions defined at the top level we use function createSubscription(pubsub) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

const data = {
inbox: {
emails: [
Expand Down Expand Up @@ -125,13 +122,21 @@ describe('Subscribe', () => {

// GraphQL `subscribe` has the same call signature as `execute`, but returns
// AsyncIterator instead of Promise.
const subscription = subscribe(
emailSchema,
ast,
data,
null, // context
{ priority: 1 }
);
return {
subscription: subscribe(
emailSchema,
ast,
data,
null, // context
{ priority: 1 }
),
sendImportantEmail,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small thing, but moving this to the first key in this return object would make it more obvious that more than one thing are being returned, otherwise it's easy to mistake this as another argument to subscribe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

};
};

it('produces a payload per subscription event', async () => {
const pubsub = new EventEmitter();
const { sendImportantEmail, subscription } = createSubscription(pubsub);

// Wait for the next subscription payload.
const payload = subscription.next();
Expand Down Expand Up @@ -211,4 +216,279 @@ describe('Subscribe', () => {
});
});

it('produces a payload when there are multiple events', async () => {
const pubsub = new EventEmitter();
const { sendImportantEmail, subscription } = createSubscription(pubsub);
let payload = subscription.next();

// A new email arrives!
expect(sendImportantEmail({
from: '[email protected]',
subject: 'Alright',
message: 'Tests are good',
unread: true,
})).to.equal(true);

expect(await payload).to.deep.equal({
done: false,
value: {
data: {
importantEmail: {
email: {
from: '[email protected]',
subject: 'Alright',
},
inbox: {
unread: 1,
total: 2,
},
},
},
},
});

payload = subscription.next();

// A new email arrives!
expect(sendImportantEmail({
from: '[email protected]',
subject: 'Alright 2',
message: 'Tests are good 2',
unread: true,
})).to.equal(true);

expect(await payload).to.deep.equal({
done: false,
value: {
data: {
importantEmail: {
email: {
from: '[email protected]',
subject: 'Alright 2',
},
inbox: {
unread: 2,
total: 3,
},
},
},
},
});
});

it('should not trigger when subscription is already done', async () => {
const pubsub = new EventEmitter();
const { sendImportantEmail, subscription } = createSubscription(pubsub);
let payload = subscription.next();

// A new email arrives!
expect(sendImportantEmail({
from: '[email protected]',
subject: 'Alright',
message: 'Tests are good',
unread: true,
})).to.equal(true);

expect(await payload).to.deep.equal({
done: false,
value: {
data: {
importantEmail: {
email: {
from: '[email protected]',
subject: 'Alright',
},
inbox: {
unread: 1,
total: 2,
},
},
},
},
});

payload = subscription.next();
subscription.return();

// A new email arrives!
expect(sendImportantEmail({
from: '[email protected]',
subject: 'Alright 2',
message: 'Tests are good 2',
unread: true,
})).to.equal(false);

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

it('events order is correct when multiple triggered together', async () => {
const pubsub = new EventEmitter();
const { sendImportantEmail, subscription } = createSubscription(pubsub);
let payload = subscription.next();

// A new email arrives!
expect(sendImportantEmail({
from: '[email protected]',
subject: 'Message',
message: 'Tests are good',
unread: true,
})).to.equal(true);

// A new email arrives!
expect(sendImportantEmail({
from: '[email protected]',
subject: 'Message 2',
message: 'Tests are good 2',
unread: true,
})).to.equal(true);

expect(await payload).to.deep.equal({
done: false,
value: {
data: {
importantEmail: {
email: {
from: '[email protected]',
subject: 'Message',
},
inbox: {
unread: 2,
total: 3,
},
},
},
},
});

payload = subscription.next();

expect(await payload).to.deep.equal({
done: false,
value: {
data: {
importantEmail: {
email: {
from: '[email protected]',
subject: 'Message 2',
},
inbox: {
unread: 2,
total: 3,
},
},
},
},
});
});

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

expect(() => {
subscribe(
emailSchema,
invalidAST,
null,
null, // context
{ priority: 1 });
}).to.throw('This subscription is not defined by the schema.');
});

it('throws when subscription definition doesnt return iterator', () => {
const invalidEmailSchema = new GraphQLSchema({
query: QueryType,
subscription: new GraphQLObjectType({
name: 'Subscription',
fields: {
importantEmail: {
type: GraphQLString,
resolve: () => 'test',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean to test subscribe: () => 'test' here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another test to add (which will not have correct behavior just yet) is if a subscription resolver returns an error or throws one: subscribe: () => new Error('test') or subscribe: () => { throw new Error('test'); }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the first one, and added a test case for the error

},
}
})
});

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

expect(() => {
subscribe(
invalidEmailSchema,
ast,
null,
null, // context
{ priority: 1 });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: all the {priority: 1} aren't being used by the test AST (those are variables), so these tests could all be simplified to subscribe(schema, ast)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

}).to.throw('Subscription must return Async Iterable.');
});

it('expects to have subscribe on type definition with iterator', () => {
const pubsub = new EventEmitter();
const invalidEmailSchema = new GraphQLSchema({
query: QueryType,
subscription: new GraphQLObjectType({
name: 'Subscription',
fields: {
importantEmail: {
type: GraphQLString,
subscribe: () => eventEmitterAsyncIterator(pubsub, 'importantEmail')
},
}
})
});

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

expect(() => {
subscribe(
invalidEmailSchema,
ast,
null,
null, // context
{ priority: 1 });
}).not.to.throw();
});

it('throws when subscribe does not return a valid iterator', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the same test as the one on line 404?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

const invalidEmailSchema = new GraphQLSchema({
query: QueryType,
subscription: new GraphQLObjectType({
name: 'Subscription',
fields: {
importantEmail: {
type: GraphQLString,
subscribe: () => 'test'
},
}
})
});

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

expect(() => {
subscribe(
invalidEmailSchema,
ast,
null,
null, // context
{ priority: 1 });
}).to.throw('Subscription must return Async Iterable.');
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add another test case with two subscriptions on importantEmail and verify that they both receive the payload?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!