2 /* eslint-disable capitalized-comments, sonarjs/no-duplicate-string, sonarjs/no-identical-functions */
6 const assert
= require('assert');
7 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
9 const Manager
= require('../../src/manager');
10 const Config
= require('../../config');
11 const common
= require('../../src/common');
12 const Errors
= require('../../src/errors');
13 const DBErrors
= require('../../src/db/errors');
15 const stubDb
= require('../stub-db');
16 const stubLogger
= require('../stub-logger');
17 const testData
= require('../test-data/manager');
19 const noExpectedException
= 'did not get expected exception';
21 describe('Manager', function () {
25 beforeEach(function () {
26 options
= new Config('test');
28 getHeader : sinon
.stub(),
32 setHeader: sinon
.stub(),
37 manager
= new Manager(stubLogger
, stubDb
, options
);
38 sinon
.stub(manager
.communication
, 'verificationProcess');
39 sinon
.stub(manager
.communication
, 'topicFetchProcess');
40 sinon
.stub(manager
.communication
, 'topicFetchClaimAndProcessById');
44 afterEach(function () {
48 it('instantiates', function () {
52 describe('getRoot', function () {
53 beforeEach(function () {
54 sinon
.stub(common
, 'isClientCached');
57 it('normal response', async
function () {
58 common
.isClientCached
.returns(false);
59 await manager
.getRoot(req
, res
, ctx
);
60 assert(res
.end
.called
);
62 it('repeat response', async
function () {
63 manager
.startTime
= (new Date()).toGMTString();
64 common
.isClientCached
.returns(true);
65 await manager
.getRoot(req
, res
, ctx
);
66 assert(res
.end
.called
);
68 it('cached response', async
function () {
69 common
.isClientCached
.returns(true);
70 await manager
.getRoot(req
, res
, ctx
);
71 assert(res
.end
.called
);
72 assert
.strictEqual(res
.statusCode
, 304);
76 describe('getHealthcheck', function () {
77 it('normal response', async
function () {
78 await manager
.getHealthcheck(res
, ctx
);
79 assert(res
.end
.called
);
83 describe('getInfo', function () {
84 it('requires query param', async
function() {
87 await manager
.getInfo(res
, ctx
);
88 assert
.fail(noExpectedException
);
90 assert
.strictEqual(e
.statusCode
, 400);
93 it('requires parsable query param', async
function() {
94 ctx
.queryParams
= { topic: 'not a url' };
96 await manager
.getInfo(res
, ctx
);
97 assert
.fail(noExpectedException
);
99 assert
.strictEqual(e
.statusCode
, 400);
102 it('does not find unhandled topic', async
function() {
103 ctx
.queryParams
= { topic: 'https://example.com/blog/' };
105 await manager
.getInfo(res
, ctx
);
106 assert
.fail(noExpectedException
);
108 assert
.strictEqual(e
.statusCode
, 404);
111 it('returns a count', async
function() {
112 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
114 topic: 'https://example.com/blog/',
116 await manager
.getInfo(res
, ctx
);
117 assert(res
.end
.called
);
119 it('returns a count as json', async
function() {
120 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
121 ctx
.responseType
= 'application/json';
123 topic: 'https://example.com/blog/',
125 await manager
.getInfo(res
, ctx
);
126 assert(res
.end
.called
);
128 it('returns a count as json as override format', async
function() {
129 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
130 ctx
.responseType
= 'text/html';
132 topic: 'https://example.com/blog/',
135 await manager
.getInfo(res
, ctx
);
136 assert(res
.end
.called
);
137 assert(res
.setHeader
.called
);
139 it('returns an svg badge as override format', async
function() {
140 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
141 ctx
.responseType
= 'text/html';
143 topic: 'https://example.com/blog/',
146 await manager
.getInfo(res
, ctx
);
147 assert(res
.end
.called
);
148 assert(res
.setHeader
.called
);
152 describe('getAdminOverview', function () {
153 beforeEach(function () {
154 manager
.db
.topicGetAll
.resolves([
156 id: '56c557ce-e667-11eb-bd80-0025905f714a',
158 url: 'https://example.com/',
159 leaseSecondsPreferred: 123,
161 leaseSecondsMax: 123456789,
162 publisherValidationUrl: null,
163 contentHashAlgorithm: 'hashy',
166 lastPublish: new Date(-Infinity
),
167 contentFetchNextAttempt: undefined,
168 contentFetchAttemptsSinceSuccess: 3,
169 contentUpdated: new Date(0),
176 it('covers', async
function () {
177 await manager
.getAdminOverview(res
, ctx
);
178 assert(res
.end
.called
);
180 it('covers non-matching profile', async
function () {
182 authenticatedProfile: 'https://different.example.com/profile',
184 await manager
.getAdminOverview(res
, ctx
);
185 assert
.deepStrictEqual(ctx
.topics
, []);
186 assert(res
.end
.called
);
188 }); // getAdminOverview
190 describe('getTopicDetails', function () {
191 beforeEach(function () {
192 ctx
.params
.topicId
= '56c557ce-e667-11eb-bd80-0025905f714a';
193 manager
.db
.topicGetById
.resolves({
194 id: '56c557ce-e667-11eb-bd80-0025905f714a',
196 url: 'https://example.com/',
197 leaseSecondsPreferred: 123,
199 leaseSecondsMax: 123456789,
200 publisherValidationUrl: null,
201 contentHashAlgorithm: 'hashy',
204 lastPublish: new Date(-Infinity
),
205 contentFetchNextAttempt: undefined,
206 contentFetchAttemptsSinceSuccess: 3,
207 contentUpdated: new Date(0),
212 manager
.db
.subscriptionsByTopicId
.resolves([{
215 topicId: '56c557ce-e667-11eb-bd80-0025905f714a',
217 verified: new Date(),
220 signatureAlgorithm: 'hmacy',
223 contentDelivered: new Date(),
224 deliveryAttemptsSinceSuccess: 0,
225 deliveryNextAttempt: new Date(-Infinity
),
228 it('covers', async
function() {
229 await manager
.getTopicDetails(res
, ctx
);
230 assert(res
.end
.called
);
232 it('covers non-matching profile', async
function () {
234 authenticatedProfile: 'https://different.example.com/profile',
236 await manager
.getTopicDetails(res
, ctx
);
237 assert
.strictEqual(ctx
.topic
, null);
238 assert(res
.end
.called
);
240 it('covers matching profile', async
function () {
242 authenticatedProfile: 'https://example.com/profile',
244 await manager
.getTopicDetails(res
, ctx
);
246 assert(res
.end
.called
);
248 }); // getTopicDetails
250 describe('postRoot', function () {
251 let origProcessImmediately
;
252 beforeEach(function () {
253 origProcessImmediately
= manager
.options
.manager
.processImmediately
;
256 this.afterEach(function () {
257 manager
.options
.manager
.processImmediately
= origProcessImmediately
;
259 it('requires parameters', async
function () {
261 await manager
.postRoot(req
, res
, ctx
);
262 assert
.fail(noExpectedException
);
264 assert
.strictEqual(e
.message
, 'Bad Request');
267 it('accepts valid subscription', async
function () {
268 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
269 manager
.db
.topicGetByUrl
.resolves({
272 manager
.db
.verificationInsert
.resolves({
274 lastInsertRowid: undefined,
277 await manager
.postRoot(req
, res
, ctx
);
278 assert(manager
.db
.verificationInsert
.called
);
279 assert(res
.end
.called
);
281 it('accepts valid subscription without claiming work', async
function () {
282 manager
.options
.manager
.processImmediately
= false;
283 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
284 manager
.db
.topicGetByUrl
.resolves({
287 manager
.db
.verificationInsert
.resolves({
289 lastInsertRowid: undefined,
292 await manager
.postRoot(req
, res
, ctx
);
293 assert(manager
.db
.verificationInsert
.called
);
294 assert(!manager
.communication
.verificationProcess
.called
);
295 assert(res
.end
.called
);
297 it('accepts valid subscription, covers processVerification failure', async
function () {
298 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
299 manager
.communication
.verificationProcess
.rejects('failed');
300 manager
.db
.topicGetByUrl
.resolves({
303 manager
.db
.verificationInsert
.resolves({
305 lastInsertRowid: undefined,
308 await manager
.postRoot(req
, res
, ctx
);
309 assert(manager
.db
.verificationInsert
.called
);
310 assert(res
.end
.called
);
311 assert(manager
.logger
.error
.called
);
313 it('covers db.verificationInsert failure', async
function () {
314 const expectedException
= new Error('failure');
315 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
316 manager
.db
.topicGetByUrl
.resolves({
319 manager
.db
.verificationInsert
.rejects(expectedException
);
320 assert
.rejects(async () => {
321 await manager
.postRoot(req
, res
, ctx
);
322 }, expectedException
);
324 it('accepts valid unsubscription', async
function () {
325 ctx
= Object
.assign({}, testData
.validUnsubscribeCtx
);
326 manager
.db
.topicGetByUrl
.resolves({
329 manager
.db
.subscriptionGet
.resolves({
332 manager
.db
.verificationInsert
.resolves({
334 lastInsertRowid: undefined,
337 await manager
.postRoot(req
, res
, ctx
);
338 assert(res
.end
.called
);
340 it('accepts valid publish', async
function () {
341 ctx
= Object
.assign({}, testData
.validPublishCtx
);
342 manager
.db
.topicGetByUrl
.resolves({
345 manager
.db
.topicFetchRequested
.resolves({
347 lastInsertRowid: undefined,
350 await manager
.postRoot(req
, res
, ctx
);
351 assert(res
.end
.called
);
355 describe('_getRootData', function () {
356 it('extracts expected values', function () {
357 req
.getHeader
.returns('user@example.com');
358 ctx
= Object
.assign({}, testData
.validSubscribeCtx
)
359 const result
= Manager
._getRootData(req
, ctx
);
360 assert
.deepStrictEqual(result
, testData
.validRootData
);
364 describe('_validateRootData', function () {
365 // This only wraps the other _check functions, not bothering with coverage.
366 }); // _validateRootData
368 describe('_checkTopic', function () {
369 let dbCtx
, data
, warn
, err
;
372 leaseSecondsPreferred: 86400 * 10,
373 leaseSecondsMax: 86400 * 20,
374 leaseSecondsMin: 86400,
376 beforeEach(function () {
382 it('succeeds', async
function () {
384 topic: 'http://example.com/blog',
386 manager
.db
.topicGetByUrl
.resolves(topic
);
387 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
388 assert
.strictEqual(warn
.length
, 0, warn
);
389 assert
.strictEqual(err
.length
, 0, err
);
390 assert
.strictEqual(data
.topicId
, 111);
391 assert
.strictEqual(data
.leaseSeconds
, 864000);
393 it('errors on unknown topic', async
function () {
394 manager
.db
.topicGetByUrl
.resolves();
395 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
396 assert
.strictEqual(warn
.length
, 0, warn
);
397 assert
.strictEqual(err
.length
, 1, err
);
399 it('warns on lease under min range', async
function () {
401 topic: 'http://example.com/blog',
404 manager
.db
.topicGetByUrl
.resolves(topic
);
405 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
406 assert
.strictEqual(warn
.length
, 1, warn
);
407 assert
.strictEqual(err
.length
, 0, err
);
408 assert
.strictEqual(data
.topicId
, 111);
409 assert
.strictEqual(data
.leaseSeconds
, 86400);
411 it('warns on lease over max range', async
function () {
413 topic: 'http://example.com/blog',
414 leaseSeconds: 86400 * 100,
416 manager
.db
.topicGetByUrl
.resolves(topic
);
417 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
418 assert
.strictEqual(warn
.length
, 1, warn
);
419 assert
.strictEqual(err
.length
, 0, err
);
420 assert
.strictEqual(data
.topicId
, 111);
421 assert
.strictEqual(data
.leaseSeconds
, 86400 * 20);
423 it('sets publisher validation state when available', async
function () {
425 topic: 'http://example.com/blog',
427 manager
.db
.topicGetByUrl
.resolves(Object
.assign({}, topic
, {
428 publisherValidationUrl: 'http://example.com/validate',
430 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
431 assert
.strictEqual(warn
.length
, 0, warn
);
432 assert
.strictEqual(err
.length
, 0, err
);
433 assert
.strictEqual(data
.topicId
, 111);
434 assert
.strictEqual(data
.leaseSeconds
, 864000);
435 assert
.strictEqual(data
.isPublisherValidated
, false);
437 it('accepts new public subscribe topic', async
function () {
438 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves(topic
);
440 topic: 'http://example.com/blog',
442 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
443 assert
.strictEqual(warn
.length
, 0, 'unexpected warnings length');
444 assert
.strictEqual(err
.length
, 0, 'unexpected errors length');
445 assert
.strictEqual(data
.topicId
, 111, 'unexpected topic id');
447 it('does not accept new public subscribe for invalid topic', async
function () {
448 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves(topic
);
450 topic: 'not a topic',
452 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
453 assert
.strictEqual(warn
.length
, 0, 'unexpected warnings length');
454 assert
.strictEqual(err
.length
, 1, 'unexpected errors length');
458 describe('_checkCallbackAndSecrets', function () {
460 let origStrictSecrets
;
462 origStrictSecrets
= manager
.options
.manager
.strictSecrets
;
464 beforeEach(function () {
469 afterEach(function () {
470 manager
.options
.manager
.strictSecrets
= origStrictSecrets
;
472 it('succeeds', function () {
474 callback: 'https://example.com/callback',
478 manager
._checkCallbackAndSecrets(data
, warn
, err
);
479 assert
.strictEqual(warn
.length
, 0, warn
);
480 assert
.strictEqual(err
.length
, 0, err
);
482 it('errors with invalid callback', function () {
484 callback: 'not a url',
488 manager
._checkCallbackAndSecrets(data
, warn
, err
);
489 assert
.strictEqual(warn
.length
, 0, warn
);
490 assert
.strictEqual(err
.length
, 1, err
);
492 it('errors when secret too large', function () {
494 callback: 'https://example.com/callback',
495 secret: 'x'.repeat(256),
498 manager
._checkCallbackAndSecrets(data
, warn
, err
);
499 assert
.strictEqual(warn
.length
, 0, warn
);
500 assert
.strictEqual(err
.length
, 1, err
);
502 it('warns when callback is insecure', function () {
504 callback: 'http://example.com/callback',
507 manager
._checkCallbackAndSecrets(data
, warn
, err
);
508 assert
.strictEqual(warn
.length
, 1, warn
);
509 assert
.strictEqual(err
.length
, 0, err
);
511 it('warns when hub is insecure with secret', function () {
513 callback: 'https://example.com/callback',
517 manager
._checkCallbackAndSecrets(data
, warn
, err
);
518 assert
.strictEqual(warn
.length
, 1, warn
);
519 assert
.strictEqual(err
.length
, 0, err
);
521 it('errors when callback is insecure with secret and strict', function () {
522 manager
.options
.manager
.strictSecrets
= true;
524 callback: 'http://example.com/callback',
528 manager
._checkCallbackAndSecrets(data
, warn
, err
);
529 assert
.strictEqual(warn
.length
, 1, warn
);
530 assert
.strictEqual(err
.length
, 1, err
);
532 }); // _checkCallbackAndSecrets
534 describe('_checkMode', function () {
535 let dbCtx
, data
, warn
, err
;
536 beforeEach(function () {
542 it('subscribe succeeds', async
function () {
546 await manager
._checkMode(dbCtx
, data
, warn
, err
);
547 assert
.strictEqual(warn
.length
, 0);
548 assert
.strictEqual(err
.length
, 0);
550 it('unsubscribe succeeds', async
function () {
553 callback: 'http://example.com',
556 manager
.db
.subscriptionGet
.resolves({
557 expires: (Date
.now() / 1000) + 60,
559 await manager
._checkMode(dbCtx
, data
, warn
, err
);
560 assert
.strictEqual(warn
.length
, 0, warn
);
561 assert
.strictEqual(err
.length
, 0, err
);
563 it('unsubscribe requires valid data', async
function () {
566 callback: 'http://example.com',
569 manager
.db
.subscriptionGet
.resolves({
570 expires: (Date
.now() / 1000) - 60,
572 await manager
._checkMode(dbCtx
, data
, warn
, err
);
573 assert
.strictEqual(warn
.length
, 0, warn
);
574 assert
.strictEqual(err
.length
, 1, err
);
576 it('unsubscribe ignores expired subscription', async
function () {
579 callback: 'http://example.com',
582 manager
.db
.subscriptionGet
.resolves({
583 expires: (Date
.now() / 1000) - 60,
585 await manager
._checkMode(dbCtx
, data
, warn
, err
);
586 assert
.strictEqual(warn
.length
, 0, warn
);
587 assert
.strictEqual(err
.length
, 1, err
);
591 describe('_publishTopics', function () {
592 let dbCtx
, data
, requestId
;
593 beforeEach(function () {
598 it('succeeds', async
function () {
599 manager
.db
.topicGetByUrl
.resolves({
602 Object
.assign(data
, testData
.validPublishRootData
);
603 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
604 assert
.strictEqual(topicResults
.length
, 1);
605 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
606 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
607 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
609 it('fails bad url', async
function () {
610 Object
.assign(data
, testData
.validPublishRootData
, { topic: 'not_a_url' });
611 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
612 assert
.strictEqual(topicResults
.length
, 1);
613 assert
.strictEqual(topicResults
[0].err
.length
, 1, 'unexpected errors length');
614 assert
.strictEqual(topicResults
[0].warn
.length
, 0);
616 it('accepts new public publish topic', async
function () {
617 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
620 Object
.assign(data
, testData
.validPublishRootData
);
621 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
622 assert
.strictEqual(topicResults
.length
, 1);
623 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
624 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
625 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
627 it('does not publish deleted topic', async
function () {
628 manager
.db
.topicGetByUrl
.resolves({
632 Object
.assign(data
, testData
.validPublishRootData
);
633 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
634 assert
.strictEqual(topicResults
.length
, 1);
635 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
636 assert
.strictEqual(topicResults
[0].err
.length
, 1, 'unexpected errors length');
637 assert
.strictEqual(topicResults
[0].topicId
, undefined, 'unexpected topic id');
639 it('no topics', async
function() {
640 Object
.assign(data
, testData
.validPublishRootData
);
642 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
643 assert
.strictEqual(topicResults
.length
, 0);
645 it('multiple valid topics', async
function () {
646 manager
.db
.topicGetByUrl
.resolves({
649 Object
.assign(data
, testData
.validPublishRootData
);
650 data
.url
= ['https://example.com/first', 'https://example.com/second'];
651 data
.topic
= ['https://example.com/third'];
652 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
653 assert
.strictEqual(topicResults
.length
, 3);
654 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
655 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
656 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
657 assert
.strictEqual(topicResults
[1].warn
.length
, 0, 'unexpected warnings length');
658 assert
.strictEqual(topicResults
[1].err
.length
, 0, 'unexpected errors length');
659 assert
.strictEqual(topicResults
[1].topicId
, 222, 'unexpected topic id');
660 assert
.strictEqual(topicResults
[2].warn
.length
, 0, 'unexpected warnings length');
661 assert
.strictEqual(topicResults
[2].err
.length
, 0, 'unexpected errors length');
662 assert
.strictEqual(topicResults
[2].topicId
, 222, 'unexpected topic id');
664 it('mix of valid and invalid topics', async
function () {
665 manager
.db
.topicGetByUrl
.onCall(1).resolves().resolves({
668 Object
.assign(data
, testData
.validPublishRootData
);
669 data
.url
= ['https://example.com/first', 'not a url'];
670 data
.topic
= ['https://example.com/third'];
671 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
672 assert
.strictEqual(topicResults
.length
, 3);
673 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
674 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
675 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
676 assert
.strictEqual(topicResults
[1].warn
.length
, 0, 'unexpected warnings length');
677 assert
.strictEqual(topicResults
[1].err
.length
, 1, 'unexpected errors length');
678 assert
.strictEqual(topicResults
[1].topicId
, undefined, 'unexpected topic id');
679 assert
.strictEqual(topicResults
[2].warn
.length
, 0, 'unexpected warnings length');
680 assert
.strictEqual(topicResults
[2].err
.length
, 0, 'unexpected errors length');
681 assert
.strictEqual(topicResults
[2].topicId
, 222, 'unexpected topic id');
683 }); // _publishTopics
685 describe('_publishRequest', function () {
686 let dbCtx
, data
, res
, ctx
;
687 beforeEach(function () {
695 it('requires a topic', async
function () {
697 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
698 assert
.fail(noExpectedException
);
700 assert(e
instanceof Errors
.ResponseError
);
703 it('processes one topic', async
function() {
704 manager
.db
.topicGetByUrl
.resolves({
707 Object
.assign(data
, testData
.validPublishRootData
);
708 manager
.db
.topicFetchRequested
.resolves();
709 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
710 assert(manager
.db
.topicFetchRequested
.called
);
711 assert
.strictEqual(res
.statusCode
, 202);
712 assert(res
.end
.called
);
714 it('processes mix of valid and invalid topics', async
function () {
715 ctx
.responseType
= 'application/json';
716 manager
.db
.topicGetByUrl
.onCall(1).resolves().resolves({
719 Object
.assign(data
, testData
.validPublishRootData
);
720 data
.url
= ['https://example.com/first', 'not a url'];
721 data
.topic
= ['https://example.com/third'];
722 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
723 assert
.strictEqual(res
.statusCode
, 207);
724 assert(res
.end
.called
);
726 it('covers topicFetchRequest failure', async
function () {
727 manager
.db
.topicGetByUrl
.resolves({
730 Object
.assign(data
, testData
.validPublishRootData
);
731 const expected
= new Error('boo');
732 manager
.db
.topicFetchRequested
.rejects(expected
);
734 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
735 assert
.fail(noExpectedException
);
737 assert
.deepStrictEqual(e
, expected
);
740 it('covers immediate processing error', async
function() {
741 manager
.options
.manager
.processImmediately
= true;
742 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
745 manager
.communication
.topicFetchClaimAndProcessById
.rejects();
746 Object
.assign(data
, testData
.validPublishRootData
);
747 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
748 assert(manager
.db
.topicFetchRequested
.called
);
749 assert
.strictEqual(res
.statusCode
, 202);
750 assert(res
.end
.called
);
751 assert(manager
.communication
.topicFetchClaimAndProcessById
.called
)
753 it('covers no immediate processing', async
function() {
754 manager
.options
.manager
.processImmediately
= false;
755 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
758 Object
.assign(data
, testData
.validPublishRootData
);
759 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
760 assert(manager
.db
.topicFetchRequested
.called
);
761 assert
.strictEqual(res
.statusCode
, 202);
762 assert(res
.end
.called
);
763 assert(!manager
.communication
.topicFetchClaimAndProcessById
.called
)
765 }); // _publishRequest
767 describe('multiPublishContent', function () {
769 beforeEach(function () {
771 url: 'https://example.com/first',
776 statusMessage: 'Accepted',
781 err: [ 'invalid topic url (failed to parse url)' ],
784 statusMessage: 'Bad Request',
787 it('covers json response', function () {
788 ctx
.responseType
= 'application/json';
789 const expected
= '[{"href":"https://example.com/first","status":202,"statusMessage":"Accepted","errors":[],"warnings":[]},{"href":"not a url","status":400,"statusMessage":"Bad Request","errors":["invalid topic url (failed to parse url)"],"warnings":[]}]';
790 const result
= Manager
.multiPublishContent(ctx
, publishTopics
);
791 assert
.deepStrictEqual(result
, expected
);
793 it('covers text response', function () {
794 ctx
.responseType
= 'text/plain';
795 const expected
= `https://example.com/first [202 Accepted]
797 not a url [400 Bad Request]
798 \terror: invalid topic url (failed to parse url)`;
799 const result
= Manager
.multiPublishContent(ctx
, publishTopics
);
800 assert
.deepStrictEqual(result
, expected
);
802 }); // multiPublishContent
804 describe('processTasks', function () {
805 it('covers', async
function () {
806 sinon
.stub(manager
.communication
.worker
, 'process').resolves();
807 await manager
.processTasks(res
, ctx
);
808 assert(manager
.communication
.worker
.process
.called
);
809 assert(res
.end
.called
);
811 it('covers error', async
function () {
812 sinon
.stub(manager
.communication
.worker
, 'process').rejects();
813 await manager
.processTasks(res
, ctx
);
814 assert(manager
.communication
.worker
.process
.called
);
815 assert(res
.end
.called
);
819 describe('updateTopic', function () {
820 it('fails if no topic exists', async
function () {
822 await manager
.updateTopic(res
, ctx
);
823 assert
.fail(noExpectedException
);
825 assert(e
instanceof Errors
.ResponseError
);
828 it('deletes', async
function () {
829 ctx
.method
= 'DELETE';
830 manager
.db
.topicGetById
.resolves({});
831 await manager
.updateTopic(res
, ctx
);
832 assert(manager
.db
.topicDeleted
.called
);
834 it('does not patch without data', async
function () {
835 manager
.db
.topicGetById
.resolves({});
836 await manager
.updateTopic(res
, ctx
);
837 assert(!manager
.db
.topicUpdate
.called
);
838 assert
.strictEqual(res
.statusCode
, 204);
840 it('does not patch with same data', async
function () {
841 manager
.db
.topicGetById
.resolves({
842 leaseSecondsPreferred: '86400',
845 leaseSecondsPreferred: '86400',
847 await manager
.updateTopic(res
, ctx
);
848 assert(!manager
.db
.topicUpdate
.called
);
849 assert
.strictEqual(res
.statusCode
, 204);
851 it('patches', async
function () {
853 leaseSecondsPreferred: '86400',
855 manager
.db
.topicGetById
.resolves({});
856 await manager
.updateTopic(res
, ctx
);
857 assert(manager
.db
.topicUpdate
.called
);
859 it('handles validation error', async
function () {
861 leaseSecondsPreferred: 'blorp',
863 manager
.db
.topicGetById
.resolves({});
864 manager
.db
.topicUpdate
.rejects(new DBErrors
.DataValidation('something'));
866 await manager
.updateTopic(res
, ctx
);
867 assert
.fail(noExpectedException
);
869 assert(e
instanceof Errors
.ResponseError
);
870 assert
.strictEqual(e
.statusCode
, 400);
873 it('handles generic error', async
function () {
874 const expected
= new Error('blah');
876 leaseSecondsPreferred: '123',
878 manager
.db
.topicGetById
.resolves({});
879 manager
.db
.topicUpdate
.rejects(expected
);
881 await manager
.updateTopic(res
, ctx
);
882 assert
.fail(noExpectedException
);
884 assert
.deepStrictEqual(e
, expected
);
889 describe('updateSubscription', function () {
890 it('fails if no subscription exists', async
function () {
892 await manager
.updateSubscription(res
, ctx
);
893 assert
.fail(noExpectedException
);
895 assert(e
instanceof Errors
.ResponseError
);
898 it('deletes', async
function () {
899 ctx
.method
= 'DELETE';
900 manager
.db
.subscriptionGetById
.resolves({});
901 await manager
.updateSubscription(res
, ctx
);
902 assert(manager
.db
.verificationInsert
.called
);
904 it('does not patch without data', async
function () {
905 manager
.db
.subscriptionGetById
.resolves({});
906 await manager
.updateSubscription(res
, ctx
);
907 assert(!manager
.db
.subscriptionUpdate
.called
);
908 assert
.strictEqual(res
.statusCode
, 204);
910 it('does not patch with same data', async
function () {
911 manager
.db
.subscriptionGetById
.resolves({
912 signatureAlgorithm: 'sha256',
915 signatureAlgorithm: 'sha256',
917 await manager
.updateSubscription(res
, ctx
);
918 assert(!manager
.db
.subscriptionUpdate
.called
);
919 assert
.strictEqual(res
.statusCode
, 204);
921 it('patches', async
function () {
923 signatureAlgorithm: 'sha256',
925 manager
.db
.subscriptionGetById
.resolves({});
926 await manager
.updateSubscription(res
, ctx
);
927 assert(manager
.db
.subscriptionUpdate
.called
);
929 it('handles validation error', async
function () {
931 signatureAlgorithm: 123,
933 manager
.db
.subscriptionGetById
.resolves({});
934 manager
.db
.subscriptionUpdate
.rejects(new DBErrors
.DataValidation('something'));
936 await manager
.updateSubscription(res
, ctx
);
937 assert
.fail(noExpectedException
);
939 assert(e
instanceof Errors
.ResponseError
);
940 assert
.strictEqual(e
.statusCode
, 400);
943 it('handles generic error', async
function () {
944 const expected
= new Error('blah');
946 signatureAlgorithm: 'blorp',
948 manager
.db
.subscriptionGetById
.resolves({});
949 manager
.db
.subscriptionUpdate
.rejects(expected
);
951 await manager
.updateSubscription(res
, ctx
);
952 assert
.fail(noExpectedException
);
954 assert
.deepStrictEqual(e
, expected
);
957 }); // updateSubscription