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(),
38 manager
= new Manager(stubLogger
, stubDb
, options
);
39 sinon
.stub(manager
.communication
, 'verificationProcess');
40 sinon
.stub(manager
.communication
, 'topicFetchProcess');
41 sinon
.stub(manager
.communication
, 'topicFetchClaimAndProcessById');
45 afterEach(function () {
49 it('instantiates', function () {
53 describe('getRoot', function () {
54 beforeEach(function () {
55 sinon
.stub(common
, 'isClientCached');
58 it('normal response', async
function () {
59 common
.isClientCached
.returns(false);
60 await manager
.getRoot(req
, res
, ctx
);
61 assert(res
.end
.called
);
65 describe('getHealthcheck', function () {
66 it('normal response', async
function () {
67 await manager
.getHealthcheck(res
, ctx
);
68 assert(res
.end
.called
);
72 describe('getInfo', function () {
73 it('requires query param', async
function() {
76 await manager
.getInfo(res
, ctx
);
77 assert
.fail(noExpectedException
);
79 assert
.strictEqual(e
.statusCode
, 400);
82 it('requires parsable query param', async
function() {
83 ctx
.queryParams
= { topic: 'not a url' };
85 await manager
.getInfo(res
, ctx
);
86 assert
.fail(noExpectedException
);
88 assert
.strictEqual(e
.statusCode
, 400);
91 it('does not find unhandled topic', async
function() {
92 ctx
.queryParams
= { topic: 'https://example.com/blog/' };
94 await manager
.getInfo(res
, ctx
);
95 assert
.fail(noExpectedException
);
97 assert
.strictEqual(e
.statusCode
, 404);
100 it('returns a count', async
function() {
101 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
103 topic: 'https://example.com/blog/',
105 await manager
.getInfo(res
, ctx
);
106 assert(res
.end
.called
);
108 it('returns a count as json', async
function() {
109 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
110 ctx
.responseType
= 'application/json';
112 topic: 'https://example.com/blog/',
114 await manager
.getInfo(res
, ctx
);
115 assert(res
.end
.called
);
117 it('returns a count as json as override format', async
function() {
118 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
119 ctx
.responseType
= 'text/html';
121 topic: 'https://example.com/blog/',
124 await manager
.getInfo(res
, ctx
);
125 assert(res
.end
.called
);
126 assert(res
.setHeader
.called
);
128 it('returns an svg badge 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
);
141 describe('_historyBarCaption', function () {
142 it('covers today, none', function () {
143 const result
= Manager
._historyBarCaption(0, 0);
144 assert
.strictEqual(result
, 'today, no updates');
146 it('covers yesterday, singular', function () {
147 const result
= Manager
._historyBarCaption(1, 1);
148 assert
.strictEqual(result
, 'yesterday, 1 update');
150 it('covers older, plural', function () {
151 const result
= Manager
._historyBarCaption(7, 3);
152 assert
.strictEqual(result
, '7 days ago, 3 updates');
154 }); // _historyBarCaption
156 describe('getHistorySVG', function () {
157 beforeEach(function () {
158 manager
.db
.topicPublishHistory
.resolves([0, 1, 2, 1, 0, 1, 2, 0, 1]);
160 it('covers', async
function () {
161 await manager
.getHistorySVG(res
, ctx
);
162 assert(res
.end
.called
);
166 describe('getAdminOverview', function () {
167 beforeEach(function () {
168 manager
.db
.topicGetAll
.resolves([
170 id: '56c557ce-e667-11eb-bd80-0025905f714a',
172 url: 'https://example.com/',
173 leaseSecondsPreferred: 123,
175 leaseSecondsMax: 123456789,
176 publisherValidationUrl: null,
177 contentHashAlgorithm: 'hashy',
180 lastPublish: new Date(-Infinity
),
181 contentFetchNextAttempt: undefined,
182 contentFetchAttemptsSinceSuccess: 3,
183 contentUpdated: new Date(0),
190 it('covers', async
function () {
191 await manager
.getAdminOverview(res
, ctx
);
192 assert(res
.end
.called
);
194 it('covers non-matching profile', async
function () {
196 authenticatedProfile: 'https://different.example.com/profile',
198 await manager
.getAdminOverview(res
, ctx
);
199 assert
.deepStrictEqual(ctx
.topics
, []);
200 assert(res
.end
.called
);
202 }); // getAdminOverview
204 describe('getTopicDetails', function () {
205 beforeEach(function () {
206 ctx
.params
.topicId
= '56c557ce-e667-11eb-bd80-0025905f714a';
207 manager
.db
.topicGetById
.resolves({
208 id: '56c557ce-e667-11eb-bd80-0025905f714a',
210 url: 'https://example.com/',
211 leaseSecondsPreferred: 123,
213 leaseSecondsMax: 123456789,
214 publisherValidationUrl: null,
215 contentHashAlgorithm: 'hashy',
218 lastPublish: new Date(-Infinity
),
219 contentFetchNextAttempt: undefined,
220 contentFetchAttemptsSinceSuccess: 3,
221 contentUpdated: new Date(0),
226 manager
.db
.subscriptionsByTopicId
.resolves([{
229 topicId: '56c557ce-e667-11eb-bd80-0025905f714a',
231 verified: new Date(),
234 signatureAlgorithm: 'hmacy',
237 contentDelivered: new Date(),
238 deliveryAttemptsSinceSuccess: 0,
239 deliveryNextAttempt: new Date(-Infinity
),
241 manager
.db
.topicPublishHistory
.resolves([0, 1, 0, 1, 0]);
243 it('covers', async
function() {
244 await manager
.getTopicDetails(res
, ctx
);
245 assert(res
.end
.called
);
247 it('covers non-matching profile', async
function () {
249 authenticatedProfile: 'https://different.example.com/profile',
251 await manager
.getTopicDetails(res
, ctx
);
252 assert
.strictEqual(ctx
.topic
, null);
253 assert(res
.end
.called
);
255 it('covers matching profile', async
function () {
257 authenticatedProfile: 'https://example.com/profile',
259 await manager
.getTopicDetails(res
, ctx
);
261 assert(res
.end
.called
);
263 }); // getTopicDetails
265 describe('postRoot', function () {
266 let origProcessImmediately
;
267 beforeEach(function () {
268 origProcessImmediately
= manager
.options
.manager
.processImmediately
;
271 this.afterEach(function () {
272 manager
.options
.manager
.processImmediately
= origProcessImmediately
;
274 it('requires parameters', async
function () {
276 await manager
.postRoot(req
, res
, ctx
);
277 assert
.fail(noExpectedException
);
279 assert
.strictEqual(e
.message
, 'Bad Request');
282 it('accepts valid subscription', async
function () {
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(res
.end
.called
);
296 it('accepts valid subscription without claiming work', async
function () {
297 manager
.options
.manager
.processImmediately
= false;
298 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
299 manager
.db
.topicGetByUrl
.resolves({
302 manager
.db
.verificationInsert
.resolves({
304 lastInsertRowid: undefined,
307 await manager
.postRoot(req
, res
, ctx
);
308 assert(manager
.db
.verificationInsert
.called
);
309 assert(!manager
.communication
.verificationProcess
.called
);
310 assert(res
.end
.called
);
312 it('accepts valid subscription, covers processVerification failure', async
function () {
313 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
314 manager
.communication
.verificationProcess
.rejects('failed');
315 manager
.db
.topicGetByUrl
.resolves({
318 manager
.db
.verificationInsert
.resolves({
320 lastInsertRowid: undefined,
323 await manager
.postRoot(req
, res
, ctx
);
324 assert(manager
.db
.verificationInsert
.called
);
325 assert(res
.end
.called
);
326 assert(manager
.logger
.error
.called
);
328 it('covers db.verificationInsert failure', async
function () {
329 const expectedException
= new Error('failure');
330 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
331 manager
.db
.topicGetByUrl
.resolves({
334 manager
.db
.verificationInsert
.rejects(expectedException
);
335 assert
.rejects(async () => {
336 await manager
.postRoot(req
, res
, ctx
);
337 }, expectedException
);
339 it('accepts valid unsubscription', async
function () {
340 ctx
= Object
.assign({}, testData
.validUnsubscribeCtx
);
341 manager
.db
.topicGetByUrl
.resolves({
344 manager
.db
.subscriptionGet
.resolves({
347 manager
.db
.verificationInsert
.resolves({
349 lastInsertRowid: undefined,
352 await manager
.postRoot(req
, res
, ctx
);
353 assert(res
.end
.called
);
355 it('accepts valid publish', async
function () {
356 ctx
= Object
.assign({}, testData
.validPublishCtx
);
357 manager
.db
.topicGetByUrl
.resolves({
360 manager
.db
.topicFetchRequested
.resolves({
362 lastInsertRowid: undefined,
365 await manager
.postRoot(req
, res
, ctx
);
366 assert(res
.end
.called
);
370 describe('_getRootData', function () {
371 it('extracts expected values', function () {
372 req
.getHeader
.returns('user@example.com');
373 ctx
= Object
.assign({}, testData
.validSubscribeCtx
)
374 const result
= Manager
._getRootData(req
, ctx
);
375 assert
.deepStrictEqual(result
, testData
.validRootData
);
379 describe('_validateRootData', function () {
380 // This only wraps the other _check functions, not bothering with coverage.
381 }); // _validateRootData
383 describe('_checkTopic', function () {
384 let dbCtx
, data
, warn
, err
;
387 leaseSecondsPreferred: 86400 * 10,
388 leaseSecondsMax: 86400 * 20,
389 leaseSecondsMin: 86400,
391 beforeEach(function () {
397 it('succeeds', async
function () {
399 topic: 'http://example.com/blog',
401 manager
.db
.topicGetByUrl
.resolves(topic
);
402 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
403 assert
.strictEqual(warn
.length
, 0, warn
);
404 assert
.strictEqual(err
.length
, 0, err
);
405 assert
.strictEqual(data
.topicId
, 111);
406 assert
.strictEqual(data
.leaseSeconds
, 864000);
408 it('errors on unknown topic', async
function () {
409 manager
.db
.topicGetByUrl
.resolves();
410 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
411 assert
.strictEqual(warn
.length
, 0, warn
);
412 assert
.strictEqual(err
.length
, 1, err
);
414 it('warns on lease under min range', async
function () {
416 topic: 'http://example.com/blog',
419 manager
.db
.topicGetByUrl
.resolves(topic
);
420 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
421 assert
.strictEqual(warn
.length
, 1, warn
);
422 assert
.strictEqual(err
.length
, 0, err
);
423 assert
.strictEqual(data
.topicId
, 111);
424 assert
.strictEqual(data
.leaseSeconds
, 86400);
426 it('warns on lease over max range', async
function () {
428 topic: 'http://example.com/blog',
429 leaseSeconds: 86400 * 100,
431 manager
.db
.topicGetByUrl
.resolves(topic
);
432 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
433 assert
.strictEqual(warn
.length
, 1, warn
);
434 assert
.strictEqual(err
.length
, 0, err
);
435 assert
.strictEqual(data
.topicId
, 111);
436 assert
.strictEqual(data
.leaseSeconds
, 86400 * 20);
438 it('sets publisher validation state when available', async
function () {
440 topic: 'http://example.com/blog',
442 manager
.db
.topicGetByUrl
.resolves(Object
.assign({}, topic
, {
443 publisherValidationUrl: 'http://example.com/validate',
445 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
446 assert
.strictEqual(warn
.length
, 0, warn
);
447 assert
.strictEqual(err
.length
, 0, err
);
448 assert
.strictEqual(data
.topicId
, 111);
449 assert
.strictEqual(data
.leaseSeconds
, 864000);
450 assert
.strictEqual(data
.isPublisherValidated
, false);
452 it('accepts new public subscribe topic', async
function () {
453 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves(topic
);
455 topic: 'http://example.com/blog',
457 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
458 assert
.strictEqual(warn
.length
, 0, 'unexpected warnings length');
459 assert
.strictEqual(err
.length
, 0, 'unexpected errors length');
460 assert
.strictEqual(data
.topicId
, 111, 'unexpected topic id');
462 it('does not accept new public subscribe for invalid topic', async
function () {
463 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves(topic
);
465 topic: 'not a topic',
467 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
468 assert
.strictEqual(warn
.length
, 0, 'unexpected warnings length');
469 assert
.strictEqual(err
.length
, 1, 'unexpected errors length');
473 describe('_checkCallbackAndSecrets', function () {
475 let origStrictSecrets
;
477 origStrictSecrets
= manager
.options
.manager
.strictSecrets
;
479 beforeEach(function () {
484 afterEach(function () {
485 manager
.options
.manager
.strictSecrets
= origStrictSecrets
;
487 it('succeeds', function () {
489 callback: 'https://example.com/callback',
493 manager
._checkCallbackAndSecrets(data
, warn
, err
);
494 assert
.strictEqual(warn
.length
, 0, warn
);
495 assert
.strictEqual(err
.length
, 0, err
);
497 it('errors with invalid callback', function () {
499 callback: 'not a url',
503 manager
._checkCallbackAndSecrets(data
, warn
, err
);
504 assert
.strictEqual(warn
.length
, 0, warn
);
505 assert
.strictEqual(err
.length
, 1, err
);
507 it('errors when secret too large', function () {
509 callback: 'https://example.com/callback',
510 secret: 'x'.repeat(256),
513 manager
._checkCallbackAndSecrets(data
, warn
, err
);
514 assert
.strictEqual(warn
.length
, 0, warn
);
515 assert
.strictEqual(err
.length
, 1, err
);
517 it('warns when callback is insecure', function () {
519 callback: 'http://example.com/callback',
522 manager
._checkCallbackAndSecrets(data
, warn
, err
);
523 assert
.strictEqual(warn
.length
, 1, warn
);
524 assert
.strictEqual(err
.length
, 0, err
);
526 it('warns when hub is insecure with secret', function () {
528 callback: 'https://example.com/callback',
532 manager
._checkCallbackAndSecrets(data
, warn
, err
);
533 assert
.strictEqual(warn
.length
, 1, warn
);
534 assert
.strictEqual(err
.length
, 0, err
);
536 it('errors when callback is insecure with secret and strict', function () {
537 manager
.options
.manager
.strictSecrets
= true;
539 callback: 'http://example.com/callback',
543 manager
._checkCallbackAndSecrets(data
, warn
, err
);
544 assert
.strictEqual(warn
.length
, 1, warn
);
545 assert
.strictEqual(err
.length
, 1, err
);
547 }); // _checkCallbackAndSecrets
549 describe('_checkMode', function () {
550 let dbCtx
, data
, warn
, err
;
551 beforeEach(function () {
557 it('subscribe succeeds', async
function () {
561 await manager
._checkMode(dbCtx
, data
, warn
, err
);
562 assert
.strictEqual(warn
.length
, 0);
563 assert
.strictEqual(err
.length
, 0);
565 it('unsubscribe succeeds', async
function () {
568 callback: 'http://example.com',
571 manager
.db
.subscriptionGet
.resolves({
572 expires: (Date
.now() / 1000) + 60,
574 await manager
._checkMode(dbCtx
, data
, warn
, err
);
575 assert
.strictEqual(warn
.length
, 0, warn
);
576 assert
.strictEqual(err
.length
, 0, err
);
578 it('unsubscribe requires valid data', async
function () {
581 callback: 'http://example.com',
584 manager
.db
.subscriptionGet
.resolves({
585 expires: (Date
.now() / 1000) - 60,
587 await manager
._checkMode(dbCtx
, data
, warn
, err
);
588 assert
.strictEqual(warn
.length
, 0, warn
);
589 assert
.strictEqual(err
.length
, 1, err
);
591 it('unsubscribe ignores expired subscription', async
function () {
594 callback: 'http://example.com',
597 manager
.db
.subscriptionGet
.resolves({
598 expires: (Date
.now() / 1000) - 60,
600 await manager
._checkMode(dbCtx
, data
, warn
, err
);
601 assert
.strictEqual(warn
.length
, 0, warn
);
602 assert
.strictEqual(err
.length
, 1, err
);
606 describe('_publishTopics', function () {
607 let dbCtx
, data
, requestId
;
608 beforeEach(function () {
613 it('succeeds', async
function () {
614 manager
.db
.topicGetByUrl
.resolves({
617 Object
.assign(data
, testData
.validPublishRootData
);
618 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
619 assert
.strictEqual(topicResults
.length
, 1);
620 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
621 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
622 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
624 it('fails bad url', async
function () {
625 Object
.assign(data
, testData
.validPublishRootData
, { topic: 'not_a_url' });
626 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
627 assert
.strictEqual(topicResults
.length
, 1);
628 assert
.strictEqual(topicResults
[0].err
.length
, 1, 'unexpected errors length');
629 assert
.strictEqual(topicResults
[0].warn
.length
, 0);
631 it('accepts new public publish topic', async
function () {
632 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
635 Object
.assign(data
, testData
.validPublishRootData
);
636 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
637 assert
.strictEqual(topicResults
.length
, 1);
638 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
639 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
640 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
642 it('does not publish deleted topic', async
function () {
643 manager
.db
.topicGetByUrl
.resolves({
647 Object
.assign(data
, testData
.validPublishRootData
);
648 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
649 assert
.strictEqual(topicResults
.length
, 1);
650 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
651 assert
.strictEqual(topicResults
[0].err
.length
, 1, 'unexpected errors length');
652 assert
.strictEqual(topicResults
[0].topicId
, undefined, 'unexpected topic id');
654 it('no topics', async
function() {
655 Object
.assign(data
, testData
.validPublishRootData
);
657 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
658 assert
.strictEqual(topicResults
.length
, 0);
660 it('multiple valid topics', async
function () {
661 manager
.db
.topicGetByUrl
.resolves({
664 Object
.assign(data
, testData
.validPublishRootData
);
665 data
.url
= ['https://example.com/first', 'https://example.com/second'];
666 data
.topic
= ['https://example.com/third'];
667 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
668 assert
.strictEqual(topicResults
.length
, 3);
669 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
670 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
671 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
672 assert
.strictEqual(topicResults
[1].warn
.length
, 0, 'unexpected warnings length');
673 assert
.strictEqual(topicResults
[1].err
.length
, 0, 'unexpected errors length');
674 assert
.strictEqual(topicResults
[1].topicId
, 222, 'unexpected topic id');
675 assert
.strictEqual(topicResults
[2].warn
.length
, 0, 'unexpected warnings length');
676 assert
.strictEqual(topicResults
[2].err
.length
, 0, 'unexpected errors length');
677 assert
.strictEqual(topicResults
[2].topicId
, 222, 'unexpected topic id');
679 it('mix of valid and invalid topics', async
function () {
680 manager
.db
.topicGetByUrl
.onCall(1).resolves().resolves({
683 Object
.assign(data
, testData
.validPublishRootData
);
684 data
.url
= ['https://example.com/first', 'not a url'];
685 data
.topic
= ['https://example.com/third'];
686 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
687 assert
.strictEqual(topicResults
.length
, 3);
688 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
689 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
690 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
691 assert
.strictEqual(topicResults
[1].warn
.length
, 0, 'unexpected warnings length');
692 assert
.strictEqual(topicResults
[1].err
.length
, 1, 'unexpected errors length');
693 assert
.strictEqual(topicResults
[1].topicId
, undefined, 'unexpected topic id');
694 assert
.strictEqual(topicResults
[2].warn
.length
, 0, 'unexpected warnings length');
695 assert
.strictEqual(topicResults
[2].err
.length
, 0, 'unexpected errors length');
696 assert
.strictEqual(topicResults
[2].topicId
, 222, 'unexpected topic id');
698 }); // _publishTopics
700 describe('_publishRequest', function () {
701 let dbCtx
, data
, res
, ctx
;
702 beforeEach(function () {
710 it('requires a topic', async
function () {
712 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
713 assert
.fail(noExpectedException
);
715 assert(e
instanceof Errors
.ResponseError
);
718 it('processes one topic', async
function() {
719 manager
.db
.topicGetByUrl
.resolves({
722 Object
.assign(data
, testData
.validPublishRootData
);
723 manager
.db
.topicFetchRequested
.resolves();
724 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
725 assert(manager
.db
.topicFetchRequested
.called
);
726 assert
.strictEqual(res
.statusCode
, 202);
727 assert(res
.end
.called
);
729 it('processes mix of valid and invalid topics', async
function () {
730 ctx
.responseType
= 'application/json';
731 manager
.db
.topicGetByUrl
.onCall(1).resolves().resolves({
734 Object
.assign(data
, testData
.validPublishRootData
);
735 data
.url
= ['https://example.com/first', 'not a url'];
736 data
.topic
= ['https://example.com/third'];
737 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
738 assert
.strictEqual(res
.statusCode
, 207);
739 assert(res
.end
.called
);
741 it('covers topicFetchRequest failure', async
function () {
742 manager
.db
.topicGetByUrl
.resolves({
745 Object
.assign(data
, testData
.validPublishRootData
);
746 const expected
= new Error('boo');
747 manager
.db
.topicFetchRequested
.rejects(expected
);
749 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
750 assert
.fail(noExpectedException
);
752 assert
.deepStrictEqual(e
, expected
);
755 it('covers immediate processing error', async
function() {
756 manager
.options
.manager
.processImmediately
= true;
757 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
760 manager
.communication
.topicFetchClaimAndProcessById
.rejects();
761 Object
.assign(data
, testData
.validPublishRootData
);
762 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
763 assert(manager
.db
.topicFetchRequested
.called
);
764 assert
.strictEqual(res
.statusCode
, 202);
765 assert(res
.end
.called
);
766 assert(manager
.communication
.topicFetchClaimAndProcessById
.called
)
768 it('covers no immediate processing', async
function() {
769 manager
.options
.manager
.processImmediately
= false;
770 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
773 Object
.assign(data
, testData
.validPublishRootData
);
774 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
775 assert(manager
.db
.topicFetchRequested
.called
);
776 assert
.strictEqual(res
.statusCode
, 202);
777 assert(res
.end
.called
);
778 assert(!manager
.communication
.topicFetchClaimAndProcessById
.called
)
780 }); // _publishRequest
782 describe('multiPublishContent', function () {
784 beforeEach(function () {
786 url: 'https://example.com/first',
791 statusMessage: 'Accepted',
796 err: [ 'invalid topic url (failed to parse url)' ],
799 statusMessage: 'Bad Request',
802 it('covers json response', function () {
803 ctx
.responseType
= 'application/json';
804 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":[]}]';
805 const result
= Manager
.multiPublishContent(ctx
, publishTopics
);
806 assert
.deepStrictEqual(result
, expected
);
808 it('covers text response', function () {
809 ctx
.responseType
= 'text/plain';
810 const expected
= `https://example.com/first [202 Accepted]
812 not a url [400 Bad Request]
813 \terror: invalid topic url (failed to parse url)`;
814 const result
= Manager
.multiPublishContent(ctx
, publishTopics
);
815 assert
.deepStrictEqual(result
, expected
);
817 }); // multiPublishContent
819 describe('processTasks', function () {
820 it('covers', async
function () {
821 sinon
.stub(manager
.communication
.worker
, 'process').resolves();
822 await manager
.processTasks(res
, ctx
);
823 assert(manager
.communication
.worker
.process
.called
);
824 assert(res
.end
.called
);
826 it('covers error', async
function () {
827 sinon
.stub(manager
.communication
.worker
, 'process').rejects();
828 await manager
.processTasks(res
, ctx
);
829 assert(manager
.communication
.worker
.process
.called
);
830 assert(res
.end
.called
);
834 describe('updateTopic', function () {
835 it('fails if no topic exists', async
function () {
837 await manager
.updateTopic(res
, ctx
);
838 assert
.fail(noExpectedException
);
840 assert(e
instanceof Errors
.ResponseError
);
843 it('deletes', async
function () {
844 ctx
.method
= 'DELETE';
845 manager
.db
.topicGetById
.resolves({});
846 await manager
.updateTopic(res
, ctx
);
847 assert(manager
.db
.topicDeleted
.called
);
849 it('does not patch without data', async
function () {
850 manager
.db
.topicGetById
.resolves({});
851 await manager
.updateTopic(res
, ctx
);
852 assert(!manager
.db
.topicUpdate
.called
);
853 assert
.strictEqual(res
.statusCode
, 204);
855 it('does not patch with same data', async
function () {
856 manager
.db
.topicGetById
.resolves({
857 leaseSecondsPreferred: '86400',
860 leaseSecondsPreferred: '86400',
862 await manager
.updateTopic(res
, ctx
);
863 assert(!manager
.db
.topicUpdate
.called
);
864 assert
.strictEqual(res
.statusCode
, 204);
866 it('patches', async
function () {
868 leaseSecondsPreferred: '86400',
870 manager
.db
.topicGetById
.resolves({});
871 await manager
.updateTopic(res
, ctx
);
872 assert(manager
.db
.topicUpdate
.called
);
874 it('handles validation error', async
function () {
876 leaseSecondsPreferred: 'blorp',
878 manager
.db
.topicGetById
.resolves({});
879 manager
.db
.topicUpdate
.rejects(new DBErrors
.DataValidation('something'));
881 await manager
.updateTopic(res
, ctx
);
882 assert
.fail(noExpectedException
);
884 assert(e
instanceof Errors
.ResponseError
);
885 assert
.strictEqual(e
.statusCode
, 400);
888 it('handles generic error', async
function () {
889 const expected
= new Error('blah');
891 leaseSecondsPreferred: '123',
893 manager
.db
.topicGetById
.resolves({});
894 manager
.db
.topicUpdate
.rejects(expected
);
896 await manager
.updateTopic(res
, ctx
);
897 assert
.fail(noExpectedException
);
899 assert
.deepStrictEqual(e
, expected
);
904 describe('updateSubscription', function () {
905 it('fails if no subscription exists', async
function () {
907 await manager
.updateSubscription(res
, ctx
);
908 assert
.fail(noExpectedException
);
910 assert(e
instanceof Errors
.ResponseError
);
913 it('deletes', async
function () {
914 ctx
.method
= 'DELETE';
915 manager
.db
.subscriptionGetById
.resolves({});
916 await manager
.updateSubscription(res
, ctx
);
917 assert(manager
.db
.verificationInsert
.called
);
919 it('does not patch without data', async
function () {
920 manager
.db
.subscriptionGetById
.resolves({});
921 await manager
.updateSubscription(res
, ctx
);
922 assert(!manager
.db
.subscriptionUpdate
.called
);
923 assert
.strictEqual(res
.statusCode
, 204);
925 it('does not patch with same data', async
function () {
926 manager
.db
.subscriptionGetById
.resolves({
927 signatureAlgorithm: 'sha256',
930 signatureAlgorithm: 'sha256',
932 await manager
.updateSubscription(res
, ctx
);
933 assert(!manager
.db
.subscriptionUpdate
.called
);
934 assert
.strictEqual(res
.statusCode
, 204);
936 it('patches', async
function () {
938 signatureAlgorithm: 'sha256',
940 manager
.db
.subscriptionGetById
.resolves({});
941 await manager
.updateSubscription(res
, ctx
);
942 assert(manager
.db
.subscriptionUpdate
.called
);
944 it('handles validation error', async
function () {
946 signatureAlgorithm: 123,
948 manager
.db
.subscriptionGetById
.resolves({});
949 manager
.db
.subscriptionUpdate
.rejects(new DBErrors
.DataValidation('something'));
951 await manager
.updateSubscription(res
, ctx
);
952 assert
.fail(noExpectedException
);
954 assert(e
instanceof Errors
.ResponseError
);
955 assert
.strictEqual(e
.statusCode
, 400);
958 it('handles generic error', async
function () {
959 const expected
= new Error('blah');
961 signatureAlgorithm: 'blorp',
963 manager
.db
.subscriptionGetById
.resolves({});
964 manager
.db
.subscriptionUpdate
.rejects(expected
);
966 await manager
.updateSubscription(res
, ctx
);
967 assert
.fail(noExpectedException
);
969 assert
.deepStrictEqual(e
, expected
);
972 }); // updateSubscription