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