display history of topic updates on topic details page
[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('topicPublishHistory', function () {
1326 beforeEach(function () {
1327 sinon.stub(db.db, 'manyOrNone');
1328 });
1329 it('success', async function () {
1330 db.db.manyOrNone.returns([
1331 { daysAgo: 1, contentUpdates: 1 },
1332 { daysAgo: 3, contentUpdates: 2 },
1333 ]);
1334 const result = await db.topicPublishHistory(dbCtx, topicId, 7);
1335 const expected = [0, 1, 0, 2, 0, 0, 0];
1336 assert.deepStrictEqual(result, expected);
1337 });
1338 }); // topicPublishHistory
1339
1340 describe('topicSet', function () {
1341 let data;
1342 beforeEach(function () {
1343 data = {
1344 url: topicUrl,
1345 };
1346 });
1347 it('success', async function() {
1348 const dbResult = {
1349 rowCount: 1,
1350 rows: [{ id: topicId }],
1351 duration: 10,
1352 };
1353 const expected = {
1354 changes: 1,
1355 lastInsertRowid: topicId,
1356 duration: 10,
1357 };
1358 sinon.stub(db.db, 'result').resolves(dbResult);
1359 const result = await db.topicSet(dbCtx, data);
1360 assert.deepStrictEqual(result, expected);
1361 });
1362 it('failure', async function () {
1363 const dbResult = {
1364 rowCount: 0,
1365 rows: [],
1366 duration: 10,
1367 };
1368 sinon.stub(db.db, 'result').resolves(dbResult);
1369 try {
1370 await db.topicSet(dbCtx, data);
1371 assert.fail(noExpectedException);
1372 } catch (e) {
1373 assert(e instanceof DBErrors.UnexpectedResult);
1374 }
1375 });
1376 it('fails invalid value', async function () {
1377 sinon.stub(db.db, 'result');
1378 try {
1379 data.leaseSecondsPreferred = -100;
1380 await db.topicSet(dbCtx, data);
1381 assert.fail(noExpectedException);
1382 } catch (e) {
1383 assert(e instanceof DBErrors.DataValidation);
1384 }
1385 assert(!db.db.result.called);
1386 });
1387 it('fails invalid values', async function () {
1388 sinon.stub(db.db, 'result');
1389 try {
1390 data.leaseSecondsPreferred = 10;
1391 data.leaseSecondsMax = 100;
1392 data.leaseSecondsMin = 50;
1393 await db.topicSet(dbCtx, data);
1394 assert.fail(noExpectedException);
1395 } catch (e) {
1396 assert(e instanceof DBErrors.DataValidation);
1397 }
1398 assert(!db.db.result.called);
1399 });
1400 }); // topicSet
1401
1402 describe('topicSetContent', function () {
1403 let data;
1404 beforeEach(function () {
1405 data = {
1406 content: 'content',
1407 contentType: 'text/plain',
1408 contentHash: 'abc123',
1409 };
1410 sinon.stub(db.db, 'result');
1411 });
1412 it('success', async function() {
1413 const dbResult = {
1414 rowCount: 1,
1415 rows: [],
1416 duration: 10,
1417 };
1418 const expected = {
1419 changes: 1,
1420 lastInsertRowid: undefined,
1421 duration: 10,
1422 };
1423 db.db.result.resolves(dbResult);
1424 const result = await db.topicSetContent(dbCtx, data);
1425 assert.deepStrictEqual(result, expected);
1426 });
1427 it('failure', async function () {
1428 const dbResult = {
1429 rowCount: 0,
1430 rows: [],
1431 duration: 10,
1432 };
1433 db.db.result.resolves(dbResult);
1434 try {
1435 await db.topicSetContent(dbCtx, data);
1436 assert.fail(noExpectedException);
1437 } catch (e) {
1438 assert(e instanceof DBErrors.UnexpectedResult);
1439 }
1440 });
1441 it('failure 2', async function () {
1442 const dbResultSuccess = {
1443 rowCount: 1,
1444 rows: [],
1445 duration: 10,
1446 };
1447 const dbResultFail = {
1448 rowCount: 0,
1449 rows: [],
1450 duration: 10,
1451 };
1452 db.db.result
1453 .onCall(0).resolves(dbResultSuccess)
1454 .onCall(1).resolves(dbResultFail);
1455 try {
1456 await db.topicSetContent(dbCtx, data);
1457 assert.fail(noExpectedException);
1458 } catch (e) {
1459 assert(e instanceof DBErrors.UnexpectedResult);
1460 }
1461 });
1462 }); // topicSetContent
1463
1464 describe('topicUpdate', function () {
1465 let data;
1466 beforeEach(function () {
1467 data = {
1468 leaseSecondsPreferred: 123,
1469 leaseSecondsMin: 100,
1470 leaseSecondsMax: 1000,
1471 publisherValidationUrl: null,
1472 contentHashAlgorithm: 'sha256',
1473 };
1474 });
1475 it('success', async function() {
1476 const dbResult = {
1477 rowCount: 1,
1478 rows: [],
1479 duration: 10,
1480 };
1481 sinon.stub(db.db, 'result').resolves(dbResult);
1482 await db.topicUpdate(dbCtx, data);
1483 });
1484 it('failure', async function () {
1485 const dbResult = {
1486 rowCount: 0,
1487 rows: [],
1488 duration: 10,
1489 };
1490 sinon.stub(db.db, 'result').resolves(dbResult);
1491 try {
1492 await db.topicUpdate(dbCtx, data);
1493 assert.fail(noExpectedException);
1494 } catch (e) {
1495 assert(e instanceof DBErrors.UnexpectedResult);
1496 }
1497 });
1498
1499 }); // topicUpdate
1500
1501 describe('verificationClaim', function () {
1502 it('success', async function() {
1503 const dbManyOrNone = [{ id: verificationId }];
1504 const expected = [verificationId];
1505 sinon.stub(db.db, 'manyOrNone').resolves(dbManyOrNone);
1506 const result = await db.verificationClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
1507 assert.deepStrictEqual(result, expected);
1508 });
1509 it('failure', async function () {
1510 const expected = new Error();
1511 sinon.stub(db.db, 'manyOrNone').throws(expected);
1512 try {
1513 await db.verificationClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
1514 assert.fail(noExpectedException);
1515 } catch (e) {
1516 assert.deepStrictEqual(e, expected);
1517 }
1518 });
1519 }); // verificationClaim
1520
1521 describe('verificationClaimById', function () {
1522 it('success', async function() {
1523 const dbResult = {
1524 rowCount: 1,
1525 rows: [ { id: verificationId } ],
1526 duration: 10,
1527 };
1528 const expected = {
1529 changes: 1,
1530 lastInsertRowid: verificationId,
1531 duration: 10,
1532 };
1533 sinon.stub(db.db, 'result').resolves(dbResult);
1534 const result = await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
1535 assert.deepStrictEqual(result, expected);
1536 });
1537 it('failure', async function () {
1538 const expected = new Error();
1539 sinon.stub(db.db, 'result').throws(expected);
1540 try {
1541 await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
1542 assert.fail(noExpectedException);
1543 } catch (e) {
1544 assert.deepStrictEqual(e, expected);
1545 }
1546 });
1547 }); // verificationClaimById
1548
1549 describe('verificationComplete', function () {
1550 it('success', async function() {
1551 const dbResult = {
1552 rowCount: 1,
1553 rows: [],
1554 duration: 10,
1555 };
1556 sinon.stub(db.db, 'result').resolves(dbResult);
1557 await db.verificationComplete(dbCtx, verificationId, callback, topicId);
1558 });
1559 it('failure', async function () {
1560 const dbResult = {
1561 rowCount: 0,
1562 rows: [],
1563 duration: 10,
1564 };
1565 sinon.stub(db.db, 'result').resolves(dbResult);
1566 try {
1567 await db.verificationComplete(dbCtx, verificationId, callback, topicId);
1568 assert.fail(noExpectedException);
1569 } catch (e) {
1570 assert(e instanceof DBErrors.UnexpectedResult);
1571 }
1572 });
1573 }); // verificationComplete
1574
1575 describe('verificationGetById', function () {
1576 it('success', async function() {
1577 const dbOneOrNone = { id: verificationId };
1578 const expected = { id: verificationId };
1579 sinon.stub(db.db, 'oneOrNone').resolves(dbOneOrNone);
1580 const result = await db.verificationGetById(dbCtx, verificationId);
1581 assert.deepStrictEqual(result, expected);
1582 });
1583 it('failure', async function () {
1584 const expected = new Error();
1585 sinon.stub(db.db, 'oneOrNone').throws(expected);
1586 try {
1587 await db.verificationGetById(dbCtx, verificationId);
1588 assert.fail(noExpectedException);
1589 } catch (e) {
1590 assert.deepStrictEqual(e, expected);
1591 }
1592 });
1593 }); // verificationGetById
1594
1595 describe('verificationIncomplete', function () {
1596 it('success', async function() {
1597 const dbOne = { attempts: 0 };
1598 const dbResult0 = {
1599 rowCount: 1,
1600 rows: [],
1601 duration: 10,
1602 };
1603 const dbResult1 = {
1604 rowCount: 1,
1605 rows: [],
1606 duration: 10,
1607 };
1608 sinon.stub(db.db, 'one').resolves(dbOne);
1609 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1610 await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
1611 });
1612 it('covers defaults', async function() {
1613 const dbOne = { attempts: 0 };
1614 const dbResult0 = {
1615 rowCount: 1,
1616 rows: [],
1617 duration: 10,
1618 };
1619 const dbResult1 = {
1620 rowCount: 1,
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 await db.verificationIncomplete(dbCtx, verificationId);
1627 });
1628 it('failure', async function () {
1629 const dbOne = { attempts: 0 };
1630 const dbResult0 = {
1631 rowCount: 0,
1632 rows: [],
1633 duration: 10,
1634 };
1635 const dbResult1 = {
1636 rowCount: 1,
1637 rows: [],
1638 duration: 10,
1639 };
1640 sinon.stub(db.db, 'one').resolves(dbOne);
1641 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1642 try {
1643 await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
1644 assert.fail(noExpectedException);
1645 } catch (e) {
1646 assert(e instanceof DBErrors.UnexpectedResult);
1647 }
1648 });
1649 it('second failure', async function () {
1650 const dbOne = { attempts: 0 };
1651 const dbResult0 = {
1652 rowCount: 1,
1653 rows: [],
1654 duration: 10,
1655 };
1656 const dbResult1 = {
1657 rowCount: 0,
1658 rows: [],
1659 duration: 10,
1660 };
1661 sinon.stub(db.db, 'one').resolves(dbOne);
1662 sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
1663 try {
1664 await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
1665 assert.fail(noExpectedException);
1666 } catch (e) {
1667 assert(e instanceof DBErrors.UnexpectedResult);
1668 }
1669 });
1670 }); // verificationIncomplete
1671
1672 describe('verificationInsert', function () {
1673 let verification;
1674 beforeEach(function () {
1675 verification = {
1676 topicId,
1677 callback,
1678 mode: 'subscribe',
1679 isPublisherValidated: true,
1680 leaseSeconds: 86400,
1681 };
1682 });
1683 it('success', async function() {
1684 const dbResult = {
1685 rowCount: 1,
1686 rows: [{ id: verificationId }],
1687 duration: 10,
1688 };
1689 const expected = verificationId;
1690 sinon.stub(db.db, 'result').resolves(dbResult);
1691 const result = await db.verificationInsert(dbCtx, verification);
1692 assert.deepStrictEqual(result, expected);
1693 });
1694 it('failure', async function () {
1695 const dbResult = {
1696 rowCount: 0,
1697 rows: [],
1698 duration: 10,
1699 };
1700 sinon.stub(db.db, 'result').resolves(dbResult);
1701 try {
1702 await db.verificationInsert(dbCtx, verification);
1703 assert.fail(noExpectedException);
1704 } catch (e) {
1705 assert(e instanceof DBErrors.UnexpectedResult);
1706 }
1707 });
1708 it('fails validation', async function () {
1709 delete verification.leaseSeconds;
1710 try {
1711 await db.verificationInsert(dbCtx, verification);
1712 assert.fail(noExpectedException);
1713 } catch (e) {
1714 assert(e instanceof DBErrors.DataValidation);
1715 }
1716 });
1717 }); // verificationInsert
1718
1719 describe('verificationRelease', function () {
1720 it('success', async function() {
1721 const dbResult = {
1722 rowCount: 1,
1723 rows: [],
1724 duration: 10,
1725 };
1726 sinon.stub(db.db, 'result').resolves(dbResult);
1727 await db.verificationRelease(dbCtx, verificationId);
1728 });
1729 it('failure', async function () {
1730 const dbResult = {
1731 rowCount: 0,
1732 rows: [],
1733 duration: 10,
1734 };
1735 sinon.stub(db.db, 'result').resolves(dbResult);
1736 try {
1737 await db.verificationRelease(dbCtx, verificationId);
1738 assert.fail(noExpectedException);
1739 } catch (e) {
1740 assert(e instanceof DBErrors.UnexpectedResult);
1741 }
1742 });
1743 }); // verificationRelease
1744
1745 describe('verificationUpdate', function () {
1746 let data;
1747 beforeEach(function () {
1748 data = {
1749 mode: 'subscribe',
1750 isPublisherValidated: true,
1751 };
1752 });
1753 it('success', async function() {
1754 const dbResult = {
1755 rowCount: 1,
1756 rows: [],
1757 duration: 10,
1758 };
1759 sinon.stub(db.db, 'result').resolves(dbResult);
1760 await db.verificationUpdate(dbCtx, verificationId, data);
1761 });
1762 it('failure', async function () {
1763 const dbResult = {
1764 rowCount: 0,
1765 rows: [],
1766 duration: 10,
1767 }
1768 sinon.stub(db.db, 'result').resolves(dbResult);
1769 try {
1770 await db.verificationUpdate(dbCtx, verificationId, data);
1771 assert.fail(noExpectedException);
1772 } catch (e) {
1773 assert(e instanceof DBErrors.UnexpectedResult, e.name);
1774 }
1775 });
1776 it('fails validation', async function () {
1777 delete data.mode;
1778 try {
1779 await db.verificationUpdate(dbCtx, verificationId, data);
1780 assert.fail(noExpectedException);
1781 } catch (e) {
1782 assert(e instanceof DBErrors.DataValidation);
1783 }
1784 });
1785 }); // verificationUpdate
1786
1787 describe('verificationValidated', function () {
1788 it('success', async function() {
1789 const dbResult = {
1790 rowCount: 1,
1791 rows: [],
1792 duration: 10,
1793 }
1794 sinon.stub(db.db, 'result').resolves(dbResult);
1795 await db.verificationValidated(dbCtx, verificationId);
1796 });
1797 it('failure', async function () {
1798 const dbResult = {
1799 rowCount: 0,
1800 rows: [],
1801 duration: 10,
1802 }
1803 sinon.stub(db.db, 'result').resolves(dbResult);
1804 try {
1805 await db.verificationValidated(dbCtx, verificationId);
1806 assert.fail(noExpectedException);
1807 } catch (e) {
1808 assert(e instanceof DBErrors.UnexpectedResult);
1809 }
1810 });
1811 }); // verificationValidated
1812
1813 }); // DatabasePostgres