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