1 /* eslint-disable sonarjs/no-identical-functions */
3 /* eslint-disable sonarjs/no-duplicate-string */
6 /* This provides implementation coverage, stubbing pg-promise. */
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');
18 const noExpectedException
= 'did not receive expected exception';
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
;
27 result: () => ({ rows: [] }),
32 manyOrNone: common
.nop
,
33 oneOrNone: common
.nop
,
36 multiResult: common
.nop
,
39 stub
.tx
= (fn
) => fn(stub
);
40 stub
.txIf
= (fn
) => fn(stub
);
41 stub
.task
= (fn
) => fn(stub
);
47 pgpStub
.QueryFile
= class {};
48 pgpStub
.end
= common
.nop
,
49 options
= new Config('test');
50 db
= new DB(stubLogger
, options
, pgpStub
);
52 beforeEach(function () {
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';
65 httpRemoteAddr
= '127.0.0.1';
66 httpFrom
= 'user@example.com';
69 afterEach(function () {
73 it('covers listener', function () {
74 const listenerOptions
= new Config('test');
75 listenerOptions
.db
.cacheEnabled
= true;
76 const listenerDb
= new DB(stubLogger
, listenerOptions
, pgpStub
);
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
) => {
85 // eslint-disable-next-line security/detect-object-injection
88 assert(!(e
instanceof DBErrors
.NotImplemented
), `${fn} not implemented`);
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(': '));
99 describe('pgpInitOptions', function () {
100 describe('error', function () {
101 it('covers', function () {
104 db
.pgpInitOptions
.error(err
, event
);
105 assert(db
.logger
.error
.called
);
108 describe('query', function () {
109 it('covers', function () {
111 db
.pgpInitOptions
.query(event
);
112 assert(db
.logger
.debug
.called
);
114 it('covers NOTIFY', function () {
115 const event
= { query: 'NOTIFY thing' };
116 db
.pgpInitOptions
.query(event
);
117 assert(!db
.logger
.debug
.called
);
120 describe('receive', function () {
121 it('covers', function () {
124 column_one: 'one', // eslint-disable-line camelcase
125 column_two: 2, // eslint-disable-line camelcase
128 column_one: 'foo', // eslint-disable-line camelcase
129 column_two: 4, // eslint-disable-line camelcase
134 const expectedData
= [
144 db
.pgpInitOptions
.receive(data
, result
, event
)
145 assert(db
.logger
.debug
.called
);
146 assert
.deepStrictEqual(data
, expectedData
);
148 it('covers NOTIFY', function () {
151 column_one: 'one', // eslint-disable-line camelcase
152 column_two: 2, // eslint-disable-line camelcase
155 column_one: 'foo', // eslint-disable-line camelcase
156 column_two: 4, // eslint-disable-line camelcase
163 const expectedData
= [
173 db
.pgpInitOptions
.receive(data
, result
, event
)
174 assert(!db
.logger
.debug
.called
);
175 assert
.deepStrictEqual(data
, expectedData
);
178 }); // pgpInitOptions
180 describe('_initTables', function () {
181 beforeEach(function () {
182 sinon
.stub(db
.db
, 'oneOrNone');
183 sinon
.stub(db
.db
, 'multiResult');
184 sinon
.stub(db
, '_currentSchema');
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();
192 it('covers exists', async
function() {
193 db
.db
.oneOrNone
.resolves({});
194 db
._currentSchema
.resolves(db
.schemaVersionsSupported
.max
);
195 await db
._initTables();
199 describe('initialize', function () {
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);
208 it('fails low version', async
function () {
209 const version
= { major: 0, minor: 0, patch: 0 };
210 sinon
.stub(db
.db
, 'one').resolves(version
);
212 await db
.initialize(false);
213 assert
.fail(noExpectedException
);
215 assert(e
instanceof DBErrors
.MigrationNeeded
);
218 it('fails high version', async
function () {
219 const version
= { major: 100, minor: 100, patch: 100 };
220 sinon
.stub(db
.db
, 'one').resolves(version
);
222 await db
.initialize(false);
223 assert
.fail(noExpectedException
);
225 assert(e
instanceof DBErrors
.MigrationNeeded
);
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();
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
);
242 await db
.initialize();
243 assert
.fail(noExpectedException
);
245 assert
.deepStrictEqual(e
, expected
);
248 it('covers listener', async
function() {
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
);
259 describe('healthCheck', function () {
260 beforeEach(function () {
261 sinon
.stub(db
.db
, 'connect').resolves({
264 serverVersion: '0.0',
268 it('covers', async
function () {
269 const result
= await db
.healthCheck();
270 assert
.deepStrictEqual(result
, { serverVersion: '0.0' });
274 describe('_queryFileHelper', function () {
275 it('covers success', function () {
276 const _queryFile
= db
._queryFileHelper(pgpStub
);
279 it('covers failure', function () {
280 const err
= new Error();
281 pgpStub
.QueryFile
= class {
286 const _queryFile
= db
._queryFileHelper(pgpStub
);
289 assert
.fail(noExpectedException
);
291 assert
.strictEqual(e
, err
);
294 }); // _queryFileHelper
296 describe('_closeConnection', function () {
300 it('success', async
function () {
301 sinon
.stub(db
._pgp
, 'end');
302 await db
._closeConnection();
303 assert(db
._pgp
.end
.called
);
305 it('failure', async
function () {
306 const expected
= new Error();
307 sinon
.stub(db
._pgp
, 'end').throws(expected
);
309 await db
._closeConnection();
310 assert
.fail(noExpectedException
);
312 assert
.deepStrictEqual(e
, expected
);
315 it('covers listener', async
function () {
319 sinon
.stub(db
._pgp
, 'end');
320 await db
._closeConnection();
321 assert(db
._pgp
.end
.called
);
323 }); // _closeConnection
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
);
331 it('success', async
function () {
332 sinon
.stub(db
.db
, 'batch');
333 await db
._purgeTables(true);
334 assert(db
.db
.batch
.called
);
336 it('failure', async
function () {
337 const expected
= new Error();
338 sinon
.stub(db
.db
, 'tx').rejects(expected
);
340 await db
._purgeTables(true);
341 assert
.fail(noExpectedException
);
343 assert
.deepStrictEqual(e
, expected
);
348 describe('_topicChanged', function () {
349 beforeEach(function () {
350 db
.cache
= new Map();
351 sinon
.stub(db
.cache
, 'delete');
356 it('covers', function () {
357 db
._topicChanged('topic-id');
358 assert(db
.cache
.delete.called
);
360 it('ignores ping', function () {
361 db
._topicChanged('ping');
362 assert(!db
.cache
.delete.called
);
366 describe('_listenerEstablished', function () {
367 it('creates cache', function () {
369 db
._listenerEstablished();
370 assert(db
.cache
instanceof Map
);
372 }); // _listenerEstablished
374 describe('_listenerLost', function () {
375 it('removes cache', function () {
376 db
.cache
= new Map();
382 describe('_cacheGet', function () {
384 beforeEach(function () {
387 it('nothing if no cache', function () {
389 const result
= db
._cacheGet(key
);
390 assert
.strictEqual(result
, undefined);
392 it('nothing if no entry', function () {
393 db
.cache
= new Map();
394 const result
= db
._cacheGet(key
);
395 assert
.strictEqual(result
, undefined);
397 it('returns cached entry', function () {
398 db
.cache
= new Map();
402 db
._cacheSet(key
, expected
);
403 const result
= db
._cacheGet(key
);
404 assert
.deepStrictEqual(result
, expected
);
408 describe('_cacheSet', function () {
410 beforeEach(function () {
413 it('covers no cache', function () {
415 db
._cacheSet(key
, 'data');
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
);
426 describe('context', function () {
427 it('covers', async
function () {
428 await db
.context(common
.nop
);
432 describe('transaction', function () {
433 it('covers', async
function () {
434 await db
.transaction(db
.db
, common
.nop
);
438 describe('authenticationSuccess', function () {
440 beforeEach(function () {
441 identifier
= 'username';
443 it('success', async
function () {
449 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
450 await db
.authenticationSuccess(dbCtx
, identifier
);
452 it('failure', async
function() {
458 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
460 await db
.authenticationSuccess(dbCtx
, identifier
);
461 assert
.fail(noExpectedException
);
463 assert(e
instanceof DBErrors
.UnexpectedResult
);
466 }); // authenticationSuccess
468 describe('authenticationGet', function () {
469 let identifier
, credential
;
470 beforeEach(function () {
471 identifier
= 'username';
472 credential
= '$z$foo';
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
);
480 it('failure', async
function() {
481 const expected
= new Error('blah');
482 sinon
.stub(db
.db
, 'oneOrNone').rejects(expected
);
484 await db
.authenticationGet(dbCtx
, identifier
, credential
);
485 assert
.fail(noExpectedException
);
487 assert
.deepStrictEqual(e
, expected
);
490 }); // authenticationGet
492 describe('authenticationUpsert', function () {
493 let identifier
, credential
;
494 beforeEach(function () {
495 identifier
= 'username';
496 credential
= '$z$foo';
498 it('success', async
function () {
504 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
505 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
507 it('failure', async
function() {
508 credential
= undefined;
514 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
516 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
517 assert
.fail(noExpectedException
);
519 assert(e
instanceof DBErrors
.UnexpectedResult
);
522 }); // authenticationUpsert
524 describe('subscriptionsByTopicId', function () {
525 it('success', async
function () {
527 sinon
.stub(db
.db
, 'manyOrNone').resolves(expected
);
528 const result
= await db
.subscriptionsByTopicId(dbCtx
, topicUrl
);
529 assert
.deepStrictEqual(result
, expected
);
531 it('failure', async
function () {
532 const expected
= new Error();
533 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
535 await db
.subscriptionsByTopicId(dbCtx
, topicUrl
);
536 assert
.fail(noExpectedException
);
538 assert
.deepStrictEqual(e
, expected
);
541 }); // subscriptionsByTopicId
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
);
550 it('failure', async
function () {
551 const expected
= new Error();
552 sinon
.stub(db
.db
, 'one').throws(expected
);
554 await db
.subscriptionCountByTopicUrl(dbCtx
, topicUrl
);
555 assert
.fail(noExpectedException
);
557 assert
.deepStrictEqual(e
, expected
);
560 }); // subscriptionCountByTopicUrl
562 describe('subscriptionDelete', function () {
563 it('success', async
function() {
571 lastInsertRowid: undefined,
574 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
575 const result
= await db
.subscriptionDelete(dbCtx
, callback
, topicId
);
576 assert
.deepStrictEqual(result
, expected
);
578 it('failure', async
function () {
579 const expected
= new Error();
580 sinon
.stub(db
.db
, 'result').throws(expected
);
582 await db
.subscriptionDelete(dbCtx
, callback
, topicId
);
583 assert
.fail(noExpectedException
);
585 assert
.deepStrictEqual(e
, expected
);
588 }); // subscriptionDelete
590 describe('subscriptionDeleteExpired', function () {
591 it('success', async
function () {
599 lastInsertRowid: undefined,
602 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
603 const result
= await db
.subscriptionDeleteExpired(dbCtx
, topicId
);
604 assert
.deepStrictEqual(result
, expected
);
606 it('failure', async
function() {
607 const expected
= new Error();
608 sinon
.stub(db
.db
, 'result').rejects(expected
);
610 await db
.subscriptionDeleteExpired(dbCtx
, topicId
);
611 assert
.fail(noExpectedException
);
613 assert
.deepStrictEqual(e
, expected
);
618 describe('subscriptionDeliveryClaim', function () {
619 it('success', async
function() {
622 id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
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
);
630 it('failure', async
function () {
631 const expected
= new Error();
632 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
634 await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
635 assert
.fail(noExpectedException
);
637 assert
.deepStrictEqual(e
, expected
);
640 }); // subscriptionDeliveryClaim
642 describe('subscriptionDeliveryClaimById', function () {
643 it('success', async
function() {
646 rows: [{ id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb' }],
651 lastInsertRowid: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
654 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
655 const result
= await db
.subscriptionDeliveryClaimById(dbCtx
, subscriptionId
, claimTimeoutSeconds
, claimant
);
656 assert
.deepStrictEqual(result
, expected
);
658 it('failure', async
function () {
664 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
666 await db
.subscriptionDeliveryClaimById(dbCtx
, callback
, topicId
);
667 assert
.fail(noExpectedException
);
669 assert(e
instanceof DBErrors
.UnexpectedResult
);
672 }); // subscriptionDeliveryClaimById
674 describe('subscriptionDeliveryComplete', function () {
675 let topicContentUpdated
;
677 topicContentUpdated
= new Date();
679 it('success', async
function() {
683 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
684 await db
.subscriptionDeliveryComplete(dbCtx
, callback
, topicId
, topicContentUpdated
);
686 it('failure', async
function () {
690 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult
);
692 await db
.subscriptionDeliveryComplete(dbCtx
, callback
, topicId
, topicContentUpdated
);
693 assert
.fail(noExpectedException
);
695 assert(e
instanceof DBErrors
.UnexpectedResult
);
698 it('second failure', async
function () {
705 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
707 await db
.subscriptionDeliveryComplete(dbCtx
, callback
, topicId
, topicContentUpdated
);
708 assert
.fail(noExpectedException
);
710 assert(e
instanceof DBErrors
.UnexpectedResult
);
713 }); // subscriptionDeliveryComplete
715 describe('subscriptionDeliveryGone', function () {
716 it('success', async
function() {
720 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
721 await db
.subscriptionDeliveryGone(dbCtx
, callback
, topicId
);
723 it('failure', async
function () {
727 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
729 await db
.subscriptionDeliveryGone(dbCtx
, callback
, topicId
);
730 assert
.fail(noExpectedException
);
732 assert(e
instanceof DBErrors
.UnexpectedResult
);
735 }); // subscriptionDeliveryGone
737 describe('subscriptionDeliveryIncomplete', function () {
738 it('success', async
function() {
739 const dbOne
= { deliveryAttemptsSinceSuccess: 0 };
743 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
744 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
745 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
, retryDelays
);
747 it('success covers default', async
function() {
748 const dbOne
= { deliveryAttemptsSinceSuccess: 0 };
752 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
753 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
754 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
);
756 it('failure', async
function () {
757 const dbOne
= { deliveryAttemptsSinceSuccess: 0 };
761 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
762 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
764 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
, retryDelays
);
765 assert
.fail(noExpectedException
);
767 assert(e
instanceof DBErrors
.UnexpectedResult
);
770 it('second failure', async
function () {
771 const dbOne
= { deliveryAttemptsSinceSuccess: 0 };
778 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
779 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
781 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
, retryDelays
);
782 assert
.fail(noExpectedException
);
784 assert(e
instanceof DBErrors
.UnexpectedResult
);
787 }); // subscriptionDeliveryIncomplete
789 describe('subscriptionGet', function () {
790 it('success', async
function() {
794 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
795 const result
= await db
.subscriptionGet(dbCtx
, callback
, topicId
);
796 assert
.deepStrictEqual(result
, expected
);
798 it('failure', async
function () {
799 const expected
= new Error();
800 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
802 await db
.subscriptionGet(dbCtx
, callback
, topicId
);
803 assert
.fail(noExpectedException
);
805 assert
.deepStrictEqual(e
, expected
);
808 }); // subscriptionGet
810 describe('subscriptionGetById', function () {
811 it('success', async
function() {
815 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
816 const result
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
817 assert
.deepStrictEqual(result
, expected
);
819 it('failure', async
function () {
820 const expected
= new Error();
821 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
823 await db
.subscriptionGetById(dbCtx
, subscriptionId
);
824 assert
.fail(noExpectedException
);
826 assert
.deepStrictEqual(e
, expected
);
829 }); // subscriptionGetById
831 describe('subscriptionUpsert', function () {
833 beforeEach(function () {
843 it('success', async
function() {
846 rows: [{ id: subscriptionId
}],
851 lastInsertRowid: subscriptionId
,
854 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
855 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
856 assert
.deepStrictEqual(result
, expected
);
858 it('failure', async
function () {
862 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
864 await db
.subscriptionUpsert(dbCtx
, data
);
865 assert
.fail(noExpectedException
);
867 assert(e
instanceof DBErrors
.UnexpectedResult
);
870 }); // subscriptionUpsert
872 describe('subscriptionUpdate', function () {
874 beforeEach(function () {
876 signatureAlgorithm: 'sha256',
879 it('success', async
function() {
885 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
886 await db
.subscriptionUpdate(dbCtx
, data
);
888 it('failure', async
function () {
892 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
894 await db
.subscriptionUpdate(dbCtx
, data
);
895 assert
.fail(noExpectedException
);
897 assert(e
instanceof DBErrors
.UnexpectedResult
);
900 }); // subscriptionUpdate
902 describe('topicDeleted', function () {
903 it('success', async
function() {
907 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
908 await db
.topicDeleted(dbCtx
, topicId
);
910 it('failure', async
function() {
914 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
916 await db
.topicDeleted(dbCtx
, topicId
);
917 assert
.fail(noExpectedException
);
919 assert(e
instanceof DBErrors
.UnexpectedResult
);
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
);
932 it('failure', async
function () {
933 const expected
= new Error();
934 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
936 await db
.topicFetchClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
937 assert
.fail(noExpectedException
);
939 assert
.deepStrictEqual(e
, expected
);
942 }); // topicFetchClaim
944 describe('topicFetchClaimById', function () {
945 it('success', async
function() {
953 lastInsertRowid: undefined,
956 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
957 const result
= await db
.topicFetchClaimById(dbCtx
, topicId
, claimTimeoutSeconds
, claimant
);
958 assert
.deepStrictEqual(result
, expected
);
960 it('failure', async
function () {
961 const expected
= new Error();
962 sinon
.stub(db
.db
, 'result').throws(expected
);
964 await db
.topicFetchClaimById(dbCtx
, topicId
, claimTimeoutSeconds
, claimant
);
965 assert
.fail(noExpectedException
);
967 assert
.deepStrictEqual(e
, expected
);
970 }); // topicFetchClaimById
972 describe('topicFetchComplete', function () {
973 it('success', async
function() {
979 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
980 await db
.topicFetchComplete(dbCtx
, topicId
);
982 it('failure', async
function () {
988 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
990 await db
.topicFetchComplete(dbCtx
, topicId
);
991 assert
.fail(noExpectedException
);
993 assert(e
instanceof DBErrors
.UnexpectedResult
);
996 it('second failure', async
function () {
1007 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1009 await db
.topicFetchComplete(dbCtx
, topicId
);
1010 assert
.fail(noExpectedException
);
1012 assert(e
instanceof DBErrors
.UnexpectedResult
);
1015 }); // topicFetchComplete
1017 describe('topicFetchIncomplete', function () {
1018 it('success', async
function() {
1019 const dbOne
= { currentAttempt: 0 };
1032 lastInsertRowid: undefined,
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
);
1040 it('covers defaults', async
function() {
1041 const dbOne
= { currentAttempt: 0 };
1054 lastInsertRowid: undefined,
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
);
1062 it('failure', async
function () {
1063 const dbOne
= { currentAttempt: 0 };
1074 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1075 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1077 await db
.topicFetchIncomplete(dbCtx
, topicId
, retryDelays
);
1078 assert
.fail(noExpectedException
);
1080 assert(e
instanceof DBErrors
.UnexpectedResult
);
1083 it('second failure', async
function () {
1084 const dbOne
= { currentAttempt: 0 };
1095 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1096 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1098 await db
.topicFetchIncomplete(dbCtx
, topicId
, retryDelays
);
1099 assert
.fail(noExpectedException
);
1101 assert(e
instanceof DBErrors
.UnexpectedResult
);
1104 }); // topicFetchIncomplete
1106 describe('topicFetchRequested', function () {
1107 it('success', async
function() {
1115 lastInsertRowid: undefined,
1118 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1119 const result
= await db
.topicFetchRequested(dbCtx
, topicId
);
1120 assert
.deepStrictEqual(result
, expected
);
1122 it('failure', async
function () {
1128 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1130 await db
.topicFetchRequested(dbCtx
, topicId
);
1131 assert
.fail(noExpectedException
);
1133 assert(e
instanceof DBErrors
.UnexpectedResult
);
1136 }); // topicFetchRequested
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
);
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
);
1151 it('failure', async
function () {
1152 const expected
= new Error();
1153 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
1155 await db
.topicGetAll(dbCtx
);
1156 assert
.fail(noExpectedException
);
1158 assert
.deepStrictEqual(e
, expected
);
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
);
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
);
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
);
1182 it('failure', async
function () {
1183 const expected
= new Error();
1184 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
1186 await db
.topicGetById(dbCtx
, topicId
);
1187 assert
.fail(noExpectedException
);
1189 assert
.deepStrictEqual(e
, expected
);
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
);
1201 it('failure', async
function () {
1202 const expected
= new Error();
1203 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
1205 await db
.topicGetByUrl(dbCtx
, topicUrl
);
1206 assert
.fail(noExpectedException
);
1208 assert
.deepStrictEqual(e
, expected
);
1211 }); // topicGetByUrl
1213 describe('topicGetContentById', function () {
1215 beforeEach(function () {
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
);
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
);
1233 it('failure', async
function () {
1234 const expected
= new Error();
1235 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
1237 await db
.topicGetContentById(dbCtx
, topicId
);
1238 assert
.fail(noExpectedException
);
1240 assert
.deepStrictEqual(e
, expected
);
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
);
1250 it('covers cached entry', async
function() {
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
);
1260 }); // topicGetContentById
1262 describe('topicPendingDelete', function () {
1263 beforeEach(function () {
1264 sinon
.stub(db
.db
, 'one');
1265 sinon
.stub(db
.db
, 'result');
1267 it('success', async
function () {
1268 db
.db
.one
.onCall(0).resolves({
1271 }).onCall(1).resolves({
1279 db
.db
.result
.resolves(dbResult
);
1280 await db
.topicPendingDelete(dbCtx
, topicId
);
1281 assert(db
.db
.result
.called
);
1283 it('does not delete non-deleted topic', async
function () {
1284 db
.db
.one
.onCall(0).resolves({
1287 }).onCall(1).resolves({
1290 await db
.topicPendingDelete(dbCtx
, topicId
);
1291 assert(!db
.db
.result
.called
);
1293 it('does not delete topic with active subscriptions', async
function () {
1294 db
.db
.one
.onCall(0).resolves({
1297 }).onCall(1).resolves({
1300 await db
.topicPendingDelete(dbCtx
, topicId
);
1301 assert(!db
.db
.result
.called
);
1303 it('covers no deletion', async
function () {
1304 db
.db
.one
.onCall(0).resolves({
1307 }).onCall(1).resolves({
1315 db
.db
.result
.resolves(dbResult
);
1317 await db
.topicPendingDelete(dbCtx
, topicId
);
1318 assert
.fail(noExpectedException
);
1320 assert(e
instanceof DBErrors
.UnexpectedResult
);
1325 describe('topicPublishHistory', function () {
1326 beforeEach(function () {
1327 sinon
.stub(db
.db
, 'manyOrNone');
1329 it('success', async
function () {
1330 db
.db
.manyOrNone
.returns([
1331 { daysAgo: 1, contentUpdates: 1 },
1332 { daysAgo: 3, contentUpdates: 2 },
1334 const result
= await db
.topicPublishHistory(dbCtx
, topicId
, 7);
1335 const expected
= [0, 1, 0, 2, 0, 0, 0];
1336 assert
.deepStrictEqual(result
, expected
);
1338 }); // topicPublishHistory
1340 describe('topicSet', function () {
1342 beforeEach(function () {
1347 it('success', async
function() {
1350 rows: [{ id: topicId
}],
1355 lastInsertRowid: topicId
,
1358 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1359 const result
= await db
.topicSet(dbCtx
, data
);
1360 assert
.deepStrictEqual(result
, expected
);
1362 it('failure', async
function () {
1368 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1370 await db
.topicSet(dbCtx
, data
);
1371 assert
.fail(noExpectedException
);
1373 assert(e
instanceof DBErrors
.UnexpectedResult
);
1376 it('fails invalid value', async
function () {
1377 sinon
.stub(db
.db
, 'result');
1379 data
.leaseSecondsPreferred
= -100;
1380 await db
.topicSet(dbCtx
, data
);
1381 assert
.fail(noExpectedException
);
1383 assert(e
instanceof DBErrors
.DataValidation
);
1385 assert(!db
.db
.result
.called
);
1387 it('fails invalid values', async
function () {
1388 sinon
.stub(db
.db
, 'result');
1390 data
.leaseSecondsPreferred
= 10;
1391 data
.leaseSecondsMax
= 100;
1392 data
.leaseSecondsMin
= 50;
1393 await db
.topicSet(dbCtx
, data
);
1394 assert
.fail(noExpectedException
);
1396 assert(e
instanceof DBErrors
.DataValidation
);
1398 assert(!db
.db
.result
.called
);
1402 describe('topicSetContent', function () {
1404 beforeEach(function () {
1407 contentType: 'text/plain',
1408 contentHash: 'abc123',
1410 sinon
.stub(db
.db
, 'result');
1412 it('success', async
function() {
1420 lastInsertRowid: undefined,
1423 db
.db
.result
.resolves(dbResult
);
1424 const result
= await db
.topicSetContent(dbCtx
, data
);
1425 assert
.deepStrictEqual(result
, expected
);
1427 it('failure', async
function () {
1433 db
.db
.result
.resolves(dbResult
);
1435 await db
.topicSetContent(dbCtx
, data
);
1436 assert
.fail(noExpectedException
);
1438 assert(e
instanceof DBErrors
.UnexpectedResult
);
1441 it('failure 2', async
function () {
1442 const dbResultSuccess
= {
1447 const dbResultFail
= {
1453 .onCall(0).resolves(dbResultSuccess
)
1454 .onCall(1).resolves(dbResultFail
);
1456 await db
.topicSetContent(dbCtx
, data
);
1457 assert
.fail(noExpectedException
);
1459 assert(e
instanceof DBErrors
.UnexpectedResult
);
1462 }); // topicSetContent
1464 describe('topicUpdate', function () {
1466 beforeEach(function () {
1468 leaseSecondsPreferred: 123,
1469 leaseSecondsMin: 100,
1470 leaseSecondsMax: 1000,
1471 publisherValidationUrl: null,
1472 contentHashAlgorithm: 'sha256',
1475 it('success', async
function() {
1481 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1482 await db
.topicUpdate(dbCtx
, data
);
1484 it('failure', async
function () {
1490 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1492 await db
.topicUpdate(dbCtx
, data
);
1493 assert
.fail(noExpectedException
);
1495 assert(e
instanceof DBErrors
.UnexpectedResult
);
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
);
1509 it('failure', async
function () {
1510 const expected
= new Error();
1511 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
1513 await db
.verificationClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
1514 assert
.fail(noExpectedException
);
1516 assert
.deepStrictEqual(e
, expected
);
1519 }); // verificationClaim
1521 describe('verificationClaimById', function () {
1522 it('success', async
function() {
1525 rows: [ { id: verificationId
} ],
1530 lastInsertRowid: verificationId
,
1533 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1534 const result
= await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
1535 assert
.deepStrictEqual(result
, expected
);
1537 it('failure', async
function () {
1538 const expected
= new Error();
1539 sinon
.stub(db
.db
, 'result').throws(expected
);
1541 await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
1542 assert
.fail(noExpectedException
);
1544 assert
.deepStrictEqual(e
, expected
);
1547 }); // verificationClaimById
1549 describe('verificationComplete', function () {
1550 it('success', async
function() {
1556 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1557 await db
.verificationComplete(dbCtx
, verificationId
, callback
, topicId
);
1559 it('failure', async
function () {
1565 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1567 await db
.verificationComplete(dbCtx
, verificationId
, callback
, topicId
);
1568 assert
.fail(noExpectedException
);
1570 assert(e
instanceof DBErrors
.UnexpectedResult
);
1573 }); // verificationComplete
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
);
1583 it('failure', async
function () {
1584 const expected
= new Error();
1585 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
1587 await db
.verificationGetById(dbCtx
, verificationId
);
1588 assert
.fail(noExpectedException
);
1590 assert
.deepStrictEqual(e
, expected
);
1593 }); // verificationGetById
1595 describe('verificationIncomplete', function () {
1596 it('success', async
function() {
1597 const dbOne
= { attempts: 0 };
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
);
1612 it('covers defaults', async
function() {
1613 const dbOne
= { attempts: 0 };
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
);
1628 it('failure', async
function () {
1629 const dbOne
= { attempts: 0 };
1640 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1641 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1643 await db
.verificationIncomplete(dbCtx
, verificationId
, retryDelays
);
1644 assert
.fail(noExpectedException
);
1646 assert(e
instanceof DBErrors
.UnexpectedResult
);
1649 it('second failure', async
function () {
1650 const dbOne
= { attempts: 0 };
1661 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1662 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1664 await db
.verificationIncomplete(dbCtx
, verificationId
, retryDelays
);
1665 assert
.fail(noExpectedException
);
1667 assert(e
instanceof DBErrors
.UnexpectedResult
);
1670 }); // verificationIncomplete
1672 describe('verificationInsert', function () {
1674 beforeEach(function () {
1679 isPublisherValidated: true,
1680 leaseSeconds: 86400,
1683 it('success', async
function() {
1686 rows: [{ id: verificationId
}],
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
);
1694 it('failure', async
function () {
1700 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1702 await db
.verificationInsert(dbCtx
, verification
);
1703 assert
.fail(noExpectedException
);
1705 assert(e
instanceof DBErrors
.UnexpectedResult
);
1708 it('fails validation', async
function () {
1709 delete verification
.leaseSeconds
;
1711 await db
.verificationInsert(dbCtx
, verification
);
1712 assert
.fail(noExpectedException
);
1714 assert(e
instanceof DBErrors
.DataValidation
);
1717 }); // verificationInsert
1719 describe('verificationRelease', function () {
1720 it('success', async
function() {
1726 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1727 await db
.verificationRelease(dbCtx
, verificationId
);
1729 it('failure', async
function () {
1735 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1737 await db
.verificationRelease(dbCtx
, verificationId
);
1738 assert
.fail(noExpectedException
);
1740 assert(e
instanceof DBErrors
.UnexpectedResult
);
1743 }); // verificationRelease
1745 describe('verificationUpdate', function () {
1747 beforeEach(function () {
1750 isPublisherValidated: true,
1753 it('success', async
function() {
1759 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1760 await db
.verificationUpdate(dbCtx
, verificationId
, data
);
1762 it('failure', async
function () {
1768 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1770 await db
.verificationUpdate(dbCtx
, verificationId
, data
);
1771 assert
.fail(noExpectedException
);
1773 assert(e
instanceof DBErrors
.UnexpectedResult
, e
.name
);
1776 it('fails validation', async
function () {
1779 await db
.verificationUpdate(dbCtx
, verificationId
, data
);
1780 assert
.fail(noExpectedException
);
1782 assert(e
instanceof DBErrors
.DataValidation
);
1785 }); // verificationUpdate
1787 describe('verificationValidated', function () {
1788 it('success', async
function() {
1794 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1795 await db
.verificationValidated(dbCtx
, verificationId
);
1797 it('failure', async
function () {
1803 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1805 await db
.verificationValidated(dbCtx
, verificationId
);
1806 assert
.fail(noExpectedException
);
1808 assert(e
instanceof DBErrors
.UnexpectedResult
);
1811 }); // verificationValidated
1813 }); // DatabasePostgres