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
);
64 describe('getHealthcheck', function () {
65 it('normal response', async
function () {
66 await manager
.getHealthcheck(res
, ctx
);
67 assert(res
.end
.called
);
71 describe('getInfo', function () {
72 it('requires query param', async
function() {
75 await manager
.getInfo(res
, ctx
);
76 assert
.fail(noExpectedException
);
78 assert
.strictEqual(e
.statusCode
, 400);
81 it('requires parsable query param', async
function() {
82 ctx
.queryParams
= { topic: 'not a url' };
84 await manager
.getInfo(res
, ctx
);
85 assert
.fail(noExpectedException
);
87 assert
.strictEqual(e
.statusCode
, 400);
90 it('does not find unhandled topic', async
function() {
91 ctx
.queryParams
= { topic: 'https://example.com/blog/' };
93 await manager
.getInfo(res
, ctx
);
94 assert
.fail(noExpectedException
);
96 assert
.strictEqual(e
.statusCode
, 404);
99 it('returns a count', async
function() {
100 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
102 topic: 'https://example.com/blog/',
104 await manager
.getInfo(res
, ctx
);
105 assert(res
.end
.called
);
107 it('returns a count as json', async
function() {
108 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
109 ctx
.responseType
= 'application/json';
111 topic: 'https://example.com/blog/',
113 await manager
.getInfo(res
, ctx
);
114 assert(res
.end
.called
);
116 it('returns a count as json as override format', async
function() {
117 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
118 ctx
.responseType
= 'text/html';
120 topic: 'https://example.com/blog/',
123 await manager
.getInfo(res
, ctx
);
124 assert(res
.end
.called
);
125 assert(res
.setHeader
.called
);
127 it('returns an svg badge as override format', async
function() {
128 manager
.db
.subscriptionCountByTopicUrl
.resolves({ count: 4 });
129 ctx
.responseType
= 'text/html';
131 topic: 'https://example.com/blog/',
134 await manager
.getInfo(res
, ctx
);
135 assert(res
.end
.called
);
136 assert(res
.setHeader
.called
);
140 describe('getAdminOverview', function () {
141 beforeEach(function () {
142 manager
.db
.topicGetAll
.resolves([
144 id: '56c557ce-e667-11eb-bd80-0025905f714a',
146 url: 'https://example.com/',
147 leaseSecondsPreferred: 123,
149 leaseSecondsMax: 123456789,
150 publisherValidationUrl: null,
151 contentHashAlgorithm: 'hashy',
154 lastPublish: new Date(-Infinity
),
155 contentFetchNextAttempt: undefined,
156 contentFetchAttemptsSinceSuccess: 3,
157 contentUpdated: new Date(0),
164 it('covers', async
function () {
165 await manager
.getAdminOverview(res
, ctx
);
166 assert(res
.end
.called
);
168 it('covers non-matching profile', async
function () {
170 authenticatedProfile: 'https://different.example.com/profile',
172 await manager
.getAdminOverview(res
, ctx
);
173 assert
.deepStrictEqual(ctx
.topics
, []);
174 assert(res
.end
.called
);
176 }); // getAdminOverview
178 describe('getTopicDetails', function () {
179 beforeEach(function () {
180 ctx
.params
.topicId
= '56c557ce-e667-11eb-bd80-0025905f714a';
181 manager
.db
.topicGetById
.resolves({
182 id: '56c557ce-e667-11eb-bd80-0025905f714a',
184 url: 'https://example.com/',
185 leaseSecondsPreferred: 123,
187 leaseSecondsMax: 123456789,
188 publisherValidationUrl: null,
189 contentHashAlgorithm: 'hashy',
192 lastPublish: new Date(-Infinity
),
193 contentFetchNextAttempt: undefined,
194 contentFetchAttemptsSinceSuccess: 3,
195 contentUpdated: new Date(0),
200 manager
.db
.subscriptionsByTopicId
.resolves([{
203 topicId: '56c557ce-e667-11eb-bd80-0025905f714a',
205 verified: new Date(),
208 signatureAlgorithm: 'hmacy',
211 contentDelivered: new Date(),
212 deliveryAttemptsSinceSuccess: 0,
213 deliveryNextAttempt: new Date(-Infinity
),
216 it('covers', async
function() {
217 await manager
.getTopicDetails(res
, ctx
);
218 assert(res
.end
.called
);
220 it('covers non-matching profile', async
function () {
222 authenticatedProfile: 'https://different.example.com/profile',
224 await manager
.getTopicDetails(res
, ctx
);
225 assert
.strictEqual(ctx
.topic
, null);
226 assert(res
.end
.called
);
228 it('covers matching profile', async
function () {
230 authenticatedProfile: 'https://example.com/profile',
232 await manager
.getTopicDetails(res
, ctx
);
234 assert(res
.end
.called
);
236 }); // getTopicDetails
238 describe('postRoot', function () {
239 let origProcessImmediately
;
240 beforeEach(function () {
241 origProcessImmediately
= manager
.options
.manager
.processImmediately
;
244 this.afterEach(function () {
245 manager
.options
.manager
.processImmediately
= origProcessImmediately
;
247 it('requires parameters', async
function () {
249 await manager
.postRoot(req
, res
, ctx
);
250 assert
.fail(noExpectedException
);
252 assert
.strictEqual(e
.message
, 'Bad Request');
255 it('accepts valid subscription', async
function () {
256 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
257 manager
.db
.topicGetByUrl
.resolves({
260 manager
.db
.verificationInsert
.resolves({
262 lastInsertRowid: undefined,
265 await manager
.postRoot(req
, res
, ctx
);
266 assert(manager
.db
.verificationInsert
.called
);
267 assert(res
.end
.called
);
269 it('accepts valid subscription without claiming work', async
function () {
270 manager
.options
.manager
.processImmediately
= false;
271 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
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(!manager
.communication
.verificationProcess
.called
);
283 assert(res
.end
.called
);
285 it('accepts valid subscription, covers processVerification failure', async
function () {
286 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
287 manager
.communication
.verificationProcess
.rejects('failed');
288 manager
.db
.topicGetByUrl
.resolves({
291 manager
.db
.verificationInsert
.resolves({
293 lastInsertRowid: undefined,
296 await manager
.postRoot(req
, res
, ctx
);
297 assert(manager
.db
.verificationInsert
.called
);
298 assert(res
.end
.called
);
299 assert(manager
.logger
.error
.called
);
301 it('covers db.verificationInsert failure', async
function () {
302 const expectedException
= new Error('failure');
303 ctx
= Object
.assign({}, testData
.validSubscribeCtx
);
304 manager
.db
.topicGetByUrl
.resolves({
307 manager
.db
.verificationInsert
.rejects(expectedException
);
308 assert
.rejects(async () => {
309 await manager
.postRoot(req
, res
, ctx
);
310 }, expectedException
);
312 it('accepts valid unsubscription', async
function () {
313 ctx
= Object
.assign({}, testData
.validUnsubscribeCtx
);
314 manager
.db
.topicGetByUrl
.resolves({
317 manager
.db
.subscriptionGet
.resolves({
320 manager
.db
.verificationInsert
.resolves({
322 lastInsertRowid: undefined,
325 await manager
.postRoot(req
, res
, ctx
);
326 assert(res
.end
.called
);
328 it('accepts valid publish', async
function () {
329 ctx
= Object
.assign({}, testData
.validPublishCtx
);
330 manager
.db
.topicGetByUrl
.resolves({
333 manager
.db
.topicFetchRequested
.resolves({
335 lastInsertRowid: undefined,
338 await manager
.postRoot(req
, res
, ctx
);
339 assert(res
.end
.called
);
343 describe('_getRootData', function () {
344 it('extracts expected values', function () {
345 req
.getHeader
.returns('user@example.com');
346 ctx
= Object
.assign({}, testData
.validSubscribeCtx
)
347 const result
= Manager
._getRootData(req
, ctx
);
348 assert
.deepStrictEqual(result
, testData
.validRootData
);
352 describe('_validateRootData', function () {
353 // This only wraps the other _check functions, not bothering with coverage.
354 }); // _validateRootData
356 describe('_checkTopic', function () {
357 let dbCtx
, data
, warn
, err
;
360 leaseSecondsPreferred: 86400 * 10,
361 leaseSecondsMax: 86400 * 20,
362 leaseSecondsMin: 86400,
364 beforeEach(function () {
370 it('succeeds', async
function () {
372 topic: 'http://example.com/blog',
374 manager
.db
.topicGetByUrl
.resolves(topic
);
375 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
376 assert
.strictEqual(warn
.length
, 0, warn
);
377 assert
.strictEqual(err
.length
, 0, err
);
378 assert
.strictEqual(data
.topicId
, 111);
379 assert
.strictEqual(data
.leaseSeconds
, 864000);
381 it('errors on unknown topic', async
function () {
382 manager
.db
.topicGetByUrl
.resolves();
383 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
384 assert
.strictEqual(warn
.length
, 0, warn
);
385 assert
.strictEqual(err
.length
, 1, err
);
387 it('warns on lease under min range', async
function () {
389 topic: 'http://example.com/blog',
392 manager
.db
.topicGetByUrl
.resolves(topic
);
393 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
394 assert
.strictEqual(warn
.length
, 1, warn
);
395 assert
.strictEqual(err
.length
, 0, err
);
396 assert
.strictEqual(data
.topicId
, 111);
397 assert
.strictEqual(data
.leaseSeconds
, 86400);
399 it('warns on lease over max range', async
function () {
401 topic: 'http://example.com/blog',
402 leaseSeconds: 86400 * 100,
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 * 20);
411 it('sets publisher validation state when available', async
function () {
413 topic: 'http://example.com/blog',
415 manager
.db
.topicGetByUrl
.resolves(Object
.assign({}, topic
, {
416 publisherValidationUrl: 'http://example.com/validate',
418 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
419 assert
.strictEqual(warn
.length
, 0, warn
);
420 assert
.strictEqual(err
.length
, 0, err
);
421 assert
.strictEqual(data
.topicId
, 111);
422 assert
.strictEqual(data
.leaseSeconds
, 864000);
423 assert
.strictEqual(data
.isPublisherValidated
, false);
425 it('accepts new public subscribe topic', async
function () {
426 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves(topic
);
428 topic: 'http://example.com/blog',
430 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
431 assert
.strictEqual(warn
.length
, 0, 'unexpected warnings length');
432 assert
.strictEqual(err
.length
, 0, 'unexpected errors length');
433 assert
.strictEqual(data
.topicId
, 111, 'unexpected topic id');
435 it('does not accept new public subscribe for invalid topic', async
function () {
436 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves(topic
);
438 topic: 'not a topic',
440 await manager
._checkTopic(dbCtx
, data
, warn
, err
);
441 assert
.strictEqual(warn
.length
, 0, 'unexpected warnings length');
442 assert
.strictEqual(err
.length
, 1, 'unexpected errors length');
446 describe('_checkCallbackAndSecrets', function () {
448 let origStrictSecrets
;
450 origStrictSecrets
= manager
.options
.manager
.strictSecrets
;
452 beforeEach(function () {
457 afterEach(function () {
458 manager
.options
.manager
.strictSecrets
= origStrictSecrets
;
460 it('succeeds', function () {
462 callback: 'https://example.com/callback',
466 manager
._checkCallbackAndSecrets(data
, warn
, err
);
467 assert
.strictEqual(warn
.length
, 0, warn
);
468 assert
.strictEqual(err
.length
, 0, err
);
470 it('errors with invalid callback', function () {
472 callback: 'not a url',
476 manager
._checkCallbackAndSecrets(data
, warn
, err
);
477 assert
.strictEqual(warn
.length
, 0, warn
);
478 assert
.strictEqual(err
.length
, 1, err
);
480 it('errors when secret too large', function () {
482 callback: 'https://example.com/callback',
483 secret: 'x'.repeat(256),
486 manager
._checkCallbackAndSecrets(data
, warn
, err
);
487 assert
.strictEqual(warn
.length
, 0, warn
);
488 assert
.strictEqual(err
.length
, 1, err
);
490 it('warns when callback is insecure', function () {
492 callback: 'http://example.com/callback',
495 manager
._checkCallbackAndSecrets(data
, warn
, err
);
496 assert
.strictEqual(warn
.length
, 1, warn
);
497 assert
.strictEqual(err
.length
, 0, err
);
499 it('warns when hub is insecure with secret', function () {
501 callback: 'https://example.com/callback',
505 manager
._checkCallbackAndSecrets(data
, warn
, err
);
506 assert
.strictEqual(warn
.length
, 1, warn
);
507 assert
.strictEqual(err
.length
, 0, err
);
509 it('errors when callback is insecure with secret and strict', function () {
510 manager
.options
.manager
.strictSecrets
= true;
512 callback: 'http://example.com/callback',
516 manager
._checkCallbackAndSecrets(data
, warn
, err
);
517 assert
.strictEqual(warn
.length
, 1, warn
);
518 assert
.strictEqual(err
.length
, 1, err
);
520 }); // _checkCallbackAndSecrets
522 describe('_checkMode', function () {
523 let dbCtx
, data
, warn
, err
;
524 beforeEach(function () {
530 it('subscribe succeeds', async
function () {
534 await manager
._checkMode(dbCtx
, data
, warn
, err
);
535 assert
.strictEqual(warn
.length
, 0);
536 assert
.strictEqual(err
.length
, 0);
538 it('unsubscribe succeeds', async
function () {
541 callback: 'http://example.com',
544 manager
.db
.subscriptionGet
.resolves({
545 expires: (Date
.now() / 1000) + 60,
547 await manager
._checkMode(dbCtx
, data
, warn
, err
);
548 assert
.strictEqual(warn
.length
, 0, warn
);
549 assert
.strictEqual(err
.length
, 0, err
);
551 it('unsubscribe requires valid data', async
function () {
554 callback: 'http://example.com',
557 manager
.db
.subscriptionGet
.resolves({
558 expires: (Date
.now() / 1000) - 60,
560 await manager
._checkMode(dbCtx
, data
, warn
, err
);
561 assert
.strictEqual(warn
.length
, 0, warn
);
562 assert
.strictEqual(err
.length
, 1, err
);
564 it('unsubscribe ignores expired subscription', async
function () {
567 callback: 'http://example.com',
570 manager
.db
.subscriptionGet
.resolves({
571 expires: (Date
.now() / 1000) - 60,
573 await manager
._checkMode(dbCtx
, data
, warn
, err
);
574 assert
.strictEqual(warn
.length
, 0, warn
);
575 assert
.strictEqual(err
.length
, 1, err
);
579 describe('_publishTopics', function () {
580 let dbCtx
, data
, requestId
;
581 beforeEach(function () {
586 it('succeeds', async
function () {
587 manager
.db
.topicGetByUrl
.resolves({
590 Object
.assign(data
, testData
.validPublishRootData
);
591 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
592 assert
.strictEqual(topicResults
.length
, 1);
593 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
594 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
595 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
597 it('fails bad url', async
function () {
598 Object
.assign(data
, testData
.validPublishRootData
, { topic: 'not_a_url' });
599 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
600 assert
.strictEqual(topicResults
.length
, 1);
601 assert
.strictEqual(topicResults
[0].err
.length
, 1, 'unexpected errors length');
602 assert
.strictEqual(topicResults
[0].warn
.length
, 0);
604 it('accepts new public publish topic', async
function () {
605 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
608 Object
.assign(data
, testData
.validPublishRootData
);
609 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
610 assert
.strictEqual(topicResults
.length
, 1);
611 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
612 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
613 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
615 it('does not publish deleted topic', async
function () {
616 manager
.db
.topicGetByUrl
.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
, 1, 'unexpected errors length');
625 assert
.strictEqual(topicResults
[0].topicId
, undefined, 'unexpected topic id');
627 it('no topics', async
function() {
628 Object
.assign(data
, testData
.validPublishRootData
);
630 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
631 assert
.strictEqual(topicResults
.length
, 0);
633 it('multiple valid topics', async
function () {
634 manager
.db
.topicGetByUrl
.resolves({
637 Object
.assign(data
, testData
.validPublishRootData
);
638 data
.url
= ['https://example.com/first', 'https://example.com/second'];
639 data
.topic
= ['https://example.com/third'];
640 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
641 assert
.strictEqual(topicResults
.length
, 3);
642 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
643 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
644 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
645 assert
.strictEqual(topicResults
[1].warn
.length
, 0, 'unexpected warnings length');
646 assert
.strictEqual(topicResults
[1].err
.length
, 0, 'unexpected errors length');
647 assert
.strictEqual(topicResults
[1].topicId
, 222, 'unexpected topic id');
648 assert
.strictEqual(topicResults
[2].warn
.length
, 0, 'unexpected warnings length');
649 assert
.strictEqual(topicResults
[2].err
.length
, 0, 'unexpected errors length');
650 assert
.strictEqual(topicResults
[2].topicId
, 222, 'unexpected topic id');
652 it('mix of valid and invalid topics', async
function () {
653 manager
.db
.topicGetByUrl
.onCall(1).resolves().resolves({
656 Object
.assign(data
, testData
.validPublishRootData
);
657 data
.url
= ['https://example.com/first', 'not a url'];
658 data
.topic
= ['https://example.com/third'];
659 const topicResults
= await manager
._publishTopics(dbCtx
, data
, requestId
);
660 assert
.strictEqual(topicResults
.length
, 3);
661 assert
.strictEqual(topicResults
[0].warn
.length
, 0, 'unexpected warnings length');
662 assert
.strictEqual(topicResults
[0].err
.length
, 0, 'unexpected errors length');
663 assert
.strictEqual(topicResults
[0].topicId
, 222, 'unexpected topic id');
664 assert
.strictEqual(topicResults
[1].warn
.length
, 0, 'unexpected warnings length');
665 assert
.strictEqual(topicResults
[1].err
.length
, 1, 'unexpected errors length');
666 assert
.strictEqual(topicResults
[1].topicId
, undefined, 'unexpected topic id');
667 assert
.strictEqual(topicResults
[2].warn
.length
, 0, 'unexpected warnings length');
668 assert
.strictEqual(topicResults
[2].err
.length
, 0, 'unexpected errors length');
669 assert
.strictEqual(topicResults
[2].topicId
, 222, 'unexpected topic id');
671 }); // _publishTopics
673 describe('_publishRequest', function () {
674 let dbCtx
, data
, res
, ctx
;
675 beforeEach(function () {
683 it('requires a topic', async
function () {
685 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
686 assert
.fail(noExpectedException
);
688 assert(e
instanceof Errors
.ResponseError
);
691 it('processes one topic', async
function() {
692 manager
.db
.topicGetByUrl
.resolves({
695 Object
.assign(data
, testData
.validPublishRootData
);
696 manager
.db
.topicFetchRequested
.resolves();
697 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
698 assert(manager
.db
.topicFetchRequested
.called
);
699 assert
.strictEqual(res
.statusCode
, 202);
700 assert(res
.end
.called
);
702 it('processes mix of valid and invalid topics', async
function () {
703 ctx
.responseType
= 'application/json';
704 manager
.db
.topicGetByUrl
.onCall(1).resolves().resolves({
707 Object
.assign(data
, testData
.validPublishRootData
);
708 data
.url
= ['https://example.com/first', 'not a url'];
709 data
.topic
= ['https://example.com/third'];
710 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
711 assert
.strictEqual(res
.statusCode
, 207);
712 assert(res
.end
.called
);
714 it('covers topicFetchRequest failure', async
function () {
715 manager
.db
.topicGetByUrl
.resolves({
718 Object
.assign(data
, testData
.validPublishRootData
);
719 const expected
= new Error('boo');
720 manager
.db
.topicFetchRequested
.rejects(expected
);
722 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
723 assert
.fail(noExpectedException
);
725 assert
.deepStrictEqual(e
, expected
);
728 it('covers immediate processing error', async
function() {
729 manager
.options
.manager
.processImmediately
= true;
730 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
733 manager
.communication
.topicFetchClaimAndProcessById
.rejects();
734 Object
.assign(data
, testData
.validPublishRootData
);
735 await manager
._publishRequest(dbCtx
, data
, res
, ctx
);
736 assert(manager
.db
.topicFetchRequested
.called
);
737 assert
.strictEqual(res
.statusCode
, 202);
738 assert(res
.end
.called
);
739 assert(manager
.communication
.topicFetchClaimAndProcessById
.called
)
741 it('covers no immediate processing', async
function() {
742 manager
.options
.manager
.processImmediately
= false;
743 manager
.db
.topicGetByUrl
.onCall(0).resolves().onCall(1).resolves({
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 }); // _publishRequest
755 describe('multiPublishContent', function () {
757 beforeEach(function () {
759 url: 'https://example.com/first',
764 statusMessage: 'Accepted',
769 err: [ 'invalid topic url (failed to parse url)' ],
772 statusMessage: 'Bad Request',
775 it('covers json response', function () {
776 ctx
.responseType
= 'application/json';
777 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":[]}]';
778 const result
= Manager
.multiPublishContent(ctx
, publishTopics
);
779 assert
.deepStrictEqual(result
, expected
);
781 it('covers text response', function () {
782 ctx
.responseType
= 'text/plain';
783 const expected
= `https://example.com/first [202 Accepted]
785 not a url [400 Bad Request]
786 \terror: invalid topic url (failed to parse url)`;
787 const result
= Manager
.multiPublishContent(ctx
, publishTopics
);
788 assert
.deepStrictEqual(result
, expected
);
790 }); // multiPublishContent
792 describe('processTasks', function () {
793 it('covers', async
function () {
794 sinon
.stub(manager
.communication
.worker
, 'process').resolves();
795 await manager
.processTasks(res
, ctx
);
796 assert(manager
.communication
.worker
.process
.called
);
797 assert(res
.end
.called
);
799 it('covers error', async
function () {
800 sinon
.stub(manager
.communication
.worker
, 'process').rejects();
801 await manager
.processTasks(res
, ctx
);
802 assert(manager
.communication
.worker
.process
.called
);
803 assert(res
.end
.called
);
807 describe('updateTopic', function () {
808 it('fails if no topic exists', async
function () {
810 await manager
.updateTopic(res
, ctx
);
811 assert
.fail(noExpectedException
);
813 assert(e
instanceof Errors
.ResponseError
);
816 it('deletes', async
function () {
817 ctx
.method
= 'DELETE';
818 manager
.db
.topicGetById
.resolves({});
819 await manager
.updateTopic(res
, ctx
);
820 assert(manager
.db
.topicDeleted
.called
);
822 it('does not patch without data', async
function () {
823 manager
.db
.topicGetById
.resolves({});
824 await manager
.updateTopic(res
, ctx
);
825 assert(!manager
.db
.topicUpdate
.called
);
826 assert
.strictEqual(res
.statusCode
, 204);
828 it('does not patch with same data', async
function () {
829 manager
.db
.topicGetById
.resolves({
830 leaseSecondsPreferred: '86400',
833 leaseSecondsPreferred: '86400',
835 await manager
.updateTopic(res
, ctx
);
836 assert(!manager
.db
.topicUpdate
.called
);
837 assert
.strictEqual(res
.statusCode
, 204);
839 it('patches', async
function () {
841 leaseSecondsPreferred: '86400',
843 manager
.db
.topicGetById
.resolves({});
844 await manager
.updateTopic(res
, ctx
);
845 assert(manager
.db
.topicUpdate
.called
);
847 it('handles validation error', async
function () {
849 leaseSecondsPreferred: 'blorp',
851 manager
.db
.topicGetById
.resolves({});
852 manager
.db
.topicUpdate
.rejects(new DBErrors
.DataValidation('something'));
854 await manager
.updateTopic(res
, ctx
);
855 assert
.fail(noExpectedException
);
857 assert(e
instanceof Errors
.ResponseError
);
858 assert
.strictEqual(e
.statusCode
, 400);
861 it('handles generic error', async
function () {
862 const expected
= new Error('blah');
864 leaseSecondsPreferred: '123',
866 manager
.db
.topicGetById
.resolves({});
867 manager
.db
.topicUpdate
.rejects(expected
);
869 await manager
.updateTopic(res
, ctx
);
870 assert
.fail(noExpectedException
);
872 assert
.deepStrictEqual(e
, expected
);
877 describe('updateSubscription', function () {
878 it('fails if no subscription exists', async
function () {
880 await manager
.updateSubscription(res
, ctx
);
881 assert
.fail(noExpectedException
);
883 assert(e
instanceof Errors
.ResponseError
);
886 it('deletes', async
function () {
887 ctx
.method
= 'DELETE';
888 manager
.db
.subscriptionGetById
.resolves({});
889 await manager
.updateSubscription(res
, ctx
);
890 assert(manager
.db
.verificationInsert
.called
);
892 it('does not patch without data', async
function () {
893 manager
.db
.subscriptionGetById
.resolves({});
894 await manager
.updateSubscription(res
, ctx
);
895 assert(!manager
.db
.subscriptionUpdate
.called
);
896 assert
.strictEqual(res
.statusCode
, 204);
898 it('does not patch with same data', async
function () {
899 manager
.db
.subscriptionGetById
.resolves({
900 signatureAlgorithm: 'sha256',
903 signatureAlgorithm: 'sha256',
905 await manager
.updateSubscription(res
, ctx
);
906 assert(!manager
.db
.subscriptionUpdate
.called
);
907 assert
.strictEqual(res
.statusCode
, 204);
909 it('patches', async
function () {
911 signatureAlgorithm: 'sha256',
913 manager
.db
.subscriptionGetById
.resolves({});
914 await manager
.updateSubscription(res
, ctx
);
915 assert(manager
.db
.subscriptionUpdate
.called
);
917 it('handles validation error', async
function () {
919 signatureAlgorithm: 123,
921 manager
.db
.subscriptionGetById
.resolves({});
922 manager
.db
.subscriptionUpdate
.rejects(new DBErrors
.DataValidation('something'));
924 await manager
.updateSubscription(res
, ctx
);
925 assert
.fail(noExpectedException
);
927 assert(e
instanceof Errors
.ResponseError
);
928 assert
.strictEqual(e
.statusCode
, 400);
931 it('handles generic error', async
function () {
932 const expected
= new Error('blah');
934 signatureAlgorithm: 'blorp',
936 manager
.db
.subscriptionGetById
.resolves({});
937 manager
.db
.subscriptionUpdate
.rejects(expected
);
939 await manager
.updateSubscription(res
, ctx
);
940 assert
.fail(noExpectedException
);
942 assert
.deepStrictEqual(e
, expected
);
945 }); // updateSubscription