update dependencies and devDependencies, fix lint issues
[websub-hub] / test / src / communication.js
1 'use strict';
2
3 const assert = require('node:assert');
4 const sinon = require('sinon');
5
6 const Communication = require('../../src/communication');
7 const Config = require('../../config');
8 const Errors = require('../../src/errors');
9
10 const stubDb = require('../stub-db');
11 const stubLogger = require('../stub-logger');
12
13 const noExpectedException = 'did not get expected exception';
14
15 describe('Communication', function () {
16 let communication, options;
17
18 beforeEach(function () {
19 options = new Config('test');
20 communication = new Communication(stubLogger, stubDb, options);
21 stubDb._reset();
22 stubLogger._reset();
23 });
24 afterEach(function () {
25 sinon.restore();
26 });
27
28 it('instantiates', function () {
29 assert(communication);
30 });
31
32 it('covers config value', function () {
33 options.dingus.selfBaseUrl = undefined;
34 communication = new Communication(stubLogger, stubDb, options);
35 });
36
37 describe('_init', function () {
38 it('covers', async function () {
39 await communication._init();
40 await communication._init();
41 assert(communication.Got);
42 assert(communication.got);
43 });
44 });
45
46 describe('_onRetry', function () {
47 it('covers', function () {
48 const error = {};
49 const retryCount = 1;
50 communication._onRetry(error, retryCount);
51 assert(communication.logger.debug.called);
52 });
53 });
54
55 describe('userAgentString', function () {
56 it('has default behavior', function () {
57 const result = Communication.userAgentString();
58 assert(result);
59 assert(result.length > 30);
60 });
61 it('is settable', function () {
62 const result = Communication.userAgentString({
63 product: 'myhub',
64 version: '9.9.9',
65 implementation: 'custom',
66 });
67 assert(result);
68 assert.strictEqual(result, 'myhub/9.9.9 (custom)');
69 });
70 it('covers branches', function () {
71 const result = Communication.userAgentString({
72 product: 'myhub',
73 version: '9.9.9',
74 implementation: '',
75 });
76 assert(result);
77 assert.strictEqual(result, 'myhub/9.9.9');
78 });
79 });
80
81 describe('generateChallenge', function () {
82 it('generates a thing', async function () {
83 const result = await Communication.generateChallenge();
84 assert(result);
85 assert(result.length);
86 });
87 });
88
89 describe('signature', function () {
90 let message, secret, algorithm, expected;
91 beforeEach(function () {
92 message = 'Jackdaws love my big sphinx of quartz.';
93 secret = 'secretsecret';
94 algorithm = 'sha256';
95 expected = 'sha256=ee92148d9cd043cdfb8da7cf5ee1897abaafdb5ab840e85010abd4bf235fa31e';
96 });
97 it('signs a thing', function () {
98 const result = Communication.signature(message, secret, algorithm);
99 assert.strictEqual(result, expected);
100 });
101 });
102
103 describe('contentHash', function () {
104 let content, algorithm, expected;
105 beforeEach(function () {
106 content = 'Jived fox nymph grabs quick waltz.';
107 algorithm = 'sha256';
108 expected = '6e5e1a93bde78910b0d7c5fd8aba393294d4eca5d3fbf2bfd49100df3d5cc85d';
109 });
110 it('hashes', function () {
111 const result = Communication.contentHash(content, algorithm);
112 assert.strictEqual(result, expected);
113 });
114 });
115
116 describe('verificationProcess', function () {
117 const challenge = 'a_challenge';
118 let dbCtx, callback, requestId, topicId;
119 let topic, verification;
120 beforeEach(function () {
121 dbCtx = {};
122 callback = 'https://example.com/callback/?id=123';
123 requestId = '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
124 topicId = '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
125 topic = {
126 id: topicId,
127 url: 'https://example.com/blog/',
128 isActive: true,
129 isDeleted: false,
130 };
131 verification = {
132 callback,
133 mode: 'subscribe',
134 isPublisherValidated: true,
135 leaseSeconds: 864000,
136 };
137
138 sinon.stub(Communication, 'generateChallenge').resolves(challenge);
139 sinon.stub(communication, 'publisherValidate').resolves(true);
140 sinon.stub(communication, 'got').resolves({
141 statusCode: 200,
142 statusMessage: 'OK',
143 headers: {
144 'content-type': 'text/plain',
145 },
146 body: challenge,
147 });
148
149 communication.db.verificationGetById.resolves(verification);
150 communication.db.topicGetById.resolves(topic);
151 communication.db.verificationRelease.resolves({});
152 communication.db.verificationUpdate.resolves({});
153 communication.db.verificationIncomplete.resolves({});
154 communication.db.verificationComplete.resolves({});
155 });
156
157 it('errors on non-existent verification', async function () {
158 communication.db.verificationGetById.restore();
159 sinon.stub(communication.db, 'verificationGetById').resolves();
160
161 try {
162 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
163 assert.fail(noExpectedException);
164 } catch (e) {
165 assert(e instanceof Errors.InternalInconsistencyError);
166 }
167 });
168
169 it('errors on non-existent topic', async function () {
170 communication.db.topicGetById.restore();
171 sinon.stub(communication.db, 'topicGetById').resolves();
172
173 try {
174 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
175 assert.fail(noExpectedException);
176 } catch (e) {
177 assert(e instanceof Errors.InternalInconsistencyError);
178 }
179 });
180
181 it('skips inactive topic', async function () {
182 communication.db.topicGetById.restore();
183 topic.isActive = false;
184 sinon.stub(communication.db, 'topicGetById').resolves(topic);
185
186 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
187
188 assert(communication.db.verificationRelease.called);
189 assert(!communication.got.called);
190 });
191
192 it('denies subscription to deleted topic', async function () {
193 communication.db.topicGetById.restore();
194 topic.isDeleted = true;
195 sinon.stub(communication.db, 'topicGetById').resolves(topic);
196
197 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
198
199 assert(communication.db.verificationUpdate.called);
200 assert.strictEqual(verification.mode, 'denied');
201 });
202
203 it('checks publisher validation if needed', async function() {
204 communication.db.verificationGetById.restore();
205 verification.isPublisherValidated = false;
206 sinon.stub(communication.db, 'verificationGetById').resolves(verification);
207 communication.db.topicGetById.restore();
208 topic.publisherValidationUrl = 'https://example.com/publisher/';
209 sinon.stub(communication.db, 'topicGetById').resolves(topic);
210
211 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
212
213 assert(communication.publisherValidate.called);
214 assert(communication.db.verificationComplete.called);
215 });
216
217 it('handles publisher validation failure', async function() {
218 communication.db.verificationGetById.restore();
219 verification.isPublisherValidated = false;
220 sinon.stub(communication.db, 'verificationGetById').resolves(verification);
221 communication.db.topicGetById.restore();
222 topic.publisherValidationUrl = 'https://example.com/publisher/';
223 sinon.stub(communication.db, 'topicGetById').resolves(topic);
224 communication.publisherValidate.restore();
225 sinon.stub(communication, 'publisherValidate').resolves(false);
226
227 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
228
229 assert(communication.publisherValidate.called);
230 assert(communication.db.verificationIncomplete.called);
231 });
232
233 it('handles request error', async function () {
234 communication.got.restore();
235 sinon.stub(communication, 'got').rejects(new Error());
236
237 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
238
239 assert(communication.db.verificationIncomplete.called);
240 });
241
242 it('handles 500 response', async function () {
243 communication.got.restore();
244 sinon.stub(communication, 'got').resolves({
245 statusCode: 500,
246 });
247
248 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
249
250 assert(communication.db.verificationIncomplete.called);
251 });
252
253 it('handles non-200 response', async function () {
254 communication.got.restore();
255 sinon.stub(communication, 'got').resolves({
256 statusCode: 400,
257 });
258
259 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
260
261 assert(communication.db.verificationComplete.called);
262 });
263
264 it('subscription succeeds', async function () {
265 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
266
267 assert(communication.db.subscriptionUpsert.called);
268 assert(communication.db.verificationComplete.called);
269 });
270
271 it('unsubscription succeeds', async function () {
272 communication.db.verificationGetById.restore();
273 verification.mode = 'unsubscribe';
274 sinon.stub(communication.db, 'verificationGetById').resolves(verification);
275
276 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
277
278 assert(communication.db.subscriptionDelete.called);
279 assert(communication.db.verificationComplete.called);
280 });
281
282 it('unsubscription from deleted topic deletes topic', async function () {
283 communication.db.verificationGetById.restore();
284 verification.mode = 'unsubscribe';
285 sinon.stub(communication.db, 'verificationGetById').resolves(verification);
286 communication.db.topicGetById.restore();
287 sinon.stub(communication.db, 'topicGetById').resolves({
288 ...topic,
289 isDeleted: true,
290 });
291
292 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
293
294 assert(communication.db.subscriptionDelete.called);
295 assert(communication.db.verificationComplete.called);
296 assert(communication.db.topicPendingDelete.called);
297 });
298
299 it('unsubscription denial succeeds', async function () {
300 communication.db.verificationGetById.restore();
301 verification.mode = 'unsubscribe';
302 sinon.stub(communication.db, 'verificationGetById').resolves(verification);
303 communication.got.restore();
304 sinon.stub(communication, 'got').resolves({
305 statusCode: 200,
306 statusMessage: 'OK',
307 headers: {
308 'content-type': 'text/plain',
309 },
310 body: 'not the challenge',
311 });
312
313 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
314
315 assert(!communication.db.subscriptionDelete.called);
316 assert(communication.db.verificationComplete.called);
317 });
318
319 it('does not handle strange mode', async function() {
320 communication.db.verificationGetById.restore();
321 verification.mode = 'flarp';
322 sinon.stub(communication.db, 'verificationGetById').resolves(verification);
323
324 try {
325 await communication.verificationProcess(dbCtx, callback, topicId, requestId);
326 assert.fail(noExpectedException);
327 } catch (e) {
328 assert(e instanceof Errors.InternalInconsistencyError);
329 }
330 });
331 }); // verificationProcess
332
333 describe('publisherValidate', function () {
334 let dbCtx, topic, verification;
335 beforeEach(function () {
336 dbCtx = {};
337 topic = {
338 url: 'https://example.com/topic/',
339 publisherValidationUrl: 'https://example.com/pub_valid/',
340 };
341 verification = {
342 callback: 'https://exmaple.com/callback/?id=123',
343 httpFrom: 'user@example.com',
344 httpRemoteAddr: '127.0.0.0',
345 };
346
347 sinon.stub(communication, 'got').resolves({
348 statusCode: 200,
349 statusMessage: 'OK',
350 headers: {
351 'content-type': 'application/json',
352 },
353 });
354
355 communication.db.verificationIncomplete.resolves();
356 communication.db.verificationUpdate.resolves();
357 communication.db.verificationValidated.resolves();
358 });
359
360 it('succeeds', async function () {
361 const result = await communication.publisherValidate(dbCtx, topic, verification);
362
363 assert(communication.db.verificationValidated.called);
364 assert.strictEqual(result, true);
365 });
366
367 it('succeeds with rejection', async function () {
368 communication.got.restore();
369 sinon.stub(communication, 'got').resolves({
370 statusCode: 400,
371 statusMessage: 'Bad Request',
372 headers: {
373 'content-type': 'application/json',
374 },
375 });
376
377 const result = await communication.publisherValidate(dbCtx, topic, verification);
378
379 assert(communication.db.verificationValidated.called);
380 assert(communication.db.verificationUpdate.called);
381 assert.strictEqual(result, true);
382 });
383
384 it('defers on request server error', async function () {
385 communication.got.restore();
386 sinon.stub(communication, 'got').resolves({
387 statusCode: 502,
388 statusMessage: 'Bad Gateway',
389 headers: {
390 'content-type': 'text/plain',
391 },
392 });
393
394 const result = await communication.publisherValidate(dbCtx, topic, verification);
395
396 assert.strictEqual(result, false);
397 });
398
399 it('handles request error', async function () {
400 communication.got.restore();
401 sinon.stub(communication, 'got').rejects(new Error());
402
403 const result = await communication.publisherValidate(dbCtx, topic, verification);
404
405 assert.strictEqual(result, false);
406 });
407
408 }); // publisherValidate
409
410 describe('topicFetchProcess', function () {
411 let dbCtx, topic, requestId, topicId;
412
413 beforeEach(function () {
414 dbCtx = {};
415 topic = {
416 url: 'https://example.com/topic/',
417 isDeleted: false,
418 contentHashAlgorithm: 'sha512',
419 };
420 requestId = '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
421 topicId = '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
422
423 sinon.stub(communication, 'got').resolves({
424 statusCode: 200,
425 statusMessage: 'OK',
426 headers: {
427 'content-type': 'text/plain',
428 link: '<https://example.com/hub/>; rel="hub"',
429 'last-modified': 'Thu, 18 Nov 2021 20:34:35 GMT',
430 'etag': '"9c104-1673e-5d1161636d742"',
431 },
432 body: 'Jackdaws love my big sphinx of quartz.',
433 });
434
435 communication.db.topicGetById.resolves(topic);
436 });
437
438 it('requires topic exists', async function () {
439 communication.db.topicGetById.restore();
440 sinon.stub(communication.db, 'topicGetById').resolves();
441
442 try {
443 await communication.topicFetchProcess(dbCtx, topicId, requestId);
444 assert.fail(noExpectedException);
445 } catch (e) {
446 assert(e instanceof Errors.InternalInconsistencyError);
447 }
448 });
449
450 it ('skips deleted topic', async function () {
451 communication.db.topicGetById.restore();
452 topic.isDeleted = true;
453 sinon.stub(communication.db, 'topicGetById').resolves(topic);
454
455 await communication.topicFetchProcess(dbCtx, topicId, requestId);
456
457 assert(!communication.got.called);
458 });
459
460 it('handles request error', async function () {
461 communication.got.restore();
462 sinon.stub(communication, 'got').rejects(new Error());
463
464 await communication.topicFetchProcess(dbCtx, topicId, requestId);
465
466 assert(communication.db.topicFetchIncomplete.called);
467 });
468
469 it('handles 500 response', async function () {
470 communication.got.restore();
471 sinon.stub(communication, 'got').resolves({
472 statusCode: 500,
473 statusMessage: 'Internal Server Error',
474 headers: {
475 'content-type': 'text/plain',
476 },
477 });
478
479 await communication.topicFetchProcess(dbCtx, topicId, requestId);
480
481 assert(communication.db.topicFetchIncomplete.called);
482 });
483
484 it('handles bad response', async function () {
485 communication.got.restore();
486 sinon.stub(communication, 'got').resolves({
487 statusCode: 404,
488 statusMessage: 'Not Found',
489 headers: {
490 'content-type': 'text/plain',
491 },
492 });
493
494 await communication.topicFetchProcess(dbCtx, topicId, requestId);
495
496 assert(communication.db.topicFetchIncomplete.called);
497 });
498
499 it('recognizes unchanged content', async function () {
500 communication.db.topicGetById.restore();
501 topic.contentHash = 'a630999c61738f3e066d79a1b299a295c5d0598c173e0904d04a707d43988e3e81660bfc1b1779377f4ec26f837d1bb31fa2b860c9ad2d37495d83de32647fea';
502 sinon.stub(communication.db, 'topicGetById').resolves(topic);
503
504 await communication.topicFetchProcess(dbCtx, topicId, requestId);
505
506 assert(communication.db.topicFetchComplete.called);
507 assert(!communication.db.topicSetContent.called);
508 });
509
510 it('recognizes 304 response', async function () {
511 topic.httpLastModified = 'Thu, 18 Nov 2021 20:34:35 GMT';
512 topic.httpEtag = '"9c104-1673e-5d1161636d742"';
513 communication.db.topicGetById.resolves(topic);
514 communication.got.resolves({
515 statusCode: 304,
516 });
517
518 await communication.topicFetchProcess(dbCtx, topicId, requestId);
519
520 assert(communication.db.topicFetchComplete.called);
521 assert(!communication.db.topicSetContent.called);
522 });
523
524 it('updates content', async function () {
525 await communication.topicFetchProcess(dbCtx, topicId, requestId);
526
527 assert(communication.db.topicFetchComplete.called);
528 assert(communication.db.topicSetContent.called);
529 });
530
531 it('updates content with lax link enforcement', async function () {
532 communication.got.restore();
533 sinon.stub(communication, 'got').resolves({
534 statusCode: 200,
535 statusMessage: 'OK',
536 headers: {
537 'content-type': 'text/plain',
538 link: '<https://example.com/other/hub/>; rel="hub"',
539 },
540 body: 'Jackdaws love my big sphinx of quartz.',
541 });
542
543 communication.options.communication.strictTopicHubLink = false;
544
545 await communication.topicFetchProcess(dbCtx, topicId, requestId);
546
547 assert(communication.db.topicFetchComplete.called);
548 assert(communication.db.topicSetContent.called);
549 });
550
551 it('deletes topic when hub relation unsatisfied', async function () {
552 communication.got.restore();
553 sinon.stub(communication, 'got').resolves({
554 statusCode: 200,
555 statusMessage: 'OK',
556 headers: {
557 'content-type': 'text/plain',
558 link: '<https://example.com/other/hub/>; rel="hub"',
559 },
560 body: 'Jackdaws love my big sphinx of quartz.',
561 });
562
563 await communication.topicFetchProcess(dbCtx, topicId, requestId);
564
565 assert(communication.db.topicFetchComplete.called);
566 assert(communication.db.topicDeleted.called);
567 });
568 }); // topicFetchProcess
569
570 describe('subscriptionDeliveryProcess', function () {
571 let dbCtx, requestId, topic, topicId, subscription, subscriptionId;
572
573 beforeEach(function () {
574 dbCtx = {};
575 topic = {
576 url: 'https://example.com/topic/',
577 isDeleted: false,
578 contentHashAlgorithm: 'sha512',
579 content: 'Jackdaws love my big sphinx of quartz.',
580 };
581 requestId = '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
582 topicId = '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
583 subscriptionId = 'c5e6a3ac-dab8-11eb-b758-0025905f714a';
584 subscription = {
585 topicId,
586 callback: 'https://example.com/callback/123',
587 secret: 'superdupersecret',
588 signatureAlgorithm: 'sha512',
589 };
590
591 sinon.stub(communication, 'got').resolves({
592 statusCode: 200,
593 statusMessage: 'OK',
594 headers: {
595 'content-type': 'text/plain',
596 },
597 body: 'Jackdaws love my big sphinx of quartz.',
598 });
599
600 communication.db.topicGetContentById.resolves(topic);
601 communication.db.subscriptionGetById.resolves(subscription);
602 });
603
604 it('requires subscription to exist', async function () {
605 communication.db.subscriptionGetById.restore();
606 sinon.stub(communication.db, 'subscriptionGetById').resolves();
607 try {
608 await communication.subscriptionDeliveryProcess(dbCtx, subscriptionId, requestId);
609 assert.fail(noExpectedException);
610 } catch (e) {
611 assert(e instanceof Errors.InternalInconsistencyError);
612 }
613 });
614
615 it('requires topic to exist', async function () {
616 communication.db.topicGetContentById.restore();
617 sinon.stub(communication.db, 'topicGetContentById').resolves();
618 try {
619 await communication.subscriptionDeliveryProcess(dbCtx, subscriptionId, requestId);
620 assert.fail(noExpectedException);
621 } catch (e) {
622 assert(e instanceof Errors.InternalInconsistencyError);
623 }
624 });
625
626 it('succeeds', async function () {
627 await communication.subscriptionDeliveryProcess(dbCtx, subscriptionId, requestId);
628
629 assert(communication.db.subscriptionDeliveryComplete.called);
630 });
631
632 it('handles request error', async function () {
633 communication.got.restore();
634 sinon.stub(communication, 'got').throws();
635
636 await communication.subscriptionDeliveryProcess(dbCtx, subscriptionId, requestId);
637
638 assert(communication.db.subscriptionDeliveryIncomplete.called);
639 });
640
641 it('handles 5xx response', async function () {
642 communication.got.restore();
643 sinon.stub(communication, 'got').resolves({
644 statusCode: 500,
645 statusMessage: 'Internal Server Error',
646 headers: {
647 'content-type': 'text/plain',
648 },
649 });
650
651 await communication.subscriptionDeliveryProcess(dbCtx, subscriptionId, requestId);
652
653 assert(communication.db.subscriptionDeliveryIncomplete.called);
654 });
655
656 it('handles 4xx response', async function () {
657 communication.got.restore();
658 sinon.stub(communication, 'got').resolves({
659 statusCode: 404,
660 statusMessage: 'Not Found',
661 headers: {
662 'content-type': 'text/plain',
663 },
664 });
665
666 await communication.subscriptionDeliveryProcess(dbCtx, subscriptionId, requestId);
667
668 assert(communication.db.subscriptionDeliveryIncomplete.called);
669 });
670
671 it('handles 410 response', async function () {
672 communication.got.restore();
673 sinon.stub(communication, 'got').resolves({
674 statusCode: 410,
675 statusMessage: 'Gone',
676 headers: {
677 'content-type': 'text/plain',
678 },
679 });
680
681 await communication.subscriptionDeliveryProcess(dbCtx, subscriptionId, requestId);
682
683 assert(communication.db.subscriptionDeliveryGone.called);
684 });
685
686 it('unsubscribes when topic is deleted', async function () {
687 topic.isDeleted = true;
688 communication.db.topicGetContentById.restore();
689 sinon.stub(communication.db, 'topicGetContentById').resolves(topic);
690
691 await communication.subscriptionDeliveryProcess(dbCtx, subscriptionId, requestId);
692
693 assert(communication.db.verificationInsert.called);
694 assert(communication.db.subscriptionDeliveryComplete.called);
695 });
696 }); // subscriptionDeliveryProcess
697
698 describe('topicFetchClaimAndProcessById', function () {
699 let dbCtx, topicId, requestId;
700 beforeEach(function () {
701 dbCtx = {};
702 requestId = '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
703 topicId = '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
704 sinon.stub(communication, 'topicFetchProcess');
705 });
706 it('covers claim', async function () {
707 communication.db.topicFetchClaimById.resolves({
708 changes: 1,
709 });
710 await communication.topicFetchClaimAndProcessById(dbCtx, topicId, requestId);
711 assert(communication.topicFetchProcess.called);
712 });
713 it('covers no claim', async function () {
714 communication.db.topicFetchClaimById.resolves({
715 changes: 0,
716 });
717 await communication.topicFetchClaimAndProcessById(dbCtx, topicId, requestId);
718 assert(!communication.topicFetchProcess.called);
719 });
720 }); // topicFetchClaimAndProcessById
721
722 describe('verificationClaimAndProcessById', function () {
723 let dbCtx, verificationId, requestId;
724 beforeEach(function () {
725 dbCtx = {};
726 verificationId = '28488311-6652-42ea-9839-7bbc42b246cb';
727 requestId = '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
728 sinon.stub(communication, 'verificationProcess');
729 });
730 it('covers claim', async function () {
731 communication.db.verificationClaimById.resolves({
732 changes: 1,
733 });
734 await communication.verificationClaimAndProcessById(dbCtx, verificationId, requestId);
735 assert(communication.verificationProcess.called);
736 });
737 it('covers no claim', async function () {
738 communication.db.verificationClaimById.resolves({
739 changes: 0,
740 });
741 await communication.verificationClaimAndProcessById(dbCtx, verificationId, requestId);
742 assert(!communication.verificationProcess.called);
743 });
744 }); // verificationClaimAndProcessById
745
746 describe('workFeed', function () {
747 let stubCtx, wanted;
748 beforeEach(function () {
749 stubCtx = {};
750 sinon.stub(communication, 'topicFetchProcess');
751 sinon.stub(communication, 'verificationProcess');
752 sinon.stub(communication, 'subscriptionDeliveryProcess');
753 });
754 it('succeeds', async function () {
755 const topicIds = [ { id: '' }, { id: '' } ];
756 communication.db.topicFetchClaim.resolves(topicIds);
757 const verificationIds = [ { id: '' }, { id: '' } ];
758 communication.db.verificationClaim.resolves(verificationIds);
759 const subscriptionIds = [ { id: '' }, { id: '' } ];
760 communication.db.subscriptionDeliveryClaim.resolves(subscriptionIds);
761 const expectedLength = [topicIds, verificationIds, subscriptionIds].map((x) => x.length).reduce((a, b) => a + b, 0);
762 wanted = 10;
763
764 const result = await communication.workFeed(stubCtx, wanted);
765
766 assert.strictEqual(result.length, expectedLength);
767 });
768 it('covers no wanted work', async function () {
769 wanted = 0;
770 const result = await communication.workFeed(stubCtx, wanted);
771 assert.strictEqual(result.length, 0);
772 assert(!communication.db.topicFetchClaim.called);
773 assert(!communication.db.verificationClaim.called);
774 assert(!communication.db.subscriptionDeliveryClaim.called);
775 });
776 it('deals with failure', async function () {
777 const topicIds = [ { id: '' }, { id: '' } ];
778 communication.db.topicFetchClaim.resolves(topicIds);
779 communication.db.verificationClaim.throws();
780 const expectedLength = topicIds.length;
781 wanted = 10;
782
783 const result = await communication.workFeed(stubCtx, wanted);
784
785 assert.strictEqual(result.length, expectedLength);
786 });
787 }); // workFeed
788
789 }); // Communication