1 | "use strict";
|
2 | var sinon = require('sinon');
|
3 | var chai = require('chai');
|
4 | var chaiAsPromised = require('chai-as-promised');
|
5 | var sinonChai = require('sinon-chai');
|
6 | var graphql_1 = require('graphql');
|
7 | var pubsub_1 = require('../pubsub');
|
8 | var validation_1 = require('../validation');
|
9 | chai.use(chaiAsPromised);
|
10 | chai.use(sinonChai);
|
11 | var expect = chai.expect;
|
12 | var assert = chai.assert;
|
13 | describe('PubSub', function () {
|
14 | it('can subscribe and is called when events happen', function (done) {
|
15 | var ps = new pubsub_1.PubSub();
|
16 | ps.subscribe('a', function (payload) {
|
17 | expect(payload).to.equals('test');
|
18 | done();
|
19 | }).then(function () {
|
20 | var succeed = ps.publish('a', 'test');
|
21 | expect(succeed).to.be.true;
|
22 | });
|
23 | });
|
24 | it('can unsubscribe', function (done) {
|
25 | var ps = new pubsub_1.PubSub();
|
26 | ps.subscribe('a', function (payload) {
|
27 | assert(false);
|
28 | }).then(function (subId) {
|
29 | ps.unsubscribe(subId);
|
30 | var succeed = ps.publish('a', 'test');
|
31 | expect(succeed).to.be.true;
|
32 | done();
|
33 | });
|
34 | });
|
35 | });
|
36 | var schema = new graphql_1.GraphQLSchema({
|
37 | query: new graphql_1.GraphQLObjectType({
|
38 | name: 'Query',
|
39 | fields: {
|
40 | testString: {
|
41 | type: graphql_1.GraphQLString,
|
42 | resolve: function (_, args) {
|
43 | return 'works';
|
44 | },
|
45 | },
|
46 | },
|
47 | }),
|
48 | subscription: new graphql_1.GraphQLObjectType({
|
49 | name: 'Subscription',
|
50 | fields: {
|
51 | testSubscription: {
|
52 | type: graphql_1.GraphQLString,
|
53 | resolve: function (root) {
|
54 | return root;
|
55 | },
|
56 | },
|
57 | testContext: {
|
58 | type: graphql_1.GraphQLString,
|
59 | resolve: function (rootValue, args, context) {
|
60 | return context;
|
61 | },
|
62 | },
|
63 | testFilter: {
|
64 | type: graphql_1.GraphQLString,
|
65 | resolve: function (root, _a) {
|
66 | var filterBoolean = _a.filterBoolean;
|
67 | return filterBoolean ? 'goodFilter' : 'badFilter';
|
68 | },
|
69 | args: {
|
70 | filterBoolean: { type: graphql_1.GraphQLBoolean },
|
71 | },
|
72 | },
|
73 | testFilterMulti: {
|
74 | type: graphql_1.GraphQLString,
|
75 | resolve: function (root, _a) {
|
76 | var filterBoolean = _a.filterBoolean;
|
77 | return filterBoolean ? 'goodFilter' : 'badFilter';
|
78 | },
|
79 | args: {
|
80 | filterBoolean: { type: graphql_1.GraphQLBoolean },
|
81 | a: { type: graphql_1.GraphQLString },
|
82 | b: { type: graphql_1.GraphQLInt },
|
83 | },
|
84 | },
|
85 | testChannelOptions: {
|
86 | type: graphql_1.GraphQLString,
|
87 | resolve: function (root) {
|
88 | return root;
|
89 | },
|
90 | },
|
91 | },
|
92 | }),
|
93 | });
|
94 | describe('SubscriptionManager', function () {
|
95 | var pubsub = new pubsub_1.PubSub();
|
96 | var subManager = new pubsub_1.SubscriptionManager({
|
97 | schema: schema,
|
98 | setupFunctions: {
|
99 | 'testFilter': function (options, _a) {
|
100 | var filterBoolean = _a.filterBoolean;
|
101 | return {
|
102 | 'Filter1': {
|
103 | filter: function (root) { return root.filterBoolean === filterBoolean; },
|
104 | },
|
105 | };
|
106 | },
|
107 | 'testFilterMulti': function (options) {
|
108 | return {
|
109 | 'Trigger1': {
|
110 | filter: function () { return true; },
|
111 | },
|
112 | 'Trigger2': {
|
113 | filter: function () { return true; },
|
114 | },
|
115 | };
|
116 | },
|
117 | 'testChannelOptions': function () {
|
118 | return {
|
119 | 'Trigger1': {
|
120 | channelOptions: {
|
121 | foo: 'bar',
|
122 | },
|
123 | },
|
124 | };
|
125 | },
|
126 | testContext: function (options) {
|
127 | return {
|
128 | contextTrigger: function (rootValue, context) {
|
129 | return context === 'trigger';
|
130 | },
|
131 | };
|
132 | },
|
133 | },
|
134 | pubsub: pubsub,
|
135 | });
|
136 | beforeEach(function () {
|
137 | sinon.spy(pubsub, 'subscribe');
|
138 | });
|
139 | afterEach(function () {
|
140 | sinon.restore(pubsub.subscribe);
|
141 | });
|
142 | it('throws an error if query is not valid', function () {
|
143 | var query = 'query a{ testInt }';
|
144 | var callback = function () { return null; };
|
145 | return expect(subManager.subscribe({ query: query, operationName: 'a', callback: callback }))
|
146 | .to.eventually.be.rejectedWith('Subscription query has validation errors');
|
147 | });
|
148 | it('rejects subscriptions with more than one root field', function () {
|
149 | var query = 'subscription X{ a: testSubscription, b: testSubscription }';
|
150 | var callback = function () { return null; };
|
151 | return expect(subManager.subscribe({ query: query, operationName: 'X', callback: callback }))
|
152 | .to.eventually.be.rejectedWith('Subscription query has validation errors');
|
153 | });
|
154 | it('can subscribe with a valid query and gets a subId back', function () {
|
155 | var query = 'subscription X{ testSubscription }';
|
156 | var callback = function () { return null; };
|
157 | subManager.subscribe({ query: query, operationName: 'X', callback: callback }).then(function (subId) {
|
158 | expect(subId).to.be.a('number');
|
159 | subManager.unsubscribe(subId);
|
160 | });
|
161 | });
|
162 | it('can subscribe with a valid query and get the root value', function (done) {
|
163 | var query = 'subscription X{ testSubscription }';
|
164 | var callback = function (err, payload) {
|
165 | try {
|
166 | expect(payload.data.testSubscription).to.equals('good');
|
167 | }
|
168 | catch (e) {
|
169 | done(e);
|
170 | return;
|
171 | }
|
172 | done();
|
173 | };
|
174 | subManager.subscribe({ query: query, operationName: 'X', callback: callback }).then(function (subId) {
|
175 | subManager.publish('testSubscription', 'good');
|
176 | subManager.unsubscribe(subId);
|
177 | });
|
178 | });
|
179 | it('can use filter functions properly', function (done) {
|
180 | var query = "subscription Filter1($filterBoolean: Boolean){\n testFilter(filterBoolean: $filterBoolean)\n }";
|
181 | var callback = function (err, payload) {
|
182 | try {
|
183 | expect(payload.data.testFilter).to.equals('goodFilter');
|
184 | }
|
185 | catch (e) {
|
186 | done(e);
|
187 | return;
|
188 | }
|
189 | done();
|
190 | };
|
191 | subManager.subscribe({
|
192 | query: query,
|
193 | operationName: 'Filter1',
|
194 | variables: { filterBoolean: true },
|
195 | callback: callback,
|
196 | }).then(function (subId) {
|
197 | subManager.publish('Filter1', { filterBoolean: false });
|
198 | subManager.publish('Filter1', { filterBoolean: true });
|
199 | subManager.unsubscribe(subId);
|
200 | });
|
201 | });
|
202 | it('can subscribe to more than one trigger', function (done) {
|
203 | var triggerCount = 0;
|
204 | var query = "subscription multiTrigger($filterBoolean: Boolean, $uga: String){\n testFilterMulti(filterBoolean: $filterBoolean, a: $uga, b: 66)\n }";
|
205 | var callback = function (err, payload) {
|
206 | try {
|
207 | expect(payload.data.testFilterMulti).to.equals('goodFilter');
|
208 | triggerCount++;
|
209 | }
|
210 | catch (e) {
|
211 | done(e);
|
212 | return;
|
213 | }
|
214 | if (triggerCount === 2) {
|
215 | done();
|
216 | }
|
217 | };
|
218 | subManager.subscribe({
|
219 | query: query,
|
220 | operationName: 'multiTrigger',
|
221 | variables: { filterBoolean: true, uga: 'UGA' },
|
222 | callback: callback,
|
223 | }).then(function (subId) {
|
224 | subManager.publish('NotATrigger', { filterBoolean: false });
|
225 | subManager.publish('Trigger1', { filterBoolean: true });
|
226 | subManager.publish('Trigger2', { filterBoolean: true });
|
227 | subManager.unsubscribe(subId);
|
228 | });
|
229 | });
|
230 | it('can subscribe to a trigger and pass options to PubSub using "channelOptions"', function (done) {
|
231 | var query = 'subscription X{ testChannelOptions }';
|
232 | subManager.subscribe({
|
233 | query: query,
|
234 | operationName: 'X',
|
235 | callback: function () { return null; },
|
236 | }).then(function () {
|
237 | expect(pubsub.subscribe).to.have.been.calledOnce;
|
238 | var expectedChannelOptions = {
|
239 | foo: 'bar',
|
240 | };
|
241 | expect(pubsub.subscribe).to.have.been.calledWith(sinon.match.string, sinon.match.func, expectedChannelOptions);
|
242 | done();
|
243 | }).catch(function (err) {
|
244 | done(err);
|
245 | });
|
246 | });
|
247 | it('can unsubscribe', function (done) {
|
248 | var query = 'subscription X{ testSubscription }';
|
249 | var callback = function (err, payload) {
|
250 | try {
|
251 | assert(false);
|
252 | }
|
253 | catch (e) {
|
254 | done(e);
|
255 | return;
|
256 | }
|
257 | done();
|
258 | };
|
259 | subManager.subscribe({ query: query, operationName: 'X', callback: callback }).then(function (subId) {
|
260 | subManager.unsubscribe(subId);
|
261 | subManager.publish('testSubscription', 'bad');
|
262 | setTimeout(done, 30);
|
263 | });
|
264 | });
|
265 | it('throws an error when trying to unsubscribe from unknown id', function () {
|
266 | expect(function () { return subManager.unsubscribe(123); })
|
267 | .to.throw('undefined');
|
268 | });
|
269 | it('calls the error callback if there is an execution error', function (done) {
|
270 | var query = "subscription X($uga: Boolean!){\n testSubscription @skip(if: $uga)\n }";
|
271 | var callback = function (err, payload) {
|
272 | try {
|
273 | expect(payload).to.be.undefined;
|
274 | expect(err.message).to.equals('Variable "$uga" of required type "Boolean!" was not provided.');
|
275 | }
|
276 | catch (e) {
|
277 | done(e);
|
278 | return;
|
279 | }
|
280 | done();
|
281 | };
|
282 | subManager.subscribe({ query: query, operationName: 'X', callback: callback }).then(function (subId) {
|
283 | subManager.publish('testSubscription', 'good');
|
284 | subManager.unsubscribe(subId);
|
285 | });
|
286 | });
|
287 | it('calls context if it is a function', function (done) {
|
288 | var query = "subscription TestContext { testContext }";
|
289 | var callback = function (error, payload) {
|
290 | expect(error).to.be.null;
|
291 | expect(payload.data.testContext).to.eq('trigger');
|
292 | done();
|
293 | };
|
294 | var context = function () {
|
295 | return 'trigger';
|
296 | };
|
297 | subManager.subscribe({
|
298 | query: query,
|
299 | context: context,
|
300 | operationName: 'TestContext',
|
301 | variables: {},
|
302 | callback: callback,
|
303 | }).then(function (subId) {
|
304 | subManager.publish('contextTrigger', 'ignored');
|
305 | subManager.unsubscribe(subId);
|
306 | });
|
307 | });
|
308 | });
|
309 | var validationSchema = new graphql_1.GraphQLSchema({
|
310 | query: new graphql_1.GraphQLObjectType({
|
311 | name: 'Query',
|
312 | fields: {
|
313 | placeholder: { type: graphql_1.GraphQLString },
|
314 | },
|
315 | }),
|
316 | subscription: new graphql_1.GraphQLObjectType({
|
317 | name: 'Subscription',
|
318 | fields: {
|
319 | test1: { type: graphql_1.GraphQLString },
|
320 | test2: { type: graphql_1.GraphQLString },
|
321 | },
|
322 | }),
|
323 | });
|
324 | describe('SubscriptionValidationRule', function () {
|
325 | it('should allow a valid subscription', function () {
|
326 | var sub = "subscription S1{\n test1\n }";
|
327 | var errors = graphql_1.validate(validationSchema, graphql_1.parse(sub), [validation_1.subscriptionHasSingleRootField]);
|
328 | expect(errors.length).to.equals(0);
|
329 | });
|
330 | it('should allow another valid subscription', function () {
|
331 | var sub = "\n subscription S1{\n test1\n }\n subscription S2{\n test2\n }";
|
332 | var errors = graphql_1.validate(validationSchema, graphql_1.parse(sub), [validation_1.subscriptionHasSingleRootField]);
|
333 | expect(errors.length).to.equals(0);
|
334 | });
|
335 | it('should allow two valid subscription definitions', function () {
|
336 | var sub = "subscription S2{\n test2\n }";
|
337 | var errors = graphql_1.validate(validationSchema, graphql_1.parse(sub), [validation_1.subscriptionHasSingleRootField]);
|
338 | expect(errors.length).to.equals(0);
|
339 | });
|
340 | it('should not allow two fields in the subscription', function () {
|
341 | var sub = "subscription S3{\n test1\n test2\n }";
|
342 | var errors = graphql_1.validate(validationSchema, graphql_1.parse(sub), [validation_1.subscriptionHasSingleRootField]);
|
343 | expect(errors.length).to.equals(1);
|
344 | expect(errors[0].message).to.equals('Subscription "S3" must have only one field.');
|
345 | });
|
346 | it('should not allow inline fragments', function () {
|
347 | var sub = "subscription S4{\n ... on Subscription {\n test1\n }\n }";
|
348 | var errors = graphql_1.validate(validationSchema, graphql_1.parse(sub), [validation_1.subscriptionHasSingleRootField]);
|
349 | expect(errors.length).to.equals(1);
|
350 | expect(errors[0].message).to.equals('Apollo subscriptions do not support fragments on the root field');
|
351 | });
|
352 | it('should not allow named fragments', function () {
|
353 | var sub = "subscription S5{\n ...testFragment\n }\n\n fragment testFragment on Subscription{\n test2\n }";
|
354 | var errors = graphql_1.validate(validationSchema, graphql_1.parse(sub), [validation_1.subscriptionHasSingleRootField]);
|
355 | expect(errors.length).to.equals(1);
|
356 | expect(errors[0].message).to.equals('Apollo subscriptions do not support fragments on the root field');
|
357 | });
|
358 | });
|
359 |
|
\ | No newline at end of file |