1 /* eslint-disable sonarjs/no-identical-functions */
3 /* eslint-disable sonarjs/no-duplicate-string */
6 /* This provides implementation coverage, stubbing parts of better-sqlite3. */
8 const assert
= require('assert');
9 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
10 const StubDatabase
= require('../../stub-db');
11 const StubLogger
= require('../../stub-logger');
12 const DB
= require('../../../src/db/sqlite');
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('DatabaseSQLite', function () {
20 let db
, options
, logger
, stubDb
;
23 logger
= new StubLogger();
25 stubDb
= new StubDatabase();
27 beforeEach(function () {
28 options
= new Config('test');
29 options
.db
.connectionString
= 'sqlite://:memory:';
30 db
= new DB(logger
, options
);
33 afterEach(function () {
37 it('covers constructor options', function () {
38 delete options
.db
.connectionString
;
39 db
= new DB(logger
, options
);
42 // Ensure all interface methods are implemented
43 describe('Implementation', function () {
44 it('implements interface', async
function () {
45 const results
= await Promise
.allSettled(stubDb
._implementation
.map((fn
) => {
47 // eslint-disable-next-line security/detect-object-injection
50 assert(!(e
instanceof DBErrors
.NotImplemented
), `${fn} not implemented`);
53 const failures
= results
.filter((x
) => x
.status
=== 'rejected');
54 assert(!failures
.length
, failures
.map((x
) => {
55 x
= x
.reason
.toString();
56 return x
.slice(x
.indexOf(': '));
61 describe('_currentSchema', function () {
62 it('covers', function () {
63 const version
= { major: 1, minor: 0, patch: 0 };
64 sinon
.stub(db
.db
, 'prepare').returns({
67 const result
= db
._currentSchema();
68 assert
.deepStrictEqual(result
, version
);
72 describe('_closeConnection', function () {
73 it('success', function () {
74 sinon
.stub(db
.db
, 'close');
75 db
._closeConnection();
76 assert(db
.db
.close
.called
);
78 it('failure', function () {
79 sinon
.stub(db
.db
, 'close').throws(expectedException
);
80 assert
.throws(() => db
._closeConnection(), expectedException
);
82 }); // _closeConnection
84 describe('_purgeTables', function () {
85 beforeEach(function () {
86 sinon
.stub(db
.db
, 'prepare').returns({
90 it('covers not really', function () {
91 db
._purgeTables(false);
92 assert(!db
.db
.prepare
.called
);
94 it('success', function () {
95 db
._purgeTables(true);
96 assert(db
.db
.prepare
.called
);
98 it('failure', function () {
99 db
.db
.prepare
.restore();
100 sinon
.stub(db
.db
, 'prepare').throws(expectedException
);
101 assert
.throws(() => db
._purgeTables(true), expectedException
);
105 describe('_optimize', function () {
106 beforeEach(function () {
107 sinon
.stub(db
.statement
._optimize
, 'all');
108 sinon
.stub(db
.db
, 'pragma');
110 it('covers', function () {
111 db
.changesSinceLastOptimize
= BigInt(20);
113 assert(db
.db
.pragma
.called
);
114 assert(db
.statement
._optimize
.all
.called
);
115 assert
.strictEqual(db
.changesSinceLastOptimize
, 0n
)
119 describe('_updateChanges', function () {
121 beforeEach(function () {
125 sinon
.stub(db
, '_optimize');
127 it('does not optimize if not wanted', function () {
128 db
.optimizeAfterChanges
= 0n
;
129 db
._updateChanges(dbResult
);
130 assert(db
._optimize
.notCalled
);
132 it('does not optimize if under threshold', function () {
133 db
.optimizeAfterChanges
= 100n
;
134 db
._updateChanges(dbResult
);
135 assert(db
._optimize
.notCalled
);
137 it('optimizes over threshold', function () {
138 db
.optimizeAfterChanges
= 1n
;
139 db
._updateChanges(dbResult
);
140 assert(db
._optimize
.called
);
142 }); // _updateChanges
144 describe('_deOphidiate', function () {
145 it('covers non-array', function () {
152 const result
= DB
._deOphidiate(obj
);
153 assert
.deepStrictEqual(result
, expected
);
155 it('covers array', function () {
172 const result
= DB
._deOphidiate(rows
);
173 assert
.deepStrictEqual(result
, expected
);
177 describe('healthCheck', function () {
178 it('covers', function () {
181 it('covers failure', function () {
182 db
.db
= { open: false };
183 assert
.throws(() => db
.healthCheck(), DBErrors
.UnexpectedResult
);
187 describe('context', function () {
188 it('covers', function () {
189 db
.context(common
.nop
);
193 describe('transaction', function () {
194 it('covers', function () {
195 db
.transaction(db
.db
, common
.nop
);
197 it('covers no context', function () {
198 db
.transaction(undefined, common
.nop
);
202 describe('almanacGetAll', function () {
203 beforeEach(function () {
204 sinon
.stub(db
.statement
.almanacGetAll
, 'all');
206 it('success', function () {
207 const dbResult
= [{ event: 'someEvent', epoch: '1668887796' } ];
208 const expected
= [{ event: 'someEvent', date: new Date('Sat Nov 19 11:56:36 AM PST 2022') }];
209 db
.statement
.almanacGetAll
.all
.returns(dbResult
);
210 const result
= db
.almanacGetAll(dbCtx
);
211 assert
.deepStrictEqual(result
, expected
);
213 it('failure', function () {
214 db
.statement
.almanacGetAll
.all
.throws(expectedException
);
215 assert
.throws(() => db
.almanacGetAll(dbCtx
), expectedException
);
219 describe('almanacUpsert', function () {
220 let event
, date
, dbResult
;
221 beforeEach(function () {
222 event
= 'test_event';
223 date
= new Date('Fri Dec 22 03:27 UTC 2023')
224 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
227 lastInsertRowid: undefined,
230 it('success', function () {
231 db
.statement
.almanacUpsert
.run
.returns(dbResult
);
232 db
.almanacUpsert(dbCtx
, event
, date
);
234 it('success with default date', function () {
235 db
.statement
.almanacUpsert
.run
.returns(dbResult
);
236 db
.almanacUpsert(dbCtx
, event
);
238 it('failure', function () {
239 dbResult
.changes
= 0;
240 db
.statement
.almanacUpsert
.run
.returns(dbResult
);
241 assert
.throws(() => db
.almanacUpsert(dbCtx
, { event
, date
}), DBErrors
.UnexpectedResult
);
245 describe('authenticationGet', function () {
246 let identifier
, credential
;
247 beforeEach(function () {
248 identifier
= 'username';
249 credential
= '$z$foo';
250 sinon
.stub(db
.statement
.authenticationGet
, 'get');
252 it('success', function() {
257 db
.statement
.authenticationGet
.get.returns(expected
);
258 const result
= db
.authenticationGet(dbCtx
, identifier
);
259 assert
.deepStrictEqual(result
, expected
);
261 it('failure', function () {
262 db
.statement
.authenticationGet
.get.throws(expectedException
);
263 assert
.throws(() => db
.authenticationGet(dbCtx
, identifier
), expectedException
);
265 }); // authenticationGet
267 describe('authenticationSuccess', function () {
268 let dbResult
, identifier
;
269 beforeEach(function () {
270 identifier
= 'username';
271 sinon
.stub(db
.statement
.authenticationSuccess
, 'run');
274 lastInsertRowid: undefined,
277 it('success', function() {
278 db
.statement
.authenticationSuccess
.run
.returns(dbResult
);
279 db
.authenticationSuccess(dbCtx
, identifier
);
281 it('failure', function () {
282 dbResult
.changes
= 0;
283 db
.statement
.authenticationSuccess
.run
.returns(dbResult
);
284 assert
.throws(() => db
.authenticationSuccess(dbCtx
, identifier
), DBErrors
.UnexpectedResult
);
286 }); // authenticationSuccess
288 describe('authenticationUpsert', function () {
289 let identifier
, credential
;
290 beforeEach(function () {
291 identifier
= 'username';
292 credential
= '$z$foo';
294 it('success', function() {
297 lastInsertRowid: undefined,
299 sinon
.stub(db
.statement
.authenticationUpsert
, 'run').returns(dbResult
);
300 db
.authenticationUpsert(dbCtx
, identifier
, credential
);
302 it('failure', function () {
305 lastInsertRowid: undefined,
307 sinon
.stub(db
.statement
.authenticationUpsert
, 'run').returns(dbResult
);
308 assert
.throws(() => db
.authenticationUpsert(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
310 }); // authenticationUpsert
312 describe('authenticationUpdateCredential', function () {
313 let identifier
, credential
;
314 beforeEach(function () {
315 identifier
= 'username';
316 credential
= '$z$foo';
318 it('success', function() {
321 lastInsertRowid: undefined,
323 sinon
.stub(db
.statement
.authenticationUpdateCredential
, 'run').returns(dbResult
);
324 db
.authenticationUpdateCredential(dbCtx
, identifier
, credential
);
326 it('failure', function () {
329 lastInsertRowid: undefined,
331 sinon
.stub(db
.statement
.authenticationUpdateCredential
, 'run').returns(dbResult
);
332 assert
.throws(() => db
.authenticationUpdateCredential(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
334 }); // authenticationUpdateCredential
336 describe('authenticationUpdateOTPKey', function () {
337 let identifier
, otpKey
;
338 beforeEach(function () {
339 identifier
= 'username';
340 otpKey
= '1234567890123456789012';
342 it('success', function() {
345 lastInsertRowid: undefined,
347 sinon
.stub(db
.statement
.authenticationUpdateOtpKey
, 'run').returns(dbResult
);
348 db
.authenticationUpdateOTPKey(dbCtx
, identifier
, otpKey
);
350 it('failure', function () {
353 lastInsertRowid: undefined,
355 sinon
.stub(db
.statement
.authenticationUpdateOtpKey
, 'run').returns(dbResult
);
356 assert
.throws(() => db
.authenticationUpdateOTPKey(dbCtx
, identifier
, otpKey
), DBErrors
.UnexpectedResult
);
358 }); // authenticationUpdateOTPKey
360 describe('profileIdentifierInsert', function () {
361 let profile
, identifier
;
362 beforeEach(function () {
363 profile
= 'https://profile.example.com/';
364 identifier
= 'identifier';
365 sinon
.stub(db
.statement
.profileIdentifierInsert
, 'run');
367 it('success', function () {
368 db
.statement
.profileIdentifierInsert
.run
.returns({ changes: 1 });
369 db
.profileIdentifierInsert(dbCtx
, profile
, identifier
);
371 it('failure', function () {
372 db
.statement
.profileIdentifierInsert
.run
.returns({ changes: 0 });
373 assert
.throws(() => db
.profileIdentifierInsert(dbCtx
, profile
, identifier
), DBErrors
.UnexpectedResult
);
375 }); // profileIdentifierInsert
377 describe('profileScopeInsert', function () {
379 beforeEach(function () {
380 profile
= 'https://profile.example.com/';
382 sinon
.stub(db
.statement
.profileScopeInsert
, 'run');
384 it('success', function () {
385 db
.statement
.profileScopeInsert
.run
.returns({ changes: 1 });
386 db
.profileScopeInsert(dbCtx
, profile
, scope
);
388 it('failure', function () {
389 db
.statement
.profileScopeInsert
.run
.returns({ changes: 2 });
390 assert
.throws(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), DBErrors
.UnexpectedResult
);
392 }); // profileScopeInsert
394 describe('profileIsValid', function () {
396 beforeEach(function () {
397 profile
= 'https://profile.exmaple.com';
399 it('valid profile', function () {
400 sinon
.stub(db
.statement
.profileGet
, 'get').returns({ profile
});
401 const result
= db
.profileIsValid(dbCtx
, profile
);
402 assert
.deepStrictEqual(result
, true);
404 it('invalid profile', function () {
405 sinon
.stub(db
.statement
.profileGet
, 'get').returns();
406 const result
= db
.profileIsValid(dbCtx
, profile
);
407 assert
.deepStrictEqual(result
, false);
409 it('failure', function() {
410 sinon
.stub(db
.statement
.profileGet
, 'get').throws(expectedException
);
411 assert
.throws(() => db
.profileIsValid(dbCtx
, profile
), expectedException
);
413 }); // profileIsValid
415 describe('profilesScopesByIdentifier', function () {
416 let identifier
, scopeIndex
, profileScopes
, profiles
;
417 beforeEach(function () {
418 identifier
= 'identifier';
421 description: 'A scope.',
424 isManuallyAdded: false,
425 profiles: ['https://first.example.com/', 'https://second.example.com/'],
428 description: 'Another scope.',
429 application: 'another test',
431 isManuallyAdded: false,
432 profiles: ['https://first.example.com/'],
436 'https://first.example.com/': {
437 'scope': scopeIndex
['scope'],
438 'another_scope': scopeIndex
['another_scope'],
440 'https://second.example.com/': {
441 'scope': scopeIndex
['scope'],
444 profiles
= ['https://first.example.com/', 'https://second.example.com/'];
446 it('success', function () {
448 { profile: 'https://first.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: false },
449 { profile: 'https://first.example.com/', scope: 'another_scope', application: 'another test', description: 'Another scope.', isPermanent: false, isManuallyAdded: false },
450 { profile: 'https://second.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: false },
457 sinon
.stub(db
.statement
.profilesScopesByIdentifier
, 'all').returns(dbResult
);
458 const result
= db
.profilesScopesByIdentifier(dbCtx
, identifier
);
459 assert
.deepStrictEqual(result
, expected
);
461 it('failure', function() {
462 sinon
.stub(db
.statement
.profilesScopesByIdentifier
, 'all').throws(expectedException
);
463 assert
.throws(() => db
.profilesScopesByIdentifier(dbCtx
, identifier
), expectedException
);
465 }); // profilesScopesByIdentifier
467 describe('profileScopesSetAll', function () {
469 beforeEach(function () {
470 profile
= 'https://example.com/';
471 scopes
= ['scope1', 'scope2'];
472 sinon
.stub(db
.statement
.profileScopesClear
, 'run').returns();
473 sinon
.stub(db
.statement
.profileScopeInsert
, 'run');
475 it('success, no scopes', function () {
476 db
.statement
.profileScopeInsert
.run
.returns();
478 db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
480 it('success, scopes', function () {
481 db
.statement
.profileScopeInsert
.run
.returns();
482 scopes
.push('profile', 'email', 'create');
483 db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
485 it('failure', function () {
486 db
.statement
.profileScopeInsert
.run
.throws(expectedException
);
487 assert
.throws(() => db
.profileScopesSetAll(dbCtx
, profile
, scopes
), expectedException
);
490 }); // profileScopesSetAll
492 describe('redeemCode', function () {
493 let codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
;
494 beforeEach(function () {
495 codeId
= '2f226616-3e79-11ec-ad0f-0025905f714a';
497 clientId
= 'https://app.exmaple.com/';
498 profile
= 'https://profile.example.com/';
499 identifier
= 'username';
500 scopes
= ['scope1', 'scope2'];
501 lifespanSeconds
= 600;
502 profileData
= undefined;
503 created
= new Date();
505 sinon
.stub(db
.statement
.scopeInsert
, 'run');
506 sinon
.stub(db
.statement
.tokenScopeSet
, 'run');
507 sinon
.stub(db
.statement
.redeemCode
, 'get');
509 it('success', function() {
512 lastInsertRowid: undefined,
517 db
.statement
.scopeInsert
.run
.returns(dbResult
);
518 db
.statement
.tokenScopeSet
.run
.returns(dbResult
);
519 db
.statement
.redeemCode
.get.returns(dbGet
);
523 const result
= db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
});
524 assert
.strictEqual(result
, true);
526 it('success (revoked)', function() {
529 lastInsertRowid: undefined,
534 db
.statement
.scopeInsert
.run
.returns(dbResult
);
535 db
.statement
.tokenScopeSet
.run
.returns(dbResult
);
536 db
.statement
.redeemCode
.get.returns(dbGet
);
537 const result
= db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
});
538 assert
.strictEqual(result
, false);
540 it('failure', function () {
541 db
.statement
.scopeInsert
.run
.throws();
542 db
.statement
.tokenScopeSet
.run
.throws();
543 db
.statement
.redeemCode
.get.returns();
544 assert
.throws(() => db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
}), DBErrors
.UnexpectedResult
);
548 describe('refreshCode', function () {
549 let refreshResponse
, removeResponse
, scopesResponse
, codeId
, refreshed
, removeScopes
;
550 beforeEach(function () {
551 sinon
.stub(db
.statement
.refreshCode
, 'get');
552 sinon
.stub(db
.statement
.tokenScopeRemove
, 'run');
553 sinon
.stub(db
.statement
.tokenScopesGetByCodeId
, 'all');
554 codeId
= '73db7b18-27bb-11ed-8edd-0025905f714a';
555 refreshed
= new Date();
556 removeScopes
= ['foop'];
557 const refreshedEpoch
= Math
.ceil(refreshed
.getTime() / 1000);
559 expires: refreshedEpoch
+ 86400,
560 refreshExpires: refreshedEpoch
+ 172800,
563 changes: removeScopes
.length
,
569 it('success', function () {
570 db
.statement
.refreshCode
.get.returns(refreshResponse
);
571 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
572 db
.statement
.tokenScopesGetByCodeId
.all
.returns(scopesResponse
);
573 const expectedResponse
= {
574 expires: new Date(refreshResponse
.expires
* 1000),
575 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
578 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
579 assert
.deepStrictEqual(response
, expectedResponse
);
581 it('success without scope removal', function () {
582 db
.statement
.refreshCode
.get.returns(refreshResponse
);
583 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
584 const expectedResponse
= {
585 expires: new Date(refreshResponse
.expires
* 1000),
586 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
589 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
590 assert
.deepStrictEqual(response
, expectedResponse
);
592 it('success with no scopes left', function () {
593 db
.statement
.refreshCode
.get.returns(refreshResponse
);
594 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
595 const expectedResponse
= {
596 expires: new Date(refreshResponse
.expires
* 1000),
597 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
600 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
601 assert
.deepStrictEqual(response
, expectedResponse
);
603 it('no code', function () {
604 db
.statement
.refreshCode
.get.returns();
605 removeResponse
.changes
= 0;
606 db
.statement
.tokenScopeRemove
.run
.returns();
607 const expectedResponse
= undefined;
608 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
609 assert
.deepStrictEqual(response
, expectedResponse
);
611 it('failure', function () {
612 db
.statement
.refreshCode
.get.throws(expectedException
);
613 assert
.throws(() => db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
), expectedException
);
615 it('scope removal failure', function () {
616 removeResponse
.changes
= 0;
617 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
618 db
.statement
.refreshCode
.get.returns(refreshResponse
);
619 assert
.throws(() => db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
), DBErrors
.UnexpectedResult
);
622 describe('_refreshCodeResponseToNative', function () {
623 it('coverage', function () {
624 const expected
= { foo: 'bar' };
625 const result
= DB
._refreshCodeResponseToNative(expected
);
626 assert
.deepStrictEqual(result
, expected
);
628 it('coverage', function () {
629 const result
= DB
._refreshCodeResponseToNative();
630 assert
.strictEqual(result
, undefined);
635 describe('resourceGet', function () {
637 beforeEach(function () {
638 sinon
.stub(db
.statement
.resourceGet
, 'get');
639 identifier
= '05b81112-b224-11ec-a9c6-0025905f714a';
641 it('success', function () {
646 db
.statement
.resourceGet
.get.returns(dbResult
);
647 const result
= db
.resourceGet(dbCtx
, identifier
);
648 assert
.deepStrictEqual(result
, dbResult
);
650 it('failure', function() {
651 db
.statement
.resourceGet
.get.throws(expectedException
);
652 assert
.throws(() => db
.resourceGet(dbCtx
, identifier
), expectedException
);
656 describe('resourceUpsert', function () {
657 let resourceId
, secret
, description
;
658 beforeEach(function () {
659 resourceId
= '4086661a-f980-11ec-ba19-0025905f714a';
661 description
= 'some application';
663 it('success', function() {
666 lastInsertRowid: undefined,
668 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
669 db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
671 it('creates id if not provided', function () {
672 resourceId
= undefined;
675 lastInsertRowid: undefined,
677 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
678 db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
680 it('failure', function () {
683 lastInsertRowid: undefined,
685 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
686 assert
.throws(() => db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
), DBErrors
.UnexpectedResult
);
688 }); // resourceUpsert
690 describe('scopeCleanup', function () {
691 let atLeastMsSinceLast
;
692 beforeEach(function () {
693 atLeastMsSinceLast
= 86400000;
694 sinon
.stub(db
.statement
.scopeCleanup
, 'run');
695 sinon
.stub(db
.statement
.almanacGet
, 'get');
696 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
698 it('success, empty almanac', function () {
700 db
.statement
.almanacGet
.get.returns();
701 db
.statement
.scopeCleanup
.run
.returns({ changes: cleaned
});
702 db
.statement
.almanacUpsert
.run
.returns({ changes: 1 });
703 const result
= db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
704 assert
.strictEqual(result
, cleaned
);
706 it('success, too soon', function () {
707 db
.statement
.almanacGet
.get.returns({ epoch: BigInt(Math
.ceil(Date
.now() / 1000) - 4) });
708 const result
= db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
709 assert
.strictEqual(result
, undefined);
710 assert(db
.statement
.scopeCleanup
.run
.notCalled
);
712 it('failure', function () {
713 db
.statement
.almanacGet
.get.returns({ epoch: 0n
});
714 db
.statement
.scopeCleanup
.run
.returns({ changes: 1 });
715 db
.statement
.almanacUpsert
.run
.returns({ changes: 0 });
716 assert
.throws(() => db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
720 describe('scopeDelete', function () {
721 let dbGetResult
, dbRunResult
, scope
;
722 beforeEach(function () {
723 sinon
.stub(db
.statement
.scopeInUse
, 'get');
727 sinon
.stub(db
.statement
.scopeDelete
, 'run');
731 scope
= 'some_scope';
733 it('success', function () {
734 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
735 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
736 const result
= db
.scopeDelete(dbCtx
, scope
);
737 assert
.strictEqual(result
, true);
739 it('in use', function () {
740 dbGetResult
.inUse
= true;
741 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
742 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
743 const result
= db
.scopeDelete(dbCtx
, scope
);
744 assert
.strictEqual(result
, false);
746 it('no scope', function () {
747 dbRunResult
.changes
= 0;
748 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
749 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
750 const result
= db
.scopeDelete(dbCtx
, scope
);
751 assert
.strictEqual(result
, true);
753 it('failure', function () {
754 db
.statement
.scopeInUse
.get.throws(expectedException
);
755 assert
.throws(() => db
.scopeDelete(dbCtx
, scope
), expectedException
);
759 describe('scopeUpsert', function () {
760 let dbResult
, scope
, application
, description
;
761 beforeEach(function () {
763 application
= undefined;
764 description
= 'description';
765 sinon
.stub(db
.statement
.scopeUpsert
, 'run');
768 lastInsertRowid: undefined,
771 it('success', function() {
772 db
.statement
.scopeUpsert
.run
.returns(dbResult
);
773 db
.scopeUpsert(dbCtx
, scope
, application
, description
);
775 it('failure', function () {
776 dbResult
.changes
= 0;
777 db
.statement
.scopeUpsert
.run
.returns(dbResult
);
778 assert
.throws(() => db
.scopeUpsert(dbCtx
, scope
, application
, description
), DBErrors
.UnexpectedResult
);
780 it('failure, error', function () {
781 db
.statement
.scopeUpsert
.run
.throws(expectedException
);
782 assert
.throws(() => db
.scopeUpsert(dbCtx
, scope
, application
, description
), expectedException
);
786 describe('tokenCleanup', function () {
787 let codeLifespanSeconds
, atLeastMsSinceLast
;
788 beforeEach(function () {
789 codeLifespanSeconds
= 600;
790 atLeastMsSinceLast
= 86400000;
791 sinon
.stub(db
.statement
.tokenCleanup
, 'run');
792 sinon
.stub(db
.statement
.almanacGet
, 'get');
793 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
795 it('success, empty almanac', function() {
797 db
.statement
.almanacGet
.get.returns();
798 db
.statement
.tokenCleanup
.run
.returns({ changes: cleaned
});
799 db
.statement
.almanacUpsert
.run
.returns({ changes: 1 });
800 const result
= db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
801 assert
.strictEqual(result
, cleaned
);
803 it('success, too soon', function () {
804 db
.statement
.almanacGet
.get.returns({ epoch: BigInt(Math
.ceil(Date
.now() / 1000) - 4) });
805 const result
= db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
806 assert
.strictEqual(result
, undefined);
807 assert(db
.statement
.tokenCleanup
.run
.notCalled
);
809 it('failure', function () {
810 db
.statement
.almanacGet
.get.returns({ epoch: 0n
});
811 db
.statement
.tokenCleanup
.run
.returns({ changes: 10 });
812 db
.statement
.almanacUpsert
.run
.returns({ changes: 0 });
813 assert
.throws(() => db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
817 describe('tokenGetByCodeId', function () {
819 beforeEach(function () {
820 codeId
= '184a26f6-2612-11ec-9e88-0025905f714a';
821 token
= 'TokenTokenTokenToken';
822 sinon
.stub(db
.statement
.tokenGetByCodeId
, 'get');
823 sinon
.stub(db
.statement
.tokenScopesGetByCodeId
, 'all');
825 it('success', function() {
826 const now
= new Date();
827 const nowEpoch
= Math
.ceil(now
/ 1000);
829 created: new Date(nowEpoch
* 1000),
831 refreshExpires: null,
843 created: Math
.ceil(nowEpoch
),
845 refreshExpires: null,
850 profileData: '{"name":"Some Name"}',
852 db
.statement
.tokenGetByCodeId
.get.returns(dbResult
);
853 const result
= db
.tokenGetByCodeId(dbCtx
, codeId
);
854 assert
.deepStrictEqual(result
, expected
);
856 it('success without profile data', function () {
857 const now
= new Date();
858 const nowEpoch
= Math
.ceil(now
/ 1000);
860 created: new Date(nowEpoch
* 1000),
862 refreshExpires: null,
868 scopes: ['foop', 'baa'],
871 created: Math
.ceil(nowEpoch
),
873 refreshExpires: null,
879 db
.statement
.tokenGetByCodeId
.get.returns(dbResult
);
880 db
.statement
.tokenScopesGetByCodeId
.all
.returns([{ scope: 'foop' }, { scope: 'baa' }]);
881 const result
= db
.tokenGetByCodeId(dbCtx
, codeId
);
882 assert
.deepStrictEqual(result
, expected
);
884 it('failure', function () {
885 db
.statement
.tokenGetByCodeId
.get.throws(expectedException
);
886 assert
.throws(() => db
.tokenGetByCodeId(dbCtx
, codeId
), expectedException
);
889 describe('_tokenToNative', function () {
890 it('covers', function () {
891 const result
= DB
._tokenToNative();
892 assert
.strictEqual(result
, undefined);
894 }); // _tokenToNative
895 }); // tokenGetByCodeId
897 describe('tokenRevokeByCodeId', function () {
898 let dbResult
, codeId
;
899 beforeEach(function () {
900 codeId
= '2f226616-3e79-11ec-ad0f-0025905f714a';
901 sinon
.stub(db
.statement
.tokenRevokeByCodeId
, 'run')
904 lastInsertRowid: undefined,
907 it('success', function() {
908 db
.statement
.tokenRevokeByCodeId
.run
.returns(dbResult
);
909 db
.tokenRevokeByCodeId(dbCtx
, codeId
);
911 it('failure', function () {
912 dbResult
.changes
= 0;
913 db
.statement
.tokenRevokeByCodeId
.run
.returns(dbResult
);
914 assert
.throws(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
916 it('failure, error', function () {
917 db
.statement
.tokenRevokeByCodeId
.run
.throws(expectedException
);
918 assert
.throws(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), expectedException
);
920 }); // tokenRevokeByCodeId
922 describe('tokenRefreshRevokeByCodeId', function () {
923 let dbResult
, codeId
;
924 beforeEach(function () {
927 lastInsertRowid: undefined,
929 codeId
= 'eabba58e-2633-11ed-bbad-0025905f714a';
930 sinon
.stub(db
.statement
.tokenRefreshRevokeByCodeId
, 'run');
932 it('success', function () {
933 db
.statement
.tokenRefreshRevokeByCodeId
.run
.returns(dbResult
);
934 db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
);
936 it('failure', function () {
937 dbResult
.changes
= 0;
938 db
.statement
.tokenRefreshRevokeByCodeId
.run
.returns(dbResult
);
939 assert
.throws(() => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
941 it('failure, error', function () {
942 const expected
= new Error('oh no');
943 db
.statement
.tokenRefreshRevokeByCodeId
.run
.throws(expected
);
944 assert
.throws(() => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), expected
);
946 }); // tokenRefreshRevokeByCodeId
948 describe('tokensGetByIdentifier', function () {
950 beforeEach(function () {
951 identifier
= 'identifier';
952 sinon
.stub(db
.statement
.tokensGetByIdentifier
, 'all');
954 it('success', function () {
955 const nowEpoch
= Math
.ceil(Date
.now() / 1000);
959 expires: nowEpoch
+ 86400,
961 refreshed: nowEpoch
+ 600,
962 refreshExpires: nowEpoch
+ 172800,
965 codeId: 'c0a7cef4-2637-11ed-a830-0025905f714a',
966 profile: 'https://profile.example.com/',
967 profileData: '{"name":"Some Name"}',
968 identifier: 'username',
972 Object
.assign({}, dbResult
[0], {
973 created: new Date(dbResult
[0].created
* 1000),
974 expires: new Date(dbResult
[0].expires
* 1000),
975 refreshed: new Date(dbResult
[0].refreshed
* 1000),
976 refreshExpires: new Date(dbResult
[0].refreshExpires
* 1000),
982 db
.statement
.tokensGetByIdentifier
.all
.returns(dbResult
);
983 const result
= db
.tokensGetByIdentifier(dbCtx
, identifier
);
984 assert
.deepStrictEqual(result
, expected
);
986 it('failure', function() {
987 db
.statement
.tokensGetByIdentifier
.all
.throws(expectedException
);
988 assert
.throws(() => db
.tokensGetByIdentifier(dbCtx
, identifier
), expectedException
);
990 }); // tokensGetByIdentifier
992 describe('ticketRedeemed', function () {
993 let redeemedData
, dbResult
;
994 beforeEach(function () {
996 resource: 'https://resource.example.com/',
997 subject: 'https://subject.example.com/',
998 iss: 'https://idp.example.com/',
999 ticket: 'xxxTICKETxxx',
1000 token: 'xxxTOKENxxx',
1002 sinon
.stub(db
.statement
.ticketRedeemed
, 'run');
1005 lastInsertRowid: undefined,
1008 it('success', function () {
1009 db
.statement
.ticketRedeemed
.run
.returns(dbResult
);
1010 db
.ticketRedeemed(dbCtx
, redeemedData
);
1012 it('failure', function () {
1013 dbResult
.changes
= 0;
1014 db
.statement
.ticketRedeemed
.run
.returns(dbResult
);
1015 assert
.throws(() => db
.ticketRedeemed(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1017 }); // ticketRedeemed
1019 describe('ticketTokenPublished', function () {
1020 let redeemedData
, dbResult
;
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
.statement
.ticketTokenPublished
, 'run');
1030 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
1033 lastInsertRowid: undefined,
1036 it('success', function () {
1037 db
.statement
.ticketTokenPublished
.run
.returns(dbResult
);
1038 db
.statement
.almanacUpsert
.run
.returns(dbResult
);
1039 db
.ticketTokenPublished(dbCtx
, redeemedData
);
1040 assert(db
.statement
.ticketTokenPublished
.run
.called
);
1041 assert(db
.statement
.almanacUpsert
.run
.called
);
1043 it('failure', function () {
1044 dbResult
.changes
= 0;
1045 db
.statement
.ticketTokenPublished
.run
.returns(dbResult
);
1046 assert
.throws(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1048 it('failure of almanac', function () {
1049 const dbResultAlmanac
= {
1053 db
.statement
.ticketTokenPublished
.run
.returns(dbResult
);
1054 db
.statement
.almanacUpsert
.run
.returns(dbResultAlmanac
);
1055 assert
.throws(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1057 }); // ticketTokenPublished
1059 describe('ticketTokenGetUnpublished', function () {
1060 beforeEach(function () {
1061 sinon
.stub(db
.statement
.ticketTokenGetUnpublished
, 'all');
1063 it('success', function () {
1064 db
.statement
.ticketTokenGetUnpublished
.all
.returns([]);
1065 const result
= db
.ticketTokenGetUnpublished();
1066 assert
.deepStrictEqual(result
, []);
1068 it('failure', function () {
1069 db
.statement
.ticketTokenGetUnpublished
.all
.throws(expectedException
);
1070 assert
.throws(() => db
.ticketTokenGetUnpublished(), expectedException
);
1072 }); // ticketTokenGetUnpublished
1074 describe('_redeemedTicketToNative', function () {
1076 beforeEach(function () {
1078 resource: 'https://resource.example.com/',
1079 subject: 'https://subject.example.com/',
1080 iss: 'https://idp.example.com/',
1081 ticket: 'xxxTICKETxxx',
1082 token: 'xxxTOKENxxx',
1083 created: 1701970607n
,
1084 published: 1701970670n
,
1087 it('covers', function () {
1090 created: new Date('2023-12-07T17:36:47.000Z'),
1091 published: new Date('2023-12-07T17:37:50.000Z'),
1093 const result
= DB
._redeemedTicketToNative(redeemedData
);
1094 assert
.deepStrictEqual(result
, expected
);
1096 it('covers no published', function () {
1097 redeemedData
.published
= null;
1100 created: new Date('2023-12-07T17:36:47.000Z'),
1103 const result
= DB
._redeemedTicketToNative(redeemedData
);
1104 assert
.deepStrictEqual(result
, expected
);
1106 }); // _redeemedTicketToNative
1108 }); // DatabaseSQLite