1 /* eslint-disable sonarjs/no-identical-functions */
3 /* eslint-disable sonarjs/no-duplicate-string */
6 /* This provides implementation coverage, stubbing pg-promise. */
8 const assert
= require('assert');
9 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
10 const StubLogger
= require('../../stub-logger');
11 const StubDatabase
= require('../../stub-db');
12 const DB
= require('../../../src/db/postgres');
13 const DBErrors
= require('../../../src/db/errors');
14 const common
= require('../../../src/common');
15 const Config
= require('../../../config');
17 const expectedException
= new Error('oh no');
19 describe('DatabasePostgres', function () {
20 let db
, logger
, options
, pgpStub
;
25 result: () => ({ rows: [] }),
30 manyOrNone: common
.nop
,
31 oneOrNone: common
.nop
,
34 multiResult: common
.nop
,
37 stub
.tx
= (fn
) => fn(stub
);
38 stub
.txIf
= (fn
) => fn(stub
);
39 stub
.task
= (fn
) => fn(stub
);
45 pgpStub
.QueryFile
= class {};
46 pgpStub
.end
= common
.nop
;
48 beforeEach(function () {
49 logger
= new StubLogger();
51 options
= new Config('test');
52 db
= new DB(logger
, options
, pgpStub
);
55 afterEach(function () {
59 it('covers no query logging', function () {
60 delete options
.db
.queryLogLevel
;
61 db
= new DB(logger
, options
, pgpStub
);
65 // Ensure all interface methods are implemented
66 describe('Implementation', function () {
67 it('implements interface', async
function () {
68 const stubDb
= new StubDatabase();
69 const results
= await Promise
.allSettled(stubDb
._implementation
.map(async (fn
) => {
71 // eslint-disable-next-line security/detect-object-injection
74 assert(!(e
instanceof DBErrors
.NotImplemented
), `${fn} not implemented`);
77 const failures
= results
.filter((x
) => x
.status
=== 'rejected');
78 assert(!failures
.length
, failures
.map((x
) => {
79 x
= x
.reason
.toString();
80 return x
.slice(x
.indexOf(': '));
85 describe('pgpInitOptions', function () {
86 describe('error', function () {
87 it('covers', function () {
90 db
.pgpInitOptions
.error(err
, event
);
91 assert(db
.logger
.error
.called
);
94 describe('query', function () {
95 it('covers', function () {
97 db
.pgpInitOptions
.query(event
);
98 assert(db
.logger
.debug
.called
);
101 describe('receive', function () {
102 it('covers', function () {
105 column_one: 'one', // eslint-disable-line camelcase
106 column_two: 2, // eslint-disable-line camelcase
109 column_one: 'foo', // eslint-disable-line camelcase
110 column_two: 4, // eslint-disable-line camelcase
115 const expectedData
= [
125 db
.pgpInitOptions
.receive({ data
, result
, ctx: event
});
126 assert(db
.logger
.debug
.called
);
127 assert
.deepStrictEqual(data
, expectedData
);
129 it('covers no query logging', function () {
130 delete options
.db
.queryLogLevel
;
131 db
= new DB(logger
, options
, pgpStub
);
134 column_one: 'one', // eslint-disable-line camelcase
135 column_two: 2, // eslint-disable-line camelcase
138 column_one: 'foo', // eslint-disable-line camelcase
139 column_two: 4, // eslint-disable-line camelcase
144 const expectedData
= [
154 db
.pgpInitOptions
.receive({ data
, result
, ctx: event
});
155 assert(db
.logger
.debug
.called
);
156 assert
.deepStrictEqual(data
, expectedData
);
160 }); // pgpInitOptions
162 describe('_initTables', function () {
163 beforeEach(function () {
164 sinon
.stub(db
.db
, 'oneOrNone');
165 sinon
.stub(db
.db
, 'multiResult');
166 sinon
.stub(db
, '_currentSchema');
169 it('covers apply', async
function() {
170 db
.db
.oneOrNone
.onCall(0).resolves(null).onCall(1).resolves({});
171 db
._currentSchema
.resolves({ major: 0, minor: 0, patch: 0 });
172 await db
._initTables();
174 it('covers exists', async
function() {
175 db
.db
.oneOrNone
.resolves({});
176 db
._currentSchema
.resolves(db
.schemaVersionsSupported
.max
);
177 await db
._initTables();
181 describe('initialize', function () {
185 it('passes supported version', async
function () {
186 const version
= { major: 1, minor: 0, patch: 0 };
187 sinon
.stub(db
.db
, 'one').resolves(version
);
188 await db
.initialize(false);
190 it('fails low version', async
function () {
191 const version
= { major: 0, minor: 0, patch: 0 };
192 sinon
.stub(db
.db
, 'one').resolves(version
);
193 await assert
.rejects(() => db
.initialize(false), DBErrors
.MigrationNeeded
);
195 it('fails high version', async
function () {
196 const version
= { major: 100, minor: 100, patch: 100 };
197 sinon
.stub(db
.db
, 'one').resolves(version
);
198 await assert
.rejects(() => db
.initialize(false));
200 it('covers migration', async
function() {
201 sinon
.stub(db
.db
, 'oneOrNone').resolves({});
202 sinon
.stub(db
.db
, 'multiResult');
203 sinon
.stub(db
, '_currentSchema').resolves(db
.schemaVersionsSupported
.max
);
204 sinon
.stub(db
.db
, 'one').resolves(db
.schemaVersionsSupported
.max
);
205 await db
.initialize();
207 it('covers listener', async
function() {
211 const version
= { major: 1, minor: 0, patch: 0 };
212 sinon
.stub(db
.db
, 'one').resolves(version
);
213 await db
.initialize(false);
214 assert(db
.listener
.start
.called
);
218 describe('healthCheck', function () {
219 beforeEach(function () {
220 sinon
.stub(db
.db
, 'connect').resolves({
223 serverVersion: '0.0',
227 it('covers', async
function () {
228 const result
= await db
.healthCheck();
229 assert
.deepStrictEqual(result
, { serverVersion: '0.0' });
233 describe('_queryFileHelper', function () {
234 it('covers success', function () {
235 const _queryFile
= db
._queryFileHelper(pgpStub
);
238 it('covers failure', function () {
239 pgpStub
.QueryFile
= class {
241 this.error
= expectedException
;
244 const _queryFile
= db
._queryFileHelper(pgpStub
);
245 assert
.throws(() => _queryFile(), expectedException
);
247 }); // _queryFileHelper
249 describe('_closeConnection', function () {
253 it('success', async
function () {
254 sinon
.stub(db
._pgp
, 'end');
255 await db
._closeConnection();
256 assert(db
._pgp
.end
.called
);
258 it('failure', async
function () {
259 sinon
.stub(db
._pgp
, 'end').throws(expectedException
);
260 await assert
.rejects(() => db
._closeConnection(), expectedException
);
262 it('covers listener', async
function () {
266 sinon
.stub(db
._pgp
, 'end');
267 await db
._closeConnection();
268 assert(db
._pgp
.end
.called
);
270 }); // _closeConnection
272 describe('_purgeTables', function () {
273 it('covers not really', async
function () {
274 sinon
.stub(db
.db
, 'tx');
275 await db
._purgeTables(false);
276 assert(!db
.db
.tx
.called
);
278 it('success', async
function () {
279 sinon
.stub(db
.db
, 'batch');
280 await db
._purgeTables(true);
281 assert(db
.db
.batch
.called
);
283 it('failure', async
function () {
284 sinon
.stub(db
.db
, 'tx').rejects(expectedException
)
285 await assert
.rejects(() => db
._purgeTables(true), expectedException
);
289 describe('context', function () {
290 it('covers', async
function () {
291 await db
.context(common
.nop
);
295 describe('transaction', function () {
296 it('covers', async
function () {
297 await db
.transaction(db
.db
, common
.nop
);
301 describe('almanacGetAll', function () {
302 beforeEach(function () {
303 sinon
.stub(db
.db
, 'manyOrNone');
305 it('success', async
function () {
306 const expected
= [{ event: 'someEvent', date: new Date() }];
307 db
.db
.manyOrNone
.resolves(expected
);
308 const result
= await db
.almanacGetAll(dbCtx
);
309 assert
.deepStrictEqual(result
, expected
);
311 it('failure', async
function () {
312 db
.db
.manyOrNone
.rejects(expectedException
);
313 await assert
.rejects(() => db
.almanacGetAll(dbCtx
), expectedException
);
317 describe('authenticationSuccess', function () {
319 beforeEach(function () {
320 identifier
= 'username';
322 it('success', async
function () {
328 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
329 await db
.authenticationSuccess(dbCtx
, identifier
);
331 it('failure', async
function() {
337 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
338 await assert
.rejects(() => db
.authenticationSuccess(dbCtx
, identifier
), DBErrors
.UnexpectedResult
);
340 }); // authenticationSuccess
342 describe('authenticationGet', function () {
343 let identifier
, credential
;
344 beforeEach(function () {
345 identifier
= 'username';
346 credential
= '$z$foo';
348 it('success', async
function () {
349 const dbResult
= { identifier
, credential
};
350 sinon
.stub(db
.db
, 'oneOrNone').resolves(dbResult
);
351 const result
= await db
.authenticationGet(dbCtx
, identifier
);
352 assert
.deepStrictEqual(result
, dbResult
);
354 it('failure', async
function() {
355 sinon
.stub(db
.db
, 'oneOrNone').rejects(expectedException
);
356 await assert
.rejects(() => db
.authenticationGet(dbCtx
, identifier
, credential
), expectedException
);
358 }); // authenticationGet
360 describe('authenticationUpsert', function () {
361 let identifier
, credential
;
362 beforeEach(function () {
363 identifier
= 'username';
364 credential
= '$z$foo';
366 it('success', async
function () {
372 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
373 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
375 it('failure', async
function() {
376 credential
= undefined;
382 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
383 await assert
.rejects(() => db
.authenticationUpsert(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
385 }); // authenticationUpsert
387 describe('profileIdentifierInsert', function () {
388 let profile
, identifier
;
389 beforeEach(function () {
390 profile
= 'https://profile.example.com/';
391 identifier
= 'username';
393 it('success', async
function () {
397 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
398 await db
.profileIdentifierInsert(dbCtx
, profile
, identifier
);
400 it('failure', async
function () {
404 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
405 await assert
.rejects(() => db
.profileIdentifierInsert(dbCtx
, profile
, identifier
), DBErrors
.UnexpectedResult
);
407 }); // profileIdentifierInsert
409 describe('profileIsValid', function () {
411 beforeEach(function () {
412 profile
= 'https://profile.exmaple.com';
414 it('valid profile', async
function () {
415 sinon
.stub(db
.db
, 'oneOrNone').resolves({ profile
});
416 const result
= await db
.profileIsValid(dbCtx
, profile
);
417 assert
.strictEqual(result
, true);
419 it('invalid profile', async
function () {
420 sinon
.stub(db
.db
, 'oneOrNone').resolves();
421 const result
= await db
.profileIsValid(dbCtx
, profile
);
422 assert
.strictEqual(result
, false);
424 it('failure', async
function () {
425 sinon
.stub(db
.db
, 'oneOrNone').rejects(expectedException
);
426 await assert
.rejects(() => db
.profileIsValid(dbCtx
, profile
), expectedException
);
428 }); // profileIsValid
430 describe('tokenGetByCodeId', function () {
432 beforeEach(function () {
433 sinon
.stub(db
.db
, 'oneOrNone');
436 it('success', async
function() {
441 expires: new Date(Date
.now() + 24 * 60 * 60 * 1000),
443 db
.db
.oneOrNone
.resolves(dbResult
);
444 const result
= await db
.tokenGetByCodeId(dbCtx
, codeId
);
445 assert
.deepStrictEqual(result
, dbResult
);
447 it('failure', async
function () {
448 db
.db
.oneOrNone
.rejects(expectedException
);
449 await assert
.rejects(() => db
.tokenGetByCodeId(dbCtx
, codeId
), expectedException
);
451 }); // tokenGetByCodeId
453 describe('profileScopeInsert', function () {
455 beforeEach(function () {
456 profile
= 'https://profile.example.com/';
459 it('success', async
function () {
463 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
464 await db
.profileScopeInsert(dbCtx
, profile
, scope
);
466 it('failure', async
function () {
467 sinon
.stub(db
.db
, 'result').rejects(expectedException
);
468 await assert
.rejects(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), expectedException
);
470 it('failure', async
function () {
474 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
475 await assert
.rejects(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), DBErrors
.UnexpectedResult
);
477 }); // profileScopeInsert
479 describe('profileScopesSetAll', function () {
481 beforeEach(function () {
482 profile
= 'https://example.com/';
484 sinon
.stub(db
.db
, 'result');
486 it('success, no scopes', async
function () {
487 db
.db
.result
.resolves();
488 await db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
490 it('success, scopes', async
function () {
491 db
.db
.result
.resolves();
492 scopes
.push('profile', 'email', 'create');
493 await db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
495 it('failure', async
function () {
496 db
.db
.result
.rejects(expectedException
);
497 await assert
.rejects(() => db
.profileScopesSetAll(dbCtx
, profile
, scopes
), expectedException
);
499 }); // profileScopesSetAll
501 describe('profilesScopesByIdentifier', function () {
502 let identifier
, scopeIndex
, profileScopes
, profiles
;
503 beforeEach(function () {
504 identifier
= 'identifier';
507 description: 'A scope.',
510 isManuallyAdded: true,
511 profiles: ['https://first.example.com/', 'https://second.example.com/'],
514 description: 'Another scope.',
515 application: 'another test',
517 isManuallyAdded: false,
518 profiles: ['https://first.example.com/'],
521 description: 'A scope without application.',
524 isManuallyAdded: false,
525 profiles: ['https://second.example.com/'],
527 'no_profile_scope': {
528 description: 'A scope without profiles.',
531 isManuallyAdded: false,
536 'https://first.example.com/': {
537 'scope': scopeIndex
['scope'],
538 'another_scope': scopeIndex
['another_scope'],
540 'https://second.example.com/': {
541 'scope': scopeIndex
['scope'],
542 'no_app_scope': scopeIndex
['no_app_scope'],
544 'https://scopeless.example.com/': {},
547 'https://first.example.com/',
548 'https://second.example.com/',
549 'https://scopeless.example.com/',
552 it('success', async
function () {
554 { profile: 'https://first.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: true },
555 { profile: 'https://first.example.com/', scope: 'another_scope', application: 'another test', description: 'Another scope.', isPermanent: true, isManuallyAdded: false },
556 { profile: 'https://second.example.com/', scope: 'no_app_scope', application: '', description: 'A scope without application.', isPermanent: false, isManuallyAdded: false },
557 { profile: 'https://second.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: true },
558 { profile: null, scope: 'no_profile_scope', application: 'test', description: 'A scope without profiles.', isPermanent: false, isManuallyAdded: false },
559 { profile: 'https://scopeless.example.com/', scope: null, application: null, description: null, isPermanent: null, isManuallyAdded: null },
566 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
567 const result
= await db
.profilesScopesByIdentifier(dbCtx
, identifier
);
568 assert
.deepStrictEqual(result
, expected
);
570 it('failure', async
function () {
571 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
572 await assert
.rejects(() => db
.profilesScopesByIdentifier(dbCtx
, identifier
), expectedException
);
574 }); // profilesScopesByIdentifier
576 describe('redeemCode', function () {
577 let codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
;
578 beforeEach(function () {
579 codeId
= '41945b8e-3e82-11ec-82d1-0025905f714a';
581 clientId
= 'https://app.example.com/';
582 profile
= 'https://profile.example.com/';
583 identifier
= 'username';
584 scopes
= ['scope1', 'scope2'];
585 lifespanSeconds
= 600;
586 refreshId
= undefined;
587 profileData
= undefined;
589 it('success redeem', async
function () {
592 rows: [{ isRevoked: false }],
595 const dbResultScopes
= {
596 rowCount: scopes
.length
,
600 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(2).resolves(dbResultScopes
);
601 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
602 assert
.strictEqual(result
, true);
604 it('success redeem, no scopes', async
function () {
608 rows: [{ isRevoked: false }],
611 const dbResultScopes
= {
612 rowCount: scopes
.length
,
616 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(1).resolves(dbResultScopes
);
617 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
618 assert
.strictEqual(result
, true);
620 it('success revoke', async
function () {
623 rows: [{ isRevoked: true }],
626 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
627 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
628 assert
.strictEqual(result
, false);
630 it('failure', async
function() {
636 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
637 await assert
.rejects(() => db
.redeemCode(dbCtx
, { codeId
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
}), DBErrors
.UnexpectedResult
);
639 it('failure token scopes', async
function () {
642 rows: [{ isRevoked: false }],
645 const dbResultNone
= {
650 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(2).resolves(dbResultNone
);
651 await assert
.rejects(() => db
.redeemCode(dbCtx
, { codeId
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
}), DBErrors
.UnexpectedResult
);
655 describe('refreshCode', function () {
656 let codeId
, now
, removeScopes
;
657 beforeEach(function () {
658 codeId
= '41945b8e-3e82-11ec-82d1-0025905f714a';
661 sinon
.stub(db
.db
, 'result').resolves({ rowCount: removeScopes
.length
});
662 sinon
.stub(db
.db
, 'oneOrNone');
664 it('success', async
function () {
665 db
.db
.oneOrNone
.resolves({
669 const result
= await db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
);
670 assert(db
.db
.result
.notCalled
);
672 assert(result
.expires
);
673 assert(result
.refreshExpires
);
674 assert(!result
.scopes
);
676 it('success with scope reduction', async
function () {
677 removeScopes
= ['create'];
678 db
.db
.oneOrNone
.resolves({
683 db
.db
.result
.resolves({ rowCount: removeScopes
.length
});
684 const result
= await db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
);
686 assert(result
.expires
);
687 assert(result
.refreshExpires
);
688 assert(!result
.scopes
.includes('create'));
690 it('failure', async
function () {
691 db
.db
.oneOrNone
.rejects(expectedException
);
692 await assert
.rejects(async () => db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
), expectedException
);
694 it('failure with scope reduction', async
function () {
695 removeScopes
= ['create'];
696 db
.db
.oneOrNone
.resolves({});
697 db
.db
.result
.resolves({ rowCount: 0 });
698 await assert
.rejects(async () => db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
), DBErrors
.UnexpectedResult
);
702 describe('resourceGet', function () {
704 beforeEach(function () {
705 sinon
.stub(db
.db
, 'oneOrNone');
706 identifier
= '05b81112-b224-11ec-a9c6-0025905f714a';
708 it('success', async
function () {
713 db
.db
.oneOrNone
.resolves(dbResult
);
714 const result
= await db
.resourceGet(dbCtx
, identifier
);
715 assert
.deepStrictEqual(result
, dbResult
);
717 it('failure', async
function() {
718 db
.db
.oneOrNone
.rejects(expectedException
);
719 await assert
.rejects(() => db
.resourceGet(dbCtx
, identifier
), expectedException
);
723 describe('resourceUpsert', function () {
724 let resourceId
, secret
, description
;
725 beforeEach(function () {
726 resourceId
= '98b8d9ec-f8e2-11ec-aceb-0025905f714a';
727 secret
= 'supersecret';
728 description
= 'some service';
730 it('success', async
function () {
736 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
737 await db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
)
739 it('failure', async
function () {
745 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
746 await assert
.rejects(() => db
.resourceUpsert(dbCtx
, resourceId
, undefined, description
), DBErrors
.UnexpectedResult
);
748 }); // resourceUpsert
750 describe('scopeCleanup', function () {
751 let atLeastMsSinceLast
;
752 beforeEach(function () {
753 sinon
.stub(db
.db
, 'result');
754 sinon
.stub(db
.db
, 'oneOrNone');
755 atLeastMsSinceLast
= 86400000;
757 it('success, empty almanac', async
function () {
760 .onFirstCall().resolves({ rowCount: cleaned
})
761 .onSecondCall().resolves({ rowCount: 1 });
762 const result
= await db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
763 assert
.strictEqual(result
, cleaned
);
765 it('success, too soon', async
function () {
766 db
.db
.oneOrNone
.resolves({ date: new Date(Date
.now() - 4000) });
767 const result
= await db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
768 assert
.strictEqual(result
, undefined);
769 assert(db
.db
.result
.notCalled
);
771 it('failure', async
function () {
772 db
.db
.result
.resolves({ rowCount: 0 });
773 await assert
.rejects(async () => db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
777 describe('scopeDelete', function () {
779 beforeEach(function () {
782 it('success', async
function () {
788 sinon
.stub(db
.db
, 'one').resolves({ inUse: false });
789 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
790 const result
= await db
.scopeDelete(dbCtx
, scope
);
791 assert(db
.db
.result
.called
);
792 assert
.strictEqual(result
, true);
794 it('success, no scope', async
function () {
800 sinon
.stub(db
.db
, 'one').resolves({ inUse: false });
801 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
802 const result
= await db
.scopeDelete(dbCtx
, scope
);
803 assert(db
.db
.result
.called
);
804 assert
.strictEqual(result
, true);
806 it('scope in use', async
function () {
812 sinon
.stub(db
.db
, 'one').resolves({ inUse: true });
813 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
814 const result
= await db
.scopeDelete(dbCtx
, scope
);
815 assert(db
.db
.result
.notCalled
);
816 assert
.strictEqual(result
, false);
818 it('failure', async
function () {
819 sinon
.stub(db
.db
, 'one').rejects(expectedException
);
820 await assert
.rejects(() => db
.scopeDelete(dbCtx
, scope
), expectedException
);
824 describe('scopeUpsert', function () {
825 let scope
, description
;
826 beforeEach(function () {
828 description
= '$z$foo';
830 it('success', async
function () {
836 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
837 await db
.scopeUpsert(dbCtx
, scope
, description
);
839 it('failure', async
function() {
846 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
847 await assert
.rejects(() => db
.scopeUpsert(dbCtx
, scope
, description
), DBErrors
.UnexpectedResult
);
851 describe('tokenCleanup', function () {
852 let codeLifespanSeconds
, atLeastMsSinceLast
;
853 beforeEach(function () {
854 sinon
.stub(db
.db
, 'result');
855 sinon
.stub(db
.db
, 'oneOrNone');
856 codeLifespanSeconds
= 600000;
857 atLeastMsSinceLast
= 86400000;
859 it('success, empty almanac', async
function () {
862 .onFirstCall().resolves({ rowCount: cleaned
})
863 .onSecondCall().resolves({ rowCount: 1 });
864 const result
= await db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
865 assert
.strictEqual(result
, cleaned
);
867 it('success, too soon', async
function () {
868 db
.db
.oneOrNone
.resolves({ date: new Date(Date
.now() - 4000) });
869 const result
= await db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
870 assert
.strictEqual(result
, undefined);
871 assert(db
.db
.result
.notCalled
);
873 it('failure', async
function () {
874 db
.db
.result
.resolves({ rowCount: 0 });
875 await assert
.rejects(() => db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
879 describe('tokenRevokeByCodeId', function () {
881 beforeEach(function () {
882 codeId
= 'a74bda94-3dae-11ec-8908-0025905f714a';
884 it('success', async
function () {
890 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
891 await db
.tokenRevokeByCodeId(dbCtx
, codeId
);
893 it('failure', async
function() {
899 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
900 await assert
.rejects(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
902 }); // tokenRevokeByCodeId
904 describe('tokenRefreshRevokeByCodeId', function () {
906 beforeEach(function () {
907 codeId
= '279947c8-2584-11ed-a2d6-0025905f714a';
908 sinon
.stub(db
.db
, 'result');
910 it('success', async
function () {
911 db
.db
.result
.resolves({ rowCount: 1 });
912 await db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
);
914 it('failure, no code', async
function () {
915 db
.db
.result
.resolves({ rowCount: 0 });
916 assert
.rejects(async () => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
918 it('failure', async
function () {
919 db
.db
.result
.rejects(expectedException
);
920 assert
.rejects(async () => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), expectedException
);
922 }); // tokenRefreshRevokeByCodeId
924 describe('tokensGetByIdentifier', function () {
926 beforeEach(function () {
927 identifier
= 'identifier';
929 it('success', async
function () {
932 'created': new Date(),
933 'expires': new Date(),
941 const expected
= dbResult
;
942 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
943 const result
= await db
.tokensGetByIdentifier(dbCtx
, identifier
);
944 assert
.deepStrictEqual(result
, expected
);
946 it('failure', async
function () {
947 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
948 await assert
.rejects(() => db
.tokensGetByIdentifier(dbCtx
, identifier
), expectedException
);
950 }); // tokensGetByIdentifier
953 }); // DatabasePostgres