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 it('covers', async
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),
175 await manager
.getAdminOverview(res
, ctx
);
176 assert(res
.end
.called
);
178 }); // getAdminOverview
180 describe('getTopicDetails', function () {
181 it('covers', async
function() {
182 ctx
.params
.topicId
= '56c557ce-e667-11eb-bd80-0025905f714a';
183 manager
.db
.topicGetById
.resolves({
184 id: '56c557ce-e667-11eb-bd80-0025905f714a',
186 url: 'https://example.com/',
187 leaseSecondsPreferred: 123,
189 leaseSecondsMax: 123456789,
190 publisherValidationUrl: null,
191 contentHashAlgorithm: 'hashy',
194 lastPublish: new Date(-Infinity
),
195 contentFetchNextAttempt: undefined,
196 contentFetchAttemptsSinceSuccess: 3,
197 contentUpdated: new Date(0),
202 manager
.db
.subscriptionsByTopicId
.resolves([{
205 topicId: '56c557ce-e667-11eb-bd80-0025905f714a',
207 verified: new Date(),
210 signatureAlgorithm: 'hmacy',
213 contentDelivered: new Date(),
214 deliveryAttemptsSinceSuccess: 0,
215 deliveryNextAttempt: new Date(-Infinity
),
217 await manager
.getTopicDetails(res
, ctx
);
218 assert(res
.end
.called
);
220 }); // getTopicDetails
222 describe('postRoot', function () {
223 let origProcessImmediately
;
224 beforeEach(function () {
225 origProcessImmediately
= manager
.options
.manager
.processImmediately
;
228 this.afterEach(function () {
229 manager
.options
.manager
.processImmediately
= origProcessImmediately
;
231 it('requires parameters', async
function () {
233 await manager
.postRoot(req
, res
, ctx
);
234 assert
.fail(noExpectedException
);
236 assert
.strictEqual(e
.message
, 'Bad Request');
239 it('accepts valid subscription', async
function () {
240 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
241 manager
.db
.topicGetByUrl
.resolves({
244 manager
.db
.verificationInsert
.resolves({
246 lastInsertRowid: undefined,
249 await manager
.postRoot(req
, res
, ctx
);
250 assert(manager
.db
.verificationInsert
.called
);
251 assert(res
.end
.called
);
253 it('accepts valid subscription without claiming work', async
function () {
254 manager
.options
.manager
.processImmediately
= false;
255 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
256 manager
.db
.topicGetByUrl
.resolves({
259 manager
.db
.verificationInsert
.resolves({
261 lastInsertRowid: undefined,
264 await manager
.postRoot(req
, res
, ctx
);
265 assert(manager
.db
.verificationInsert
.called
);
266 assert(!manager
.communication
.verificationProcess
.called
);
267 assert(res
.end
.called
);
269 it('accepts valid subscription, covers processVerification failure', async
function () {
270 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
271 manager
.communication
.verificationProcess
.rejects('failed');
272 manager
.db
.topicGetByUrl
.resolves({
275 manager
.db
.verificationInsert
.resolves({
277 lastInsertRowid: undefined,
280 await manager
.postRoot(req
, res
, ctx
);
281 assert(manager
.db
.verificationInsert
.called
);
282 assert(res
.end
.called
);
283 assert(manager
.logger
.error
.called
);
285 it('covers db.verificationInsert failure', async
function () {
286 const expectedException
= new Error('failure');
287 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
288 manager
.db
.topicGetByUrl
.resolves({
291 manager
.db
.verificationInsert
.rejects(expectedException
);
292 assert
.rejects(async () => {
293 await manager
.postRoot(req
, res
, ctx
);
294 }, expectedException
);
296 it('accepts valid unsubscription', async
function () {
297 ctx
= Object
.assign({}, testData
.validUnsubscribeCtx
);
298 manager
.db
.topicGetByUrl
.resolves({
301 manager
.db
.subscriptionGet
.resolves({
304 manager
.db
.verificationInsert
.resolves({
306 lastInsertRowid: undefined,
309 await manager
.postRoot(req
, res
, ctx
);
310 assert(res
.end
.called
);
312 it('accepts valid publish', async
function () {
313 ctx
= Object
.assign({}, testData
.validPublishCtx
);
314 manager
.db
.topicGetByUrl
.resolves({
317 manager
.db
.topicFetchRequested
.resolves({
319 lastInsertRowid: undefined,
322 await manager
.postRoot(req
, res
, ctx
);
323 assert(res
.end
.called
);
327 describe('_getRootData', function () {
328 it('extracts expected values', function () {
329 req
.getHeader
.returns('user@example.com');
330 ctx
= Object
.assign({}, testData
.validSubscribeCtx
)
331 const result
= Manager
._getRootData(req
, ctx
);
332 assert
.deepStrictEqual(result
, testData
.validRootData
);
336 describe('_validateRootData', function () {
337 // This only wraps the other _check functions, not bothering with coverage.
338 }); // _validateRootData
340 describe('_checkTopic', function () {
341 let dbCtx
, data
, warn
, err
;
344 leaseSecondsPreferred: 86400 * 10,
345 leaseSecondsMax: 86400 * 20,
346 leaseSecondsMin: 86400,
348 beforeEach(function () {
354 it('succeeds', async
function () {
356 topic: 'http://example.com/blog',
358 manager
.db
.topicGetByUrl
.resolves(topic
);
359 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
360 assert
.strictEqual(warn
.length
, 0, warn
);
361 assert
.strictEqual(err
.length
, 0, err
);
362 assert
.strictEqual(data
.topicId
, 111);
363 assert
.strictEqual(data
.leaseSeconds
, 864000);
365 it('errors on unknown topic', async
function () {
366 manager
.db
.topicGetByUrl
.resolves();
367 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
368 assert
.strictEqual(warn
.length
, 0, warn
);
369 assert
.strictEqual(err
.length
, 1, err
);
371 it('warns on lease under min range', async
function () {
373 topic: 'http://example.com/blog',
376 manager
.db
.topicGetByUrl
.resolves(topic
);
377 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
378 assert
.strictEqual(warn
.length
, 1, warn
);
379 assert
.strictEqual(err
.length
, 0, err
);
380 assert
.strictEqual(data
.topicId
, 111);
381 assert
.strictEqual(data
.leaseSeconds
, 86400);
383 it('warns on lease over max range', async
function () {
385 topic: 'http://example.com/blog',
386 leaseSeconds: 86400 * 100,
388 manager
.db
.topicGetByUrl
.resolves(topic
);
389 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
390 assert
.strictEqual(warn
.length
, 1, warn
);
391 assert
.strictEqual(err
.length
, 0, err
);
392 assert
.strictEqual(data
.topicId
, 111);
393 assert
.strictEqual(data
.leaseSeconds
, 86400 * 20);
395 it('sets publisher validation state when available', async
function () {
397 topic: 'http://example.com/blog',
399 manager
.db
.topicGetByUrl
.resolves(Object
.assign({}, topic
, {
400 publisherValidationUrl: 'http://example.com/validate',
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);
407 assert
.strictEqual(data
.isPublisherValidated
, false);
409 it('accepts new public subscribe topic', async
function () {
410 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves(topic
);
412 topic: 'http://example.com/blog',
414 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
415 assert
.strictEqual(warn
.length
, 0, 'unexpected warnings length');
416 assert
.strictEqual(err
.length
, 0, 'unexpected errors length');
417 assert
.strictEqual(data
.topicId
, 111, 'unexpected topic id');
419 it('does not accept new public subscribe for invalid topic', async
function () {
420 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves(topic
);
422 topic: 'not a topic',
424 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
425 assert
.strictEqual(warn
.length
, 0, 'unexpected warnings length');
426 assert
.strictEqual(err
.length
, 1, 'unexpected errors length');
430 describe('_checkCallbackAndSecrets', function () {
432 let origStrictSecrets
;
434 origStrictSecrets
= manager
.options
.manager
.strictSecrets
;
436 beforeEach(function () {
441 afterEach(function () {
442 manager
.options
.manager
.strictSecrets
= origStrictSecrets
;
444 it('succeeds', function () {
446 callback: 'https://example.com/callback',
450 manager
._checkCallbackAndSecrets(data
, warn
, err
);
451 assert
.strictEqual(warn
.length
, 0, warn
);
452 assert
.strictEqual(err
.length
, 0, err
);
454 it('errors with invalid callback', function () {
456 callback: 'not a url',
460 manager
._checkCallbackAndSecrets(data
, warn
, err
);
461 assert
.strictEqual(warn
.length
, 0, warn
);
462 assert
.strictEqual(err
.length
, 1, err
);
464 it('errors when secret too large', function () {
466 callback: 'https://example.com/callback',
467 secret: 'x'.repeat(256),
470 manager
._checkCallbackAndSecrets(data
, warn
, err
);
471 assert
.strictEqual(warn
.length
, 0, warn
);
472 assert
.strictEqual(err
.length
, 1, err
);
474 it('warns when callback is insecure', function () {
476 callback: 'http://example.com/callback',
479 manager
._checkCallbackAndSecrets(data
, warn
, err
);
480 assert
.strictEqual(warn
.length
, 1, warn
);
481 assert
.strictEqual(err
.length
, 0, err
);
483 it('warns when hub is insecure with secret', function () {
485 callback: 'https://example.com/callback',
489 manager
._checkCallbackAndSecrets(data
, warn
, err
);
490 assert
.strictEqual(warn
.length
, 1, warn
);
491 assert
.strictEqual(err
.length
, 0, err
);
493 it('errors when callback is insecure with secret and strict', function () {
494 manager
.options
.manager
.strictSecrets
= true;
496 callback: 'http://example.com/callback',
500 manager
._checkCallbackAndSecrets(data
, warn
, err
);
501 assert
.strictEqual(warn
.length
, 1, warn
);
502 assert
.strictEqual(err
.length
, 1, err
);
504 }); // _checkCallbackAndSecrets
506 describe('_checkMode', function () {
507 let dbCtx
, data
, warn
, err
;
508 beforeEach(function () {
514 it('subscribe succeeds', async
function () {
518 await manager
._checkMode(dbCtx
, data
, warn
, err
);
519 assert
.strictEqual(warn
.length
, 0);
520 assert
.strictEqual(err
.length
, 0);
522 it('unsubscribe succeeds', async
function () {
525 callback: 'http://example.com',
528 manager
.db
.subscriptionGet
.resolves({
529 expires: (Date
.now() / 1000) + 60,
531 await manager
._checkMode(dbCtx
, data
, warn
, err
);
532 assert
.strictEqual(warn
.length
, 0, warn
);
533 assert
.strictEqual(err
.length
, 0, err
);
535 it('unsubscribe requires valid data', async
function () {
538 callback: 'http://example.com',
541 manager
.db
.subscriptionGet
.resolves({
542 expires: (Date
.now() / 1000) - 60,
544 await manager
._checkMode(dbCtx
, data
, warn
, err
);
545 assert
.strictEqual(warn
.length
, 0, warn
);
546 assert
.strictEqual(err
.length
, 1, err
);
548 it('unsubscribe ignores expired subscription', async
function () {
551 callback: 'http://example.com',
554 manager
.db
.subscriptionGet
.resolves({
555 expires: (Date
.now() / 1000) - 60,
557 await manager
._checkMode(dbCtx
, data
, warn
, err
);
558 assert
.strictEqual(warn
.length
, 0, warn
);
559 assert
.strictEqual(err
.length
, 1, err
);
563 describe('_publishTopics', function () {
564 let dbCtx
, data
, requestId
;
565 beforeEach(function () {
570 it('succeeds', async
function () {
571 manager
.db
.topicGetByUrl
.resolves({
574 Object
.assign(data
, testData
.validPublishRootData
);
575 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
576 assert
.strictEqual(topicResults
.length
, 1);
577 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
578 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
579 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
581 it('fails bad url', async
function () {
582 Object
.assign(data
, testData
.validPublishRootData
, { topic: 'not_a_url' });
583 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
584 assert
.strictEqual(topicResults
.length
, 1);
585 assert
.strictEqual(topicResults
[0].err
.length
, 1, 'unexpected errors length');
586 assert
.strictEqual(topicResults
[0].warn
.length
, 0);
588 it('accepts new public publish topic', async
function () {
589 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
592 Object
.assign(data
, testData
.validPublishRootData
);
593 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
594 assert
.strictEqual(topicResults
.length
, 1);
595 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
596 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
597 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
599 it('does not publish deleted topic', async
function () {
600 manager
.db
.topicGetByUrl
.resolves({
604 Object
.assign(data
, testData
.validPublishRootData
);
605 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
606 assert
.strictEqual(topicResults
.length
, 1);
607 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
608 assert
.strictEqual(topicResults
[0].err
.length
, 1, 'unexpected errors length');
609 assert
.strictEqual(topicResults
[0].topicId
, undefined, 'unexpected topic id');
611 it('no topics', async
function() {
612 Object
.assign(data
, testData
.validPublishRootData
);
614 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
615 assert
.strictEqual(topicResults
.length
, 0);
617 it('multiple valid topics', async
function () {
618 manager
.db
.topicGetByUrl
.resolves({
621 Object
.assign(data
, testData
.validPublishRootData
);
622 data
.url
= ['https://example.com/first', 'https://example.com/second'];
623 data
.topic
= ['https://example.com/third'];
624 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
625 assert
.strictEqual(topicResults
.length
, 3);
626 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
627 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
628 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
629 assert
.strictEqual(topicResults
[1].warn
.length
, 0, 'unexpected warnings length');
630 assert
.strictEqual(topicResults
[1].err
.length
, 0, 'unexpected errors length');
631 assert
.strictEqual(topicResults
[1].topicId
, 222, 'unexpected topic id');
632 assert
.strictEqual(topicResults
[2].warn
.length
, 0, 'unexpected warnings length');
633 assert
.strictEqual(topicResults
[2].err
.length
, 0, 'unexpected errors length');
634 assert
.strictEqual(topicResults
[2].topicId
, 222, 'unexpected topic id');
636 it('mix of valid and invalid topics', async
function () {
637 manager
.db
.topicGetByUrl
.onCall(1).resolves().resolves({
640 Object
.assign(data
, testData
.validPublishRootData
);
641 data
.url
= ['https://example.com/first', 'not a url'];
642 data
.topic
= ['https://example.com/third'];
643 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
644 assert
.strictEqual(topicResults
.length
, 3);
645 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
646 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
647 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
648 assert
.strictEqual(topicResults
[1].warn
.length
, 0, 'unexpected warnings length');
649 assert
.strictEqual(topicResults
[1].err
.length
, 1, 'unexpected errors length');
650 assert
.strictEqual(topicResults
[1].topicId
, undefined, 'unexpected topic id');
651 assert
.strictEqual(topicResults
[2].warn
.length
, 0, 'unexpected warnings length');
652 assert
.strictEqual(topicResults
[2].err
.length
, 0, 'unexpected errors length');
653 assert
.strictEqual(topicResults
[2].topicId
, 222, 'unexpected topic id');
655 }); // _publishTopics
657 describe('_publishRequest', function () {
658 let dbCtx
, data
, res
, ctx
;
659 beforeEach(function () {
667 it('requires a topic', async
function () {
669 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
670 assert
.fail(noExpectedException
);
672 assert(e
instanceof Errors
.ResponseError
);
675 it('processes one topic', async
function() {
676 manager
.db
.topicGetByUrl
.resolves({
679 Object
.assign(data
, testData
.validPublishRootData
);
680 manager
.db
.topicFetchRequested
.resolves();
681 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
682 assert(manager
.db
.topicFetchRequested
.called
);
683 assert
.strictEqual(res
.statusCode
, 202);
684 assert(res
.end
.called
);
686 it('processes mix of valid and invalid topics', async
function () {
687 ctx
.responseType
= 'application/json';
688 manager
.db
.topicGetByUrl
.onCall(1).resolves().resolves({
691 Object
.assign(data
, testData
.validPublishRootData
);
692 data
.url
= ['https://example.com/first', 'not a url'];
693 data
.topic
= ['https://example.com/third'];
694 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
695 assert
.strictEqual(res
.statusCode
, 207);
696 assert(res
.end
.called
);
698 it('covers topicFetchRequest failure', async
function () {
699 manager
.db
.topicGetByUrl
.resolves({
702 Object
.assign(data
, testData
.validPublishRootData
);
703 const expected
= new Error('boo');
704 manager
.db
.topicFetchRequested
.rejects(expected
);
706 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
707 assert
.fail(noExpectedException
);
709 assert
.deepStrictEqual(e
, expected
);
712 it('covers immediate processing error', async
function() {
713 manager
.options
.manager
.processImmediately
= true;
714 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
717 manager
.communication
.topicFetchClaimAndProcessById
.rejects();
718 Object
.assign(data
, testData
.validPublishRootData
);
719 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
720 assert(manager
.db
.topicFetchRequested
.called
);
721 assert
.strictEqual(res
.statusCode
, 202);
722 assert(res
.end
.called
);
723 assert(manager
.communication
.topicFetchClaimAndProcessById
.called
)
725 it('covers no immediate processing', async
function() {
726 manager
.options
.manager
.processImmediately
= false;
727 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
730 Object
.assign(data
, testData
.validPublishRootData
);
731 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
732 assert(manager
.db
.topicFetchRequested
.called
);
733 assert
.strictEqual(res
.statusCode
, 202);
734 assert(res
.end
.called
);
735 assert(!manager
.communication
.topicFetchClaimAndProcessById
.called
)
737 }); // _publishRequest
739 describe('multiPublishContent', function () {
741 beforeEach(function () {
743 url: 'https://example.com/first',
748 statusMessage: 'Accepted',
753 err: [ 'invalid topic url (failed to parse url)' ],
756 statusMessage: 'Bad Request',
759 it('covers json response', function () {
760 ctx
.responseType
= 'application/json';
761 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":[]}]';
762 const result
= Manager
.multiPublishContent(ctx
, publishTopics
);
763 assert
.deepStrictEqual(result
, expected
);
765 it('covers text response', function () {
766 ctx
.responseType
= 'text/plain';
767 const expected
= `https://example.com/first [202 Accepted]
769 not a url [400 Bad Request]
770 \terror: invalid topic url (failed to parse url)`;
771 const result
= Manager
.multiPublishContent(ctx
, publishTopics
);
772 assert
.deepStrictEqual(result
, expected
);
774 }); // multiPublishContent
776 describe('processTasks', function () {
777 it('covers', async
function () {
778 sinon
.stub(manager
.communication
.worker
, 'process').resolves();
779 await manager
.processTasks(res
, ctx
);
780 assert(manager
.communication
.worker
.process
.called
);
781 assert(res
.end
.called
);
783 it('covers error', async
function () {
784 sinon
.stub(manager
.communication
.worker
, 'process').rejects();
785 await manager
.processTasks(res
, ctx
);
786 assert(manager
.communication
.worker
.process
.called
);
787 assert(res
.end
.called
);
791 describe('updateTopic', function () {
792 it('fails if no topic exists', async
function () {
794 await manager
.updateTopic(res
, ctx
);
795 assert
.fail(noExpectedException
);
797 assert(e
instanceof Errors
.ResponseError
);
800 it('deletes', async
function () {
801 ctx
.method
= 'DELETE';
802 manager
.db
.topicGetById
.resolves({});
803 await manager
.updateTopic(res
, ctx
);
804 assert(manager
.db
.topicDeleted
.called
);
806 it('does not patch without data', async
function () {
807 manager
.db
.topicGetById
.resolves({});
808 await manager
.updateTopic(res
, ctx
);
809 assert(!manager
.db
.topicUpdate
.called
);
810 assert
.strictEqual(res
.statusCode
, 204);
812 it('does not patch with same data', async
function () {
813 manager
.db
.topicGetById
.resolves({
814 leaseSecondsPreferred: '86400',
817 leaseSecondsPreferred: '86400',
819 await manager
.updateTopic(res
, ctx
);
820 assert(!manager
.db
.topicUpdate
.called
);
821 assert
.strictEqual(res
.statusCode
, 204);
823 it('patches', async
function () {
825 leaseSecondsPreferred: '86400',
827 manager
.db
.topicGetById
.resolves({});
828 await manager
.updateTopic(res
, ctx
);
829 assert(manager
.db
.topicUpdate
.called
);
831 it('handles validation error', async
function () {
833 leaseSecondsPreferred: 'blorp',
835 manager
.db
.topicGetById
.resolves({});
836 manager
.db
.topicUpdate
.rejects(new DBErrors
.DataValidation('something'));
838 await manager
.updateTopic(res
, ctx
);
839 assert
.fail(noExpectedException
);
841 assert(e
instanceof Errors
.ResponseError
);
842 assert
.strictEqual(e
.statusCode
, 400);
845 it('handles generic error', async
function () {
846 const expected
= new Error('blah');
848 leaseSecondsPreferred: '123',
850 manager
.db
.topicGetById
.resolves({});
851 manager
.db
.topicUpdate
.rejects(expected
);
853 await manager
.updateTopic(res
, ctx
);
854 assert
.fail(noExpectedException
);
856 assert
.deepStrictEqual(e
, expected
);
861 describe('updateSubscription', function () {
862 it('fails if no subscription exists', async
function () {
864 await manager
.updateSubscription(res
, ctx
);
865 assert
.fail(noExpectedException
);
867 assert(e
instanceof Errors
.ResponseError
);
870 it('deletes', async
function () {
871 ctx
.method
= 'DELETE';
872 manager
.db
.subscriptionGetById
.resolves({});
873 await manager
.updateSubscription(res
, ctx
);
874 assert(manager
.db
.verificationInsert
.called
);
876 it('does not patch without data', async
function () {
877 manager
.db
.subscriptionGetById
.resolves({});
878 await manager
.updateSubscription(res
, ctx
);
879 assert(!manager
.db
.subscriptionUpdate
.called
);
880 assert
.strictEqual(res
.statusCode
, 204);
882 it('does not patch with same data', async
function () {
883 manager
.db
.subscriptionGetById
.resolves({
884 signatureAlgorithm: 'sha256',
887 signatureAlgorithm: 'sha256',
889 await manager
.updateSubscription(res
, ctx
);
890 assert(!manager
.db
.subscriptionUpdate
.called
);
891 assert
.strictEqual(res
.statusCode
, 204);
893 it('patches', async
function () {
895 signatureAlgorithm: 'sha256',
897 manager
.db
.subscriptionGetById
.resolves({});
898 await manager
.updateSubscription(res
, ctx
);
899 assert(manager
.db
.subscriptionUpdate
.called
);
901 it('handles validation error', async
function () {
903 signatureAlgorithm: 123,
905 manager
.db
.subscriptionGetById
.resolves({});
906 manager
.db
.subscriptionUpdate
.rejects(new DBErrors
.DataValidation('something'));
908 await manager
.updateSubscription(res
, ctx
);
909 assert
.fail(noExpectedException
);
911 assert(e
instanceof Errors
.ResponseError
);
912 assert
.strictEqual(e
.statusCode
, 400);
915 it('handles generic error', async
function () {
916 const expected
= new Error('blah');
918 signatureAlgorithm: 'blorp',
920 manager
.db
.subscriptionGetById
.resolves({});
921 manager
.db
.subscriptionUpdate
.rejects(expected
);
923 await manager
.updateSubscription(res
, ctx
);
924 assert
.fail(noExpectedException
);
926 assert
.deepStrictEqual(e
, expected
);
929 }); // updateSubscription