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"',
556 data: 'Jackdaws love my big sphinx of quartz.',
559 communication
.db
.topicGetById
.resolves(topic
);
562 it('requires topic exists', async
function () {
563 communication
.db
.topicGetById
.restore();
564 sinon
.stub(communication
.db
, 'topicGetById').resolves();
567 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
568 assert
.fail(noExpectedException
);
570 assert(e
instanceof Errors
.InternalInconsistencyError
);
574 it ('skips deleted topic', async
function () {
575 communication
.db
.topicGetById
.restore();
576 topic
.isDeleted
= true;
577 sinon
.stub(communication
.db
, 'topicGetById').resolves(topic
);
579 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
581 assert(!communication
.axios
.called
);
584 it('handles request error', async
function () {
585 communication
.axios
.restore();
586 sinon
.stub(communication
, 'axios').throws(new Error());
588 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
590 assert(communication
.db
.topicFetchIncomplete
.called
);
593 it('handles 500 response', async
function () {
594 communication
.axios
.restore();
595 sinon
.stub(communication
, 'axios').resolves({
597 statusText: 'Internal Server Error',
599 'content-type': 'text/plain',
603 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
605 assert(communication
.db
.topicFetchIncomplete
.called
);
608 it('handles bad response', async
function () {
609 communication
.axios
.restore();
610 sinon
.stub(communication
, 'axios').resolves({
612 statusText: 'Not Found',
614 'content-type': 'text/plain',
618 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
620 assert(communication
.db
.topicFetchIncomplete
.called
);
623 it('recognizes unchanged content', async
function () {
624 communication
.db
.topicGetById
.restore();
625 topic
.contentHash
= 'a630999c61738f3e066d79a1b299a295c5d0598c173e0904d04a707d43988e3e81660bfc1b1779377f4ec26f837d1bb31fa2b860c9ad2d37495d83de32647fea';
626 sinon
.stub(communication
.db
, 'topicGetById').resolves(topic
);
628 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
630 assert(communication
.db
.topicFetchComplete
.called
);
631 assert(!communication
.db
.topicSetContent
.called
);
634 it('updates content', async
function () {
635 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
637 assert(communication
.db
.topicFetchComplete
.called
);
638 assert(communication
.db
.topicSetContent
.called
);
641 it('updates content with lax link enforcement', async
function () {
642 communication
.axios
.restore();
643 sinon
.stub(communication
, 'axios').resolves({
647 'content-type': 'text/plain',
648 link: '<https://example.com/other/hub/>; rel="hub"',
650 data: 'Jackdaws love my big sphinx of quartz.',
653 communication
.options
.communication
.strictTopicHubLink
= false;
655 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
657 assert(communication
.db
.topicFetchComplete
.called
);
658 assert(communication
.db
.topicSetContent
.called
);
661 it('deletes topic when hub relation unsatisfied', async
function () {
662 communication
.axios
.restore();
663 sinon
.stub(communication
, 'axios').resolves({
667 'content-type': 'text/plain',
668 link: '<https://example.com/other/hub/>; rel="hub"',
670 data: 'Jackdaws love my big sphinx of quartz.',
673 await communication
.topicFetchProcess(dbCtx
, topicId
, requestId
);
675 assert(communication
.db
.topicFetchComplete
.called
);
676 assert(communication
.db
.topicDeleted
.called
);
678 }); // topicFetchProcess
680 describe('subscriptionDeliveryProcess', function () {
681 let dbCtx
, requestId
, topic
, topicId
, subscription
, subscriptionId
;
683 beforeEach(function () {
686 url: 'https://example.com/topic/',
688 contentHashAlgorithm: 'sha512',
689 content: 'Jackdaws love my big sphinx of quartz.',
691 requestId
= '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
692 topicId
= '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
693 subscriptionId
= 'c5e6a3ac-dab8-11eb-b758-0025905f714a';
696 callback: 'https://example.com/callback/123',
697 secret: 'superdupersecret',
698 signatureAlgorithm: 'sha512',
701 sinon
.stub(communication
, 'axios').resolves({
705 'content-type': 'text/plain',
707 data: 'Jackdaws love my big sphinx of quartz.',
710 communication
.db
.topicGetContentById
.resolves(topic
);
711 communication
.db
.subscriptionGetById
.resolves(subscription
);
714 it('requires subscription to exist', async
function () {
715 communication
.db
.subscriptionGetById
.restore();
716 sinon
.stub(communication
.db
, 'subscriptionGetById').resolves();
718 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
719 assert
.fail(noExpectedException
);
721 assert(e
instanceof Errors
.InternalInconsistencyError
);
725 it('requires topic to exist', async
function () {
726 communication
.db
.topicGetContentById
.restore();
727 sinon
.stub(communication
.db
, 'topicGetContentById').resolves();
729 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
730 assert
.fail(noExpectedException
);
732 assert(e
instanceof Errors
.InternalInconsistencyError
);
736 it('succeeds', async
function () {
737 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
739 assert(communication
.db
.subscriptionDeliveryComplete
.called
);
742 it('handles request error', async
function () {
743 communication
.axios
.restore();
744 sinon
.stub(communication
, 'axios').throws();
746 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
748 assert(communication
.db
.subscriptionDeliveryIncomplete
.called
);
751 it('handles 5xx response', async
function () {
752 communication
.axios
.restore();
753 sinon
.stub(communication
, 'axios').resolves({
755 statusText: 'Internal Server Error',
757 'content-type': 'text/plain',
761 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
763 assert(communication
.db
.subscriptionDeliveryIncomplete
.called
);
766 it('handles 4xx response', async
function () {
767 communication
.axios
.restore();
768 sinon
.stub(communication
, 'axios').resolves({
770 statusText: 'Not Found',
772 'content-type': 'text/plain',
776 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
778 assert(communication
.db
.subscriptionDeliveryIncomplete
.called
);
781 it('handles 410 response', async
function () {
782 communication
.axios
.restore();
783 sinon
.stub(communication
, 'axios').resolves({
787 'content-type': 'text/plain',
791 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
793 assert(communication
.db
.subscriptionDeliveryGone
.called
);
796 it('unsubscribes when topic is deleted', async
function () {
797 topic
.isDeleted
= true;
798 communication
.db
.topicGetContentById
.restore();
799 sinon
.stub(communication
.db
, 'topicGetContentById').resolves(topic
);
801 await communication
.subscriptionDeliveryProcess(dbCtx
, subscriptionId
, requestId
);
803 assert(communication
.db
.verificationInsert
.called
);
804 assert(communication
.db
.subscriptionDeliveryComplete
.called
);
806 }); // subscriptionDeliveryProcess
808 describe('topicFetchClaimAndProcessById', function () {
809 let dbCtx
, topicId
, requestId
;
810 beforeEach(function () {
812 requestId
= '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
813 topicId
= '234ec6fb-f1cd-4ac3-8ea9-29ed42ae0e21';
814 sinon
.stub(communication
, 'topicFetchProcess');
816 it('covers claim', async
function () {
817 communication
.db
.topicFetchClaimById
.resolves({
820 await communication
.topicFetchClaimAndProcessById(dbCtx
, topicId
, requestId
);
821 assert(communication
.topicFetchProcess
.called
);
823 it('covers no claim', async
function () {
824 communication
.db
.topicFetchClaimById
.resolves({
827 await communication
.topicFetchClaimAndProcessById(dbCtx
, topicId
, requestId
);
828 assert(!communication
.topicFetchProcess
.called
);
830 }); // topicFetchClaimAndProcessById
832 describe('verificationClaimAndProcessById', function () {
833 let dbCtx
, verificationId
, requestId
;
834 beforeEach(function () {
836 verificationId
= '28488311-6652-42ea-9839-7bbc42b246cb';
837 requestId
= '7d37ea20-4ef7-417e-a08d-c0ba71269ab1';
838 sinon
.stub(communication
, 'verificationProcess');
840 it('covers claim', async
function () {
841 communication
.db
.verificationClaimById
.resolves({
844 await communication
.verificationClaimAndProcessById(dbCtx
, verificationId
, requestId
);
845 assert(communication
.verificationProcess
.called
);
847 it('covers no claim', async
function () {
848 communication
.db
.verificationClaimById
.resolves({
851 await communication
.verificationClaimAndProcessById(dbCtx
, verificationId
, requestId
);
852 assert(!communication
.verificationProcess
.called
);
854 }); // verificationClaimAndProcessById
856 describe('workFeed', function () {
858 beforeEach(function () {
860 sinon
.stub(communication
, 'topicFetchProcess');
861 sinon
.stub(communication
, 'verificationProcess');
862 sinon
.stub(communication
, 'subscriptionDeliveryProcess');
864 it('succeeds', async
function () {
865 const topicIds
= [ { id: '' }, { id: '' } ];
866 communication
.db
.topicFetchClaim
.resolves(topicIds
);
867 const verificationIds
= [ { id: '' }, { id: '' } ];
868 communication
.db
.verificationClaim
.resolves(verificationIds
);
869 const subscriptionIds
= [ { id: '' }, { id: '' } ];
870 communication
.db
.subscriptionDeliveryClaim
.resolves(subscriptionIds
);
871 const expectedLength
= [topicIds
, verificationIds
, subscriptionIds
].map((x
) => x
.length
).reduce((a
, b
) => a
+ b
, 0);
874 const result
= await communication
.workFeed(stubCtx
, wanted
);
876 assert
.strictEqual(result
.length
, expectedLength
);
878 it('covers no wanted work', async
function () {
880 const result
= await communication
.workFeed(stubCtx
, wanted
);
881 assert
.strictEqual(result
.length
, 0);
882 assert(!communication
.db
.topicFetchClaim
.called
);
883 assert(!communication
.db
.verificationClaim
.called
);
884 assert(!communication
.db
.subscriptionDeliveryClaim
.called
);
886 it('deals with failure', async
function () {
887 const topicIds
= [ { id: '' }, { id: '' } ];
888 communication
.db
.topicFetchClaim
.resolves(topicIds
);
889 communication
.db
.verificationClaim
.throws();
890 const expectedLength
= topicIds
.length
;
893 const result
= await communication
.workFeed(stubCtx
, wanted
);
895 assert
.strictEqual(result
.length
, expectedLength
);