3920d340f53e2471b4fe5df55a32d25546996153
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('almanacUpsert', function () {
319 beforeEach(function () {
320 event
= 'test_event';
321 date
= new Date('Fri Dec 22 03:27 UTC 2023')
323 it('success', async
function () {
329 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
330 await db
.almanacUpsert(dbCtx
, event
, date
);
332 it('success with default date', async
function () {
338 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
339 await db
.almanacUpsert(dbCtx
, event
);
341 it('failure', async
function () {
347 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
348 await assert
.rejects(() => db
.almanacUpsert(dbCtx
, event
, date
), DBErrors
.UnexpectedResult
);
353 describe('authenticationSuccess', function () {
355 beforeEach(function () {
356 identifier
= 'username';
358 it('success', async
function () {
364 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
365 await db
.authenticationSuccess(dbCtx
, identifier
);
367 it('failure', async
function() {
373 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
374 await assert
.rejects(() => db
.authenticationSuccess(dbCtx
, identifier
), DBErrors
.UnexpectedResult
);
376 }); // authenticationSuccess
378 describe('authenticationGet', function () {
379 let identifier
, credential
;
380 beforeEach(function () {
381 identifier
= 'username';
382 credential
= '$z$foo';
384 it('success', async
function () {
385 const dbResult
= { identifier
, credential
};
386 sinon
.stub(db
.db
, 'oneOrNone').resolves(dbResult
);
387 const result
= await db
.authenticationGet(dbCtx
, identifier
);
388 assert
.deepStrictEqual(result
, dbResult
);
390 it('failure', async
function() {
391 sinon
.stub(db
.db
, 'oneOrNone').rejects(expectedException
);
392 await assert
.rejects(() => db
.authenticationGet(dbCtx
, identifier
, credential
), expectedException
);
394 }); // authenticationGet
396 describe('authenticationUpsert', function () {
397 let identifier
, credential
;
398 beforeEach(function () {
399 identifier
= 'username';
400 credential
= '$z$foo';
402 it('success', async
function () {
408 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
409 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
411 it('failure', async
function() {
412 credential
= undefined;
418 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
419 await assert
.rejects(() => db
.authenticationUpsert(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
421 }); // authenticationUpsert
423 describe('profileIdentifierInsert', function () {
424 let profile
, identifier
;
425 beforeEach(function () {
426 profile
= 'https://profile.example.com/';
427 identifier
= 'username';
429 it('success', async
function () {
433 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
434 await db
.profileIdentifierInsert(dbCtx
, profile
, identifier
);
436 it('failure', async
function () {
440 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
441 await assert
.rejects(() => db
.profileIdentifierInsert(dbCtx
, profile
, identifier
), DBErrors
.UnexpectedResult
);
443 }); // profileIdentifierInsert
445 describe('profileIsValid', function () {
447 beforeEach(function () {
448 profile
= 'https://profile.exmaple.com';
450 it('valid profile', async
function () {
451 sinon
.stub(db
.db
, 'oneOrNone').resolves({ profile
});
452 const result
= await db
.profileIsValid(dbCtx
, profile
);
453 assert
.strictEqual(result
, true);
455 it('invalid profile', async
function () {
456 sinon
.stub(db
.db
, 'oneOrNone').resolves();
457 const result
= await db
.profileIsValid(dbCtx
, profile
);
458 assert
.strictEqual(result
, false);
460 it('failure', async
function () {
461 sinon
.stub(db
.db
, 'oneOrNone').rejects(expectedException
);
462 await assert
.rejects(() => db
.profileIsValid(dbCtx
, profile
), expectedException
);
464 }); // profileIsValid
466 describe('tokenGetByCodeId', function () {
468 beforeEach(function () {
469 sinon
.stub(db
.db
, 'oneOrNone');
472 it('success', async
function() {
477 expires: new Date(Date
.now() + 24 * 60 * 60 * 1000),
479 db
.db
.oneOrNone
.resolves(dbResult
);
480 const result
= await db
.tokenGetByCodeId(dbCtx
, codeId
);
481 assert
.deepStrictEqual(result
, dbResult
);
483 it('failure', async
function () {
484 db
.db
.oneOrNone
.rejects(expectedException
);
485 await assert
.rejects(() => db
.tokenGetByCodeId(dbCtx
, codeId
), expectedException
);
487 }); // tokenGetByCodeId
489 describe('profileScopeInsert', function () {
491 beforeEach(function () {
492 profile
= 'https://profile.example.com/';
495 it('success', async
function () {
499 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
500 await db
.profileScopeInsert(dbCtx
, profile
, scope
);
502 it('failure', async
function () {
503 sinon
.stub(db
.db
, 'result').rejects(expectedException
);
504 await assert
.rejects(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), expectedException
);
506 it('failure', async
function () {
510 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
511 await assert
.rejects(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), DBErrors
.UnexpectedResult
);
513 }); // profileScopeInsert
515 describe('profileScopesSetAll', function () {
517 beforeEach(function () {
518 profile
= 'https://example.com/';
520 sinon
.stub(db
.db
, 'result');
522 it('success, no scopes', async
function () {
523 db
.db
.result
.resolves();
524 await db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
526 it('success, scopes', async
function () {
527 db
.db
.result
.resolves();
528 scopes
.push('profile', 'email', 'create');
529 await db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
531 it('failure', async
function () {
532 db
.db
.result
.rejects(expectedException
);
533 await assert
.rejects(() => db
.profileScopesSetAll(dbCtx
, profile
, scopes
), expectedException
);
535 }); // profileScopesSetAll
537 describe('profilesScopesByIdentifier', function () {
538 let identifier
, scopeIndex
, profileScopes
, profiles
;
539 beforeEach(function () {
540 identifier
= 'identifier';
543 description: 'A scope.',
546 isManuallyAdded: true,
547 profiles: ['https://first.example.com/', 'https://second.example.com/'],
550 description: 'Another scope.',
551 application: 'another test',
553 isManuallyAdded: false,
554 profiles: ['https://first.example.com/'],
557 description: 'A scope without application.',
560 isManuallyAdded: false,
561 profiles: ['https://second.example.com/'],
563 'no_profile_scope': {
564 description: 'A scope without profiles.',
567 isManuallyAdded: false,
572 'https://first.example.com/': {
573 'scope': scopeIndex
['scope'],
574 'another_scope': scopeIndex
['another_scope'],
576 'https://second.example.com/': {
577 'scope': scopeIndex
['scope'],
578 'no_app_scope': scopeIndex
['no_app_scope'],
580 'https://scopeless.example.com/': {},
583 'https://first.example.com/',
584 'https://second.example.com/',
585 'https://scopeless.example.com/',
588 it('success', async
function () {
590 { profile: 'https://first.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: true },
591 { profile: 'https://first.example.com/', scope: 'another_scope', application: 'another test', description: 'Another scope.', isPermanent: true, isManuallyAdded: false },
592 { profile: 'https://second.example.com/', scope: 'no_app_scope', application: '', description: 'A scope without application.', isPermanent: false, isManuallyAdded: false },
593 { profile: 'https://second.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: true },
594 { profile: null, scope: 'no_profile_scope', application: 'test', description: 'A scope without profiles.', isPermanent: false, isManuallyAdded: false },
595 { profile: 'https://scopeless.example.com/', scope: null, application: null, description: null, isPermanent: null, isManuallyAdded: null },
602 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
603 const result
= await db
.profilesScopesByIdentifier(dbCtx
, identifier
);
604 assert
.deepStrictEqual(result
, expected
);
606 it('failure', async
function () {
607 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
608 await assert
.rejects(() => db
.profilesScopesByIdentifier(dbCtx
, identifier
), expectedException
);
610 }); // profilesScopesByIdentifier
612 describe('redeemCode', function () {
613 let codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
;
614 beforeEach(function () {
615 codeId
= '41945b8e-3e82-11ec-82d1-0025905f714a';
617 clientId
= 'https://app.example.com/';
618 profile
= 'https://profile.example.com/';
619 identifier
= 'username';
620 scopes
= ['scope1', 'scope2'];
621 lifespanSeconds
= 600;
622 refreshId
= undefined;
623 profileData
= undefined;
625 it('success redeem', async
function () {
628 rows: [{ isRevoked: false }],
631 const dbResultScopes
= {
632 rowCount: scopes
.length
,
636 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(2).resolves(dbResultScopes
);
637 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
638 assert
.strictEqual(result
, true);
640 it('success redeem, no scopes', async
function () {
644 rows: [{ isRevoked: false }],
647 const dbResultScopes
= {
648 rowCount: scopes
.length
,
652 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(1).resolves(dbResultScopes
);
653 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
654 assert
.strictEqual(result
, true);
656 it('success revoke', async
function () {
659 rows: [{ isRevoked: true }],
662 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
663 const result
= await db
.redeemCode(dbCtx
, { codeId
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
});
664 assert
.strictEqual(result
, false);
666 it('failure', async
function() {
672 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
673 await assert
.rejects(() => db
.redeemCode(dbCtx
, { codeId
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
}), DBErrors
.UnexpectedResult
);
675 it('failure token scopes', async
function () {
678 rows: [{ isRevoked: false }],
681 const dbResultNone
= {
686 sinon
.stub(db
.db
, 'result').resolves(dbResult
).onCall(2).resolves(dbResultNone
);
687 await assert
.rejects(() => db
.redeemCode(dbCtx
, { codeId
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, refreshId
, profileData
}), DBErrors
.UnexpectedResult
);
691 describe('refreshCode', function () {
692 let codeId
, now
, removeScopes
;
693 beforeEach(function () {
694 codeId
= '41945b8e-3e82-11ec-82d1-0025905f714a';
697 sinon
.stub(db
.db
, 'result').resolves({ rowCount: removeScopes
.length
});
698 sinon
.stub(db
.db
, 'oneOrNone');
700 it('success', async
function () {
701 db
.db
.oneOrNone
.resolves({
705 const result
= await db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
);
706 assert(db
.db
.result
.notCalled
);
708 assert(result
.expires
);
709 assert(result
.refreshExpires
);
710 assert(!result
.scopes
);
712 it('success with scope reduction', async
function () {
713 removeScopes
= ['create'];
714 db
.db
.oneOrNone
.resolves({
719 db
.db
.result
.resolves({ rowCount: removeScopes
.length
});
720 const result
= await db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
);
722 assert(result
.expires
);
723 assert(result
.refreshExpires
);
724 assert(!result
.scopes
.includes('create'));
726 it('failure', async
function () {
727 db
.db
.oneOrNone
.rejects(expectedException
);
728 await assert
.rejects(async () => db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
), expectedException
);
730 it('failure with scope reduction', async
function () {
731 removeScopes
= ['create'];
732 db
.db
.oneOrNone
.resolves({});
733 db
.db
.result
.resolves({ rowCount: 0 });
734 await assert
.rejects(async () => db
.refreshCode(dbCtx
, codeId
, now
, removeScopes
), DBErrors
.UnexpectedResult
);
738 describe('resourceGet', function () {
740 beforeEach(function () {
741 sinon
.stub(db
.db
, 'oneOrNone');
742 identifier
= '05b81112-b224-11ec-a9c6-0025905f714a';
744 it('success', async
function () {
749 db
.db
.oneOrNone
.resolves(dbResult
);
750 const result
= await db
.resourceGet(dbCtx
, identifier
);
751 assert
.deepStrictEqual(result
, dbResult
);
753 it('failure', async
function() {
754 db
.db
.oneOrNone
.rejects(expectedException
);
755 await assert
.rejects(() => db
.resourceGet(dbCtx
, identifier
), expectedException
);
759 describe('resourceUpsert', function () {
760 let resourceId
, secret
, description
;
761 beforeEach(function () {
762 resourceId
= '98b8d9ec-f8e2-11ec-aceb-0025905f714a';
763 secret
= 'supersecret';
764 description
= 'some service';
766 it('success', async
function () {
772 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
773 await db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
)
775 it('failure', async
function () {
781 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
782 await assert
.rejects(() => db
.resourceUpsert(dbCtx
, resourceId
, undefined, description
), DBErrors
.UnexpectedResult
);
784 }); // resourceUpsert
786 describe('scopeCleanup', function () {
787 let atLeastMsSinceLast
;
788 beforeEach(function () {
789 sinon
.stub(db
.db
, 'result');
790 sinon
.stub(db
.db
, 'oneOrNone');
791 atLeastMsSinceLast
= 86400000;
793 it('success, empty almanac', async
function () {
796 .onFirstCall().resolves({ rowCount: cleaned
})
797 .onSecondCall().resolves({ rowCount: 1 });
798 const result
= await db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
799 assert
.strictEqual(result
, cleaned
);
801 it('success, too soon', async
function () {
802 db
.db
.oneOrNone
.resolves({ date: new Date(Date
.now() - 4000) });
803 const result
= await db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
804 assert
.strictEqual(result
, undefined);
805 assert(db
.db
.result
.notCalled
);
807 it('failure', async
function () {
808 db
.db
.result
.resolves({ rowCount: 0 });
809 await assert
.rejects(async () => db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
813 describe('scopeDelete', function () {
815 beforeEach(function () {
818 it('success', async
function () {
824 sinon
.stub(db
.db
, 'one').resolves({ inUse: false });
825 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
826 const result
= await db
.scopeDelete(dbCtx
, scope
);
827 assert(db
.db
.result
.called
);
828 assert
.strictEqual(result
, true);
830 it('success, no scope', async
function () {
836 sinon
.stub(db
.db
, 'one').resolves({ inUse: false });
837 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
838 const result
= await db
.scopeDelete(dbCtx
, scope
);
839 assert(db
.db
.result
.called
);
840 assert
.strictEqual(result
, true);
842 it('scope in use', async
function () {
848 sinon
.stub(db
.db
, 'one').resolves({ inUse: true });
849 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
850 const result
= await db
.scopeDelete(dbCtx
, scope
);
851 assert(db
.db
.result
.notCalled
);
852 assert
.strictEqual(result
, false);
854 it('failure', async
function () {
855 sinon
.stub(db
.db
, 'one').rejects(expectedException
);
856 await assert
.rejects(() => db
.scopeDelete(dbCtx
, scope
), expectedException
);
860 describe('scopeUpsert', function () {
861 let scope
, description
;
862 beforeEach(function () {
864 description
= '$z$foo';
866 it('success', async
function () {
872 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
873 await db
.scopeUpsert(dbCtx
, scope
, description
);
875 it('failure', async
function() {
882 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
883 await assert
.rejects(() => db
.scopeUpsert(dbCtx
, scope
, description
), DBErrors
.UnexpectedResult
);
887 describe('tokenCleanup', function () {
888 let codeLifespanSeconds
, atLeastMsSinceLast
;
889 beforeEach(function () {
890 sinon
.stub(db
.db
, 'result');
891 sinon
.stub(db
.db
, 'oneOrNone');
892 codeLifespanSeconds
= 600000;
893 atLeastMsSinceLast
= 86400000;
895 it('success, empty almanac', async
function () {
898 .onFirstCall().resolves({ rowCount: cleaned
})
899 .onSecondCall().resolves({ rowCount: 1 });
900 const result
= await db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
901 assert
.strictEqual(result
, cleaned
);
903 it('success, too soon', async
function () {
904 db
.db
.oneOrNone
.resolves({ date: new Date(Date
.now() - 4000) });
905 const result
= await db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
906 assert
.strictEqual(result
, undefined);
907 assert(db
.db
.result
.notCalled
);
909 it('failure', async
function () {
910 db
.db
.result
.resolves({ rowCount: 0 });
911 await assert
.rejects(() => db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
915 describe('tokenRevokeByCodeId', function () {
917 beforeEach(function () {
918 codeId
= 'a74bda94-3dae-11ec-8908-0025905f714a';
920 it('success', async
function () {
926 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
927 await db
.tokenRevokeByCodeId(dbCtx
, codeId
);
929 it('failure', async
function() {
935 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
936 await assert
.rejects(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
938 }); // tokenRevokeByCodeId
940 describe('tokenRefreshRevokeByCodeId', function () {
942 beforeEach(function () {
943 codeId
= '279947c8-2584-11ed-a2d6-0025905f714a';
944 sinon
.stub(db
.db
, 'result');
946 it('success', async
function () {
947 db
.db
.result
.resolves({ rowCount: 1 });
948 await db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
);
950 it('failure, no code', async
function () {
951 db
.db
.result
.resolves({ rowCount: 0 });
952 assert
.rejects(async () => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
954 it('failure', async
function () {
955 db
.db
.result
.rejects(expectedException
);
956 assert
.rejects(async () => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), expectedException
);
958 }); // tokenRefreshRevokeByCodeId
960 describe('tokensGetByIdentifier', function () {
962 beforeEach(function () {
963 identifier
= 'identifier';
965 it('success', async
function () {
968 'created': new Date(),
969 'expires': new Date(),
977 const expected
= dbResult
;
978 sinon
.stub(db
.db
, 'manyOrNone').resolves(dbResult
);
979 const result
= await db
.tokensGetByIdentifier(dbCtx
, identifier
);
980 assert
.deepStrictEqual(result
, expected
);
982 it('failure', async
function () {
983 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
984 await assert
.rejects(() => db
.tokensGetByIdentifier(dbCtx
, identifier
), expectedException
);
986 }); // tokensGetByIdentifier
988 describe('ticketRedeemed', function () {
990 beforeEach(function () {
992 resource: 'https://resource.example.com/',
993 subject: 'https://subject.example.com/',
994 iss: 'https://idp.example.com/',
995 ticket: 'xxxTICKETxxx',
996 token: 'xxxTOKENxxx',
999 it('success', async
function () {
1005 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1006 await db
.ticketRedeemed(dbCtx
, redeemedData
);
1008 it('failure', async
function () {
1014 sinon
.stub(db
.db
, 'result').resolves(dbResult
);
1015 await assert
.rejects(() => db
.ticketRedeemed(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1017 }); // ticketRedeemed
1019 describe('ticketTokenPublished', function () {
1021 beforeEach(function () {
1023 resource: 'https://resource.example.com/',
1024 subject: 'https://subject.example.com/',
1025 iss: 'https://idp.example.com/',
1026 ticket: 'xxxTICKETxxx',
1027 token: 'xxxTOKENxxx',
1029 sinon
.stub(db
.db
, 'result');
1031 it('success', async
function () {
1037 db
.db
.result
.resolves(dbResult
);
1038 await db
.ticketTokenPublished(dbCtx
, redeemedData
);
1040 it('failure', async
function () {
1046 db
.db
.result
.resolves(dbResult
);
1047 await assert
.rejects(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1049 it('failure of almanac', async
function () {
1055 const dbResultAlmanac
= {
1059 db
.db
.result
.resolves(dbResult
).onCall(1).resolves(dbResultAlmanac
);
1060 await assert
.rejects(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1062 }); // ticketTokenPublished
1064 describe('ticketTokenGetUnpublished', function () {
1065 it('success', async
function () {
1067 resource: 'https://resource.example.com/',
1068 subject: 'https://subject.example.com/',
1069 iss: 'https://idp.example.com/',
1070 ticket: 'xxxTICKETxxx',
1071 token: 'xxxTOKENxxx',
1072 created: new Date(),
1075 sinon
.stub(db
.db
, 'manyOrNone').resolves(expected
);
1076 const result
= await db
.ticketTokenGetUnpublished(dbCtx
);
1077 assert
.deepStrictEqual(result
, expected
);
1079 it('failure', async
function () {
1080 sinon
.stub(db
.db
, 'manyOrNone').rejects(expectedException
);
1081 await assert
.rejects(() => db
.ticketTokenGetUnpublished(dbCtx
), expectedException
);
1083 }); // ticketTokenGetUnpublished
1085 }); // DatabasePostgres