1 /* eslint-disable sonarjs/no-duplicate-string */
4 /* This provides implementation coverage, stubbing pg-promise. */
6 const assert
= require('assert');
7 const sinon
= require('sinon');
8 const StubLogger
= require('../../stub-logger');
9 const StubDatabase
= require('../../stub-db');
10 const DB
= require('../../../src/db/postgres');
11 const DBErrors
= require('../../../src/db/errors');
12 const common
= require('../../../src/common');
13 const Config
= require('../../../config');
15 const expectedException
= new Error('oh no');
17 describe('DatabasePostgres', function () {
18 let db
, logger
, options
, pgpStub
;
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
;
46 beforeEach(function () {
47 logger
= new StubLogger();
49 options
= new Config('test');
50 db
= new DB(logger
, options
, pgpStub
);
53 afterEach(function () {
57 it('covers no query logging', function () {
58 delete options
.db
.queryLogLevel
;
59 db
= new DB(logger
, options
, pgpStub
);
63 // Ensure all interface methods are implemented
64 describe('Implementation', function () {
65 it('implements interface', async
function () {
66 const stubDb
= new StubDatabase();
67 const results
= await Promise
.allSettled(stubDb
._implementation
.map(async (fn
) => {
69 // eslint-disable-next-line security/detect-object-injection
72 assert(!(e
instanceof DBErrors
.NotImplemented
), `${fn} not implemented`);
75 const failures
= results
.filter((x
) => x
.status
=== 'rejected');
76 assert(!failures
.length
, failures
.map((x
) => {
77 x
= x
.reason
.toString();
78 return x
.slice(x
.indexOf(': '));
83 describe('pgpInitOptions', function () {
84 describe('error', function () {
85 it('covers', function () {
88 db
.pgpInitOptions
.error(err
, event
);
89 assert(db
.logger
.error
.called
);
92 describe('query', function () {
93 it('covers', function () {
95 db
.pgpInitOptions
.query(event
);
96 assert(db
.logger
.debug
.called
);
99 describe('receive', function () {
100 it('covers', function () {
103 column_one: 'one', // eslint-disable-line camelcase
104 column_two: 2, // eslint-disable-line camelcase
107 column_one: 'foo', // eslint-disable-line camelcase
108 column_two: 4, // eslint-disable-line camelcase
113 const expectedData
= [
123 db
.pgpInitOptions
.receive({ data
, result
, ctx: event
});
124 assert(db
.logger
.debug
.called
);
125 assert
.deepStrictEqual(data
, expectedData
);
127 it('covers no query logging', function () {
128 delete options
.db
.queryLogLevel
;
129 db
= new DB(logger
, options
, pgpStub
);
132 column_one: 'one', // eslint-disable-line camelcase
133 column_two: 2, // eslint-disable-line camelcase
136 column_one: 'foo', // eslint-disable-line camelcase
137 column_two: 4, // eslint-disable-line camelcase
142 const expectedData
= [
152 db
.pgpInitOptions
.receive({ data
, result
, ctx: event
});
153 assert(db
.logger
.debug
.called
);
154 assert
.deepStrictEqual(data
, expectedData
);
158 }); // pgpInitOptions
160 describe('_initTables', function () {
161 beforeEach(function () {
162 sinon
.stub(db
.db
, 'oneOrNone');
163 sinon
.stub(db
.db
, 'multiResult');
164 sinon
.stub(db
, '_currentSchema');
167 it('covers apply', async
function() {
168 db
.db
.oneOrNone
.onCall(0).resolves(null).onCall(1).resolves({});
169 db
._currentSchema
.resolves({ major: 0, minor: 0, patch: 0 });
170 await db
._initTables();
172 it('covers exists', async
function() {
173 db
.db
.oneOrNone
.resolves({});
174 db
._currentSchema
.resolves(db
.schemaVersionsSupported
.max
);
175 await db
._initTables();
179 describe('initialize', function () {
183 it('passes supported version', async
function () {
184 const version
= { major: 1, minor: 0, patch: 0 };
185 sinon
.stub(db
.db
, 'one').resolves(version
);
186 await db
.initialize(false);
188 it('fails low version', async
function () {
189 const version
= { major: 0, minor: 0, patch: 0 };
190 sinon
.stub(db
.db
, 'one').resolves(version
);
191 await assert
.rejects(() => db
.initialize(false), DBErrors
.MigrationNeeded
);
193 it('fails high version', async
function () {
194 const version
= { major: 100, minor: 100, patch: 100 };
195 sinon
.stub(db
.db
, 'one').resolves(version
);
196 await assert
.rejects(() => db
.initialize(false));
198 it('covers migration', async
function() {
199 sinon
.stub(db
.db
, 'oneOrNone').resolves({});
200 sinon
.stub(db
.db
, 'multiResult');
201 sinon
.stub(db
, '_currentSchema').resolves(db
.schemaVersionsSupported
.max
);
202 sinon
.stub(db
.db
, 'one').resolves(db
.schemaVersionsSupported
.max
);
203 await db
.initialize();
205 it('covers listener', async
function() {
209 const version
= { major: 1, minor: 0, patch: 0 };
210 sinon
.stub(db
.db
, 'one').resolves(version
);
211 await db
.initialize(false);
212 assert(db
.listener
.start
.called
);
216 describe('healthCheck', function () {
217 beforeEach(function () {
218 sinon
.stub(db
.db
, 'connect').resolves({
221 serverVersion: '0.0',
225 it('covers', async
function () {
226 const result
= await db
.healthCheck();
227 assert
.deepStrictEqual(result
, { serverVersion: '0.0' });
231 describe('_queryFileHelper', function () {
232 it('covers success', function () {
233 const _queryFile
= db
._queryFileHelper(pgpStub
);
236 it('covers failure', function () {
237 pgpStub
.QueryFile
= class {
239 this.error
= expectedException
;
242 const _queryFile
= db
._queryFileHelper(pgpStub
);
243 assert
.throws(() => _queryFile(), expectedException
);
245 }); // _queryFileHelper
247 describe('_closeConnection', function () {
251 it('success', async
function () {
252 sinon
.stub(db
._pgp
, 'end');
253 await db
._closeConnection();
254 assert(db
._pgp
.end
.called
);
256 it('failure', async
function () {
257 sinon
.stub(db
._pgp
, 'end').throws(expectedException
);
258 await assert
.rejects(() => db
._closeConnection(), expectedException
);
260 it('covers listener', async
function () {
264 sinon
.stub(db
._pgp
, 'end');
265 await db
._closeConnection();
266 assert(db
._pgp
.end
.called
);
268 }); // _closeConnection
270 describe('_purgeTables', function () {
271 it('covers not really', async
function () {
272 sinon
.stub(db
.db
, 'tx');
273 await db
._purgeTables(false);
274 assert(!db
.db
.tx
.called
);
276 it('success', async
function () {
277 sinon
.stub(db
.db
, 'batch');
278 await db
._purgeTables(true);
279 assert(db
.db
.batch
.called
);
281 it('failure', async
function () {
282 sinon
.stub(db
.db
, 'tx').rejects(expectedException
);
283 await assert
.rejects(() => db
._purgeTables(true), expectedException
);
287 describe('context', function () {
288 it('covers', async
function () {
289 await db
.context(common
.nop
);
293 describe('transaction', function () {
294 it('covers', async
function () {
295 await db
.transaction(db
.db
, common
.nop
);
299 describe('almanacGetAll', function () {
300 beforeEach(function () {
301 sinon
.stub(db
.db
, 'manyOrNone');
303 it('success', async
function () {
304 const expected
= [{ event: 'someEvent', date: new Date() }];
305 db
.db
.manyOrNone
.resolves(expected
);
306 const result
= await db
.almanacGetAll(dbCtx
);
307 assert
.deepStrictEqual(result
, expected
);
309 it('failure', async
function () {
310 db
.db
.manyOrNone
.rejects(expectedException
);
311 await assert
.rejects(() => db
.almanacGetAll(dbCtx
), expectedException
);
315 describe('almanacUpsert', function () {
317 beforeEach(function () {
318 event
= 'test_event';
319 date
= new Date('Fri Dec 22 03:27 UTC 2023');
321 it('success', async
function () {
327 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
328 await db
.almanacUpsert(dbCtx
, event
, date
);
330 it('success with default date', async
function () {
336 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
337 await db
.almanacUpsert(dbCtx
, event
);
339 it('failure', async
function () {
345 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
346 await assert
.rejects(() => db
.almanacUpsert(dbCtx
, event
, date
), DBErrors
.UnexpectedResult
);
350 describe('authenticationSuccess', function () {
352 beforeEach(function () {
353 identifier
= 'username';
355 it('success', async
function () {
361 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
362 await db
.authenticationSuccess(dbCtx
, identifier
);
364 it('failure', async
function() {
370 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
371 await assert
.rejects(() => db
.authenticationSuccess(dbCtx
, identifier
), DBErrors
.UnexpectedResult
);
373 }); // authenticationSuccess
375 describe('authenticationGet', function () {
376 let identifier
, credential
;
377 beforeEach(function () {
378 identifier
= 'username';
379 credential
= '$z$foo';
381 it('success', async
function () {
382 const dbResult
= { identifier
, credential
};
383 sinon
.stub(db
.db
, 'oneOrNone').resolves(dbResult
);
384 const result
= await db
.authenticationGet(dbCtx
, identifier
);
385 assert
.deepStrictEqual(result
, dbResult
);
387 it('failure', async
function() {
388 sinon
.stub(db
.db
, 'oneOrNone').rejects(expectedException
);
389 await assert
.rejects(() => db
.authenticationGet(dbCtx
, identifier
, credential
), expectedException
);
391 }); // authenticationGet
393 describe('authenticationUpsert', function () {
394 let identifier
, credential
;
395 beforeEach(function () {
396 identifier
= 'username';
397 credential
= '$z$foo';
399 it('success', async
function () {
405 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
406 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
408 it('failure', async
function() {
409 credential
= undefined;
415 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
416 await assert
.rejects(() => db
.authenticationUpsert(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
418 }); // authenticationUpsert
420 describe('authenticationUpdateCredential', function () {
421 let identifier
, credential
;
422 beforeEach(function () {
423 identifier
= 'username';
424 credential
= '$z$foo';
426 it('success', async
function () {
432 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
433 await db
.authenticationUpdateCredential(dbCtx
, identifier
, credential
);
435 it('failure', async
function () {
436 credential
= undefined;
442 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
443 await assert
.rejects(() => db
.authenticationUpdateCredential(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
446 }); // authenticationUpdateCredential
448 describe('authenticationUpdateOTPKey', function () {
449 let identifier
, otpKey
;
450 beforeEach(function () {
451 identifier
= 'username';
452 otpKey
= '1234567890123456789012';
454 it('success', async
function () {
460 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
461 await db
.authenticationUpdateOTPKey(dbCtx
, identifier
, otpKey
);
463 it('failure', async
function () {
469 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
470 await assert
.rejects(() => db
.authenticationUpdateOTPKey(dbCtx
, identifier
, otpKey
), DBErrors
.UnexpectedResult
);
472 }); // authenticationUpdateOTPKey
474 describe('profileIdentifierInsert', function () {
475 let profile
, identifier
;
476 beforeEach(function () {
477 profile
= 'https://profile.example.com/';
478 identifier
= 'username';
480 it('success', async
function () {
484 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
485 await db
.profileIdentifierInsert(dbCtx
, profile
, identifier
);
487 it('failure', async
function () {
491 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
492 await assert
.rejects(() => db
.profileIdentifierInsert(dbCtx
, profile
, identifier
), DBErrors
.UnexpectedResult
);
494 }); // profileIdentifierInsert
496 describe('profileIsValid', function () {
498 beforeEach(function () {
499 profile
= 'https://profile.exmaple.com';
501 it('valid profile', async
function () {
502 sinon
.stub(db
.db
, 'oneOrNone').resolves({ profile
});
503 const result
= await db
.profileIsValid(dbCtx
, profile
);
504 assert
.strictEqual(result
, true);
506 it('invalid profile', async
function () {
507 sinon
.stub(db
.db
, 'oneOrNone').resolves();
508 const result
= await db
.profileIsValid(dbCtx
, profile
);
509 assert
.strictEqual(result
, false);
511 it('failure', async
function () {
512 sinon
.stub(db
.db
, 'oneOrNone').rejects(expectedException
);
513 await assert
.rejects(() => db
.profileIsValid(dbCtx
, profile
), expectedException
);
515 }); // profileIsValid
517 describe('tokenGetByCodeId', function () {
519 beforeEach(function () {
520 sinon
.stub(db
.db
, 'oneOrNone');
523 it('success', async
function() {
528 expires: new Date(Date
.now() + 24 * 60 * 60 * 1000),
530 db
.db
.oneOrNone
.resolves(dbResult
);
531 const result
= await db
.tokenGetByCodeId(dbCtx
, codeId
);
532 assert
.deepStrictEqual(result
, dbResult
);
534 it('failure', async
function () {
535 db
.db
.oneOrNone
.rejects(expectedException
);
536 await assert
.rejects(() => db
.tokenGetByCodeId(dbCtx
, codeId
), expectedException
);
538 }); // tokenGetByCodeId
540 describe('profileScopeInsert', function () {
542 beforeEach(function () {
543 profile
= 'https://profile.example.com/';
546 it('success', async
function () {
550 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
551 await db
.profileScopeInsert(dbCtx
, profile
, scope
);
553 it('failure', async
function () {
554 sinon
.stub(db
.db
, 'result').rejects(expectedException
);
555 await assert
.rejects(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), expectedException
);
557 it('failure', async
function () {
561 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
562 await assert
.rejects(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), DBErrors
.UnexpectedResult
);
564 }); // profileScopeInsert
566 describe('profileScopesSetAll', function () {
568 beforeEach(function () {
569 profile
= 'https://example.com/';
571 sinon
.stub(db
.db
, 'result');
573 it('success, no scopes', async
function () {
574 db
.db
.result
.resolves();
575 await db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
577 it('success, scopes', async
function () {
578 db
.db
.result
.resolves();
579 scopes
.push('profile', 'email', 'create');
580 await db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
582 it('failure', async
function () {
583 db
.db
.result
.rejects(expectedException
);
584 await assert
.rejects(() => db
.profileScopesSetAll(dbCtx
, profile
, scopes
), expectedException
);
586 }); // profileScopesSetAll
588 describe('profilesScopesByIdentifier', function () {
589 let identifier
, scopeIndex
, profileScopes
, profiles
;
590 beforeEach(function () {
591 identifier
= 'identifier';
594 description: 'A scope.',
597 isManuallyAdded: true,
598 profiles: ['https://first.example.com/', 'https://second.example.com/'],
601 description: 'Another scope.',
602 application: 'another test',
604 isManuallyAdded: false,
605 profiles: ['https://first.example.com/'],
608 description: 'A scope without application.',
611 isManuallyAdded: false,
612 profiles: ['https://second.example.com/'],
614 'no_profile_scope': {
615 description: 'A scope without profiles.',
618 isManuallyAdded: false,
623 'https://first.example.com/': {
624 'scope': scopeIndex
['scope'],
625 'another_scope': scopeIndex
['another_scope'],
627 'https://second.example.com/': {
628 'scope': scopeIndex
['scope'],
629 'no_app_scope': scopeIndex
['no_app_scope'],
631 'https://scopeless.example.com/': {},
634 'https://first.example.com/',
635 'https://second.example.com/',
636 'https://scopeless.example.com/',
639 it('success', async
function () {
641 { profile: 'https://first.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: true },
642 { profile: 'https://first.example.com/', scope: 'another_scope', application: 'another test', description: 'Another scope.', isPermanent: true, isManuallyAdded: false },
643 { profile: 'https://second.example.com/', scope: 'no_app_scope', application: '', description: 'A scope without application.', isPermanent: false, isManuallyAdded: false },
644 { profile: 'https://second.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: true },
645 { profile: null, scope: 'no_profile_scope', application: 'test', description: 'A scope without profiles.', isPermanent: false, isManuallyAdded: false },
646 { profile: 'https://scopeless.example.com/', scope: null, application: null, description: null, isPermanent: null, isManuallyAdded: null },
653 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
654 const result
= await db
.profilesScopesByIdentifier(dbCtx
, identifier
);
655 assert
.deepStrictEqual(result
, expected
);
657 it('failure', async
function () {
658 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
659 await assert
.rejects(() => db
.profilesScopesByIdentifier(dbCtx
, identifier
), expectedException
);
661 }); // profilesScopesByIdentifier
663 describe('redeemCode', function () {
664 let codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
;
665 beforeEach(function () {
666 codeId
= '41945b8e-3e82-11ec-82d1-0025905f714a';
668 clientId
= 'https://app.example.com/';
669 profile
= 'https://profile.example.com/';
670 identifier
= 'username';
671 scopes
= ['scope1', 'scope2'];
672 lifespanSeconds
= 600;
673 refreshId
= undefined;
674 profileData
= undefined;
676 it('success redeem', async
function () {
679 rows: [{ isRevoked: false }],
682 const dbResultScopes
= {
683 rowCount: scopes
.length
,
687 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(2).resolves(dbResultScopes
);
688 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
689 assert
.strictEqual(result
, true);
691 it('success redeem, no scopes', async
function () {
695 rows: [{ isRevoked: false }],
698 const dbResultScopes
= {
699 rowCount: scopes
.length
,
703 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(1).resolves(dbResultScopes
);
704 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
705 assert
.strictEqual(result
, true);
707 it('success revoke', async
function () {
710 rows: [{ isRevoked: true }],
713 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
714 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
715 assert
.strictEqual(result
, false);
717 it('failure', async
function() {
723 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
724 await assert
.rejects(() => db
.redeemCode(dbCtx
, { codeId
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
}), DBErrors
.UnexpectedResult
);
726 it('failure token scopes', async
function () {
729 rows: [{ isRevoked: false }],
732 const dbResultNone
= {
737 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(2).resolves(dbResultNone
);
738 await assert
.rejects(() => db
.redeemCode(dbCtx
, { codeId
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
}), DBErrors
.UnexpectedResult
);
742 describe('refreshCode', function () {
743 let codeId
, now
, removeScopes
;
744 beforeEach(function () {
745 codeId
= '41945b8e-3e82-11ec-82d1-0025905f714a';
748 sinon
.stub(db
.db
, 'result').resolves({ rowCount: removeScopes
.length
});
749 sinon
.stub(db
.db
, 'oneOrNone');
751 it('success', async
function () {
752 db
.db
.oneOrNone
.resolves({
756 const result
= await db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
);
757 assert(db
.db
.result
.notCalled
);
759 assert(result
.expires
);
760 assert(result
.refreshExpires
);
761 assert(!result
.scopes
);
763 it('success with scope reduction', async
function () {
764 removeScopes
= ['create'];
765 db
.db
.oneOrNone
.resolves({
770 db
.db
.result
.resolves({ rowCount: removeScopes
.length
});
771 const result
= await db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
);
773 assert(result
.expires
);
774 assert(result
.refreshExpires
);
775 assert(!result
.scopes
.includes('create'));
777 it('failure', async
function () {
778 db
.db
.oneOrNone
.rejects(expectedException
);
779 await assert
.rejects(async () => db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
), expectedException
);
781 it('failure with scope reduction', async
function () {
782 removeScopes
= ['create'];
783 db
.db
.oneOrNone
.resolves({});
784 db
.db
.result
.resolves({ rowCount: 0 });
785 await assert
.rejects(async () => db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
), DBErrors
.UnexpectedResult
);
789 describe('resourceGet', function () {
791 beforeEach(function () {
792 sinon
.stub(db
.db
, 'oneOrNone');
793 identifier
= '05b81112-b224-11ec-a9c6-0025905f714a';
795 it('success', async
function () {
800 db
.db
.oneOrNone
.resolves(dbResult
);
801 const result
= await db
.resourceGet(dbCtx
, identifier
);
802 assert
.deepStrictEqual(result
, dbResult
);
804 it('failure', async
function() {
805 db
.db
.oneOrNone
.rejects(expectedException
);
806 await assert
.rejects(() => db
.resourceGet(dbCtx
, identifier
), expectedException
);
810 describe('resourceUpsert', function () {
811 let resourceId
, secret
, description
;
812 beforeEach(function () {
813 resourceId
= '98b8d9ec-f8e2-11ec-aceb-0025905f714a';
814 secret
= 'supersecret';
815 description
= 'some service';
817 it('success', async
function () {
823 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
824 await db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
826 it('failure', async
function () {
832 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
833 await assert
.rejects(() => db
.resourceUpsert(dbCtx
, resourceId
, undefined, description
), DBErrors
.UnexpectedResult
);
835 }); // resourceUpsert
837 describe('scopeCleanup', function () {
838 let atLeastMsSinceLast
;
839 beforeEach(function () {
840 sinon
.stub(db
.db
, 'result');
841 sinon
.stub(db
.db
, 'oneOrNone');
842 atLeastMsSinceLast
= 86400000;
844 it('success, empty almanac', async
function () {
847 .onFirstCall().resolves({ rowCount: cleaned
})
848 .onSecondCall().resolves({ rowCount: 1 });
849 const result
= await db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
850 assert
.strictEqual(result
, cleaned
);
852 it('success, too soon', async
function () {
853 db
.db
.oneOrNone
.resolves({ date: new Date(Date
.now() - 4000) });
854 const result
= await db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
855 assert
.strictEqual(result
, undefined);
856 assert(db
.db
.result
.notCalled
);
858 it('failure', async
function () {
859 db
.db
.result
.resolves({ rowCount: 0 });
860 await assert
.rejects(async () => db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
864 describe('scopeDelete', function () {
866 beforeEach(function () {
869 it('success', async
function () {
875 sinon
.stub(db
.db
, 'one').resolves({ inUse: false });
876 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
877 const result
= await db
.scopeDelete(dbCtx
, scope
);
878 assert(db
.db
.result
.called
);
879 assert
.strictEqual(result
, true);
881 it('success, no scope', async
function () {
887 sinon
.stub(db
.db
, 'one').resolves({ inUse: false });
888 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
889 const result
= await db
.scopeDelete(dbCtx
, scope
);
890 assert(db
.db
.result
.called
);
891 assert
.strictEqual(result
, true);
893 it('scope in use', async
function () {
899 sinon
.stub(db
.db
, 'one').resolves({ inUse: true });
900 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
901 const result
= await db
.scopeDelete(dbCtx
, scope
);
902 assert(db
.db
.result
.notCalled
);
903 assert
.strictEqual(result
, false);
905 it('failure', async
function () {
906 sinon
.stub(db
.db
, 'one').rejects(expectedException
);
907 await assert
.rejects(() => db
.scopeDelete(dbCtx
, scope
), expectedException
);
911 describe('scopeUpsert', function () {
912 let scope
, description
;
913 beforeEach(function () {
915 description
= '$z$foo';
917 it('success', async
function () {
923 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
924 await db
.scopeUpsert(dbCtx
, scope
, description
);
926 it('failure', async
function() {
933 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
934 await assert
.rejects(() => db
.scopeUpsert(dbCtx
, scope
, description
), DBErrors
.UnexpectedResult
);
938 describe('tokenCleanup', function () {
939 let codeLifespanSeconds
, atLeastMsSinceLast
;
940 beforeEach(function () {
941 sinon
.stub(db
.db
, 'result');
942 sinon
.stub(db
.db
, 'oneOrNone');
943 codeLifespanSeconds
= 600000;
944 atLeastMsSinceLast
= 86400000;
946 it('success, empty almanac', async
function () {
949 .onFirstCall().resolves({ rowCount: cleaned
})
950 .onSecondCall().resolves({ rowCount: 1 });
951 const result
= await db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
952 assert
.strictEqual(result
, cleaned
);
954 it('success, too soon', async
function () {
955 db
.db
.oneOrNone
.resolves({ date: new Date(Date
.now() - 4000) });
956 const result
= await db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
957 assert
.strictEqual(result
, undefined);
958 assert(db
.db
.result
.notCalled
);
960 it('failure', async
function () {
961 db
.db
.result
.resolves({ rowCount: 0 });
962 await assert
.rejects(() => db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
966 describe('tokenRevokeByCodeId', function () {
968 beforeEach(function () {
969 codeId
= 'a74bda94-3dae-11ec-8908-0025905f714a';
971 it('success', async
function () {
977 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
978 await db
.tokenRevokeByCodeId(dbCtx
, codeId
);
980 it('failure', async
function() {
986 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
987 await assert
.rejects(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
989 }); // tokenRevokeByCodeId
991 describe('tokenRefreshRevokeByCodeId', function () {
993 beforeEach(function () {
994 codeId
= '279947c8-2584-11ed-a2d6-0025905f714a';
995 sinon
.stub(db
.db
, 'result');
997 it('success', async
function () {
998 db
.db
.result
.resolves({ rowCount: 1 });
999 await db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
);
1001 it('failure, no code', async
function () {
1002 db
.db
.result
.resolves({ rowCount: 0 });
1003 assert
.rejects(async () => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
1005 it('failure', async
function () {
1006 db
.db
.result
.rejects(expectedException
);
1007 assert
.rejects(async () => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), expectedException
);
1009 }); // tokenRefreshRevokeByCodeId
1011 describe('tokensGetByIdentifier', function () {
1013 beforeEach(function () {
1014 identifier
= 'identifier';
1016 it('success', async
function () {
1019 'created': new Date(),
1020 'expires': new Date(),
1028 const expected
= dbResult
;
1029 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
1030 const result
= await db
.tokensGetByIdentifier(dbCtx
, identifier
);
1031 assert
.deepStrictEqual(result
, expected
);
1033 it('failure', async
function () {
1034 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
1035 await assert
.rejects(() => db
.tokensGetByIdentifier(dbCtx
, identifier
), expectedException
);
1037 }); // tokensGetByIdentifier
1039 describe('ticketRedeemed', function () {
1041 beforeEach(function () {
1043 resource: 'https://resource.example.com/',
1044 subject: 'https://subject.example.com/',
1045 iss: 'https://idp.example.com/',
1046 ticket: 'xxxTICKETxxx',
1047 token: 'xxxTOKENxxx',
1050 it('success', async
function () {
1056 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1057 await db
.ticketRedeemed(dbCtx
, redeemedData
);
1059 it('failure', async
function () {
1065 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1066 await assert
.rejects(() => db
.ticketRedeemed(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1068 }); // ticketRedeemed
1070 describe('ticketTokenPublished', function () {
1072 beforeEach(function () {
1074 resource: 'https://resource.example.com/',
1075 subject: 'https://subject.example.com/',
1076 iss: 'https://idp.example.com/',
1077 ticket: 'xxxTICKETxxx',
1078 token: 'xxxTOKENxxx',
1080 sinon
.stub(db
.db
, 'result');
1082 it('success', async
function () {
1088 db
.db
.result
.resolves(dbResult
);
1089 await db
.ticketTokenPublished(dbCtx
, redeemedData
);
1091 it('failure', async
function () {
1097 db
.db
.result
.resolves(dbResult
);
1098 await assert
.rejects(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1100 it('failure of almanac', async
function () {
1106 const dbResultAlmanac
= {
1110 db
.db
.result
.resolves(dbResult
).onCall(1).resolves(dbResultAlmanac
);
1111 await assert
.rejects(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1113 }); // ticketTokenPublished
1115 describe('ticketTokenGetUnpublished', function () {
1116 it('success', async
function () {
1118 resource: 'https://resource.example.com/',
1119 subject: 'https://subject.example.com/',
1120 iss: 'https://idp.example.com/',
1121 ticket: 'xxxTICKETxxx',
1122 token: 'xxxTOKENxxx',
1123 created: new Date(),
1126 sinon
.stub(db
.db
, 'manyOrNone').resolves(expected
);
1127 const result
= await db
.ticketTokenGetUnpublished(dbCtx
);
1128 assert
.deepStrictEqual(result
, expected
);
1130 it('failure', async
function () {
1131 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
1132 await assert
.rejects(() => db
.ticketTokenGetUnpublished(dbCtx
), expectedException
);
1134 }); // ticketTokenGetUnpublished
1136 }); // DatabasePostgres