update dependencies and devDependencies, fix lint issues
[websub-hub] / test / src / db / postgres.js
1 'use strict';
2
3 /* This provides implementation coverage, stubbing pg-promise. */
4
5 const assert = require('node:assert');
6 const sinon = require('sinon');
7 const DBStub = require('../../stub-db');
8 const stubLogger = require('../../stub-logger');
9 const DB = require('../../../src/db/postgres');
10 const DBErrors = require('../../../src/db/errors');
11 const common = require('../../../src/common');
12 const Config = require('../../../config');
13
14 const noExpectedException = 'did not receive expected exception';
15
16 describe('DatabasePostgres', function () {
17 let db, options, pgpStub;
18 let dbCtx, claimant, claimTimeoutSeconds, callback, subscriptionId, topicId, verificationId;
19 let topicUrl, leaseSeconds, secret, httpRemoteAddr, httpFrom, retryDelays, wanted;
20 before(function () {
21 pgpStub = () => {
22 const stub = {
23 result: () => ({ rows: [] }),
24 all: common.nop,
25 get: common.nop,
26 run: common.nop,
27 one: common.nop,
28 manyOrNone: common.nop,
29 oneOrNone: common.nop,
30 query: common.nop,
31 batch: common.nop,
32 multiResult: common.nop,
33 connect: common.nop,
34 };
35 stub.tx = (fn) => fn(stub);
36 stub.txIf = (fn) => fn(stub);
37 stub.task = (fn) => fn(stub);
38 return stub;
39 };
40 pgpStub.utils = {
41 enumSql: () => ({}),
42 };
43 pgpStub.QueryFile = class {};
44 pgpStub.end = common.nop,
45 options = new Config('test');
46 db = new DB(stubLogger, options, pgpStub);
47 });
48 beforeEach(function () {
49 stubLogger._reset();
50 dbCtx = db.db;
51 claimant = '19af19b8-6be3-4a6f-8946-65f5f1ccc5d7';
52 claimTimeoutSeconds = 300;
53 subscriptionId = 'fbaf8f19-ed9c-4a21-89ae-98b7005e3bf6';
54 topicUrl = 'https://example.com/blog';
55 callback = 'https://example.com/callback?id=123';
56 topicId = 'c59d4bda-10ad-41d9-99df-4ce8bc331424';
57 verificationId = '55cd7748-d2d5-11eb-b355-0025905f714a';
58 retryDelays = [60];
59 leaseSeconds = 86400;
60 secret = 'secret';
61 httpRemoteAddr = '127.0.0.1';
62 httpFrom = 'user@example.com';
63 wanted = 5;
64 });
65 afterEach(function () {
66 sinon.restore();
67 });
68
69 it('covers listener', function () {
70 const listenerOptions = new Config('test');
71 listenerOptions.db.cacheEnabled = true;
72 const listenerDb = new DB(stubLogger, listenerOptions, pgpStub);
73 assert(listenerDb);
74 });
75
76 // Ensure all interface methods are implemented
77 describe('Implementation', function () {
78 it('implements interface', async function () {
79 const results = await Promise.allSettled(DBStub._implementation.map(async (fn) => {
80 try {
81 // eslint-disable-next-line security/detect-object-injection
82 await db[fn](db.db);
83 } catch (e) {
84 assert(!(e instanceof DBErrors.NotImplemented), `${fn} not implemented`);
85 }
86 }));
87 const failures = results.filter((x) => x.status === 'rejected');
88 assert(!failures.length, failures.map((x) => {
89 x = x.reason.toString();
90 return x.slice(x.indexOf(': '));
91 }));
92 });
93 }); // Implementation
94
95 describe('pgpInitOptions', function () {
96 describe('error', function () {
97 it('covers', function () {
98 const err = {};
99 const event = {};
100 db.pgpInitOptions.error(err, event);
101 assert(db.logger.error.called);
102 });
103 }); // error
104 describe('query', function () {
105 it('covers', function () {
106 const event = {};
107 db.pgpInitOptions.query(event);
108 assert(db.logger.debug.called);
109 });
110 it('covers NOTIFY', function () {
111 const event = { query: 'NOTIFY thing' };
112 db.pgpInitOptions.query(event);
113 assert(!db.logger.debug.called);
114 });
115 }); // query
116 describe('receive', function () {
117 it('covers', function () {
118 const data = [
119 {
120 column_one: 'one', // eslint-disable-line camelcase
121 column_two: 2, // eslint-disable-line camelcase
122 },
123 {
124 column_one: 'foo', // eslint-disable-line camelcase
125 column_two: 4, // eslint-disable-line camelcase
126 },
127 ];
128 const result = {};
129 const event = {};
130 const expectedData = [
131 {
132 columnOne: 'one',
133 columnTwo: 2,
134 },
135 {
136 columnOne: 'foo',
137 columnTwo: 4,
138 },
139 ];
140 db.pgpInitOptions.receive({ data, result, ctx: event });
141 assert(db.logger.debug.called);
142 assert.deepStrictEqual(data, expectedData);
143 });
144 it('covers NOTIFY', function () {
145 const data = [
146 {
147 column_one: 'one', // eslint-disable-line camelcase
148 column_two: 2, // eslint-disable-line camelcase
149 },
150 {
151 column_one: 'foo', // eslint-disable-line camelcase
152 column_two: 4, // eslint-disable-line camelcase
153 },
154 ];
155 const result = {
156 command: 'NOTIFY',
157 };
158 const event = {};
159 const expectedData = [
160 {
161 columnOne: 'one',
162 columnTwo: 2,
163 },
164 {
165 columnOne: 'foo',
166 columnTwo: 4,
167 },
168 ];
169 db.pgpInitOptions.receive({ data, result, ctx: event });
170 assert(!db.logger.debug.called);
171 assert.deepStrictEqual(data, expectedData);
172 });
173 }); // receive
174 }); // pgpInitOptions
175
176 describe('_initTables', function () {
177 beforeEach(function () {
178 sinon.stub(db.db, 'oneOrNone');
179 sinon.stub(db.db, 'multiResult');
180 sinon.stub(db, '_currentSchema');
181 });
182
183 it('covers apply', async function() {
184 db.db.oneOrNone.onCall(0).resolves(null).onCall(1).resolves({});
185 db._currentSchema.resolves({ major: 0, minor: 0, patch: 0 });
186 await db._initTables();
187 });
188 it('covers exists', async function() {
189 db.db.oneOrNone.resolves({});
190 db._currentSchema.resolves(db.schemaVersionsSupported.max);
191 await db._initTables();
192 });
193 }); // _initTables
194
195 describe('initialize', function () {
196 after(function () {
197 delete db.listener;
198 });
199 it('passes supported version', async function () {
200 const version = { major: 1, minor: 0, patch: 0 };
201 sinon.stub(db.db, 'one').resolves(version);
202 await db.initialize(false);
203 });
204 it('fails low version', async function () {
205 const version = { major: 0, minor: 0, patch: 0 };
206 sinon.stub(db.db, 'one').resolves(version);
207 try {
208 await db.initialize(false);
209 assert.fail(noExpectedException);
210 } catch (e) {
211 assert(e instanceof DBErrors.MigrationNeeded);
212 }
213 });
214 it('fails high version', async function () {
215 const version = { major: 100, minor: 100, patch: 100 };
216 sinon.stub(db.db, 'one').resolves(version);
217 try {
218 await db.initialize(false);
219 assert.fail(noExpectedException);
220 } catch (e) {
221 assert(e instanceof DBErrors.MigrationNeeded);
222 }
223 });
224 it('covers migration', async function() {
225 sinon.stub(db.db, 'oneOrNone').resolves({});
226 sinon.stub(db.db, 'multiResult').resolves({});
227 sinon.stub(db, '_currentSchema').resolves(db.schemaVersionsSupported.min);
228 sinon.stub(db.db, 'one').resolves(db.schemaVersionsSupported.max);
229 await db.initialize();
230 });
231 it('covers migration failure', async function() {
232 const expected = new Error('oh no');
233 sinon.stub(db.db, 'oneOrNone').resolves({});
234 sinon.stub(db.db, 'multiResult').rejects(expected);
235 sinon.stub(db, '_currentSchema').resolves(db.schemaVersionsSupported.min);
236 sinon.stub(db.db, 'one').resolves(db.schemaVersionsSupported.max);
237 try {
238 await db.initialize();
239 assert.fail(noExpectedException);
240 } catch (e) {
241 assert.deepStrictEqual(e, expected);
242 }
243 });
244 it('covers listener', async function() {
245 db.listener = {
246 start: sinon.stub(),
247 };
248 const version = { major: 1, minor: 0, patch: 0 };
249 sinon.stub(db.db, 'one').resolves(version);
250 await db.initialize(false);
251 assert(db.listener.start.called);
252 });
253 }); // initialize
254
255 describe('healthCheck', function () {
256 beforeEach(function () {
257 sinon.stub(db.db, 'connect').resolves({
258 done: () => {},
259 client: {
260 serverVersion: '0.0',
261 },
262 });
263 });
264 it('covers', async function () {
265 const result = await db.healthCheck();
266 assert.deepStrictEqual(result, { serverVersion: '0.0' });
267 });
268 }); // healthCheck
269
270 describe('_queryFileHelper', function () {
271 it('covers success', function () {
272 const _queryFile = db._queryFileHelper(pgpStub);
273 _queryFile();
274 });
275 it('covers failure', function () {
276 const err = new Error();
277 pgpStub.QueryFile = class {
278 constructor() {
279 this.error = err;
280 }
281 };
282 const _queryFile = db._queryFileHelper(pgpStub);
283 try {
284 _queryFile();
285 assert.fail(noExpectedException);
286 } catch (e) {
287 assert.strictEqual(e, err);
288 }
289 });
290 }); // _queryFileHelper
291
292 describe('_closeConnection', function () {
293 after(function () {
294 delete db.listener;
295 });
296 it('success', async function () {
297 sinon.stub(db._pgp, 'end');
298 await db._closeConnection();
299 assert(db._pgp.end.called);
300 });
301 it('failure', async function () {
302 const expected = new Error();
303 sinon.stub(db._pgp, 'end').throws(expected);
304 try {
305 await db._closeConnection();
306 assert.fail(noExpectedException);
307 } catch (e) {
308 assert.deepStrictEqual(e, expected);
309 }
310 });
311 it('covers listener', async function () {
312 db.listener = {
313 stop: sinon.stub(),
314 };
315 sinon.stub(db._pgp, 'end');
316 await db._closeConnection();
317 assert(db._pgp.end.called);
318 });
319 }); // _closeConnection
320
321 describe('_purgeTables', function () {
322 it('covers not really', async function () {
323 sinon.stub(db.db, 'tx');
324 await db._purgeTables(false);
325 assert(!db.db.tx.called);
326 });
327 it('success', async function () {
328 sinon.stub(db.db, 'batch');
329 await db._purgeTables(true);
330 assert(db.db.batch.called);
331 });
332 it('failure', async function () {
333 const expected = new Error();
334 sinon.stub(db.db, 'tx').rejects(expected);
335 try {
336 await db._purgeTables(true);
337 assert.fail(noExpectedException);
338 } catch (e) {
339 assert.deepStrictEqual(e, expected);
340 }
341 });
342 }); // _purgeTables
343
344 describe('_topicChanged', function () {
345 beforeEach(function () {
346 db.cache = new Map();
347 sinon.stub(db.cache, 'delete');
348 });
349 after(function () {
350 delete db.cache;
351 });
352 it('covers', function () {
353 db._topicChanged('topic-id');
354 assert(db.cache.delete.called);
355 });
356 it('ignores ping', function () {
357 db._topicChanged('ping');
358 assert(!db.cache.delete.called);
359 });
360 }); // _topicChanged
361
362 describe('_listenerEstablished', function () {
363 it('creates cache', function () {
364 delete db.cache;
365 db._listenerEstablished();
366 assert(db.cache instanceof Map);
367 });
368 }); // _listenerEstablished
369
370 describe('_listenerLost', function () {
371 it('removes cache', function () {
372 db.cache = new Map();
373 db._listenerLost();
374 assert(!db.cache);
375 });
376 }); // _listenerLost
377
378 describe('_cacheGet', function () {
379 let key;
380 beforeEach(function () {
381 key = 'key';
382 });
383 it('nothing if no cache', function () {
384 delete db.cache;
385 const result = db._cacheGet(key);
386 assert.strictEqual(result, undefined);
387 });
388 it('nothing if no entry', function () {
389 db.cache = new Map();
390 const result = db._cacheGet(key);
391 assert.strictEqual(result, undefined);
392 });
393 it('returns cached entry', function () {
394 db.cache = new Map();
395 const expected = {
396 foo: 'bar',
397 };
398 db._cacheSet(key, expected);
399 const result = db._cacheGet(key);
400 assert.deepStrictEqual(result, expected);
401 });
402 }); // _cacheGet
403
404 describe('_cacheSet', function () {
405 let key;
406 beforeEach(function () {
407 key = 'key';
408 });
409 it('covers no cache', function () {
410 delete db.cache;
411 db._cacheSet(key, 'data');
412 });
413 it('covers cache', function () {
414 db.cache = new Map();
415 const expected = 'blah';
416 db._cacheSet(key, expected);
417 const result = db._cacheGet(key);
418 assert.deepStrictEqual(result, expected);
419 });
420 }); // _cacheSet
421
422 describe('context', function () {
423 it('covers', async function () {
424 await db.context(common.nop);
425 });
426 }); // context
427
428 describe('transaction', function () {
429 it('covers', async function () {
430 await db.transaction(db.db, common.nop);
431 });
432 }); // transaction
433
434 describe('authenticationSuccess', function () {
435 let identifier;
436 beforeEach(function () {
437 identifier = 'username';
438 });
439 it('success', async function () {
440 const dbResult = {
441 rowCount: 1,
442 rows: undefined,
443 duration: 22,
444 };
445 sinon.stub(db.db, 'result').resolves(dbResult);
446 await db.authenticationSuccess(dbCtx, identifier);
447 });
448 it('failure', async function() {
449 const dbResult = {
450 rowCount: 0,
451 rows: undefined,
452 duration: 22,
453 };
454 sinon.stub(db.db, 'result').resolves(dbResult);
455 try {
456 await db.authenticationSuccess(dbCtx, identifier);
457 assert.fail(noExpectedException);
458 } catch (e) {
459 assert(e instanceof DBErrors.UnexpectedResult);
460 }
461 });
462 }); // authenticationSuccess
463
464 describe('authenticationGet', function () {
465 let identifier, credential;
466 beforeEach(function () {
467 identifier = 'username';
468 credential = '$z$foo';
469 });
470 it('success', async function () {
471 const dbResult = { identifier, credential };
472 sinon.stub(db.db, 'oneOrNone').resolves(dbResult);
473 const result = await db.authenticationGet(dbCtx, identifier);
474 assert.deepStrictEqual(result, dbResult);
475 });
476 it('failure', async function() {
477 const expected = new Error('blah');
478 sinon.stub(db.db, 'oneOrNone').rejects(expected);
479 try {
480 await db.authenticationGet(dbCtx, identifier, credential);
481 assert.fail(noExpectedException);
482 } catch (e) {
483 assert.deepStrictEqual(e, expected);
484 }
485 });
486 }); // authenticationGet
487
488 describe('authenticationUpsert', function () {
489 let identifier, credential;
490 beforeEach(function () {
491 identifier = 'username';
492 credential = '$z$foo';
493 });
494 it('success', async function () {
495 const dbResult = {
496 rowCount: 1,
497 rows: undefined,
498 duration: 22,
499 };
500 sinon.stub(db.db, 'result').resolves(dbResult);
501 await db.authenticationUpsert(dbCtx, identifier, credential);
502 });
503 it('failure', async function() {
504 credential = undefined;
505 const dbResult = {
506 rowCount: 0,
507 rows: undefined,
508 duration: 22,
509 };
510 sinon.stub(db.db, 'result').resolves(dbResult);
511 try {
512 await db.authenticationUpsert(dbCtx, identifier, credential);
513 assert.fail(noExpectedException);
514 } catch (e) {
515 assert(e instanceof DBErrors.UnexpectedResult);
516 }
517 });
518 }); // authenticationUpsert
519
520 describe('subscriptionsByTopicId', function () {
521 it('success', async function () {
522 const expected = [];
523 sinon.stub(db.db, 'manyOrNone').resolves(expected);
524 const result = await db.subscriptionsByTopicId(dbCtx, topicUrl);
525 assert.deepStrictEqual(result, expected);
526 });
527 it('failure', async function () {
528 const expected = new Error();
529 sinon.stub(db.db, 'manyOrNone').throws(expected);
530 try {
531 await db.subscriptionsByTopicId(dbCtx, topicUrl);
532 assert.fail(noExpectedException);
533 } catch (e) {
534 assert.deepStrictEqual(e, expected);
535 }
536 });
537 }); // subscriptionsByTopicId
538
539 describe('subscriptionCountByTopicUrl', function () {
540 it('success', async function () {
541 const expected = { count: 3 };
542 sinon.stub(db.db, 'one').resolves(expected);
543 const result = await db.subscriptionCountByTopicUrl(dbCtx, topicUrl);
544 assert.deepStrictEqual(result, expected);
545 });
546 it('failure', async function () {
547 const expected = new Error();
548 sinon.stub(db.db, 'one').throws(expected);
549 try {
550 await db.subscriptionCountByTopicUrl(dbCtx, topicUrl);
551 assert.fail(noExpectedException);
552 } catch (e) {
553 assert.deepStrictEqual(e, expected);
554 }
555 });
556 }); // subscriptionCountByTopicUrl
557
558 describe('subscriptionDelete', function () {
559 it('success', async function() {
560 const dbResult = {
561 rowCount: 1,
562 rows: [ {} ],
563 duration: 10,
564 };
565 const expected = {
566 changes: 1,
567 lastInsertRowid: undefined,
568 duration: 10,
569 };
570 sinon.stub(db.db, 'result').resolves(dbResult);
571 const result = await db.subscriptionDelete(dbCtx, callback, topicId);
572 assert.deepStrictEqual(result, expected);
573 });
574 it('failure', async function () {
575 const expected = new Error();
576 sinon.stub(db.db, 'result').throws(expected);
577 try {
578 await db.subscriptionDelete(dbCtx, callback, topicId);
579 assert.fail(noExpectedException);
580 } catch (e) {
581 assert.deepStrictEqual(e, expected);
582 }
583 });
584 }); // subscriptionDelete
585
586 describe('subscriptionDeleteExpired', function () {
587 it('success', async function () {
588 const dbResult = {
589 rowCount: 1,
590 rows: [],
591 duration: 10,
592 };
593 const expected = {
594 changes: 1,
595 lastInsertRowid: undefined,
596 duration: 10,
597 };
598 sinon.stub(db.db, 'result').resolves(dbResult);
599 const result = await db.subscriptionDeleteExpired(dbCtx, topicId);
600 assert.deepStrictEqual(result, expected);
601 });
602 it('failure', async function() {
603 const expected = new Error();
604 sinon.stub(db.db, 'result').rejects(expected);
605 try {
606 await db.subscriptionDeleteExpired(dbCtx, topicId);
607 assert.fail(noExpectedException);
608 } catch (e) {
609 assert.deepStrictEqual(e, expected);
610 }
611 });
612 });
613
614 describe('subscriptionDeliveryClaim', function () {
615 it('success', async function() {
616 const dbResult = [
617 {
618 id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
619 },
620 ];
621 const expected = ['c2e254c5-aa6e-4a8f-b1a1-e474b07392bb'];
622 sinon.stub(db.db, 'manyOrNone').resolves(dbResult);
623 const result = await db.subscriptionDeliveryClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
624 assert.deepStrictEqual(result, expected);
625 });
626 it('failure', async function () {
627 const expected = new Error();
628 sinon.stub(db.db, 'manyOrNone').throws(expected);
629 try {
630 await db.subscriptionDeliveryClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
631 assert.fail(noExpectedException);
632 } catch (e) {
633 assert.deepStrictEqual(e, expected);
634 }
635 });
636 }); // subscriptionDeliveryClaim
637
638 describe('subscriptionDeliveryClaimById', function () {
639 it('success', async function() {
640 const dbResult = {
641 rowCount: 1,
642 rows: [{ id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb' }],
643 duration: 11,
644 };
645 const expected = {
646 changes: 1,
647 lastInsertRowid: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
648 duration: 11,
649 };
650 sinon.stub(db.db, 'result').resolves(dbResult);
651 const result = await db.subscriptionDeliveryClaimById(dbCtx, subscriptionId, claimTimeoutSeconds, claimant);
652 assert.deepStrictEqual(result, expected);
653 });
654 it('failure', async function () {
655 const dbResult = {
656 rowCount: 0,
657 rows: undefined,
658 duration: 11,
659 };
660 sinon.stub(db.db, 'result').resolves(dbResult);
661 try {
662 await db.subscriptionDeliveryClaimById(dbCtx, callback, topicId);
663 assert.fail(noExpectedException);
664 } catch (e) {
665 assert(e instanceof DBErrors.UnexpectedResult);
666 }
667 });
668 }); // subscriptionDeliveryClaimById
669
670 describe('subscriptionDeliveryComplete', function () {
671 let topicContentUpdated;
672 before(function () {
673 topicContentUpdated = new Date();
674 });
675 it('success', async function() {
676 const dbResult = {
677 rowCount: 1,
678 };
679 sinon.stub(db.db, 'result').resolves(dbResult);
680 await db.subscriptionDeliveryComplete(dbCtx, callback, topicId, topicContentUpdated);
681 });
682 it('failure', async function () {
683 const dbResult = {
684 rowCount: 0,
685 };
686 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult);
687 try {
688 await db.subscriptionDeliveryComplete(dbCtx, callback, topicId, topicContentUpdated);
689 assert.fail(noExpectedException);
690 } catch (e) {
691 assert(e instanceof DBErrors.UnexpectedResult);
692 }
693 });
694 it('second failure', async function () {
695 const dbResult0 = {
696 rowCount: 1,
697 };
698 const dbResult1 = {
699 rowCount: 0,
700 };
701 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
702 try {
703 await db.subscriptionDeliveryComplete(dbCtx, callback, topicId, topicContentUpdated);
704 assert.fail(noExpectedException);
705 } catch (e) {
706 assert(e instanceof DBErrors.UnexpectedResult);
707 }
708 });
709 }); // subscriptionDeliveryComplete
710
711 describe('subscriptionDeliveryGone', function () {
712 it('success', async function() {
713 const dbResult = {
714 rowCount: 1,
715 };
716 sinon.stub(db.db, 'result').resolves(dbResult);
717 await db.subscriptionDeliveryGone(dbCtx, callback, topicId);
718 });
719 it('failure', async function () {
720 const dbResult = {
721 rowCount: 0,
722 };
723 sinon.stub(db.db, 'result').resolves(dbResult);
724 try {
725 await db.subscriptionDeliveryGone(dbCtx, callback, topicId);
726 assert.fail(noExpectedException);
727 } catch (e) {
728 assert(e instanceof DBErrors.UnexpectedResult);
729 }
730 });
731 }); // subscriptionDeliveryGone
732
733 describe('subscriptionDeliveryIncomplete', function () {
734 it('success', async function() {
735 const dbOne = { deliveryAttemptsSinceSuccess: 0 };
736 const dbResult = {
737 rowCount: 1,
738 };
739 sinon.stub(db.db, 'one').resolves(dbOne);
740 sinon.stub(db.db, 'result').resolves(dbResult);
741 await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId, retryDelays);
742 });
743 it('success covers default', async function() {
744 const dbOne = { deliveryAttemptsSinceSuccess: 0 };
745 const dbResult = {
746 rowCount: 1,
747 };
748 sinon.stub(db.db, 'one').resolves(dbOne);
749 sinon.stub(db.db, 'result').resolves(dbResult);
750 await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId);
751 });
752 it('failure', async function () {
753 const dbOne = { deliveryAttemptsSinceSuccess: 0 };
754 const dbResult = {
755 rowCount: 0,
756 };
757 sinon.stub(db.db, 'one').resolves(dbOne);
758 sinon.stub(db.db, 'result').resolves(dbResult);
759 try {
760 await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId, retryDelays);
761 assert.fail(noExpectedException);
762 } catch (e) {
763 assert(e instanceof DBErrors.UnexpectedResult);
764 }
765 });
766 it('second failure', async function () {
767 const dbOne = { deliveryAttemptsSinceSuccess: 0 };
768 const dbResult0 = {
769 rowCount: 1,
770 };
771 const dbResult1 = {
772 rowCount: 0,
773 };
774 sinon.stub(db.db, 'one').resolves(dbOne);
775 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
776 try {
777 await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId, retryDelays);
778 assert.fail(noExpectedException);
779 } catch (e) {
780 assert(e instanceof DBErrors.UnexpectedResult);
781 }
782 });
783 }); // subscriptionDeliveryIncomplete
784
785 describe('subscriptionGet', function () {
786 it('success', async function() {
787 const expected = {
788 id: subscriptionId,
789 };
790 sinon.stub(db.db, 'oneOrNone').resolves(expected);
791 const result = await db.subscriptionGet(dbCtx, callback, topicId);
792 assert.deepStrictEqual(result, expected);
793 });
794 it('failure', async function () {
795 const expected = new Error();
796 sinon.stub(db.db, 'oneOrNone').throws(expected);
797 try {
798 await db.subscriptionGet(dbCtx, callback, topicId);
799 assert.fail(noExpectedException);
800 } catch (e) {
801 assert.deepStrictEqual(e, expected);
802 }
803 });
804 }); // subscriptionGet
805
806 describe('subscriptionGetById', function () {
807 it('success', async function() {
808 const expected = {
809 id: subscriptionId,
810 };
811 sinon.stub(db.db, 'oneOrNone').resolves(expected);
812 const result = await db.subscriptionGetById(dbCtx, subscriptionId);
813 assert.deepStrictEqual(result, expected);
814 });
815 it('failure', async function () {
816 const expected = new Error();
817 sinon.stub(db.db, 'oneOrNone').throws(expected);
818 try {
819 await db.subscriptionGetById(dbCtx, subscriptionId);
820 assert.fail(noExpectedException);
821 } catch (e) {
822 assert.deepStrictEqual(e, expected);
823 }
824 });
825 }); // subscriptionGetById
826
827 describe('subscriptionUpsert', function () {
828 let data;
829 beforeEach(function () {
830 data = {
831 callback,
832 topicId,
833 leaseSeconds,
834 secret,
835 httpRemoteAddr,
836 httpFrom,
837 };
838 });
839 it('success', async function() {
840 const dbResult = {
841 rowCount: 1,
842 rows: [{ id: subscriptionId }],
843 duration: 10,
844 };
845 const expected = {
846 changes: 1,
847 lastInsertRowid: subscriptionId,
848 duration: 10,
849 };
850 sinon.stub(db.db, 'result').resolves(dbResult);
851 const result = await db.subscriptionUpsert(dbCtx, data);
852 assert.deepStrictEqual(result, expected);
853 });
854 it('failure', async function () {
855 const dbResult = {
856 rowCount: 0,
857 };
858 sinon.stub(db.db, 'result').resolves(dbResult);
859 try {
860 await db.subscriptionUpsert(dbCtx, data);
861 assert.fail(noExpectedException);
862 } catch (e) {
863 assert(e instanceof DBErrors.UnexpectedResult);
864 }
865 });
866 }); // subscriptionUpsert
867
868 describe('subscriptionUpdate', function () {
869 let data;
870 beforeEach(function () {
871 data = {
872 signatureAlgorithm: 'sha256',
873 };
874 });
875 it('success', async function() {
876 const dbResult = {
877 rowCount: 1,
878 rows: [],
879 duration: 10,
880 };
881 sinon.stub(db.db, 'result').resolves(dbResult);
882 await db.subscriptionUpdate(dbCtx, data);
883 });
884 it('failure', async function () {
885 const dbResult = {
886 rowCount: 0,
887 };
888 sinon.stub(db.db, 'result').resolves(dbResult);
889 try {
890 await db.subscriptionUpdate(dbCtx, data);
891 assert.fail(noExpectedException);
892 } catch (e) {
893 assert(e instanceof DBErrors.UnexpectedResult);
894 }
895 });
896 }); // subscriptionUpdate
897
898 describe('topicDeleted', function () {
899 it('success', async function() {
900 const dbResult = {
901 rowCount: 1,
902 };
903 sinon.stub(db.db, 'result').resolves(dbResult);
904 await db.topicDeleted(dbCtx, topicId);
905 });
906 it('failure', async function() {
907 const dbResult = {
908 rowCount: 0,
909 };
910 sinon.stub(db.db, 'result').resolves(dbResult);
911 try {
912 await db.topicDeleted(dbCtx, topicId);
913 assert.fail(noExpectedException);
914 } catch (e) {
915 assert(e instanceof DBErrors.UnexpectedResult);
916 }
917 });
918 }); // topicDeleted
919
920 describe('topicFetchClaim', function () {
921 it('success', async function() {
922 const dbResult = [{ id: topicId }];
923 const expected = [topicId];
924 sinon.stub(db.db, 'manyOrNone').resolves(dbResult);
925 const result = await db.topicFetchClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
926 assert.deepStrictEqual(result, expected);
927 });
928 it('failure', async function () {
929 const expected = new Error();
930 sinon.stub(db.db, 'manyOrNone').throws(expected);
931 try {
932 await db.topicFetchClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
933 assert.fail(noExpectedException);
934 } catch (e) {
935 assert.deepStrictEqual(e, expected);
936 }
937 });
938 }); // topicFetchClaim
939
940 describe('topicFetchClaimById', function () {
941 it('success', async function() {
942 const dbResult = {
943 rowCount: 1,
944 rows: [],
945 duration: 10,
946 };
947 const expected = {
948 changes: 1,
949 lastInsertRowid: undefined,
950 duration: 10,
951 };
952 sinon.stub(db.db, 'result').resolves(dbResult);
953 const result = await db.topicFetchClaimById(dbCtx, topicId, claimTimeoutSeconds, claimant);
954 assert.deepStrictEqual(result, expected);
955 });
956 it('failure', async function () {
957 const expected = new Error();
958 sinon.stub(db.db, 'result').throws(expected);
959 try {
960 await db.topicFetchClaimById(dbCtx, topicId, claimTimeoutSeconds, claimant);
961 assert.fail(noExpectedException);
962 } catch (e) {
963 assert.deepStrictEqual(e, expected);
964 }
965 });
966 }); // topicFetchClaimById
967
968 describe('topicFetchComplete', function () {
969 it('success', async function() {
970 const dbResult = {
971 rowCount: 1,
972 rows: [],
973 duration: 10,
974 };
975 sinon.stub(db.db, 'result').resolves(dbResult);
976 await db.topicFetchComplete(dbCtx, topicId);
977 });
978 it('failure', async function () {
979 const dbResult = {
980 rowCount: 0,
981 rows: [],
982 duration: 10,
983 };
984 sinon.stub(db.db, 'result').resolves(dbResult);
985 try {
986 await db.topicFetchComplete(dbCtx, topicId);
987 assert.fail(noExpectedException);
988 } catch (e) {
989 assert(e instanceof DBErrors.UnexpectedResult);
990 }
991 });
992 it('second failure', async function () {
993 const dbResult0 = {
994 rowCount: 1,
995 rows: [],
996 duration: 10,
997 };
998 const dbResult1 = {
999 rowCount: 0,
1000 rows: [],
1001 duration: 10,
1002 };
1003 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1004 try {
1005 await db.topicFetchComplete(dbCtx, topicId);
1006 assert.fail(noExpectedException);
1007 } catch (e) {
1008 assert(e instanceof DBErrors.UnexpectedResult);
1009 }
1010 });
1011 }); // topicFetchComplete
1012
1013 describe('topicFetchIncomplete', function () {
1014 it('success', async function() {
1015 const dbOne = { currentAttempt: 0 };
1016 const dbResult0 = {
1017 rowCount: 1,
1018 rows: [],
1019 duration: 10,
1020 };
1021 const dbResult1 = {
1022 rowCount: 1,
1023 rows: [],
1024 duration: 10,
1025 };
1026 const expected = {
1027 changes: 1,
1028 lastInsertRowid: undefined,
1029 duration: 10,
1030 };
1031 sinon.stub(db.db, 'one').resolves(dbOne);
1032 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1033 const result = await db.topicFetchIncomplete(dbCtx, topicId, retryDelays);
1034 assert.deepStrictEqual(result, expected);
1035 });
1036 it('covers defaults', async function() {
1037 const dbOne = { currentAttempt: 0 };
1038 const dbResult0 = {
1039 rowCount: 1,
1040 rows: [],
1041 duration: 10,
1042 };
1043 const dbResult1 = {
1044 rowCount: 1,
1045 rows: [],
1046 duration: 10,
1047 };
1048 const expected = {
1049 changes: 1,
1050 lastInsertRowid: undefined,
1051 duration: 10,
1052 };
1053 sinon.stub(db.db, 'one').resolves(dbOne);
1054 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1055 const result = await db.topicFetchIncomplete(dbCtx, topicId);
1056 assert.deepStrictEqual(result, expected);
1057 });
1058 it('failure', async function () {
1059 const dbOne = { currentAttempt: 0 };
1060 const dbResult0 = {
1061 rowCount: 1,
1062 rows: [],
1063 duration: 10,
1064 };
1065 const dbResult1 = {
1066 rowCount: 0,
1067 rows: [],
1068 duration: 10,
1069 };
1070 sinon.stub(db.db, 'one').resolves(dbOne);
1071 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1072 try {
1073 await db.topicFetchIncomplete(dbCtx, topicId, retryDelays);
1074 assert.fail(noExpectedException);
1075 } catch (e) {
1076 assert(e instanceof DBErrors.UnexpectedResult);
1077 }
1078 });
1079 it('second failure', async function () {
1080 const dbOne = { currentAttempt: 0 };
1081 const dbResult0 = {
1082 rowCount: 0,
1083 rows: [],
1084 duration: 10,
1085 };
1086 const dbResult1 = {
1087 rowCount: 0,
1088 rows: [],
1089 duration: 10,
1090 };
1091 sinon.stub(db.db, 'one').resolves(dbOne);
1092 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1093 try {
1094 await db.topicFetchIncomplete(dbCtx, topicId, retryDelays);
1095 assert.fail(noExpectedException);
1096 } catch (e) {
1097 assert(e instanceof DBErrors.UnexpectedResult);
1098 }
1099 });
1100 }); // topicFetchIncomplete
1101
1102 describe('topicFetchRequested', function () {
1103 it('success', async function() {
1104 const dbResult = {
1105 rowCount: 1,
1106 rows: [],
1107 duration: 10,
1108 };
1109 const expected = {
1110 changes: 1,
1111 lastInsertRowid: undefined,
1112 duration: 10,
1113 };
1114 sinon.stub(db.db, 'result').resolves(dbResult);
1115 const result = await db.topicFetchRequested(dbCtx, topicId);
1116 assert.deepStrictEqual(result, expected);
1117 });
1118 it('failure', async function () {
1119 const dbResult = {
1120 rowCount: 0,
1121 rows: [],
1122 duration: 10,
1123 };
1124 sinon.stub(db.db, 'result').resolves(dbResult);
1125 try {
1126 await db.topicFetchRequested(dbCtx, topicId);
1127 assert.fail(noExpectedException);
1128 } catch (e) {
1129 assert(e instanceof DBErrors.UnexpectedResult);
1130 }
1131 });
1132 }); // topicFetchRequested
1133
1134 describe('topicGetAll', function () {
1135 it('success', async function() {
1136 const expected = [{ id: topicId }];
1137 sinon.stub(db.db, 'manyOrNone').resolves(expected);
1138 const result = await db.topicGetAll(dbCtx);
1139 assert.deepStrictEqual(result, expected);
1140 });
1141 it('covers default', async function() {
1142 const expected = undefined;
1143 sinon.stub(db.db, 'manyOrNone').resolves(expected);
1144 const result = await db.topicGetAll(dbCtx);
1145 assert.deepStrictEqual(result, expected);
1146 });
1147 it('failure', async function () {
1148 const expected = new Error();
1149 sinon.stub(db.db, 'manyOrNone').throws(expected);
1150 try {
1151 await db.topicGetAll(dbCtx);
1152 assert.fail(noExpectedException);
1153 } catch (e) {
1154 assert.deepStrictEqual(e, expected);
1155 }
1156 });
1157 }); // topicGetById
1158
1159 describe('topicGetById', function () {
1160 it('success', async function() {
1161 const expected = { id: topicId };
1162 sinon.stub(db.db, 'oneOrNone').resolves(expected);
1163 const result = await db.topicGetById(dbCtx, topicId);
1164 assert.deepStrictEqual(result, expected);
1165 });
1166 it('covers none', async function() {
1167 const expected = undefined;
1168 sinon.stub(db.db, 'oneOrNone').resolves(expected);
1169 const result = await db.topicGetById(dbCtx, topicId);
1170 assert.deepStrictEqual(result, expected);
1171 });
1172 it('covers no defaults', async function () {
1173 const expected = { id: topicId };
1174 sinon.stub(db.db, 'oneOrNone').resolves(expected);
1175 const result = await db.topicGetById(dbCtx, topicId, false);
1176 assert.deepStrictEqual(result, expected);
1177 });
1178 it('failure', async function () {
1179 const expected = new Error();
1180 sinon.stub(db.db, 'oneOrNone').throws(expected);
1181 try {
1182 await db.topicGetById(dbCtx, topicId);
1183 assert.fail(noExpectedException);
1184 } catch (e) {
1185 assert.deepStrictEqual(e, expected);
1186 }
1187 });
1188 }); // topicGetById
1189
1190 describe('topicGetByUrl', function () {
1191 it('success', async function() {
1192 const expected = [];
1193 sinon.stub(db.db, 'oneOrNone').resolves(expected);
1194 const result = await db.topicGetByUrl(dbCtx, topicUrl);
1195 assert.deepStrictEqual(result, expected);
1196 });
1197 it('failure', async function () {
1198 const expected = new Error();
1199 sinon.stub(db.db, 'oneOrNone').throws(expected);
1200 try {
1201 await db.topicGetByUrl(dbCtx, topicUrl);
1202 assert.fail(noExpectedException);
1203 } catch (e) {
1204 assert.deepStrictEqual(e, expected);
1205 }
1206 });
1207 }); // topicGetByUrl
1208
1209 describe('topicGetContentById', function () {
1210 let topic;
1211 beforeEach(function () {
1212 delete db.cache;
1213 topic = {
1214 id: topicId,
1215 };
1216 });
1217 it('success', async function() {
1218 const expected = topic;
1219 sinon.stub(db.db, 'oneOrNone').resolves(expected);
1220 const result = await db.topicGetContentById(dbCtx, topicId);
1221 assert.deepStrictEqual(result, expected);
1222 });
1223 it('covers default', async function() {
1224 const expected = undefined;
1225 sinon.stub(db.db, 'oneOrNone').resolves(expected);
1226 const result = await db.topicGetContentById(dbCtx, topicId);
1227 assert.deepStrictEqual(result, expected);
1228 });
1229 it('failure', async function () {
1230 const expected = new Error();
1231 sinon.stub(db.db, 'oneOrNone').throws(expected);
1232 try {
1233 await db.topicGetContentById(dbCtx, topicId);
1234 assert.fail(noExpectedException);
1235 } catch (e) {
1236 assert.deepStrictEqual(e, expected);
1237 }
1238 });
1239 it('caches success', async function () {
1240 db.cache = new Map();
1241 const expected = topic;
1242 sinon.stub(db.db, 'oneOrNone').resolves(expected);
1243 const result = await db.topicGetContentById(dbCtx, topicId);
1244 assert.deepStrictEqual(result, expected);
1245 });
1246 it('covers cached entry', async function() {
1247 let result;
1248 db.cache = new Map();
1249 const expected = topic;
1250 sinon.stub(db.db, 'oneOrNone').resolves(expected);
1251 result = await db.topicGetContentById(dbCtx, topicId);
1252 assert.deepStrictEqual(result, expected);
1253 result = await db.topicGetContentById(dbCtx, topicId);
1254 assert.deepStrictEqual(result, expected);
1255 });
1256 }); // topicGetContentById
1257
1258 describe('topicPendingDelete', function () {
1259 beforeEach(function () {
1260 sinon.stub(db.db, 'one');
1261 sinon.stub(db.db, 'result');
1262 });
1263 it('success', async function () {
1264 db.db.one.onCall(0).resolves({
1265 id: topicId,
1266 isDeleted: true,
1267 }).onCall(1).resolves({
1268 count: 0,
1269 });
1270 const dbResult = {
1271 rowCount: 1,
1272 rows: [],
1273 duration: 10,
1274 };
1275 db.db.result.resolves(dbResult);
1276 await db.topicPendingDelete(dbCtx, topicId);
1277 assert(db.db.result.called);
1278 });
1279 it('does not delete non-deleted topic', async function () {
1280 db.db.one.onCall(0).resolves({
1281 id: topicId,
1282 isDeleted: false,
1283 }).onCall(1).resolves({
1284 count: 0,
1285 });
1286 await db.topicPendingDelete(dbCtx, topicId);
1287 assert(!db.db.result.called);
1288 });
1289 it('does not delete topic with active subscriptions', async function () {
1290 db.db.one.onCall(0).resolves({
1291 id: topicId,
1292 isDeleted: true,
1293 }).onCall(1).resolves({
1294 count: 10,
1295 });
1296 await db.topicPendingDelete(dbCtx, topicId);
1297 assert(!db.db.result.called);
1298 });
1299 it('covers no deletion', async function () {
1300 db.db.one.onCall(0).resolves({
1301 id: topicId,
1302 isDeleted: true,
1303 }).onCall(1).resolves({
1304 count: 0,
1305 });
1306 const dbResult = {
1307 rowCount: 0,
1308 rows: [],
1309 duration: 10,
1310 };
1311 db.db.result.resolves(dbResult);
1312 try {
1313 await db.topicPendingDelete(dbCtx, topicId);
1314 assert.fail(noExpectedException);
1315 } catch (e) {
1316 assert(e instanceof DBErrors.UnexpectedResult);
1317 }
1318 });
1319 });
1320
1321 describe('topicPublishHistory', function () {
1322 beforeEach(function () {
1323 sinon.stub(db.db, 'manyOrNone');
1324 });
1325 it('success', async function () {
1326 db.db.manyOrNone.returns([
1327 { daysAgo: 1, contentUpdates: 1 },
1328 { daysAgo: 3, contentUpdates: 2 },
1329 ]);
1330 const result = await db.topicPublishHistory(dbCtx, topicId, 7);
1331 const expected = [0, 1, 0, 2, 0, 0, 0];
1332 assert.deepStrictEqual(result, expected);
1333 });
1334 }); // topicPublishHistory
1335
1336 describe('topicSet', function () {
1337 let data;
1338 beforeEach(function () {
1339 data = {
1340 url: topicUrl,
1341 };
1342 });
1343 it('success', async function() {
1344 const dbResult = {
1345 rowCount: 1,
1346 rows: [{ id: topicId }],
1347 duration: 10,
1348 };
1349 const expected = {
1350 changes: 1,
1351 lastInsertRowid: topicId,
1352 duration: 10,
1353 };
1354 sinon.stub(db.db, 'result').resolves(dbResult);
1355 const result = await db.topicSet(dbCtx, data);
1356 assert.deepStrictEqual(result, expected);
1357 });
1358 it('failure', async function () {
1359 const dbResult = {
1360 rowCount: 0,
1361 rows: [],
1362 duration: 10,
1363 };
1364 sinon.stub(db.db, 'result').resolves(dbResult);
1365 try {
1366 await db.topicSet(dbCtx, data);
1367 assert.fail(noExpectedException);
1368 } catch (e) {
1369 assert(e instanceof DBErrors.UnexpectedResult);
1370 }
1371 });
1372 it('fails invalid value', async function () {
1373 sinon.stub(db.db, 'result');
1374 try {
1375 data.leaseSecondsPreferred = -100;
1376 await db.topicSet(dbCtx, data);
1377 assert.fail(noExpectedException);
1378 } catch (e) {
1379 assert(e instanceof DBErrors.DataValidation);
1380 }
1381 assert(!db.db.result.called);
1382 });
1383 it('fails invalid values', async function () {
1384 sinon.stub(db.db, 'result');
1385 try {
1386 data.leaseSecondsPreferred = 10;
1387 data.leaseSecondsMax = 100;
1388 data.leaseSecondsMin = 50;
1389 await db.topicSet(dbCtx, data);
1390 assert.fail(noExpectedException);
1391 } catch (e) {
1392 assert(e instanceof DBErrors.DataValidation);
1393 }
1394 assert(!db.db.result.called);
1395 });
1396 }); // topicSet
1397
1398 describe('topicSetContent', function () {
1399 let data;
1400 beforeEach(function () {
1401 data = {
1402 content: 'content',
1403 contentType: 'text/plain',
1404 contentHash: 'abc123',
1405 };
1406 sinon.stub(db.db, 'result');
1407 });
1408 it('success', async function() {
1409 const dbResult = {
1410 rowCount: 1,
1411 rows: [],
1412 duration: 10,
1413 };
1414 const expected = {
1415 changes: 1,
1416 lastInsertRowid: undefined,
1417 duration: 10,
1418 };
1419 db.db.result.resolves(dbResult);
1420 const result = await db.topicSetContent(dbCtx, data);
1421 assert.deepStrictEqual(result, expected);
1422 });
1423 it('failure', async function () {
1424 const dbResult = {
1425 rowCount: 0,
1426 rows: [],
1427 duration: 10,
1428 };
1429 db.db.result.resolves(dbResult);
1430 try {
1431 await db.topicSetContent(dbCtx, data);
1432 assert.fail(noExpectedException);
1433 } catch (e) {
1434 assert(e instanceof DBErrors.UnexpectedResult);
1435 }
1436 });
1437 it('failure 2', async function () {
1438 const dbResultSuccess = {
1439 rowCount: 1,
1440 rows: [],
1441 duration: 10,
1442 };
1443 const dbResultFail = {
1444 rowCount: 0,
1445 rows: [],
1446 duration: 10,
1447 };
1448 db.db.result
1449 .onCall(0).resolves(dbResultSuccess)
1450 .onCall(1).resolves(dbResultFail);
1451 try {
1452 await db.topicSetContent(dbCtx, data);
1453 assert.fail(noExpectedException);
1454 } catch (e) {
1455 assert(e instanceof DBErrors.UnexpectedResult);
1456 }
1457 });
1458 }); // topicSetContent
1459
1460 describe('topicUpdate', function () {
1461 let data;
1462 beforeEach(function () {
1463 data = {
1464 leaseSecondsPreferred: 123,
1465 leaseSecondsMin: 100,
1466 leaseSecondsMax: 1000,
1467 publisherValidationUrl: null,
1468 contentHashAlgorithm: 'sha256',
1469 };
1470 });
1471 it('success', async function() {
1472 const dbResult = {
1473 rowCount: 1,
1474 rows: [],
1475 duration: 10,
1476 };
1477 sinon.stub(db.db, 'result').resolves(dbResult);
1478 await db.topicUpdate(dbCtx, data);
1479 });
1480 it('failure', async function () {
1481 const dbResult = {
1482 rowCount: 0,
1483 rows: [],
1484 duration: 10,
1485 };
1486 sinon.stub(db.db, 'result').resolves(dbResult);
1487 try {
1488 await db.topicUpdate(dbCtx, data);
1489 assert.fail(noExpectedException);
1490 } catch (e) {
1491 assert(e instanceof DBErrors.UnexpectedResult);
1492 }
1493 });
1494
1495 }); // topicUpdate
1496
1497 describe('verificationClaim', function () {
1498 it('success', async function() {
1499 const dbManyOrNone = [{ id: verificationId }];
1500 const expected = [verificationId];
1501 sinon.stub(db.db, 'manyOrNone').resolves(dbManyOrNone);
1502 const result = await db.verificationClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
1503 assert.deepStrictEqual(result, expected);
1504 });
1505 it('failure', async function () {
1506 const expected = new Error();
1507 sinon.stub(db.db, 'manyOrNone').throws(expected);
1508 try {
1509 await db.verificationClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
1510 assert.fail(noExpectedException);
1511 } catch (e) {
1512 assert.deepStrictEqual(e, expected);
1513 }
1514 });
1515 }); // verificationClaim
1516
1517 describe('verificationClaimById', function () {
1518 it('success', async function() {
1519 const dbResult = {
1520 rowCount: 1,
1521 rows: [ { id: verificationId } ],
1522 duration: 10,
1523 };
1524 const expected = {
1525 changes: 1,
1526 lastInsertRowid: verificationId,
1527 duration: 10,
1528 };
1529 sinon.stub(db.db, 'result').resolves(dbResult);
1530 const result = await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
1531 assert.deepStrictEqual(result, expected);
1532 });
1533 it('failure', async function () {
1534 const expected = new Error();
1535 sinon.stub(db.db, 'result').throws(expected);
1536 try {
1537 await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
1538 assert.fail(noExpectedException);
1539 } catch (e) {
1540 assert.deepStrictEqual(e, expected);
1541 }
1542 });
1543 }); // verificationClaimById
1544
1545 describe('verificationComplete', function () {
1546 it('success', async function() {
1547 const dbResult = {
1548 rowCount: 1,
1549 rows: [],
1550 duration: 10,
1551 };
1552 sinon.stub(db.db, 'result').resolves(dbResult);
1553 await db.verificationComplete(dbCtx, verificationId, callback, topicId);
1554 });
1555 it('failure', async function () {
1556 const dbResult = {
1557 rowCount: 0,
1558 rows: [],
1559 duration: 10,
1560 };
1561 sinon.stub(db.db, 'result').resolves(dbResult);
1562 try {
1563 await db.verificationComplete(dbCtx, verificationId, callback, topicId);
1564 assert.fail(noExpectedException);
1565 } catch (e) {
1566 assert(e instanceof DBErrors.UnexpectedResult);
1567 }
1568 });
1569 }); // verificationComplete
1570
1571 describe('verificationGetById', function () {
1572 it('success', async function() {
1573 const dbOneOrNone = { id: verificationId };
1574 const expected = { id: verificationId };
1575 sinon.stub(db.db, 'oneOrNone').resolves(dbOneOrNone);
1576 const result = await db.verificationGetById(dbCtx, verificationId);
1577 assert.deepStrictEqual(result, expected);
1578 });
1579 it('failure', async function () {
1580 const expected = new Error();
1581 sinon.stub(db.db, 'oneOrNone').throws(expected);
1582 try {
1583 await db.verificationGetById(dbCtx, verificationId);
1584 assert.fail(noExpectedException);
1585 } catch (e) {
1586 assert.deepStrictEqual(e, expected);
1587 }
1588 });
1589 }); // verificationGetById
1590
1591 describe('verificationIncomplete', function () {
1592 it('success', async function() {
1593 const dbOne = { attempts: 0 };
1594 const dbResult0 = {
1595 rowCount: 1,
1596 rows: [],
1597 duration: 10,
1598 };
1599 const dbResult1 = {
1600 rowCount: 1,
1601 rows: [],
1602 duration: 10,
1603 };
1604 sinon.stub(db.db, 'one').resolves(dbOne);
1605 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1606 await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
1607 });
1608 it('covers defaults', async function() {
1609 const dbOne = { attempts: 0 };
1610 const dbResult0 = {
1611 rowCount: 1,
1612 rows: [],
1613 duration: 10,
1614 };
1615 const dbResult1 = {
1616 rowCount: 1,
1617 rows: [],
1618 duration: 10,
1619 };
1620 sinon.stub(db.db, 'one').resolves(dbOne);
1621 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1622 await db.verificationIncomplete(dbCtx, verificationId);
1623 });
1624 it('failure', async function () {
1625 const dbOne = { attempts: 0 };
1626 const dbResult0 = {
1627 rowCount: 0,
1628 rows: [],
1629 duration: 10,
1630 };
1631 const dbResult1 = {
1632 rowCount: 1,
1633 rows: [],
1634 duration: 10,
1635 };
1636 sinon.stub(db.db, 'one').resolves(dbOne);
1637 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1638 try {
1639 await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
1640 assert.fail(noExpectedException);
1641 } catch (e) {
1642 assert(e instanceof DBErrors.UnexpectedResult);
1643 }
1644 });
1645 it('second failure', async function () {
1646 const dbOne = { attempts: 0 };
1647 const dbResult0 = {
1648 rowCount: 1,
1649 rows: [],
1650 duration: 10,
1651 };
1652 const dbResult1 = {
1653 rowCount: 0,
1654 rows: [],
1655 duration: 10,
1656 };
1657 sinon.stub(db.db, 'one').resolves(dbOne);
1658 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1659 try {
1660 await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
1661 assert.fail(noExpectedException);
1662 } catch (e) {
1663 assert(e instanceof DBErrors.UnexpectedResult);
1664 }
1665 });
1666 }); // verificationIncomplete
1667
1668 describe('verificationInsert', function () {
1669 let verification;
1670 beforeEach(function () {
1671 verification = {
1672 topicId,
1673 callback,
1674 mode: 'subscribe',
1675 isPublisherValidated: true,
1676 leaseSeconds: 86400,
1677 };
1678 });
1679 it('success', async function() {
1680 const dbResult = {
1681 rowCount: 1,
1682 rows: [{ id: verificationId }],
1683 duration: 10,
1684 };
1685 const expected = verificationId;
1686 sinon.stub(db.db, 'result').resolves(dbResult);
1687 const result = await db.verificationInsert(dbCtx, verification);
1688 assert.deepStrictEqual(result, expected);
1689 });
1690 it('failure', async function () {
1691 const dbResult = {
1692 rowCount: 0,
1693 rows: [],
1694 duration: 10,
1695 };
1696 sinon.stub(db.db, 'result').resolves(dbResult);
1697 try {
1698 await db.verificationInsert(dbCtx, verification);
1699 assert.fail(noExpectedException);
1700 } catch (e) {
1701 assert(e instanceof DBErrors.UnexpectedResult);
1702 }
1703 });
1704 it('fails validation', async function () {
1705 delete verification.leaseSeconds;
1706 try {
1707 await db.verificationInsert(dbCtx, verification);
1708 assert.fail(noExpectedException);
1709 } catch (e) {
1710 assert(e instanceof DBErrors.DataValidation);
1711 }
1712 });
1713 }); // verificationInsert
1714
1715 describe('verificationRelease', function () {
1716 it('success', async function() {
1717 const dbResult = {
1718 rowCount: 1,
1719 rows: [],
1720 duration: 10,
1721 };
1722 sinon.stub(db.db, 'result').resolves(dbResult);
1723 await db.verificationRelease(dbCtx, verificationId);
1724 });
1725 it('failure', async function () {
1726 const dbResult = {
1727 rowCount: 0,
1728 rows: [],
1729 duration: 10,
1730 };
1731 sinon.stub(db.db, 'result').resolves(dbResult);
1732 try {
1733 await db.verificationRelease(dbCtx, verificationId);
1734 assert.fail(noExpectedException);
1735 } catch (e) {
1736 assert(e instanceof DBErrors.UnexpectedResult);
1737 }
1738 });
1739 }); // verificationRelease
1740
1741 describe('verificationUpdate', function () {
1742 let data;
1743 beforeEach(function () {
1744 data = {
1745 mode: 'subscribe',
1746 isPublisherValidated: true,
1747 };
1748 });
1749 it('success', async function() {
1750 const dbResult = {
1751 rowCount: 1,
1752 rows: [],
1753 duration: 10,
1754 };
1755 sinon.stub(db.db, 'result').resolves(dbResult);
1756 await db.verificationUpdate(dbCtx, verificationId, data);
1757 });
1758 it('failure', async function () {
1759 const dbResult = {
1760 rowCount: 0,
1761 rows: [],
1762 duration: 10,
1763 };
1764 sinon.stub(db.db, 'result').resolves(dbResult);
1765 try {
1766 await db.verificationUpdate(dbCtx, verificationId, data);
1767 assert.fail(noExpectedException);
1768 } catch (e) {
1769 assert(e instanceof DBErrors.UnexpectedResult, e.name);
1770 }
1771 });
1772 it('fails validation', async function () {
1773 delete data.mode;
1774 try {
1775 await db.verificationUpdate(dbCtx, verificationId, data);
1776 assert.fail(noExpectedException);
1777 } catch (e) {
1778 assert(e instanceof DBErrors.DataValidation);
1779 }
1780 });
1781 }); // verificationUpdate
1782
1783 describe('verificationValidated', function () {
1784 it('success', async function() {
1785 const dbResult = {
1786 rowCount: 1,
1787 rows: [],
1788 duration: 10,
1789 };
1790 sinon.stub(db.db, 'result').resolves(dbResult);
1791 await db.verificationValidated(dbCtx, verificationId);
1792 });
1793 it('failure', async function () {
1794 const dbResult = {
1795 rowCount: 0,
1796 rows: [],
1797 duration: 10,
1798 };
1799 sinon.stub(db.db, 'result').resolves(dbResult);
1800 try {
1801 await db.verificationValidated(dbCtx, verificationId);
1802 assert.fail(noExpectedException);
1803 } catch (e) {
1804 assert(e instanceof DBErrors.UnexpectedResult);
1805 }
1806 });
1807 }); // verificationValidated
1808
1809 }); // DatabasePostgres