2 /* eslint-disable capitalized-comments, sonarjs/no-duplicate-string */
6 const assert
= require('assert');
7 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
9 const Communication
= require('../../src/communication');
10 const Config
= require('../../config');
11 const Errors
= require('../../src/errors');
13 const stubDb
= require('../stub-db');
14 const stubLogger
= require('../stub-logger');
16 const noExpectedException
= 'did not get expected exception';
18 describe('Communication', function () {
19 let communication
, options
;
21 beforeEach(function () {
22 options
= new Config('test');
23 communication
= new Communication(stubLogger
, stubDb
, options
);
27 afterEach(function () {
31 it('instantiates', function () {
32 assert(communication
);
35 it('covers config value', function () {
36 options
.dingus
.selfBaseUrl
= undefined;
37 communication
= new Communication(stubLogger
, stubDb
, options
);
40 describe('Axios timing coverage', function () {
45 it('tags request', function () {
46 communication
.axios
.interceptors
.request
.handlers
[0].fulfilled(request
);
47 assert(request
.startTimestampMs
);
49 it('tags response', function () {
50 communication
.axios
.interceptors
.response
.handlers
[0].fulfilled(response
);
51 assert(response
.elapsedTimeMs
);
53 }); // Axios timing coverage
55 describe('userAgentString', function () {
56 it('has default behavior', function () {
57 const result
= Communication
.userAgentString();
59 assert(result
.length
> 30);
61 it('is settable', function () {
62 const result
= Communication
.userAgentString({
65 implementation: 'custom',
68 assert
.strictEqual(result
, 'myhub/9.9.9 (custom)');
70 it('covers branches', function () {
71 const result
= Communication
.userAgentString({
77 assert
.strictEqual(result
, 'myhub/9.9.9');
81 describe('generateChallenge', function () {
82 it('generates a thing', async
function () {
83 const result
= await Communication
.generateChallenge();
85 assert(result
.length
);
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';
95 expected
= 'sha256=ee92148d9cd043cdfb8da7cf5ee1897abaafdb5ab840e85010abd4bf235fa31e';
97 it('signs a thing', function () {
98 const result
= Communication
.signature(message
, secret
, algorithm
);
99 assert
.strictEqual(result
, expected
);
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';
110 it('hashes', function () {
111 const result
= Communication
.contentHash(content
, algorithm
);
112 assert
.strictEqual(result
, expected
);
116 describe('Axios Configurations', function () {
117 let requestUrl
, expectedUrl
, topicUrl
;
118 beforeEach(function () {
119 requestUrl
= 'https://example.com/callback/?id=123';
120 expectedUrl
= 'https://example.com/callback/';
121 topicUrl
= 'http://example.com/blog/';
123 it('_axiosConfig', function () {
124 const method
= 'GET';
125 const contentType
= 'text/plain';
126 const body
= undefined;
128 'extra_parameter': 'foobar',
130 const expectedUrlObj
= new URL('https://example.com/callback/?id=123&extra_parameter=foobar');
133 url: 'https://example.com/callback/',
135 'Content-Type': 'text/plain',
137 params: expectedUrlObj
.searchParams
,
138 responseType: 'text',
140 const result
= Communication
._axiosConfig(method
, requestUrl
, body
, params
, {
141 'Content-Type': contentType
,
143 delete result
.transformResponse
;
144 assert
.deepStrictEqual(result
, expected
);
146 it('_axiosConfig covers defaults', function () {
147 const method
= 'OPTIONS';
148 const expectedUrlObj
= new URL(requestUrl
);
153 params: expectedUrlObj
.searchParams
,
154 responseType: 'text',
156 const result
= Communication
._axiosConfig(method
, requestUrl
);
157 delete result
.transformResponse
;
158 assert
.deepStrictEqual(result
, expected
);
160 it('covers null response transform', function () {
161 const result
= Communication
._axiosConfig('GET', 'https://example.com/', undefined, {}, {});
162 result
.transformResponse
[0]();
164 it('_intentVerifyAxiosConfig', function () {
165 const mode
= 'subscribe';
166 const leaseSeconds
= 864000;
167 const challenge
= 'abcxyz';
168 const expectedUrlObj
= new URL(`${requestUrl}&hub.mode=${mode}&hub.topic=${encodeURIComponent(topicUrl)}&hub.challenge=${challenge}&hub.lease_seconds=${leaseSeconds}`);
173 params: expectedUrlObj
.searchParams
,
174 responseType: 'text',
176 const result
= Communication
._intentVerifyAxiosConfig(requestUrl
, topicUrl
, mode
, leaseSeconds
, challenge
);
177 delete result
.transformResponse
;
178 assert
.deepStrictEqual(result
, expected
);
180 it('_intentDenyAxiosConfig', function () {
181 const reason
= 'something';
182 const expectedUrlObj
= new URL(`${requestUrl}&hub.mode=denied&hub.topic=${encodeURIComponent(topicUrl)}&hub.reason=${reason}`);
187 params: expectedUrlObj
.searchParams
,
188 responseType: 'text',
190 const result
= Communication
._intentDenyAxiosConfig(requestUrl
, topicUrl
, reason
);
191 delete result
.transformResponse
;
192 assert
.deepStrictEqual(result
, expected
);
194 it('_publisherValidationAxiosConfig', function () {
197 publisherValidationUrl: 'https://example.com/publisher/',
199 const verification
= {
200 callback: requestUrl
,
203 const expectedUrlObj
= new URL(topic
.publisherValidationUrl
);
206 url: topic
.publisherValidationUrl
,
208 callback: requestUrl
,
212 'Content-Type': 'application/json',
214 params: expectedUrlObj
.searchParams
,
215 responseType: 'text',
217 const result
= Communication
._publisherValidationAxiosConfig(topic
, verification
);
218 delete result
.transformResponse
;
219 assert
.deepStrictEqual(result
, expected
);
221 it('_topicFetchAxiosConfig', function () {
224 contentType: 'text/plain',
226 const expectedUrlObj
= new URL(topicUrl
);
230 params: expectedUrlObj
.searchParams
,
232 Accept: 'text/plain, */*;q=0.9',
234 responseType: 'text',
236 const result
= Communication
._topicFetchAxiosConfig(topic
);
237 delete result
.transformResponse
;
238 assert
.deepStrictEqual(result
, expected
);
240 }); // Axios Configurations
242 describe('verificationProcess', function () {
243 const challenge
= 'a_challenge';
244 let dbCtx
, callback
, requestId
, topicId
;
245 let topic
, verification
;
246 beforeEach(function () {
248 callback
= 'https://example.com/callback/?id=123';
249 requestId
= '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
250 topicId
= '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
253 url: 'https://example.com/blog/',
260 isPublisherValidated: true,
261 leaseSeconds: 864000,
264 sinon
.stub(Communication
, 'generateChallenge').resolves(challenge
);
265 sinon
.stub(communication
, 'publisherValidate').resolves(true);
266 sinon
.stub(communication
, 'axios').resolves({
270 'content-type': 'text/plain',
275 communication
.db
.verificationGetById
.resolves(verification
);
276 communication
.db
.topicGetById
.resolves(topic
);
277 communication
.db
.verificationRelease
.resolves({});
278 communication
.db
.verificationUpdate
.resolves({});
279 communication
.db
.verificationIncomplete
.resolves({});
280 communication
.db
.verificationComplete
.resolves({});
283 it('errors on non-existent verification', async
function () {
284 communication
.db
.verificationGetById
.restore();
285 sinon
.stub(communication
.db
, 'verificationGetById').resolves();
288 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
289 assert
.fail(noExpectedException
);
291 assert(e
instanceof Errors
.InternalInconsistencyError
);
295 it('errors on non-existent topic', async
function () {
296 communication
.db
.topicGetById
.restore();
297 sinon
.stub(communication
.db
, 'topicGetById').resolves();
300 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
301 assert
.fail(noExpectedException
);
303 assert(e
instanceof Errors
.InternalInconsistencyError
);
307 it('skips inactive topic', async
function () {
308 communication
.db
.topicGetById
.restore();
309 topic
.isActive
= false;
310 sinon
.stub(communication
.db
, 'topicGetById').resolves(topic
);
312 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
314 assert(communication
.db
.verificationRelease
.called
);
315 assert(!communication
.axios
.called
);
318 it('denies subscription to deleted topic', async
function () {
319 communication
.db
.topicGetById
.restore();
320 topic
.isDeleted
= true;
321 sinon
.stub(communication
.db
, 'topicGetById').resolves(topic
);
323 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
325 assert(communication
.db
.verificationUpdate
.called
);
326 assert
.strictEqual(verification
.mode
, 'denied');
329 it('checks publisher validation if needed', async
function() {
330 communication
.db
.verificationGetById
.restore();
331 verification
.isPublisherValidated
= false;
332 sinon
.stub(communication
.db
, 'verificationGetById').resolves(verification
);
333 communication
.db
.topicGetById
.restore();
334 topic
.publisherValidationUrl
= 'https://example.com/publisher/';
335 sinon
.stub(communication
.db
, 'topicGetById').resolves(topic
);
337 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
339 assert(communication
.publisherValidate
.called
);
340 assert(communication
.db
.verificationComplete
.called
);
343 it('handles publisher validation failure', async
function() {
344 communication
.db
.verificationGetById
.restore();
345 verification
.isPublisherValidated
= false;
346 sinon
.stub(communication
.db
, 'verificationGetById').resolves(verification
);
347 communication
.db
.topicGetById
.restore();
348 topic
.publisherValidationUrl
= 'https://example.com/publisher/';
349 sinon
.stub(communication
.db
, 'topicGetById').resolves(topic
);
350 communication
.publisherValidate
.restore();
351 sinon
.stub(communication
, 'publisherValidate').resolves(false);
353 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
355 assert(communication
.publisherValidate
.called
);
356 assert(communication
.db
.verificationIncomplete
.called
);
359 it('handles request error', async
function () {
360 communication
.axios
.restore();
361 sinon
.stub(communication
, 'axios').throws(new Error());
363 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
365 assert(communication
.db
.verificationIncomplete
.called
);
368 it('handles 500 response', async
function () {
369 communication
.axios
.restore();
370 sinon
.stub(communication
, 'axios').resolves({
374 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
376 assert(communication
.db
.verificationIncomplete
.called
);
379 it('handles non-200 response', async
function () {
380 communication
.axios
.restore();
381 sinon
.stub(communication
, 'axios').resolves({
385 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
387 assert(communication
.db
.verificationComplete
.called
);
390 it('subscription succeeds', async
function () {
391 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
393 assert(communication
.db
.subscriptionUpsert
.called
);
394 assert(communication
.db
.verificationComplete
.called
);
397 it('unsubscription succeeds', async
function () {
398 communication
.db
.verificationGetById
.restore();
399 verification
.mode
= 'unsubscribe';
400 sinon
.stub(communication
.db
, 'verificationGetById').resolves(verification
);
402 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
404 assert(communication
.db
.subscriptionDelete
.called
);
405 assert(communication
.db
.verificationComplete
.called
);
408 it('unsubscription from deleted topic deletes topic', async
function () {
409 communication
.db
.verificationGetById
.restore();
410 verification
.mode
= 'unsubscribe';
411 sinon
.stub(communication
.db
, 'verificationGetById').resolves(verification
);
412 communication
.db
.topicGetById
.restore();
413 sinon
.stub(communication
.db
, 'topicGetById').resolves({
418 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
420 assert(communication
.db
.subscriptionDelete
.called
);
421 assert(communication
.db
.verificationComplete
.called
);
422 assert(communication
.db
.topicPendingDelete
.called
);
425 it('unsubscription denial succeeds', async
function () {
426 communication
.db
.verificationGetById
.restore();
427 verification
.mode
= 'unsubscribe';
428 sinon
.stub(communication
.db
, 'verificationGetById').resolves(verification
);
429 communication
.axios
.restore();
430 sinon
.stub(communication
, 'axios').resolves({
434 'content-type': 'text/plain',
436 data: 'not the challenge',
439 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
441 assert(!communication
.db
.subscriptionDelete
.called
);
442 assert(communication
.db
.verificationComplete
.called
);
445 it('does not handle strange mode', async
function() {
446 communication
.db
.verificationGetById
.restore();
447 verification
.mode
= 'flarp';
448 sinon
.stub(communication
.db
, 'verificationGetById').resolves(verification
);
451 await communication
.verificationProcess(dbCtx
, callback
, topicId
, requestId
);
452 assert
.fail(noExpectedException
);
454 assert(e
instanceof Errors
.InternalInconsistencyError
);
457 }); // verificationProcess
459 describe('publisherValidate', function () {
460 let dbCtx
, topic
, verification
;
461 beforeEach(function () {
464 url: 'https://example.com/topic/',
465 publisherValidationUrl: 'https://example.com/pub_valid/',
468 callback: 'https://exmaple.com/callback/?id=123',
469 httpFrom: 'user@example.com',
470 httpRemoteAddr: '127.0.0.0',
473 sinon
.stub(communication
, 'axios').resolves({
477 'content-type': 'application/json',
481 communication
.db
.verificationIncomplete
.resolves();
482 communication
.db
.verificationUpdate
.resolves();
483 communication
.db
.verificationValidated
.resolves();
486 it('succeeds', async
function () {
487 const result
= await communication
.publisherValidate(dbCtx
, topic
, verification
);
489 assert(communication
.db
.verificationValidated
.called
);
490 assert
.strictEqual(result
, true);
493 it('succeeds with rejection', async
function () {
494 communication
.axios
.restore();
495 sinon
.stub(communication
, 'axios').resolves({
497 statusText: 'Bad Request',
499 'content-type': 'application/json',
503 const result
= await communication
.publisherValidate(dbCtx
, topic
, verification
);
505 assert(communication
.db
.verificationValidated
.called
);
506 assert(communication
.db
.verificationUpdate
.called
);
507 assert
.strictEqual(result
, true);
510 it('defers on request server error', async
function () {
511 communication
.axios
.restore();
512 sinon
.stub(communication
, 'axios').resolves({
514 statusText: 'Bad Gateway',
516 'content-type': 'text/plain',
520 const result
= await communication
.publisherValidate(dbCtx
, topic
, verification
);
522 assert
.strictEqual(result
, false);
525 it('handles request error', async
function () {
526 communication
.axios
.restore();
527 sinon
.stub(communication
, 'axios').throws(new Error());
529 const result
= await communication
.publisherValidate(dbCtx
, topic
, verification
);
531 assert
.strictEqual(result
, false);
534 }); // publisherValidate
536 describe('topicFetchProcess', function () {
537 let dbCtx
, topic
, requestId
, topicId
;
539 beforeEach(function () {
542 url: 'https://example.com/topic/',
544 contentHashAlgorithm: 'sha512',
546 requestId
= '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
547 topicId
= '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
549 sinon
.stub(communication
, 'axios').resolves({
553 'content-type': 'text/plain',
554 link: '<https://example.com/hub/>; rel="hub"',
555 'last-modified': 'Thu, 18 Nov 2021 20:34:35 GMT',
556 'etag': '"9c104-1673e-5d1161636d742"',
558 data: 'Jackdaws love my big sphinx of quartz.',
561 communication
.db
.topicGetById
.resolves(topic
);
564 it('requires topic exists', async
function () {
565 communication
.db
.topicGetById
.restore();
566 sinon
.stub(communication
.db
, 'topicGetById').resolves();
569 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
570 assert
.fail(noExpectedException
);
572 assert(e
instanceof Errors
.InternalInconsistencyError
);
576 it ('skips deleted topic', async
function () {
577 communication
.db
.topicGetById
.restore();
578 topic
.isDeleted
= true;
579 sinon
.stub(communication
.db
, 'topicGetById').resolves(topic
);
581 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
583 assert(!communication
.axios
.called
);
586 it('handles request error', async
function () {
587 communication
.axios
.restore();
588 sinon
.stub(communication
, 'axios').throws(new Error());
590 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
592 assert(communication
.db
.topicFetchIncomplete
.called
);
595 it('handles 500 response', async
function () {
596 communication
.axios
.restore();
597 sinon
.stub(communication
, 'axios').resolves({
599 statusText: 'Internal Server Error',
601 'content-type': 'text/plain',
605 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
607 assert(communication
.db
.topicFetchIncomplete
.called
);
610 it('handles bad response', async
function () {
611 communication
.axios
.restore();
612 sinon
.stub(communication
, 'axios').resolves({
614 statusText: 'Not Found',
616 'content-type': 'text/plain',
620 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
622 assert(communication
.db
.topicFetchIncomplete
.called
);
625 it('recognizes unchanged content', async
function () {
626 communication
.db
.topicGetById
.restore();
627 topic
.contentHash
= 'a630999c61738f3e066d79a1b299a295c5d0598c173e0904d04a707d43988e3e81660bfc1b1779377f4ec26f837d1bb31fa2b860c9ad2d37495d83de32647fea';
628 sinon
.stub(communication
.db
, 'topicGetById').resolves(topic
);
630 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
632 assert(communication
.db
.topicFetchComplete
.called
);
633 assert(!communication
.db
.topicSetContent
.called
);
636 it('recognizes 304 response', async
function () {
637 topic
.httpLastModified
= 'Thu, 18 Nov 2021 20:34:35 GMT';
638 topic
.httpEtag
= '"9c104-1673e-5d1161636d742"';
639 communication
.db
.topicGetById
.resolves(topic
);
640 communication
.axios
.resolves({
644 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
646 assert(communication
.db
.topicFetchComplete
.called
);
647 assert(!communication
.db
.topicSetContent
.called
);
650 it('updates content', async
function () {
651 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
653 assert(communication
.db
.topicFetchComplete
.called
);
654 assert(communication
.db
.topicSetContent
.called
);
657 it('updates content with lax link enforcement', async
function () {
658 communication
.axios
.restore();
659 sinon
.stub(communication
, 'axios').resolves({
663 'content-type': 'text/plain',
664 link: '<https://example.com/other/hub/>; rel="hub"',
666 data: 'Jackdaws love my big sphinx of quartz.',
669 communication
.options
.communication
.strictTopicHubLink
= false;
671 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
673 assert(communication
.db
.topicFetchComplete
.called
);
674 assert(communication
.db
.topicSetContent
.called
);
677 it('deletes topic when hub relation unsatisfied', async
function () {
678 communication
.axios
.restore();
679 sinon
.stub(communication
, 'axios').resolves({
683 'content-type': 'text/plain',
684 link: '<https://example.com/other/hub/>; rel="hub"',
686 data: 'Jackdaws love my big sphinx of quartz.',
689 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
691 assert(communication
.db
.topicFetchComplete
.called
);
692 assert(communication
.db
.topicDeleted
.called
);
694 }); // topicFetchProcess
696 describe('subscriptionDeliveryProcess', function () {
697 let dbCtx
, requestId
, topic
, topicId
, subscription
, subscriptionId
;
699 beforeEach(function () {
702 url: 'https://example.com/topic/',
704 contentHashAlgorithm: 'sha512',
705 content: 'Jackdaws love my big sphinx of quartz.',
707 requestId
= '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
708 topicId
= '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
709 subscriptionId
= 'c5e6a3ac-dab8-11eb-b758-0025905f714a';
712 callback: 'https://example.com/callback/123',
713 secret: 'superdupersecret',
714 signatureAlgorithm: 'sha512',
717 sinon
.stub(communication
, 'axios').resolves({
721 'content-type': 'text/plain',
723 data: 'Jackdaws love my big sphinx of quartz.',
726 communication
.db
.topicGetContentById
.resolves(topic
);
727 communication
.db
.subscriptionGetById
.resolves(subscription
);
730 it('requires subscription to exist', async
function () {
731 communication
.db
.subscriptionGetById
.restore();
732 sinon
.stub(communication
.db
, 'subscriptionGetById').resolves();
734 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
735 assert
.fail(noExpectedException
);
737 assert(e
instanceof Errors
.InternalInconsistencyError
);
741 it('requires topic to exist', async
function () {
742 communication
.db
.topicGetContentById
.restore();
743 sinon
.stub(communication
.db
, 'topicGetContentById').resolves();
745 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
746 assert
.fail(noExpectedException
);
748 assert(e
instanceof Errors
.InternalInconsistencyError
);
752 it('succeeds', async
function () {
753 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
755 assert(communication
.db
.subscriptionDeliveryComplete
.called
);
758 it('handles request error', async
function () {
759 communication
.axios
.restore();
760 sinon
.stub(communication
, 'axios').throws();
762 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
764 assert(communication
.db
.subscriptionDeliveryIncomplete
.called
);
767 it('handles 5xx response', async
function () {
768 communication
.axios
.restore();
769 sinon
.stub(communication
, 'axios').resolves({
771 statusText: 'Internal Server Error',
773 'content-type': 'text/plain',
777 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
779 assert(communication
.db
.subscriptionDeliveryIncomplete
.called
);
782 it('handles 4xx response', async
function () {
783 communication
.axios
.restore();
784 sinon
.stub(communication
, 'axios').resolves({
786 statusText: 'Not Found',
788 'content-type': 'text/plain',
792 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
794 assert(communication
.db
.subscriptionDeliveryIncomplete
.called
);
797 it('handles 410 response', async
function () {
798 communication
.axios
.restore();
799 sinon
.stub(communication
, 'axios').resolves({
803 'content-type': 'text/plain',
807 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
809 assert(communication
.db
.subscriptionDeliveryGone
.called
);
812 it('unsubscribes when topic is deleted', async
function () {
813 topic
.isDeleted
= true;
814 communication
.db
.topicGetContentById
.restore();
815 sinon
.stub(communication
.db
, 'topicGetContentById').resolves(topic
);
817 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
819 assert(communication
.db
.verificationInsert
.called
);
820 assert(communication
.db
.subscriptionDeliveryComplete
.called
);
822 }); // subscriptionDeliveryProcess
824 describe('topicFetchClaimAndProcessById', function () {
825 let dbCtx
, topicId
, requestId
;
826 beforeEach(function () {
828 requestId
= '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
829 topicId
= '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
830 sinon
.stub(communication
, 'topicFetchProcess');
832 it('covers claim', async
function () {
833 communication
.db
.topicFetchClaimById
.resolves({
836 await communication
.topicFetchClaimAndProcessById(dbCtx
, topicId
, requestId
);
837 assert(communication
.topicFetchProcess
.called
);
839 it('covers no claim', async
function () {
840 communication
.db
.topicFetchClaimById
.resolves({
843 await communication
.topicFetchClaimAndProcessById(dbCtx
, topicId
, requestId
);
844 assert(!communication
.topicFetchProcess
.called
);
846 }); // topicFetchClaimAndProcessById
848 describe('verificationClaimAndProcessById', function () {
849 let dbCtx
, verificationId
, requestId
;
850 beforeEach(function () {
852 verificationId
= '28488311-6652-42ea-9839-7bbc42b246cb';
853 requestId
= '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
854 sinon
.stub(communication
, 'verificationProcess');
856 it('covers claim', async
function () {
857 communication
.db
.verificationClaimById
.resolves({
860 await communication
.verificationClaimAndProcessById(dbCtx
, verificationId
, requestId
);
861 assert(communication
.verificationProcess
.called
);
863 it('covers no claim', async
function () {
864 communication
.db
.verificationClaimById
.resolves({
867 await communication
.verificationClaimAndProcessById(dbCtx
, verificationId
, requestId
);
868 assert(!communication
.verificationProcess
.called
);
870 }); // verificationClaimAndProcessById
872 describe('workFeed', function () {
874 beforeEach(function () {
876 sinon
.stub(communication
, 'topicFetchProcess');
877 sinon
.stub(communication
, 'verificationProcess');
878 sinon
.stub(communication
, 'subscriptionDeliveryProcess');
880 it('succeeds', async
function () {
881 const topicIds
= [ { id: '' }, { id: '' } ];
882 communication
.db
.topicFetchClaim
.resolves(topicIds
);
883 const verificationIds
= [ { id: '' }, { id: '' } ];
884 communication
.db
.verificationClaim
.resolves(verificationIds
);
885 const subscriptionIds
= [ { id: '' }, { id: '' } ];
886 communication
.db
.subscriptionDeliveryClaim
.resolves(subscriptionIds
);
887 const expectedLength
= [topicIds
, verificationIds
, subscriptionIds
].map((x
) => x
.length
).reduce((a
, b
) => a
+ b
, 0);
890 const result
= await communication
.workFeed(stubCtx
, wanted
);
892 assert
.strictEqual(result
.length
, expectedLength
);
894 it('covers no wanted work', async
function () {
896 const result
= await communication
.workFeed(stubCtx
, wanted
);
897 assert
.strictEqual(result
.length
, 0);
898 assert(!communication
.db
.topicFetchClaim
.called
);
899 assert(!communication
.db
.verificationClaim
.called
);
900 assert(!communication
.db
.subscriptionDeliveryClaim
.called
);
902 it('deals with failure', async
function () {
903 const topicIds
= [ { id: '' }, { id: '' } ];
904 communication
.db
.topicFetchClaim
.resolves(topicIds
);
905 communication
.db
.verificationClaim
.throws();
906 const expectedLength
= topicIds
.length
;
909 const result
= await communication
.workFeed(stubCtx
, wanted
);
911 assert
.strictEqual(result
.length
, expectedLength
);