3 /* This provides implementation coverage, stubbing pg-promise. */
5 const assert
= require('assert');
6 const sinon
= require('sinon');
7 const StubLogger
= require('../../stub-logger');
8 const StubDatabase
= require('../../stub-db');
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 expectedException
= new Error('oh no');
16 describe('DatabasePostgres', function () {
17 let db
, logger
, options
, pgpStub
;
22 result: () => ({ rows: [] }),
27 manyOrNone: common
.nop
,
28 oneOrNone: common
.nop
,
31 multiResult: common
.nop
,
34 stub
.tx
= (fn
) => fn(stub
);
35 stub
.txIf
= (fn
) => fn(stub
);
36 stub
.task
= (fn
) => fn(stub
);
42 pgpStub
.QueryFile
= class {};
43 pgpStub
.end
= common
.nop
;
45 beforeEach(function () {
46 logger
= new StubLogger();
48 options
= new Config('test');
49 db
= new DB(logger
, options
, pgpStub
);
52 afterEach(function () {
56 it('covers no query logging', function () {
57 delete options
.db
.queryLogLevel
;
58 db
= new DB(logger
, options
, pgpStub
);
62 // Ensure all interface methods are implemented
63 describe('Implementation', function () {
64 it('implements interface', async
function () {
65 const stubDb
= new StubDatabase();
66 const results
= await Promise
.allSettled(stubDb
._implementation
.map(async (fn
) => {
68 // eslint-disable-next-line security/detect-object-injection
71 assert(!(e
instanceof DBErrors
.NotImplemented
), `${fn} not implemented`);
74 const failures
= results
.filter((x
) => x
.status
=== 'rejected');
75 assert(!failures
.length
, failures
.map((x
) => {
76 x
= x
.reason
.toString();
77 return x
.slice(x
.indexOf(': '));
82 describe('pgpInitOptions', function () {
83 describe('error', function () {
84 it('covers', function () {
87 db
.pgpInitOptions
.error(err
, event
);
88 assert(db
.logger
.error
.called
);
91 describe('query', function () {
92 it('covers', function () {
94 db
.pgpInitOptions
.query(event
);
95 assert(db
.logger
.debug
.called
);
98 describe('receive', function () {
99 it('covers', function () {
102 column_one: 'one', // eslint-disable-line camelcase
103 column_two: 2, // eslint-disable-line camelcase
106 column_one: 'foo', // eslint-disable-line camelcase
107 column_two: 4, // eslint-disable-line camelcase
112 const expectedData
= [
122 db
.pgpInitOptions
.receive({ data
, result
, ctx: event
});
123 assert(db
.logger
.debug
.called
);
124 assert
.deepStrictEqual(data
, expectedData
);
126 it('covers no query logging', function () {
127 delete options
.db
.queryLogLevel
;
128 db
= new DB(logger
, options
, pgpStub
);
131 column_one: 'one', // eslint-disable-line camelcase
132 column_two: 2, // eslint-disable-line camelcase
135 column_one: 'foo', // eslint-disable-line camelcase
136 column_two: 4, // eslint-disable-line camelcase
141 const expectedData
= [
151 db
.pgpInitOptions
.receive({ data
, result
, ctx: event
});
152 assert(db
.logger
.debug
.called
);
153 assert
.deepStrictEqual(data
, expectedData
);
157 }); // pgpInitOptions
159 describe('_initTables', function () {
160 beforeEach(function () {
161 sinon
.stub(db
.db
, 'oneOrNone');
162 sinon
.stub(db
.db
, 'multiResult');
163 sinon
.stub(db
, '_currentSchema');
166 it('covers apply', async
function() {
167 db
.db
.oneOrNone
.onCall(0).resolves(null).onCall(1).resolves({});
168 db
._currentSchema
.resolves({ major: 0, minor: 0, patch: 0 });
169 await db
._initTables();
171 it('covers exists', async
function() {
172 db
.db
.oneOrNone
.resolves({});
173 db
._currentSchema
.resolves(db
.schemaVersionsSupported
.max
);
174 await db
._initTables();
178 describe('initialize', function () {
182 it('passes supported version', async
function () {
183 const version
= { major: 1, minor: 0, patch: 0 };
184 sinon
.stub(db
.db
, 'one').resolves(version
);
185 await db
.initialize(false);
187 it('fails low version', async
function () {
188 const version
= { major: 0, minor: 0, patch: 0 };
189 sinon
.stub(db
.db
, 'one').resolves(version
);
190 await assert
.rejects(() => db
.initialize(false), DBErrors
.MigrationNeeded
);
192 it('fails high version', async
function () {
193 const version
= { major: 100, minor: 100, patch: 100 };
194 sinon
.stub(db
.db
, 'one').resolves(version
);
195 await assert
.rejects(() => db
.initialize(false));
197 it('covers migration', async
function() {
198 sinon
.stub(db
.db
, 'oneOrNone').resolves({});
199 sinon
.stub(db
.db
, 'multiResult');
200 sinon
.stub(db
, '_currentSchema').resolves(db
.schemaVersionsSupported
.max
);
201 sinon
.stub(db
.db
, 'one').resolves(db
.schemaVersionsSupported
.max
);
202 await db
.initialize();
204 it('covers listener', async
function() {
208 const version
= { major: 1, minor: 0, patch: 0 };
209 sinon
.stub(db
.db
, 'one').resolves(version
);
210 await db
.initialize(false);
211 assert(db
.listener
.start
.called
);
215 describe('healthCheck', function () {
216 beforeEach(function () {
217 sinon
.stub(db
.db
, 'connect').resolves({
220 serverVersion: '0.0',
224 it('covers', async
function () {
225 const result
= await db
.healthCheck();
226 assert
.deepStrictEqual(result
, { serverVersion: '0.0' });
230 describe('_queryFileHelper', function () {
231 it('covers success', function () {
232 const _queryFile
= db
._queryFileHelper(pgpStub
);
235 it('covers failure', function () {
236 pgpStub
.QueryFile
= class {
238 this.error
= expectedException
;
241 const _queryFile
= db
._queryFileHelper(pgpStub
);
242 assert
.throws(() => _queryFile(), expectedException
);
244 }); // _queryFileHelper
246 describe('_closeConnection', function () {
250 it('success', async
function () {
251 sinon
.stub(db
._pgp
, 'end');
252 await db
._closeConnection();
253 assert(db
._pgp
.end
.called
);
255 it('failure', async
function () {
256 sinon
.stub(db
._pgp
, 'end').throws(expectedException
);
257 await assert
.rejects(() => db
._closeConnection(), expectedException
);
259 it('covers listener', async
function () {
263 sinon
.stub(db
._pgp
, 'end');
264 await db
._closeConnection();
265 assert(db
._pgp
.end
.called
);
267 }); // _closeConnection
269 describe('_purgeTables', function () {
270 it('covers not really', async
function () {
271 sinon
.stub(db
.db
, 'tx');
272 await db
._purgeTables(false);
273 assert(!db
.db
.tx
.called
);
275 it('success', async
function () {
276 sinon
.stub(db
.db
, 'batch');
277 await db
._purgeTables(true);
278 assert(db
.db
.batch
.called
);
280 it('failure', async
function () {
281 sinon
.stub(db
.db
, 'tx').rejects(expectedException
);
282 await assert
.rejects(() => db
._purgeTables(true), expectedException
);
286 describe('context', function () {
287 it('covers', async
function () {
288 await db
.context(common
.nop
);
292 describe('transaction', function () {
293 it('covers', async
function () {
294 await db
.transaction(db
.db
, common
.nop
);
298 describe('almanacGetAll', function () {
299 beforeEach(function () {
300 sinon
.stub(db
.db
, 'manyOrNone');
302 it('success', async
function () {
303 const expected
= [{ event: 'someEvent', date: new Date() }];
304 db
.db
.manyOrNone
.resolves(expected
);
305 const result
= await db
.almanacGetAll(dbCtx
);
306 assert
.deepStrictEqual(result
, expected
);
308 it('failure', async
function () {
309 db
.db
.manyOrNone
.rejects(expectedException
);
310 await assert
.rejects(() => db
.almanacGetAll(dbCtx
), expectedException
);
314 describe('almanacUpsert', function () {
316 beforeEach(function () {
317 event
= 'test_event';
318 date
= new Date('Fri Dec 22 03:27 UTC 2023');
320 it('success', async
function () {
326 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
327 await db
.almanacUpsert(dbCtx
, event
, date
);
329 it('success with default date', async
function () {
335 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
336 await db
.almanacUpsert(dbCtx
, event
);
338 it('failure', async
function () {
344 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
345 await assert
.rejects(() => db
.almanacUpsert(dbCtx
, event
, date
), DBErrors
.UnexpectedResult
);
349 describe('authenticationSuccess', function () {
351 beforeEach(function () {
352 identifier
= 'username';
354 it('success', async
function () {
360 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
361 await db
.authenticationSuccess(dbCtx
, identifier
);
363 it('failure', async
function() {
369 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
370 await assert
.rejects(() => db
.authenticationSuccess(dbCtx
, identifier
), DBErrors
.UnexpectedResult
);
372 }); // authenticationSuccess
374 describe('authenticationGet', function () {
375 let identifier
, credential
;
376 beforeEach(function () {
377 identifier
= 'username';
378 credential
= '$z$foo';
380 it('success', async
function () {
381 const dbResult
= { identifier
, credential
};
382 sinon
.stub(db
.db
, 'oneOrNone').resolves(dbResult
);
383 const result
= await db
.authenticationGet(dbCtx
, identifier
);
384 assert
.deepStrictEqual(result
, dbResult
);
386 it('failure', async
function() {
387 sinon
.stub(db
.db
, 'oneOrNone').rejects(expectedException
);
388 await assert
.rejects(() => db
.authenticationGet(dbCtx
, identifier
, credential
), expectedException
);
390 }); // authenticationGet
392 describe('authenticationUpsert', function () {
393 let identifier
, credential
;
394 beforeEach(function () {
395 identifier
= 'username';
396 credential
= '$z$foo';
398 it('success', async
function () {
404 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
405 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
407 it('failure', async
function() {
408 credential
= undefined;
414 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
415 await assert
.rejects(() => db
.authenticationUpsert(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
417 }); // authenticationUpsert
419 describe('authenticationUpdateCredential', function () {
420 let identifier
, credential
;
421 beforeEach(function () {
422 identifier
= 'username';
423 credential
= '$z$foo';
425 it('success', async
function () {
431 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
432 await db
.authenticationUpdateCredential(dbCtx
, identifier
, credential
);
434 it('failure', async
function () {
435 credential
= undefined;
441 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
442 await assert
.rejects(() => db
.authenticationUpdateCredential(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
445 }); // authenticationUpdateCredential
447 describe('authenticationUpdateOTPKey', function () {
448 let identifier
, otpKey
;
449 beforeEach(function () {
450 identifier
= 'username';
451 otpKey
= '1234567890123456789012';
453 it('success', async
function () {
459 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
460 await db
.authenticationUpdateOTPKey(dbCtx
, identifier
, otpKey
);
462 it('failure', async
function () {
468 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
469 await assert
.rejects(() => db
.authenticationUpdateOTPKey(dbCtx
, identifier
, otpKey
), DBErrors
.UnexpectedResult
);
471 }); // authenticationUpdateOTPKey
473 describe('profileIdentifierInsert', function () {
474 let profile
, identifier
;
475 beforeEach(function () {
476 profile
= 'https://profile.example.com/';
477 identifier
= 'username';
479 it('success', async
function () {
483 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
484 await db
.profileIdentifierInsert(dbCtx
, profile
, identifier
);
486 it('failure', async
function () {
490 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
491 await assert
.rejects(() => db
.profileIdentifierInsert(dbCtx
, profile
, identifier
), DBErrors
.UnexpectedResult
);
493 }); // profileIdentifierInsert
495 describe('profileIsValid', function () {
497 beforeEach(function () {
498 profile
= 'https://profile.exmaple.com';
500 it('valid profile', async
function () {
501 sinon
.stub(db
.db
, 'oneOrNone').resolves({ profile
});
502 const result
= await db
.profileIsValid(dbCtx
, profile
);
503 assert
.strictEqual(result
, true);
505 it('invalid profile', async
function () {
506 sinon
.stub(db
.db
, 'oneOrNone').resolves();
507 const result
= await db
.profileIsValid(dbCtx
, profile
);
508 assert
.strictEqual(result
, false);
510 it('failure', async
function () {
511 sinon
.stub(db
.db
, 'oneOrNone').rejects(expectedException
);
512 await assert
.rejects(() => db
.profileIsValid(dbCtx
, profile
), expectedException
);
514 }); // profileIsValid
516 describe('tokenGetByCodeId', function () {
518 beforeEach(function () {
519 sinon
.stub(db
.db
, 'oneOrNone');
522 it('success', async
function() {
527 expires: new Date(Date
.now() + 24 * 60 * 60 * 1000),
529 db
.db
.oneOrNone
.resolves(dbResult
);
530 const result
= await db
.tokenGetByCodeId(dbCtx
, codeId
);
531 assert
.deepStrictEqual(result
, dbResult
);
533 it('failure', async
function () {
534 db
.db
.oneOrNone
.rejects(expectedException
);
535 await assert
.rejects(() => db
.tokenGetByCodeId(dbCtx
, codeId
), expectedException
);
537 }); // tokenGetByCodeId
539 describe('profileScopeInsert', function () {
541 beforeEach(function () {
542 profile
= 'https://profile.example.com/';
545 it('success', async
function () {
549 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
550 await db
.profileScopeInsert(dbCtx
, profile
, scope
);
552 it('failure', async
function () {
553 sinon
.stub(db
.db
, 'result').rejects(expectedException
);
554 await assert
.rejects(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), expectedException
);
556 it('failure', async
function () {
560 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
561 await assert
.rejects(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), DBErrors
.UnexpectedResult
);
563 }); // profileScopeInsert
565 describe('profileScopesSetAll', function () {
567 beforeEach(function () {
568 profile
= 'https://example.com/';
570 sinon
.stub(db
.db
, 'result');
572 it('success, no scopes', async
function () {
573 db
.db
.result
.resolves();
574 await db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
576 it('success, scopes', async
function () {
577 db
.db
.result
.resolves();
578 scopes
.push('profile', 'email', 'create');
579 await db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
581 it('failure', async
function () {
582 db
.db
.result
.rejects(expectedException
);
583 await assert
.rejects(() => db
.profileScopesSetAll(dbCtx
, profile
, scopes
), expectedException
);
585 }); // profileScopesSetAll
587 describe('profilesScopesByIdentifier', function () {
588 let identifier
, scopeIndex
, profileScopes
, profiles
;
589 beforeEach(function () {
590 identifier
= 'identifier';
593 description: 'A scope.',
596 isManuallyAdded: true,
597 profiles: ['https://first.example.com/', 'https://second.example.com/'],
600 description: 'Another scope.',
601 application: 'another test',
603 isManuallyAdded: false,
604 profiles: ['https://first.example.com/'],
607 description: 'A scope without application.',
610 isManuallyAdded: false,
611 profiles: ['https://second.example.com/'],
613 'no_profile_scope': {
614 description: 'A scope without profiles.',
617 isManuallyAdded: false,
622 'https://first.example.com/': {
623 'scope': scopeIndex
['scope'],
624 'another_scope': scopeIndex
['another_scope'],
626 'https://second.example.com/': {
627 'scope': scopeIndex
['scope'],
628 'no_app_scope': scopeIndex
['no_app_scope'],
630 'https://scopeless.example.com/': {},
633 'https://first.example.com/',
634 'https://second.example.com/',
635 'https://scopeless.example.com/',
638 it('success', async
function () {
640 { profile: 'https://first.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: true },
641 { profile: 'https://first.example.com/', scope: 'another_scope', application: 'another test', description: 'Another scope.', isPermanent: true, isManuallyAdded: false },
642 { profile: 'https://second.example.com/', scope: 'no_app_scope', application: '', description: 'A scope without application.', isPermanent: false, isManuallyAdded: false },
643 { profile: 'https://second.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: true },
644 { profile: null, scope: 'no_profile_scope', application: 'test', description: 'A scope without profiles.', isPermanent: false, isManuallyAdded: false },
645 { profile: 'https://scopeless.example.com/', scope: null, application: null, description: null, isPermanent: null, isManuallyAdded: null },
652 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
653 const result
= await db
.profilesScopesByIdentifier(dbCtx
, identifier
);
654 assert
.deepStrictEqual(result
, expected
);
656 it('failure', async
function () {
657 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
658 await assert
.rejects(() => db
.profilesScopesByIdentifier(dbCtx
, identifier
), expectedException
);
660 }); // profilesScopesByIdentifier
662 describe('redeemCode', function () {
663 let codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
;
664 beforeEach(function () {
665 codeId
= '41945b8e-3e82-11ec-82d1-0025905f714a';
667 clientId
= 'https://app.example.com/';
668 profile
= 'https://profile.example.com/';
669 identifier
= 'username';
670 scopes
= ['scope1', 'scope2'];
671 lifespanSeconds
= 600;
672 refreshId
= undefined;
673 profileData
= undefined;
675 it('success redeem', async
function () {
678 rows: [{ isRevoked: false }],
681 const dbResultScopes
= {
682 rowCount: scopes
.length
,
686 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(2).resolves(dbResultScopes
);
687 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
688 assert
.strictEqual(result
, true);
690 it('success redeem, no scopes', async
function () {
694 rows: [{ isRevoked: false }],
697 const dbResultScopes
= {
698 rowCount: scopes
.length
,
702 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(1).resolves(dbResultScopes
);
703 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
704 assert
.strictEqual(result
, true);
706 it('success revoke', async
function () {
709 rows: [{ isRevoked: true }],
712 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
713 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
714 assert
.strictEqual(result
, false);
716 it('failure', async
function() {
722 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
723 await assert
.rejects(() => db
.redeemCode(dbCtx
, { codeId
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
}), DBErrors
.UnexpectedResult
);
725 it('failure token scopes', async
function () {
728 rows: [{ isRevoked: false }],
731 const dbResultNone
= {
736 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(2).resolves(dbResultNone
);
737 await assert
.rejects(() => db
.redeemCode(dbCtx
, { codeId
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
}), DBErrors
.UnexpectedResult
);
741 describe('refreshCode', function () {
742 let codeId
, now
, removeScopes
;
743 beforeEach(function () {
744 codeId
= '41945b8e-3e82-11ec-82d1-0025905f714a';
747 sinon
.stub(db
.db
, 'result').resolves({ rowCount: removeScopes
.length
});
748 sinon
.stub(db
.db
, 'oneOrNone');
750 it('success', async
function () {
751 db
.db
.oneOrNone
.resolves({
755 const result
= await db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
);
756 assert(db
.db
.result
.notCalled
);
758 assert(result
.expires
);
759 assert(result
.refreshExpires
);
760 assert(!result
.scopes
);
762 it('success with scope reduction', async
function () {
763 removeScopes
= ['create'];
764 db
.db
.oneOrNone
.resolves({
769 db
.db
.result
.resolves({ rowCount: removeScopes
.length
});
770 const result
= await db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
);
772 assert(result
.expires
);
773 assert(result
.refreshExpires
);
774 assert(!result
.scopes
.includes('create'));
776 it('failure', async
function () {
777 db
.db
.oneOrNone
.rejects(expectedException
);
778 await assert
.rejects(async () => db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
), expectedException
);
780 it('failure with scope reduction', async
function () {
781 removeScopes
= ['create'];
782 db
.db
.oneOrNone
.resolves({});
783 db
.db
.result
.resolves({ rowCount: 0 });
784 await assert
.rejects(async () => db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
), DBErrors
.UnexpectedResult
);
788 describe('resourceGet', function () {
790 beforeEach(function () {
791 sinon
.stub(db
.db
, 'oneOrNone');
792 identifier
= '05b81112-b224-11ec-a9c6-0025905f714a';
794 it('success', async
function () {
799 db
.db
.oneOrNone
.resolves(dbResult
);
800 const result
= await db
.resourceGet(dbCtx
, identifier
);
801 assert
.deepStrictEqual(result
, dbResult
);
803 it('failure', async
function() {
804 db
.db
.oneOrNone
.rejects(expectedException
);
805 await assert
.rejects(() => db
.resourceGet(dbCtx
, identifier
), expectedException
);
809 describe('resourceUpsert', function () {
810 let resourceId
, secret
, description
;
811 beforeEach(function () {
812 resourceId
= '98b8d9ec-f8e2-11ec-aceb-0025905f714a';
813 secret
= 'supersecret';
814 description
= 'some service';
816 it('success', async
function () {
822 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
823 await db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
825 it('failure', async
function () {
831 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
832 await assert
.rejects(() => db
.resourceUpsert(dbCtx
, resourceId
, undefined, description
), DBErrors
.UnexpectedResult
);
834 }); // resourceUpsert
836 describe('scopeCleanup', function () {
837 let atLeastMsSinceLast
;
838 beforeEach(function () {
839 sinon
.stub(db
.db
, 'result');
840 sinon
.stub(db
.db
, 'oneOrNone');
841 atLeastMsSinceLast
= 86400000;
843 it('success, empty almanac', async
function () {
846 .onFirstCall().resolves({ rowCount: cleaned
})
847 .onSecondCall().resolves({ rowCount: 1 });
848 const result
= await db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
849 assert
.strictEqual(result
, cleaned
);
851 it('success, too soon', async
function () {
852 db
.db
.oneOrNone
.resolves({ date: new Date(Date
.now() - 4000) });
853 const result
= await db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
854 assert
.strictEqual(result
, undefined);
855 assert(db
.db
.result
.notCalled
);
857 it('failure', async
function () {
858 db
.db
.result
.resolves({ rowCount: 0 });
859 await assert
.rejects(async () => db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
863 describe('scopeDelete', function () {
865 beforeEach(function () {
868 it('success', async
function () {
874 sinon
.stub(db
.db
, 'one').resolves({ inUse: false });
875 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
876 const result
= await db
.scopeDelete(dbCtx
, scope
);
877 assert(db
.db
.result
.called
);
878 assert
.strictEqual(result
, true);
880 it('success, no scope', async
function () {
886 sinon
.stub(db
.db
, 'one').resolves({ inUse: false });
887 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
888 const result
= await db
.scopeDelete(dbCtx
, scope
);
889 assert(db
.db
.result
.called
);
890 assert
.strictEqual(result
, true);
892 it('scope in use', async
function () {
898 sinon
.stub(db
.db
, 'one').resolves({ inUse: true });
899 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
900 const result
= await db
.scopeDelete(dbCtx
, scope
);
901 assert(db
.db
.result
.notCalled
);
902 assert
.strictEqual(result
, false);
904 it('failure', async
function () {
905 sinon
.stub(db
.db
, 'one').rejects(expectedException
);
906 await assert
.rejects(() => db
.scopeDelete(dbCtx
, scope
), expectedException
);
910 describe('scopeUpsert', function () {
911 let scope
, description
;
912 beforeEach(function () {
914 description
= '$z$foo';
916 it('success', async
function () {
922 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
923 await db
.scopeUpsert(dbCtx
, scope
, description
);
925 it('failure', async
function() {
932 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
933 await assert
.rejects(() => db
.scopeUpsert(dbCtx
, scope
, description
), DBErrors
.UnexpectedResult
);
937 describe('tokenCleanup', function () {
938 let codeLifespanSeconds
, atLeastMsSinceLast
;
939 beforeEach(function () {
940 sinon
.stub(db
.db
, 'result');
941 sinon
.stub(db
.db
, 'oneOrNone');
942 codeLifespanSeconds
= 600000;
943 atLeastMsSinceLast
= 86400000;
945 it('success, empty almanac', async
function () {
948 .onFirstCall().resolves({ rowCount: cleaned
})
949 .onSecondCall().resolves({ rowCount: 1 });
950 const result
= await db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
951 assert
.strictEqual(result
, cleaned
);
953 it('success, too soon', async
function () {
954 db
.db
.oneOrNone
.resolves({ date: new Date(Date
.now() - 4000) });
955 const result
= await db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
956 assert
.strictEqual(result
, undefined);
957 assert(db
.db
.result
.notCalled
);
959 it('failure', async
function () {
960 db
.db
.result
.resolves({ rowCount: 0 });
961 await assert
.rejects(() => db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
965 describe('tokenRevokeByCodeId', function () {
967 beforeEach(function () {
968 codeId
= 'a74bda94-3dae-11ec-8908-0025905f714a';
970 it('success', async
function () {
976 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
977 await db
.tokenRevokeByCodeId(dbCtx
, codeId
);
979 it('failure', async
function() {
985 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
986 await assert
.rejects(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
988 }); // tokenRevokeByCodeId
990 describe('tokenRefreshRevokeByCodeId', function () {
992 beforeEach(function () {
993 codeId
= '279947c8-2584-11ed-a2d6-0025905f714a';
994 sinon
.stub(db
.db
, 'result');
996 it('success', async
function () {
997 db
.db
.result
.resolves({ rowCount: 1 });
998 await db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
);
1000 it('failure, no code', async
function () {
1001 db
.db
.result
.resolves({ rowCount: 0 });
1002 assert
.rejects(async () => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
1004 it('failure', async
function () {
1005 db
.db
.result
.rejects(expectedException
);
1006 assert
.rejects(async () => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), expectedException
);
1008 }); // tokenRefreshRevokeByCodeId
1010 describe('tokensGetByIdentifier', function () {
1012 beforeEach(function () {
1013 identifier
= 'identifier';
1015 it('success', async
function () {
1018 'created': new Date(),
1019 'expires': new Date(),
1027 const expected
= dbResult
;
1028 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
1029 const result
= await db
.tokensGetByIdentifier(dbCtx
, identifier
);
1030 assert
.deepStrictEqual(result
, expected
);
1032 it('failure', async
function () {
1033 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
1034 await assert
.rejects(() => db
.tokensGetByIdentifier(dbCtx
, identifier
), expectedException
);
1036 }); // tokensGetByIdentifier
1038 describe('ticketRedeemed', function () {
1040 beforeEach(function () {
1042 resource: 'https://resource.example.com/',
1043 subject: 'https://subject.example.com/',
1044 iss: 'https://idp.example.com/',
1045 ticket: 'xxxTICKETxxx',
1046 token: 'xxxTOKENxxx',
1049 it('success', async
function () {
1055 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1056 await db
.ticketRedeemed(dbCtx
, redeemedData
);
1058 it('failure', async
function () {
1064 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1065 await assert
.rejects(() => db
.ticketRedeemed(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1067 }); // ticketRedeemed
1069 describe('ticketTokenPublished', function () {
1071 beforeEach(function () {
1073 resource: 'https://resource.example.com/',
1074 subject: 'https://subject.example.com/',
1075 iss: 'https://idp.example.com/',
1076 ticket: 'xxxTICKETxxx',
1077 token: 'xxxTOKENxxx',
1079 sinon
.stub(db
.db
, 'result');
1081 it('success', async
function () {
1087 db
.db
.result
.resolves(dbResult
);
1088 await db
.ticketTokenPublished(dbCtx
, redeemedData
);
1090 it('failure', async
function () {
1096 db
.db
.result
.resolves(dbResult
);
1097 await assert
.rejects(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1099 it('failure of almanac', async
function () {
1105 const dbResultAlmanac
= {
1109 db
.db
.result
.resolves(dbResult
).onCall(1).resolves(dbResultAlmanac
);
1110 await assert
.rejects(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1112 }); // ticketTokenPublished
1114 describe('ticketTokenGetUnpublished', function () {
1115 it('success', async
function () {
1117 resource: 'https://resource.example.com/',
1118 subject: 'https://subject.example.com/',
1119 iss: 'https://idp.example.com/',
1120 ticket: 'xxxTICKETxxx',
1121 token: 'xxxTOKENxxx',
1122 created: new Date(),
1125 sinon
.stub(db
.db
, 'manyOrNone').resolves(expected
);
1126 const result
= await db
.ticketTokenGetUnpublished(dbCtx
);
1127 assert
.deepStrictEqual(result
, expected
);
1129 it('failure', async
function () {
1130 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
1131 await assert
.rejects(() => db
.ticketTokenGetUnpublished(dbCtx
), expectedException
);
1133 }); // ticketTokenGetUnpublished
1135 }); // DatabasePostgres