3 /* This provides implementation coverage, stubbing pg-promise. */
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');
14 const noExpectedException
= 'did not receive expected exception';
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
;
23 result: () => ({ rows: [] }),
28 manyOrNone: common
.nop
,
29 oneOrNone: common
.nop
,
32 multiResult: common
.nop
,
35 stub
.tx
= (fn
) => fn(stub
);
36 stub
.txIf
= (fn
) => fn(stub
);
37 stub
.task
= (fn
) => fn(stub
);
43 pgpStub
.QueryFile
= class {};
44 pgpStub
.end
= common
.nop
,
45 options
= new Config('test');
46 db
= new DB(stubLogger
, options
, pgpStub
);
48 beforeEach(function () {
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';
61 httpRemoteAddr
= '127.0.0.1';
62 httpFrom
= 'user@example.com';
65 afterEach(function () {
69 it('covers listener', function () {
70 const listenerOptions
= new Config('test');
71 listenerOptions
.db
.cacheEnabled
= true;
72 const listenerDb
= new DB(stubLogger
, listenerOptions
, pgpStub
);
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
) => {
81 // eslint-disable-next-line security/detect-object-injection
84 assert(!(e
instanceof DBErrors
.NotImplemented
), `${fn} not implemented`);
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(': '));
95 describe('pgpInitOptions', function () {
96 describe('error', function () {
97 it('covers', function () {
100 db
.pgpInitOptions
.error(err
, event
);
101 assert(db
.logger
.error
.called
);
104 describe('query', function () {
105 it('covers', function () {
107 db
.pgpInitOptions
.query(event
);
108 assert(db
.logger
.debug
.called
);
110 it('covers NOTIFY', function () {
111 const event
= { query: 'NOTIFY thing' };
112 db
.pgpInitOptions
.query(event
);
113 assert(!db
.logger
.debug
.called
);
116 describe('receive', function () {
117 it('covers', function () {
120 column_one: 'one', // eslint-disable-line camelcase
121 column_two: 2, // eslint-disable-line camelcase
124 column_one: 'foo', // eslint-disable-line camelcase
125 column_two: 4, // eslint-disable-line camelcase
130 const expectedData
= [
140 db
.pgpInitOptions
.receive({ data
, result
, ctx: event
});
141 assert(db
.logger
.debug
.called
);
142 assert
.deepStrictEqual(data
, expectedData
);
144 it('covers NOTIFY', function () {
147 column_one: 'one', // eslint-disable-line camelcase
148 column_two: 2, // eslint-disable-line camelcase
151 column_one: 'foo', // eslint-disable-line camelcase
152 column_two: 4, // eslint-disable-line camelcase
159 const expectedData
= [
169 db
.pgpInitOptions
.receive({ data
, result
, ctx: event
});
170 assert(!db
.logger
.debug
.called
);
171 assert
.deepStrictEqual(data
, expectedData
);
174 }); // pgpInitOptions
176 describe('_initTables', function () {
177 beforeEach(function () {
178 sinon
.stub(db
.db
, 'oneOrNone');
179 sinon
.stub(db
.db
, 'multiResult');
180 sinon
.stub(db
, '_currentSchema');
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();
188 it('covers exists', async
function() {
189 db
.db
.oneOrNone
.resolves({});
190 db
._currentSchema
.resolves(db
.schemaVersionsSupported
.max
);
191 await db
._initTables();
195 describe('initialize', function () {
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);
204 it('fails low version', async
function () {
205 const version
= { major: 0, minor: 0, patch: 0 };
206 sinon
.stub(db
.db
, 'one').resolves(version
);
208 await db
.initialize(false);
209 assert
.fail(noExpectedException
);
211 assert(e
instanceof DBErrors
.MigrationNeeded
);
214 it('fails high version', async
function () {
215 const version
= { major: 100, minor: 100, patch: 100 };
216 sinon
.stub(db
.db
, 'one').resolves(version
);
218 await db
.initialize(false);
219 assert
.fail(noExpectedException
);
221 assert(e
instanceof DBErrors
.MigrationNeeded
);
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();
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
);
238 await db
.initialize();
239 assert
.fail(noExpectedException
);
241 assert
.deepStrictEqual(e
, expected
);
244 it('covers listener', async
function() {
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
);
255 describe('healthCheck', function () {
256 beforeEach(function () {
257 sinon
.stub(db
.db
, 'connect').resolves({
260 serverVersion: '0.0',
264 it('covers', async
function () {
265 const result
= await db
.healthCheck();
266 assert
.deepStrictEqual(result
, { serverVersion: '0.0' });
270 describe('_queryFileHelper', function () {
271 it('covers success', function () {
272 const _queryFile
= db
._queryFileHelper(pgpStub
);
275 it('covers failure', function () {
276 const err
= new Error();
277 pgpStub
.QueryFile
= class {
282 const _queryFile
= db
._queryFileHelper(pgpStub
);
285 assert
.fail(noExpectedException
);
287 assert
.strictEqual(e
, err
);
290 }); // _queryFileHelper
292 describe('_closeConnection', function () {
296 it('success', async
function () {
297 sinon
.stub(db
._pgp
, 'end');
298 await db
._closeConnection();
299 assert(db
._pgp
.end
.called
);
301 it('failure', async
function () {
302 const expected
= new Error();
303 sinon
.stub(db
._pgp
, 'end').throws(expected
);
305 await db
._closeConnection();
306 assert
.fail(noExpectedException
);
308 assert
.deepStrictEqual(e
, expected
);
311 it('covers listener', async
function () {
315 sinon
.stub(db
._pgp
, 'end');
316 await db
._closeConnection();
317 assert(db
._pgp
.end
.called
);
319 }); // _closeConnection
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
);
327 it('success', async
function () {
328 sinon
.stub(db
.db
, 'batch');
329 await db
._purgeTables(true);
330 assert(db
.db
.batch
.called
);
332 it('failure', async
function () {
333 const expected
= new Error();
334 sinon
.stub(db
.db
, 'tx').rejects(expected
);
336 await db
._purgeTables(true);
337 assert
.fail(noExpectedException
);
339 assert
.deepStrictEqual(e
, expected
);
344 describe('_topicChanged', function () {
345 beforeEach(function () {
346 db
.cache
= new Map();
347 sinon
.stub(db
.cache
, 'delete');
352 it('covers', function () {
353 db
._topicChanged('topic-id');
354 assert(db
.cache
.delete.called
);
356 it('ignores ping', function () {
357 db
._topicChanged('ping');
358 assert(!db
.cache
.delete.called
);
362 describe('_listenerEstablished', function () {
363 it('creates cache', function () {
365 db
._listenerEstablished();
366 assert(db
.cache
instanceof Map
);
368 }); // _listenerEstablished
370 describe('_listenerLost', function () {
371 it('removes cache', function () {
372 db
.cache
= new Map();
378 describe('_cacheGet', function () {
380 beforeEach(function () {
383 it('nothing if no cache', function () {
385 const result
= db
._cacheGet(key
);
386 assert
.strictEqual(result
, undefined);
388 it('nothing if no entry', function () {
389 db
.cache
= new Map();
390 const result
= db
._cacheGet(key
);
391 assert
.strictEqual(result
, undefined);
393 it('returns cached entry', function () {
394 db
.cache
= new Map();
398 db
._cacheSet(key
, expected
);
399 const result
= db
._cacheGet(key
);
400 assert
.deepStrictEqual(result
, expected
);
404 describe('_cacheSet', function () {
406 beforeEach(function () {
409 it('covers no cache', function () {
411 db
._cacheSet(key
, 'data');
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
);
422 describe('context', function () {
423 it('covers', async
function () {
424 await db
.context(common
.nop
);
428 describe('transaction', function () {
429 it('covers', async
function () {
430 await db
.transaction(db
.db
, common
.nop
);
434 describe('authenticationSuccess', function () {
436 beforeEach(function () {
437 identifier
= 'username';
439 it('success', async
function () {
445 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
446 await db
.authenticationSuccess(dbCtx
, identifier
);
448 it('failure', async
function() {
454 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
456 await db
.authenticationSuccess(dbCtx
, identifier
);
457 assert
.fail(noExpectedException
);
459 assert(e
instanceof DBErrors
.UnexpectedResult
);
462 }); // authenticationSuccess
464 describe('authenticationGet', function () {
465 let identifier
, credential
;
466 beforeEach(function () {
467 identifier
= 'username';
468 credential
= '$z$foo';
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
);
476 it('failure', async
function() {
477 const expected
= new Error('blah');
478 sinon
.stub(db
.db
, 'oneOrNone').rejects(expected
);
480 await db
.authenticationGet(dbCtx
, identifier
, credential
);
481 assert
.fail(noExpectedException
);
483 assert
.deepStrictEqual(e
, expected
);
486 }); // authenticationGet
488 describe('authenticationUpsert', function () {
489 let identifier
, credential
;
490 beforeEach(function () {
491 identifier
= 'username';
492 credential
= '$z$foo';
494 it('success', async
function () {
500 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
501 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
503 it('failure', async
function() {
504 credential
= undefined;
510 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
512 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
513 assert
.fail(noExpectedException
);
515 assert(e
instanceof DBErrors
.UnexpectedResult
);
518 }); // authenticationUpsert
520 describe('subscriptionsByTopicId', function () {
521 it('success', async
function () {
523 sinon
.stub(db
.db
, 'manyOrNone').resolves(expected
);
524 const result
= await db
.subscriptionsByTopicId(dbCtx
, topicUrl
);
525 assert
.deepStrictEqual(result
, expected
);
527 it('failure', async
function () {
528 const expected
= new Error();
529 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
531 await db
.subscriptionsByTopicId(dbCtx
, topicUrl
);
532 assert
.fail(noExpectedException
);
534 assert
.deepStrictEqual(e
, expected
);
537 }); // subscriptionsByTopicId
539 describe('subscriptionCountByTopicUrl', function () {
540 it('success', async
function () {
541 const expected
= { count: 3 };
542 sinon
.stub(db
.db
, 'one').resolves(expected
);
543 const result
= await db
.subscriptionCountByTopicUrl(dbCtx
, topicUrl
);
544 assert
.deepStrictEqual(result
, expected
);
546 it('failure', async
function () {
547 const expected
= new Error();
548 sinon
.stub(db
.db
, 'one').throws(expected
);
550 await db
.subscriptionCountByTopicUrl(dbCtx
, topicUrl
);
551 assert
.fail(noExpectedException
);
553 assert
.deepStrictEqual(e
, expected
);
556 }); // subscriptionCountByTopicUrl
558 describe('subscriptionDelete', function () {
559 it('success', async
function() {
567 lastInsertRowid: undefined,
570 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
571 const result
= await db
.subscriptionDelete(dbCtx
, callback
, topicId
);
572 assert
.deepStrictEqual(result
, expected
);
574 it('failure', async
function () {
575 const expected
= new Error();
576 sinon
.stub(db
.db
, 'result').throws(expected
);
578 await db
.subscriptionDelete(dbCtx
, callback
, topicId
);
579 assert
.fail(noExpectedException
);
581 assert
.deepStrictEqual(e
, expected
);
584 }); // subscriptionDelete
586 describe('subscriptionDeleteExpired', function () {
587 it('success', async
function () {
595 lastInsertRowid: undefined,
598 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
599 const result
= await db
.subscriptionDeleteExpired(dbCtx
, topicId
);
600 assert
.deepStrictEqual(result
, expected
);
602 it('failure', async
function() {
603 const expected
= new Error();
604 sinon
.stub(db
.db
, 'result').rejects(expected
);
606 await db
.subscriptionDeleteExpired(dbCtx
, topicId
);
607 assert
.fail(noExpectedException
);
609 assert
.deepStrictEqual(e
, expected
);
614 describe('subscriptionDeliveryClaim', function () {
615 it('success', async
function() {
618 id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
621 const expected
= ['c2e254c5-aa6e-4a8f-b1a1-e474b07392bb'];
622 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
623 const result
= await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
624 assert
.deepStrictEqual(result
, expected
);
626 it('failure', async
function () {
627 const expected
= new Error();
628 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
630 await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
631 assert
.fail(noExpectedException
);
633 assert
.deepStrictEqual(e
, expected
);
636 }); // subscriptionDeliveryClaim
638 describe('subscriptionDeliveryClaimById', function () {
639 it('success', async
function() {
642 rows: [{ id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb' }],
647 lastInsertRowid: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
650 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
651 const result
= await db
.subscriptionDeliveryClaimById(dbCtx
, subscriptionId
, claimTimeoutSeconds
, claimant
);
652 assert
.deepStrictEqual(result
, expected
);
654 it('failure', async
function () {
660 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
662 await db
.subscriptionDeliveryClaimById(dbCtx
, callback
, topicId
);
663 assert
.fail(noExpectedException
);
665 assert(e
instanceof DBErrors
.UnexpectedResult
);
668 }); // subscriptionDeliveryClaimById
670 describe('subscriptionDeliveryComplete', function () {
671 let topicContentUpdated
;
673 topicContentUpdated
= new Date();
675 it('success', async
function() {
679 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
680 await db
.subscriptionDeliveryComplete(dbCtx
, callback
, topicId
, topicContentUpdated
);
682 it('failure', async
function () {
686 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult
);
688 await db
.subscriptionDeliveryComplete(dbCtx
, callback
, topicId
, topicContentUpdated
);
689 assert
.fail(noExpectedException
);
691 assert(e
instanceof DBErrors
.UnexpectedResult
);
694 it('second failure', async
function () {
701 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
703 await db
.subscriptionDeliveryComplete(dbCtx
, callback
, topicId
, topicContentUpdated
);
704 assert
.fail(noExpectedException
);
706 assert(e
instanceof DBErrors
.UnexpectedResult
);
709 }); // subscriptionDeliveryComplete
711 describe('subscriptionDeliveryGone', function () {
712 it('success', async
function() {
716 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
717 await db
.subscriptionDeliveryGone(dbCtx
, callback
, topicId
);
719 it('failure', async
function () {
723 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
725 await db
.subscriptionDeliveryGone(dbCtx
, callback
, topicId
);
726 assert
.fail(noExpectedException
);
728 assert(e
instanceof DBErrors
.UnexpectedResult
);
731 }); // subscriptionDeliveryGone
733 describe('subscriptionDeliveryIncomplete', function () {
734 it('success', async
function() {
735 const dbOne
= { deliveryAttemptsSinceSuccess: 0 };
739 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
740 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
741 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
, retryDelays
);
743 it('success covers default', async
function() {
744 const dbOne
= { deliveryAttemptsSinceSuccess: 0 };
748 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
749 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
750 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
);
752 it('failure', async
function () {
753 const dbOne
= { deliveryAttemptsSinceSuccess: 0 };
757 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
758 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
760 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
, retryDelays
);
761 assert
.fail(noExpectedException
);
763 assert(e
instanceof DBErrors
.UnexpectedResult
);
766 it('second failure', async
function () {
767 const dbOne
= { deliveryAttemptsSinceSuccess: 0 };
774 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
775 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
777 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
, retryDelays
);
778 assert
.fail(noExpectedException
);
780 assert(e
instanceof DBErrors
.UnexpectedResult
);
783 }); // subscriptionDeliveryIncomplete
785 describe('subscriptionGet', function () {
786 it('success', async
function() {
790 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
791 const result
= await db
.subscriptionGet(dbCtx
, callback
, topicId
);
792 assert
.deepStrictEqual(result
, expected
);
794 it('failure', async
function () {
795 const expected
= new Error();
796 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
798 await db
.subscriptionGet(dbCtx
, callback
, topicId
);
799 assert
.fail(noExpectedException
);
801 assert
.deepStrictEqual(e
, expected
);
804 }); // subscriptionGet
806 describe('subscriptionGetById', function () {
807 it('success', async
function() {
811 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
812 const result
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
813 assert
.deepStrictEqual(result
, expected
);
815 it('failure', async
function () {
816 const expected
= new Error();
817 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
819 await db
.subscriptionGetById(dbCtx
, subscriptionId
);
820 assert
.fail(noExpectedException
);
822 assert
.deepStrictEqual(e
, expected
);
825 }); // subscriptionGetById
827 describe('subscriptionUpsert', function () {
829 beforeEach(function () {
839 it('success', async
function() {
842 rows: [{ id: subscriptionId
}],
847 lastInsertRowid: subscriptionId
,
850 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
851 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
852 assert
.deepStrictEqual(result
, expected
);
854 it('failure', async
function () {
858 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
860 await db
.subscriptionUpsert(dbCtx
, data
);
861 assert
.fail(noExpectedException
);
863 assert(e
instanceof DBErrors
.UnexpectedResult
);
866 }); // subscriptionUpsert
868 describe('subscriptionUpdate', function () {
870 beforeEach(function () {
872 signatureAlgorithm: 'sha256',
875 it('success', async
function() {
881 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
882 await db
.subscriptionUpdate(dbCtx
, data
);
884 it('failure', async
function () {
888 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
890 await db
.subscriptionUpdate(dbCtx
, data
);
891 assert
.fail(noExpectedException
);
893 assert(e
instanceof DBErrors
.UnexpectedResult
);
896 }); // subscriptionUpdate
898 describe('topicDeleted', function () {
899 it('success', async
function() {
903 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
904 await db
.topicDeleted(dbCtx
, topicId
);
906 it('failure', async
function() {
910 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
912 await db
.topicDeleted(dbCtx
, topicId
);
913 assert
.fail(noExpectedException
);
915 assert(e
instanceof DBErrors
.UnexpectedResult
);
920 describe('topicFetchClaim', function () {
921 it('success', async
function() {
922 const dbResult
= [{ id: topicId
}];
923 const expected
= [topicId
];
924 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
925 const result
= await db
.topicFetchClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
926 assert
.deepStrictEqual(result
, expected
);
928 it('failure', async
function () {
929 const expected
= new Error();
930 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
932 await db
.topicFetchClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
933 assert
.fail(noExpectedException
);
935 assert
.deepStrictEqual(e
, expected
);
938 }); // topicFetchClaim
940 describe('topicFetchClaimById', function () {
941 it('success', async
function() {
949 lastInsertRowid: undefined,
952 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
953 const result
= await db
.topicFetchClaimById(dbCtx
, topicId
, claimTimeoutSeconds
, claimant
);
954 assert
.deepStrictEqual(result
, expected
);
956 it('failure', async
function () {
957 const expected
= new Error();
958 sinon
.stub(db
.db
, 'result').throws(expected
);
960 await db
.topicFetchClaimById(dbCtx
, topicId
, claimTimeoutSeconds
, claimant
);
961 assert
.fail(noExpectedException
);
963 assert
.deepStrictEqual(e
, expected
);
966 }); // topicFetchClaimById
968 describe('topicFetchComplete', function () {
969 it('success', async
function() {
975 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
976 await db
.topicFetchComplete(dbCtx
, topicId
);
978 it('failure', async
function () {
984 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
986 await db
.topicFetchComplete(dbCtx
, topicId
);
987 assert
.fail(noExpectedException
);
989 assert(e
instanceof DBErrors
.UnexpectedResult
);
992 it('second failure', async
function () {
1003 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1005 await db
.topicFetchComplete(dbCtx
, topicId
);
1006 assert
.fail(noExpectedException
);
1008 assert(e
instanceof DBErrors
.UnexpectedResult
);
1011 }); // topicFetchComplete
1013 describe('topicFetchIncomplete', function () {
1014 it('success', async
function() {
1015 const dbOne
= { currentAttempt: 0 };
1028 lastInsertRowid: undefined,
1031 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1032 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1033 const result
= await db
.topicFetchIncomplete(dbCtx
, topicId
, retryDelays
);
1034 assert
.deepStrictEqual(result
, expected
);
1036 it('covers defaults', async
function() {
1037 const dbOne
= { currentAttempt: 0 };
1050 lastInsertRowid: undefined,
1053 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1054 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1055 const result
= await db
.topicFetchIncomplete(dbCtx
, topicId
);
1056 assert
.deepStrictEqual(result
, expected
);
1058 it('failure', async
function () {
1059 const dbOne
= { currentAttempt: 0 };
1070 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1071 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1073 await db
.topicFetchIncomplete(dbCtx
, topicId
, retryDelays
);
1074 assert
.fail(noExpectedException
);
1076 assert(e
instanceof DBErrors
.UnexpectedResult
);
1079 it('second failure', async
function () {
1080 const dbOne
= { currentAttempt: 0 };
1091 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1092 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1094 await db
.topicFetchIncomplete(dbCtx
, topicId
, retryDelays
);
1095 assert
.fail(noExpectedException
);
1097 assert(e
instanceof DBErrors
.UnexpectedResult
);
1100 }); // topicFetchIncomplete
1102 describe('topicFetchRequested', function () {
1103 it('success', async
function() {
1111 lastInsertRowid: undefined,
1114 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1115 const result
= await db
.topicFetchRequested(dbCtx
, topicId
);
1116 assert
.deepStrictEqual(result
, expected
);
1118 it('failure', async
function () {
1124 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1126 await db
.topicFetchRequested(dbCtx
, topicId
);
1127 assert
.fail(noExpectedException
);
1129 assert(e
instanceof DBErrors
.UnexpectedResult
);
1132 }); // topicFetchRequested
1134 describe('topicGetAll', function () {
1135 it('success', async
function() {
1136 const expected
= [{ id: topicId
}];
1137 sinon
.stub(db
.db
, 'manyOrNone').resolves(expected
);
1138 const result
= await db
.topicGetAll(dbCtx
);
1139 assert
.deepStrictEqual(result
, expected
);
1141 it('covers default', async
function() {
1142 const expected
= undefined;
1143 sinon
.stub(db
.db
, 'manyOrNone').resolves(expected
);
1144 const result
= await db
.topicGetAll(dbCtx
);
1145 assert
.deepStrictEqual(result
, expected
);
1147 it('failure', async
function () {
1148 const expected
= new Error();
1149 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
1151 await db
.topicGetAll(dbCtx
);
1152 assert
.fail(noExpectedException
);
1154 assert
.deepStrictEqual(e
, expected
);
1159 describe('topicGetById', function () {
1160 it('success', async
function() {
1161 const expected
= { id: topicId
};
1162 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
1163 const result
= await db
.topicGetById(dbCtx
, topicId
);
1164 assert
.deepStrictEqual(result
, expected
);
1166 it('covers none', async
function() {
1167 const expected
= undefined;
1168 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
1169 const result
= await db
.topicGetById(dbCtx
, topicId
);
1170 assert
.deepStrictEqual(result
, expected
);
1172 it('covers no defaults', async
function () {
1173 const expected
= { id: topicId
};
1174 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
1175 const result
= await db
.topicGetById(dbCtx
, topicId
, false);
1176 assert
.deepStrictEqual(result
, expected
);
1178 it('failure', async
function () {
1179 const expected
= new Error();
1180 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
1182 await db
.topicGetById(dbCtx
, topicId
);
1183 assert
.fail(noExpectedException
);
1185 assert
.deepStrictEqual(e
, expected
);
1190 describe('topicGetByUrl', function () {
1191 it('success', async
function() {
1192 const expected
= [];
1193 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
1194 const result
= await db
.topicGetByUrl(dbCtx
, topicUrl
);
1195 assert
.deepStrictEqual(result
, expected
);
1197 it('failure', async
function () {
1198 const expected
= new Error();
1199 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
1201 await db
.topicGetByUrl(dbCtx
, topicUrl
);
1202 assert
.fail(noExpectedException
);
1204 assert
.deepStrictEqual(e
, expected
);
1207 }); // topicGetByUrl
1209 describe('topicGetContentById', function () {
1211 beforeEach(function () {
1217 it('success', async
function() {
1218 const expected
= topic
;
1219 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
1220 const result
= await db
.topicGetContentById(dbCtx
, topicId
);
1221 assert
.deepStrictEqual(result
, expected
);
1223 it('covers default', async
function() {
1224 const expected
= undefined;
1225 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
1226 const result
= await db
.topicGetContentById(dbCtx
, topicId
);
1227 assert
.deepStrictEqual(result
, expected
);
1229 it('failure', async
function () {
1230 const expected
= new Error();
1231 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
1233 await db
.topicGetContentById(dbCtx
, topicId
);
1234 assert
.fail(noExpectedException
);
1236 assert
.deepStrictEqual(e
, expected
);
1239 it('caches success', async
function () {
1240 db
.cache
= new Map();
1241 const expected
= topic
;
1242 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
1243 const result
= await db
.topicGetContentById(dbCtx
, topicId
);
1244 assert
.deepStrictEqual(result
, expected
);
1246 it('covers cached entry', async
function() {
1248 db
.cache
= new Map();
1249 const expected
= topic
;
1250 sinon
.stub(db
.db
, 'oneOrNone').resolves(expected
);
1251 result
= await db
.topicGetContentById(dbCtx
, topicId
);
1252 assert
.deepStrictEqual(result
, expected
);
1253 result
= await db
.topicGetContentById(dbCtx
, topicId
);
1254 assert
.deepStrictEqual(result
, expected
);
1256 }); // topicGetContentById
1258 describe('topicPendingDelete', function () {
1259 beforeEach(function () {
1260 sinon
.stub(db
.db
, 'one');
1261 sinon
.stub(db
.db
, 'result');
1263 it('success', async
function () {
1264 db
.db
.one
.onCall(0).resolves({
1267 }).onCall(1).resolves({
1275 db
.db
.result
.resolves(dbResult
);
1276 await db
.topicPendingDelete(dbCtx
, topicId
);
1277 assert(db
.db
.result
.called
);
1279 it('does not delete non-deleted topic', async
function () {
1280 db
.db
.one
.onCall(0).resolves({
1283 }).onCall(1).resolves({
1286 await db
.topicPendingDelete(dbCtx
, topicId
);
1287 assert(!db
.db
.result
.called
);
1289 it('does not delete topic with active subscriptions', async
function () {
1290 db
.db
.one
.onCall(0).resolves({
1293 }).onCall(1).resolves({
1296 await db
.topicPendingDelete(dbCtx
, topicId
);
1297 assert(!db
.db
.result
.called
);
1299 it('covers no deletion', async
function () {
1300 db
.db
.one
.onCall(0).resolves({
1303 }).onCall(1).resolves({
1311 db
.db
.result
.resolves(dbResult
);
1313 await db
.topicPendingDelete(dbCtx
, topicId
);
1314 assert
.fail(noExpectedException
);
1316 assert(e
instanceof DBErrors
.UnexpectedResult
);
1321 describe('topicPublishHistory', function () {
1322 beforeEach(function () {
1323 sinon
.stub(db
.db
, 'manyOrNone');
1325 it('success', async
function () {
1326 db
.db
.manyOrNone
.returns([
1327 { daysAgo: 1, contentUpdates: 1 },
1328 { daysAgo: 3, contentUpdates: 2 },
1330 const result
= await db
.topicPublishHistory(dbCtx
, topicId
, 7);
1331 const expected
= [0, 1, 0, 2, 0, 0, 0];
1332 assert
.deepStrictEqual(result
, expected
);
1334 }); // topicPublishHistory
1336 describe('topicSet', function () {
1338 beforeEach(function () {
1343 it('success', async
function() {
1346 rows: [{ id: topicId
}],
1351 lastInsertRowid: topicId
,
1354 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1355 const result
= await db
.topicSet(dbCtx
, data
);
1356 assert
.deepStrictEqual(result
, expected
);
1358 it('failure', async
function () {
1364 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1366 await db
.topicSet(dbCtx
, data
);
1367 assert
.fail(noExpectedException
);
1369 assert(e
instanceof DBErrors
.UnexpectedResult
);
1372 it('fails invalid value', async
function () {
1373 sinon
.stub(db
.db
, 'result');
1375 data
.leaseSecondsPreferred
= -100;
1376 await db
.topicSet(dbCtx
, data
);
1377 assert
.fail(noExpectedException
);
1379 assert(e
instanceof DBErrors
.DataValidation
);
1381 assert(!db
.db
.result
.called
);
1383 it('fails invalid values', async
function () {
1384 sinon
.stub(db
.db
, 'result');
1386 data
.leaseSecondsPreferred
= 10;
1387 data
.leaseSecondsMax
= 100;
1388 data
.leaseSecondsMin
= 50;
1389 await db
.topicSet(dbCtx
, data
);
1390 assert
.fail(noExpectedException
);
1392 assert(e
instanceof DBErrors
.DataValidation
);
1394 assert(!db
.db
.result
.called
);
1398 describe('topicSetContent', function () {
1400 beforeEach(function () {
1403 contentType: 'text/plain',
1404 contentHash: 'abc123',
1406 sinon
.stub(db
.db
, 'result');
1408 it('success', async
function() {
1416 lastInsertRowid: undefined,
1419 db
.db
.result
.resolves(dbResult
);
1420 const result
= await db
.topicSetContent(dbCtx
, data
);
1421 assert
.deepStrictEqual(result
, expected
);
1423 it('failure', async
function () {
1429 db
.db
.result
.resolves(dbResult
);
1431 await db
.topicSetContent(dbCtx
, data
);
1432 assert
.fail(noExpectedException
);
1434 assert(e
instanceof DBErrors
.UnexpectedResult
);
1437 it('failure 2', async
function () {
1438 const dbResultSuccess
= {
1443 const dbResultFail
= {
1449 .onCall(0).resolves(dbResultSuccess
)
1450 .onCall(1).resolves(dbResultFail
);
1452 await db
.topicSetContent(dbCtx
, data
);
1453 assert
.fail(noExpectedException
);
1455 assert(e
instanceof DBErrors
.UnexpectedResult
);
1458 }); // topicSetContent
1460 describe('topicUpdate', function () {
1462 beforeEach(function () {
1464 leaseSecondsPreferred: 123,
1465 leaseSecondsMin: 100,
1466 leaseSecondsMax: 1000,
1467 publisherValidationUrl: null,
1468 contentHashAlgorithm: 'sha256',
1471 it('success', async
function() {
1477 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1478 await db
.topicUpdate(dbCtx
, data
);
1480 it('failure', async
function () {
1486 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1488 await db
.topicUpdate(dbCtx
, data
);
1489 assert
.fail(noExpectedException
);
1491 assert(e
instanceof DBErrors
.UnexpectedResult
);
1497 describe('verificationClaim', function () {
1498 it('success', async
function() {
1499 const dbManyOrNone
= [{ id: verificationId
}];
1500 const expected
= [verificationId
];
1501 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbManyOrNone
);
1502 const result
= await db
.verificationClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
1503 assert
.deepStrictEqual(result
, expected
);
1505 it('failure', async
function () {
1506 const expected
= new Error();
1507 sinon
.stub(db
.db
, 'manyOrNone').throws(expected
);
1509 await db
.verificationClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
1510 assert
.fail(noExpectedException
);
1512 assert
.deepStrictEqual(e
, expected
);
1515 }); // verificationClaim
1517 describe('verificationClaimById', function () {
1518 it('success', async
function() {
1521 rows: [ { id: verificationId
} ],
1526 lastInsertRowid: verificationId
,
1529 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1530 const result
= await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
1531 assert
.deepStrictEqual(result
, expected
);
1533 it('failure', async
function () {
1534 const expected
= new Error();
1535 sinon
.stub(db
.db
, 'result').throws(expected
);
1537 await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
1538 assert
.fail(noExpectedException
);
1540 assert
.deepStrictEqual(e
, expected
);
1543 }); // verificationClaimById
1545 describe('verificationComplete', function () {
1546 it('success', async
function() {
1552 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1553 await db
.verificationComplete(dbCtx
, verificationId
, callback
, topicId
);
1555 it('failure', async
function () {
1561 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1563 await db
.verificationComplete(dbCtx
, verificationId
, callback
, topicId
);
1564 assert
.fail(noExpectedException
);
1566 assert(e
instanceof DBErrors
.UnexpectedResult
);
1569 }); // verificationComplete
1571 describe('verificationGetById', function () {
1572 it('success', async
function() {
1573 const dbOneOrNone
= { id: verificationId
};
1574 const expected
= { id: verificationId
};
1575 sinon
.stub(db
.db
, 'oneOrNone').resolves(dbOneOrNone
);
1576 const result
= await db
.verificationGetById(dbCtx
, verificationId
);
1577 assert
.deepStrictEqual(result
, expected
);
1579 it('failure', async
function () {
1580 const expected
= new Error();
1581 sinon
.stub(db
.db
, 'oneOrNone').throws(expected
);
1583 await db
.verificationGetById(dbCtx
, verificationId
);
1584 assert
.fail(noExpectedException
);
1586 assert
.deepStrictEqual(e
, expected
);
1589 }); // verificationGetById
1591 describe('verificationIncomplete', function () {
1592 it('success', async
function() {
1593 const dbOne
= { attempts: 0 };
1604 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1605 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1606 await db
.verificationIncomplete(dbCtx
, verificationId
, retryDelays
);
1608 it('covers defaults', async
function() {
1609 const dbOne
= { attempts: 0 };
1620 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1621 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1622 await db
.verificationIncomplete(dbCtx
, verificationId
);
1624 it('failure', async
function () {
1625 const dbOne
= { attempts: 0 };
1636 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1637 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1639 await db
.verificationIncomplete(dbCtx
, verificationId
, retryDelays
);
1640 assert
.fail(noExpectedException
);
1642 assert(e
instanceof DBErrors
.UnexpectedResult
);
1645 it('second failure', async
function () {
1646 const dbOne
= { attempts: 0 };
1657 sinon
.stub(db
.db
, 'one').resolves(dbOne
);
1658 sinon
.stub(db
.db
, 'result').onCall(0).resolves(dbResult0
).onCall(1).resolves(dbResult1
);
1660 await db
.verificationIncomplete(dbCtx
, verificationId
, retryDelays
);
1661 assert
.fail(noExpectedException
);
1663 assert(e
instanceof DBErrors
.UnexpectedResult
);
1666 }); // verificationIncomplete
1668 describe('verificationInsert', function () {
1670 beforeEach(function () {
1675 isPublisherValidated: true,
1676 leaseSeconds: 86400,
1679 it('success', async
function() {
1682 rows: [{ id: verificationId
}],
1685 const expected
= verificationId
;
1686 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1687 const result
= await db
.verificationInsert(dbCtx
, verification
);
1688 assert
.deepStrictEqual(result
, expected
);
1690 it('failure', async
function () {
1696 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1698 await db
.verificationInsert(dbCtx
, verification
);
1699 assert
.fail(noExpectedException
);
1701 assert(e
instanceof DBErrors
.UnexpectedResult
);
1704 it('fails validation', async
function () {
1705 delete verification
.leaseSeconds
;
1707 await db
.verificationInsert(dbCtx
, verification
);
1708 assert
.fail(noExpectedException
);
1710 assert(e
instanceof DBErrors
.DataValidation
);
1713 }); // verificationInsert
1715 describe('verificationRelease', function () {
1716 it('success', async
function() {
1722 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1723 await db
.verificationRelease(dbCtx
, verificationId
);
1725 it('failure', async
function () {
1731 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1733 await db
.verificationRelease(dbCtx
, verificationId
);
1734 assert
.fail(noExpectedException
);
1736 assert(e
instanceof DBErrors
.UnexpectedResult
);
1739 }); // verificationRelease
1741 describe('verificationUpdate', function () {
1743 beforeEach(function () {
1746 isPublisherValidated: true,
1749 it('success', async
function() {
1755 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1756 await db
.verificationUpdate(dbCtx
, verificationId
, data
);
1758 it('failure', async
function () {
1764 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1766 await db
.verificationUpdate(dbCtx
, verificationId
, data
);
1767 assert
.fail(noExpectedException
);
1769 assert(e
instanceof DBErrors
.UnexpectedResult
, e
.name
);
1772 it('fails validation', async
function () {
1775 await db
.verificationUpdate(dbCtx
, verificationId
, data
);
1776 assert
.fail(noExpectedException
);
1778 assert(e
instanceof DBErrors
.DataValidation
);
1781 }); // verificationUpdate
1783 describe('verificationValidated', function () {
1784 it('success', async
function() {
1790 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1791 await db
.verificationValidated(dbCtx
, verificationId
);
1793 it('failure', async
function () {
1799 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1801 await db
.verificationValidated(dbCtx
, verificationId
);
1802 assert
.fail(noExpectedException
);
1804 assert(e
instanceof DBErrors
.UnexpectedResult
);
1807 }); // verificationValidated
1809 }); // DatabasePostgres