3 /* This provides implementation coverage, stubbing parts of better-sqlite3. */
5 const assert
= require('assert');
6 const sinon
= require('sinon');
7 const StubDatabase
= require('../../stub-db');
8 const StubLogger
= require('../../stub-logger');
9 const DB
= require('../../../src/db/sqlite');
10 const DBErrors
= require('../../../src/db/errors');
11 const common
= require('../../../src/common');
12 const Config
= require('../../../config');
14 const expectedException
= new Error('oh no');
16 describe('DatabaseSQLite', function () {
17 let db
, options
, logger
, stubDb
;
20 logger
= new StubLogger();
22 stubDb
= new StubDatabase();
24 beforeEach(function () {
25 options
= new Config('test');
26 options
.db
.connectionString
= 'sqlite://:memory:';
27 db
= new DB(logger
, options
);
30 afterEach(function () {
34 it('covers constructor options', function () {
35 delete options
.db
.connectionString
;
36 db
= new DB(logger
, options
);
39 // Ensure all interface methods are implemented
40 describe('Implementation', function () {
41 it('implements interface', async
function () {
42 const results
= await Promise
.allSettled(stubDb
._implementation
.map((fn
) => {
44 // eslint-disable-next-line security/detect-object-injection
47 assert(!(e
instanceof DBErrors
.NotImplemented
), `${fn} not implemented`);
50 const failures
= results
.filter((x
) => x
.status
=== 'rejected');
51 assert(!failures
.length
, failures
.map((x
) => {
52 x
= x
.reason
.toString();
53 return x
.slice(x
.indexOf(': '));
58 describe('_currentSchema', function () {
59 it('covers', function () {
60 const version
= { major: 1, minor: 0, patch: 0 };
61 sinon
.stub(db
.db
, 'prepare').returns({
64 const result
= db
._currentSchema();
65 assert
.deepStrictEqual(result
, version
);
69 describe('_closeConnection', function () {
70 it('success', function () {
71 sinon
.stub(db
.db
, 'close');
72 db
._closeConnection();
73 assert(db
.db
.close
.called
);
75 it('failure', function () {
76 sinon
.stub(db
.db
, 'close').throws(expectedException
);
77 assert
.throws(() => db
._closeConnection(), expectedException
);
79 }); // _closeConnection
81 describe('_purgeTables', function () {
82 beforeEach(function () {
83 sinon
.stub(db
.db
, 'prepare').returns({
87 it('covers not really', function () {
88 db
._purgeTables(false);
89 assert(!db
.db
.prepare
.called
);
91 it('success', function () {
92 db
._purgeTables(true);
93 assert(db
.db
.prepare
.called
);
95 it('failure', function () {
96 db
.db
.prepare
.restore();
97 sinon
.stub(db
.db
, 'prepare').throws(expectedException
);
98 assert
.throws(() => db
._purgeTables(true), expectedException
);
102 describe('_optimize', function () {
103 beforeEach(function () {
104 sinon
.stub(db
.statement
._optimize
, 'all');
105 sinon
.stub(db
.db
, 'pragma');
107 it('covers', function () {
108 db
.changesSinceLastOptimize
= BigInt(20);
110 assert(db
.db
.pragma
.called
);
111 assert(db
.statement
._optimize
.all
.called
);
112 assert
.strictEqual(db
.changesSinceLastOptimize
, 0n
);
116 describe('_updateChanges', function () {
118 beforeEach(function () {
122 sinon
.stub(db
, '_optimize');
124 it('does not optimize if not wanted', function () {
125 db
.optimizeAfterChanges
= 0n
;
126 db
._updateChanges(dbResult
);
127 assert(db
._optimize
.notCalled
);
129 it('does not optimize if under threshold', function () {
130 db
.optimizeAfterChanges
= 100n
;
131 db
._updateChanges(dbResult
);
132 assert(db
._optimize
.notCalled
);
134 it('optimizes over threshold', function () {
135 db
.optimizeAfterChanges
= 1n
;
136 db
._updateChanges(dbResult
);
137 assert(db
._optimize
.called
);
139 }); // _updateChanges
141 describe('_deOphidiate', function () {
142 it('covers non-array', function () {
149 const result
= DB
._deOphidiate(obj
);
150 assert
.deepStrictEqual(result
, expected
);
152 it('covers array', function () {
169 const result
= DB
._deOphidiate(rows
);
170 assert
.deepStrictEqual(result
, expected
);
174 describe('healthCheck', function () {
175 it('covers', function () {
178 it('covers failure', function () {
179 db
.db
= { open: false };
180 assert
.throws(() => db
.healthCheck(), DBErrors
.UnexpectedResult
);
184 describe('context', function () {
185 it('covers', function () {
186 db
.context(common
.nop
);
190 describe('transaction', function () {
191 it('covers', function () {
192 db
.transaction(db
.db
, common
.nop
);
194 it('covers no context', function () {
195 db
.transaction(undefined, common
.nop
);
199 describe('almanacGetAll', function () {
200 beforeEach(function () {
201 sinon
.stub(db
.statement
.almanacGetAll
, 'all');
203 it('success', function () {
204 const dbResult
= [{ event: 'someEvent', epoch: '1668887796' } ];
205 const expected
= [{ event: 'someEvent', date: new Date('Sat Nov 19 11:56:36 AM PST 2022') }];
206 db
.statement
.almanacGetAll
.all
.returns(dbResult
);
207 const result
= db
.almanacGetAll(dbCtx
);
208 assert
.deepStrictEqual(result
, expected
);
210 it('failure', function () {
211 db
.statement
.almanacGetAll
.all
.throws(expectedException
);
212 assert
.throws(() => db
.almanacGetAll(dbCtx
), expectedException
);
216 describe('almanacUpsert', function () {
217 let event
, date
, dbResult
;
218 beforeEach(function () {
219 event
= 'test_event';
220 date
= new Date('Fri Dec 22 03:27 UTC 2023');
221 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
224 lastInsertRowid: undefined,
227 it('success', function () {
228 db
.statement
.almanacUpsert
.run
.returns(dbResult
);
229 db
.almanacUpsert(dbCtx
, event
, date
);
231 it('success with default date', function () {
232 db
.statement
.almanacUpsert
.run
.returns(dbResult
);
233 db
.almanacUpsert(dbCtx
, event
);
235 it('failure', function () {
236 dbResult
.changes
= 0;
237 db
.statement
.almanacUpsert
.run
.returns(dbResult
);
238 assert
.throws(() => db
.almanacUpsert(dbCtx
, { event
, date
}), DBErrors
.UnexpectedResult
);
242 describe('authenticationGet', function () {
243 let identifier
, credential
;
244 beforeEach(function () {
245 identifier
= 'username';
246 credential
= '$z$foo';
247 sinon
.stub(db
.statement
.authenticationGet
, 'get');
249 it('success', function() {
254 db
.statement
.authenticationGet
.get.returns(expected
);
255 const result
= db
.authenticationGet(dbCtx
, identifier
);
256 assert
.deepStrictEqual(result
, expected
);
258 it('failure', function () {
259 db
.statement
.authenticationGet
.get.throws(expectedException
);
260 assert
.throws(() => db
.authenticationGet(dbCtx
, identifier
), expectedException
);
262 }); // authenticationGet
264 describe('authenticationSuccess', function () {
265 let dbResult
, identifier
;
266 beforeEach(function () {
267 identifier
= 'username';
268 sinon
.stub(db
.statement
.authenticationSuccess
, 'run');
271 lastInsertRowid: undefined,
274 it('success', function() {
275 db
.statement
.authenticationSuccess
.run
.returns(dbResult
);
276 db
.authenticationSuccess(dbCtx
, identifier
);
278 it('failure', function () {
279 dbResult
.changes
= 0;
280 db
.statement
.authenticationSuccess
.run
.returns(dbResult
);
281 assert
.throws(() => db
.authenticationSuccess(dbCtx
, identifier
), DBErrors
.UnexpectedResult
);
283 }); // authenticationSuccess
285 describe('authenticationUpsert', function () {
286 let identifier
, credential
;
287 beforeEach(function () {
288 identifier
= 'username';
289 credential
= '$z$foo';
291 it('success', function() {
294 lastInsertRowid: undefined,
296 sinon
.stub(db
.statement
.authenticationUpsert
, 'run').returns(dbResult
);
297 db
.authenticationUpsert(dbCtx
, identifier
, credential
);
299 it('failure', function () {
302 lastInsertRowid: undefined,
304 sinon
.stub(db
.statement
.authenticationUpsert
, 'run').returns(dbResult
);
305 assert
.throws(() => db
.authenticationUpsert(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
307 }); // authenticationUpsert
309 describe('authenticationUpdateCredential', function () {
310 let identifier
, credential
;
311 beforeEach(function () {
312 identifier
= 'username';
313 credential
= '$z$foo';
315 it('success', function() {
318 lastInsertRowid: undefined,
320 sinon
.stub(db
.statement
.authenticationUpdateCredential
, 'run').returns(dbResult
);
321 db
.authenticationUpdateCredential(dbCtx
, identifier
, credential
);
323 it('failure', function () {
326 lastInsertRowid: undefined,
328 sinon
.stub(db
.statement
.authenticationUpdateCredential
, 'run').returns(dbResult
);
329 assert
.throws(() => db
.authenticationUpdateCredential(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
331 }); // authenticationUpdateCredential
333 describe('authenticationUpdateOTPKey', function () {
334 let identifier
, otpKey
;
335 beforeEach(function () {
336 identifier
= 'username';
337 otpKey
= '1234567890123456789012';
339 it('success', function() {
342 lastInsertRowid: undefined,
344 sinon
.stub(db
.statement
.authenticationUpdateOtpKey
, 'run').returns(dbResult
);
345 db
.authenticationUpdateOTPKey(dbCtx
, identifier
, otpKey
);
347 it('failure', function () {
350 lastInsertRowid: undefined,
352 sinon
.stub(db
.statement
.authenticationUpdateOtpKey
, 'run').returns(dbResult
);
353 assert
.throws(() => db
.authenticationUpdateOTPKey(dbCtx
, identifier
, otpKey
), DBErrors
.UnexpectedResult
);
355 }); // authenticationUpdateOTPKey
357 describe('profileIdentifierInsert', function () {
358 let profile
, identifier
;
359 beforeEach(function () {
360 profile
= 'https://profile.example.com/';
361 identifier
= 'identifier';
362 sinon
.stub(db
.statement
.profileIdentifierInsert
, 'run');
364 it('success', function () {
365 db
.statement
.profileIdentifierInsert
.run
.returns({ changes: 1 });
366 db
.profileIdentifierInsert(dbCtx
, profile
, identifier
);
368 it('failure', function () {
369 db
.statement
.profileIdentifierInsert
.run
.returns({ changes: 0 });
370 assert
.throws(() => db
.profileIdentifierInsert(dbCtx
, profile
, identifier
), DBErrors
.UnexpectedResult
);
372 }); // profileIdentifierInsert
374 describe('profileScopeInsert', function () {
376 beforeEach(function () {
377 profile
= 'https://profile.example.com/';
379 sinon
.stub(db
.statement
.profileScopeInsert
, 'run');
381 it('success', function () {
382 db
.statement
.profileScopeInsert
.run
.returns({ changes: 1 });
383 db
.profileScopeInsert(dbCtx
, profile
, scope
);
385 it('failure', function () {
386 db
.statement
.profileScopeInsert
.run
.returns({ changes: 2 });
387 assert
.throws(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), DBErrors
.UnexpectedResult
);
389 }); // profileScopeInsert
391 describe('profileIsValid', function () {
393 beforeEach(function () {
394 profile
= 'https://profile.exmaple.com';
396 it('valid profile', function () {
397 sinon
.stub(db
.statement
.profileGet
, 'get').returns({ profile
});
398 const result
= db
.profileIsValid(dbCtx
, profile
);
399 assert
.deepStrictEqual(result
, true);
401 it('invalid profile', function () {
402 sinon
.stub(db
.statement
.profileGet
, 'get').returns();
403 const result
= db
.profileIsValid(dbCtx
, profile
);
404 assert
.deepStrictEqual(result
, false);
406 it('failure', function() {
407 sinon
.stub(db
.statement
.profileGet
, 'get').throws(expectedException
);
408 assert
.throws(() => db
.profileIsValid(dbCtx
, profile
), expectedException
);
410 }); // profileIsValid
412 describe('profilesScopesByIdentifier', function () {
413 let identifier
, scopeIndex
, profileScopes
, profiles
;
414 beforeEach(function () {
415 identifier
= 'identifier';
418 description: 'A scope.',
421 isManuallyAdded: false,
422 profiles: ['https://first.example.com/', 'https://second.example.com/'],
425 description: 'Another scope.',
426 application: 'another test',
428 isManuallyAdded: false,
429 profiles: ['https://first.example.com/'],
433 'https://first.example.com/': {
434 'scope': scopeIndex
['scope'],
435 'another_scope': scopeIndex
['another_scope'],
437 'https://second.example.com/': {
438 'scope': scopeIndex
['scope'],
441 profiles
= ['https://first.example.com/', 'https://second.example.com/'];
443 it('success', function () {
445 { profile: 'https://first.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: false },
446 { profile: 'https://first.example.com/', scope: 'another_scope', application: 'another test', description: 'Another scope.', isPermanent: false, isManuallyAdded: false },
447 { profile: 'https://second.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: false },
454 sinon
.stub(db
.statement
.profilesScopesByIdentifier
, 'all').returns(dbResult
);
455 const result
= db
.profilesScopesByIdentifier(dbCtx
, identifier
);
456 assert
.deepStrictEqual(result
, expected
);
458 it('failure', function() {
459 sinon
.stub(db
.statement
.profilesScopesByIdentifier
, 'all').throws(expectedException
);
460 assert
.throws(() => db
.profilesScopesByIdentifier(dbCtx
, identifier
), expectedException
);
462 }); // profilesScopesByIdentifier
464 describe('profileScopesSetAll', function () {
466 beforeEach(function () {
467 profile
= 'https://example.com/';
468 scopes
= ['scope1', 'scope2'];
469 sinon
.stub(db
.statement
.profileScopesClear
, 'run').returns();
470 sinon
.stub(db
.statement
.profileScopeInsert
, 'run');
472 it('success, no scopes', function () {
473 db
.statement
.profileScopeInsert
.run
.returns();
475 db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
477 it('success, scopes', function () {
478 db
.statement
.profileScopeInsert
.run
.returns();
479 scopes
.push('profile', 'email', 'create');
480 db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
482 it('failure', function () {
483 db
.statement
.profileScopeInsert
.run
.throws(expectedException
);
484 assert
.throws(() => db
.profileScopesSetAll(dbCtx
, profile
, scopes
), expectedException
);
487 }); // profileScopesSetAll
489 describe('redeemCode', function () {
490 let codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
;
491 beforeEach(function () {
492 codeId
= '2f226616-3e79-11ec-ad0f-0025905f714a';
494 clientId
= 'https://app.exmaple.com/';
495 profile
= 'https://profile.example.com/';
496 identifier
= 'username';
497 scopes
= ['scope1', 'scope2'];
498 lifespanSeconds
= 600;
499 profileData
= undefined;
500 created
= new Date();
502 sinon
.stub(db
.statement
.scopeInsert
, 'run');
503 sinon
.stub(db
.statement
.tokenScopeSet
, 'run');
504 sinon
.stub(db
.statement
.redeemCode
, 'get');
506 it('success', function() {
509 lastInsertRowid: undefined,
514 db
.statement
.scopeInsert
.run
.returns(dbResult
);
515 db
.statement
.tokenScopeSet
.run
.returns(dbResult
);
516 db
.statement
.redeemCode
.get.returns(dbGet
);
520 const result
= db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
});
521 assert
.strictEqual(result
, true);
523 it('success (revoked)', function() {
526 lastInsertRowid: undefined,
531 db
.statement
.scopeInsert
.run
.returns(dbResult
);
532 db
.statement
.tokenScopeSet
.run
.returns(dbResult
);
533 db
.statement
.redeemCode
.get.returns(dbGet
);
534 const result
= db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
});
535 assert
.strictEqual(result
, false);
537 it('failure', function () {
538 db
.statement
.scopeInsert
.run
.throws();
539 db
.statement
.tokenScopeSet
.run
.throws();
540 db
.statement
.redeemCode
.get.returns();
541 assert
.throws(() => db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
}), DBErrors
.UnexpectedResult
);
545 describe('refreshCode', function () {
546 let refreshResponse
, removeResponse
, scopesResponse
, codeId
, refreshed
, removeScopes
;
547 beforeEach(function () {
548 sinon
.stub(db
.statement
.refreshCode
, 'get');
549 sinon
.stub(db
.statement
.tokenScopeRemove
, 'run');
550 sinon
.stub(db
.statement
.tokenScopesGetByCodeId
, 'all');
551 codeId
= '73db7b18-27bb-11ed-8edd-0025905f714a';
552 refreshed
= new Date();
553 removeScopes
= ['foop'];
554 const refreshedEpoch
= Math
.ceil(refreshed
.getTime() / 1000);
556 expires: refreshedEpoch
+ 86400,
557 refreshExpires: refreshedEpoch
+ 172800,
560 changes: removeScopes
.length
,
566 it('success', function () {
567 db
.statement
.refreshCode
.get.returns(refreshResponse
);
568 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
569 db
.statement
.tokenScopesGetByCodeId
.all
.returns(scopesResponse
);
570 const expectedResponse
= {
571 expires: new Date(refreshResponse
.expires
* 1000),
572 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
575 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
576 assert
.deepStrictEqual(response
, expectedResponse
);
578 it('success without scope removal', function () {
579 db
.statement
.refreshCode
.get.returns(refreshResponse
);
580 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
581 const expectedResponse
= {
582 expires: new Date(refreshResponse
.expires
* 1000),
583 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
586 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
587 assert
.deepStrictEqual(response
, expectedResponse
);
589 it('success with no scopes left', function () {
590 db
.statement
.refreshCode
.get.returns(refreshResponse
);
591 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
592 const expectedResponse
= {
593 expires: new Date(refreshResponse
.expires
* 1000),
594 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
597 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
598 assert
.deepStrictEqual(response
, expectedResponse
);
600 it('no code', function () {
601 db
.statement
.refreshCode
.get.returns();
602 removeResponse
.changes
= 0;
603 db
.statement
.tokenScopeRemove
.run
.returns();
604 const expectedResponse
= undefined;
605 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
606 assert
.deepStrictEqual(response
, expectedResponse
);
608 it('failure', function () {
609 db
.statement
.refreshCode
.get.throws(expectedException
);
610 assert
.throws(() => db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
), expectedException
);
612 it('scope removal failure', function () {
613 removeResponse
.changes
= 0;
614 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
615 db
.statement
.refreshCode
.get.returns(refreshResponse
);
616 assert
.throws(() => db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
), DBErrors
.UnexpectedResult
);
619 describe('_refreshCodeResponseToNative', function () {
620 it('coverage', function () {
621 const expected
= { foo: 'bar' };
622 const result
= DB
._refreshCodeResponseToNative(expected
);
623 assert
.deepStrictEqual(result
, expected
);
625 it('coverage', function () {
626 const result
= DB
._refreshCodeResponseToNative();
627 assert
.strictEqual(result
, undefined);
632 describe('resourceGet', function () {
634 beforeEach(function () {
635 sinon
.stub(db
.statement
.resourceGet
, 'get');
636 identifier
= '05b81112-b224-11ec-a9c6-0025905f714a';
638 it('success', function () {
643 db
.statement
.resourceGet
.get.returns(dbResult
);
644 const result
= db
.resourceGet(dbCtx
, identifier
);
645 assert
.deepStrictEqual(result
, dbResult
);
647 it('failure', function() {
648 db
.statement
.resourceGet
.get.throws(expectedException
);
649 assert
.throws(() => db
.resourceGet(dbCtx
, identifier
), expectedException
);
653 describe('resourceUpsert', function () {
654 let resourceId
, secret
, description
;
655 beforeEach(function () {
656 resourceId
= '4086661a-f980-11ec-ba19-0025905f714a';
658 description
= 'some application';
660 it('success', function() {
663 lastInsertRowid: undefined,
665 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
666 db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
668 it('creates id if not provided', function () {
669 resourceId
= undefined;
672 lastInsertRowid: undefined,
674 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
675 db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
677 it('failure', function () {
680 lastInsertRowid: undefined,
682 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
683 assert
.throws(() => db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
), DBErrors
.UnexpectedResult
);
685 }); // resourceUpsert
687 describe('scopeCleanup', function () {
688 let atLeastMsSinceLast
;
689 beforeEach(function () {
690 atLeastMsSinceLast
= 86400000;
691 sinon
.stub(db
.statement
.scopeCleanup
, 'run');
692 sinon
.stub(db
.statement
.almanacGet
, 'get');
693 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
695 it('success, empty almanac', function () {
697 db
.statement
.almanacGet
.get.returns();
698 db
.statement
.scopeCleanup
.run
.returns({ changes: cleaned
});
699 db
.statement
.almanacUpsert
.run
.returns({ changes: 1 });
700 const result
= db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
701 assert
.strictEqual(result
, cleaned
);
703 it('success, too soon', function () {
704 db
.statement
.almanacGet
.get.returns({ epoch: BigInt(Math
.ceil(Date
.now() / 1000) - 4) });
705 const result
= db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
706 assert
.strictEqual(result
, undefined);
707 assert(db
.statement
.scopeCleanup
.run
.notCalled
);
709 it('failure', function () {
710 db
.statement
.almanacGet
.get.returns({ epoch: 0n
});
711 db
.statement
.scopeCleanup
.run
.returns({ changes: 1 });
712 db
.statement
.almanacUpsert
.run
.returns({ changes: 0 });
713 assert
.throws(() => db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
717 describe('scopeDelete', function () {
718 let dbGetResult
, dbRunResult
, scope
;
719 beforeEach(function () {
720 sinon
.stub(db
.statement
.scopeInUse
, 'get');
724 sinon
.stub(db
.statement
.scopeDelete
, 'run');
728 scope
= 'some_scope';
730 it('success', function () {
731 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
732 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
733 const result
= db
.scopeDelete(dbCtx
, scope
);
734 assert
.strictEqual(result
, true);
736 it('in use', function () {
737 dbGetResult
.inUse
= true;
738 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
739 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
740 const result
= db
.scopeDelete(dbCtx
, scope
);
741 assert
.strictEqual(result
, false);
743 it('no scope', function () {
744 dbRunResult
.changes
= 0;
745 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
746 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
747 const result
= db
.scopeDelete(dbCtx
, scope
);
748 assert
.strictEqual(result
, true);
750 it('failure', function () {
751 db
.statement
.scopeInUse
.get.throws(expectedException
);
752 assert
.throws(() => db
.scopeDelete(dbCtx
, scope
), expectedException
);
756 describe('scopeUpsert', function () {
757 let dbResult
, scope
, application
, description
;
758 beforeEach(function () {
760 application
= undefined;
761 description
= 'description';
762 sinon
.stub(db
.statement
.scopeUpsert
, 'run');
765 lastInsertRowid: undefined,
768 it('success', function() {
769 db
.statement
.scopeUpsert
.run
.returns(dbResult
);
770 db
.scopeUpsert(dbCtx
, scope
, application
, description
);
772 it('failure', function () {
773 dbResult
.changes
= 0;
774 db
.statement
.scopeUpsert
.run
.returns(dbResult
);
775 assert
.throws(() => db
.scopeUpsert(dbCtx
, scope
, application
, description
), DBErrors
.UnexpectedResult
);
777 it('failure, error', function () {
778 db
.statement
.scopeUpsert
.run
.throws(expectedException
);
779 assert
.throws(() => db
.scopeUpsert(dbCtx
, scope
, application
, description
), expectedException
);
783 describe('tokenCleanup', function () {
784 let codeLifespanSeconds
, atLeastMsSinceLast
;
785 beforeEach(function () {
786 codeLifespanSeconds
= 600;
787 atLeastMsSinceLast
= 86400000;
788 sinon
.stub(db
.statement
.tokenCleanup
, 'run');
789 sinon
.stub(db
.statement
.almanacGet
, 'get');
790 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
792 it('success, empty almanac', function() {
794 db
.statement
.almanacGet
.get.returns();
795 db
.statement
.tokenCleanup
.run
.returns({ changes: cleaned
});
796 db
.statement
.almanacUpsert
.run
.returns({ changes: 1 });
797 const result
= db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
798 assert
.strictEqual(result
, cleaned
);
800 it('success, too soon', function () {
801 db
.statement
.almanacGet
.get.returns({ epoch: BigInt(Math
.ceil(Date
.now() / 1000) - 4) });
802 const result
= db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
803 assert
.strictEqual(result
, undefined);
804 assert(db
.statement
.tokenCleanup
.run
.notCalled
);
806 it('failure', function () {
807 db
.statement
.almanacGet
.get.returns({ epoch: 0n
});
808 db
.statement
.tokenCleanup
.run
.returns({ changes: 10 });
809 db
.statement
.almanacUpsert
.run
.returns({ changes: 0 });
810 assert
.throws(() => db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
814 describe('tokenGetByCodeId', function () {
816 beforeEach(function () {
817 codeId
= '184a26f6-2612-11ec-9e88-0025905f714a';
818 token
= 'TokenTokenTokenToken';
819 sinon
.stub(db
.statement
.tokenGetByCodeId
, 'get');
820 sinon
.stub(db
.statement
.tokenScopesGetByCodeId
, 'all');
822 it('success', function() {
823 const now
= new Date();
824 const nowEpoch
= Math
.ceil(now
/ 1000);
826 created: new Date(nowEpoch
* 1000),
828 refreshExpires: null,
840 created: Math
.ceil(nowEpoch
),
842 refreshExpires: null,
847 profileData: '{"name":"Some Name"}',
849 db
.statement
.tokenGetByCodeId
.get.returns(dbResult
);
850 const result
= db
.tokenGetByCodeId(dbCtx
, codeId
);
851 assert
.deepStrictEqual(result
, expected
);
853 it('success without profile data', function () {
854 const now
= new Date();
855 const nowEpoch
= Math
.ceil(now
/ 1000);
857 created: new Date(nowEpoch
* 1000),
859 refreshExpires: null,
865 scopes: ['foop', 'baa'],
868 created: Math
.ceil(nowEpoch
),
870 refreshExpires: null,
876 db
.statement
.tokenGetByCodeId
.get.returns(dbResult
);
877 db
.statement
.tokenScopesGetByCodeId
.all
.returns([{ scope: 'foop' }, { scope: 'baa' }]);
878 const result
= db
.tokenGetByCodeId(dbCtx
, codeId
);
879 assert
.deepStrictEqual(result
, expected
);
881 it('failure', function () {
882 db
.statement
.tokenGetByCodeId
.get.throws(expectedException
);
883 assert
.throws(() => db
.tokenGetByCodeId(dbCtx
, codeId
), expectedException
);
886 describe('_tokenToNative', function () {
887 it('covers', function () {
888 const result
= DB
._tokenToNative();
889 assert
.strictEqual(result
, undefined);
891 }); // _tokenToNative
892 }); // tokenGetByCodeId
894 describe('tokenRevokeByCodeId', function () {
895 let dbResult
, codeId
;
896 beforeEach(function () {
897 codeId
= '2f226616-3e79-11ec-ad0f-0025905f714a';
898 sinon
.stub(db
.statement
.tokenRevokeByCodeId
, 'run');
901 lastInsertRowid: undefined,
904 it('success', function() {
905 db
.statement
.tokenRevokeByCodeId
.run
.returns(dbResult
);
906 db
.tokenRevokeByCodeId(dbCtx
, codeId
);
908 it('failure', function () {
909 dbResult
.changes
= 0;
910 db
.statement
.tokenRevokeByCodeId
.run
.returns(dbResult
);
911 assert
.throws(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
913 it('failure, error', function () {
914 db
.statement
.tokenRevokeByCodeId
.run
.throws(expectedException
);
915 assert
.throws(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), expectedException
);
917 }); // tokenRevokeByCodeId
919 describe('tokenRefreshRevokeByCodeId', function () {
920 let dbResult
, codeId
;
921 beforeEach(function () {
924 lastInsertRowid: undefined,
926 codeId
= 'eabba58e-2633-11ed-bbad-0025905f714a';
927 sinon
.stub(db
.statement
.tokenRefreshRevokeByCodeId
, 'run');
929 it('success', function () {
930 db
.statement
.tokenRefreshRevokeByCodeId
.run
.returns(dbResult
);
931 db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
);
933 it('failure', function () {
934 dbResult
.changes
= 0;
935 db
.statement
.tokenRefreshRevokeByCodeId
.run
.returns(dbResult
);
936 assert
.throws(() => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
938 it('failure, error', function () {
939 const expected
= new Error('oh no');
940 db
.statement
.tokenRefreshRevokeByCodeId
.run
.throws(expected
);
941 assert
.throws(() => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), expected
);
943 }); // tokenRefreshRevokeByCodeId
945 describe('tokensGetByIdentifier', function () {
947 beforeEach(function () {
948 identifier
= 'identifier';
949 sinon
.stub(db
.statement
.tokensGetByIdentifier
, 'all');
951 it('success', function () {
952 const nowEpoch
= Math
.ceil(Date
.now() / 1000);
956 expires: nowEpoch
+ 86400,
958 refreshed: nowEpoch
+ 600,
959 refreshExpires: nowEpoch
+ 172800,
962 codeId: 'c0a7cef4-2637-11ed-a830-0025905f714a',
963 profile: 'https://profile.example.com/',
964 profileData: '{"name":"Some Name"}',
965 identifier: 'username',
969 Object
.assign({}, dbResult
[0], {
970 created: new Date(dbResult
[0].created
* 1000),
971 expires: new Date(dbResult
[0].expires
* 1000),
972 refreshed: new Date(dbResult
[0].refreshed
* 1000),
973 refreshExpires: new Date(dbResult
[0].refreshExpires
* 1000),
979 db
.statement
.tokensGetByIdentifier
.all
.returns(dbResult
);
980 const result
= db
.tokensGetByIdentifier(dbCtx
, identifier
);
981 assert
.deepStrictEqual(result
, expected
);
983 it('failure', function() {
984 db
.statement
.tokensGetByIdentifier
.all
.throws(expectedException
);
985 assert
.throws(() => db
.tokensGetByIdentifier(dbCtx
, identifier
), expectedException
);
987 }); // tokensGetByIdentifier
989 describe('ticketRedeemed', function () {
990 let redeemedData
, dbResult
;
991 beforeEach(function () {
993 resource: 'https://resource.example.com/',
994 subject: 'https://subject.example.com/',
995 iss: 'https://idp.example.com/',
996 ticket: 'xxxTICKETxxx',
997 token: 'xxxTOKENxxx',
999 sinon
.stub(db
.statement
.ticketRedeemed
, 'run');
1002 lastInsertRowid: undefined,
1005 it('success', function () {
1006 db
.statement
.ticketRedeemed
.run
.returns(dbResult
);
1007 db
.ticketRedeemed(dbCtx
, redeemedData
);
1009 it('failure', function () {
1010 dbResult
.changes
= 0;
1011 db
.statement
.ticketRedeemed
.run
.returns(dbResult
);
1012 assert
.throws(() => db
.ticketRedeemed(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1014 }); // ticketRedeemed
1016 describe('ticketTokenPublished', function () {
1017 let redeemedData
, dbResult
;
1018 beforeEach(function () {
1020 resource: 'https://resource.example.com/',
1021 subject: 'https://subject.example.com/',
1022 iss: 'https://idp.example.com/',
1023 ticket: 'xxxTICKETxxx',
1024 token: 'xxxTOKENxxx',
1026 sinon
.stub(db
.statement
.ticketTokenPublished
, 'run');
1027 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
1030 lastInsertRowid: undefined,
1033 it('success', function () {
1034 db
.statement
.ticketTokenPublished
.run
.returns(dbResult
);
1035 db
.statement
.almanacUpsert
.run
.returns(dbResult
);
1036 db
.ticketTokenPublished(dbCtx
, redeemedData
);
1037 assert(db
.statement
.ticketTokenPublished
.run
.called
);
1038 assert(db
.statement
.almanacUpsert
.run
.called
);
1040 it('failure', function () {
1041 dbResult
.changes
= 0;
1042 db
.statement
.ticketTokenPublished
.run
.returns(dbResult
);
1043 assert
.throws(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1045 it('failure of almanac', function () {
1046 const dbResultAlmanac
= {
1050 db
.statement
.ticketTokenPublished
.run
.returns(dbResult
);
1051 db
.statement
.almanacUpsert
.run
.returns(dbResultAlmanac
);
1052 assert
.throws(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1054 }); // ticketTokenPublished
1056 describe('ticketTokenGetUnpublished', function () {
1057 beforeEach(function () {
1058 sinon
.stub(db
.statement
.ticketTokenGetUnpublished
, 'all');
1060 it('success', function () {
1061 db
.statement
.ticketTokenGetUnpublished
.all
.returns([]);
1062 const result
= db
.ticketTokenGetUnpublished();
1063 assert
.deepStrictEqual(result
, []);
1065 it('failure', function () {
1066 db
.statement
.ticketTokenGetUnpublished
.all
.throws(expectedException
);
1067 assert
.throws(() => db
.ticketTokenGetUnpublished(), expectedException
);
1069 }); // ticketTokenGetUnpublished
1071 describe('_redeemedTicketToNative', function () {
1073 beforeEach(function () {
1075 resource: 'https://resource.example.com/',
1076 subject: 'https://subject.example.com/',
1077 iss: 'https://idp.example.com/',
1078 ticket: 'xxxTICKETxxx',
1079 token: 'xxxTOKENxxx',
1080 created: 1701970607n
,
1081 published: 1701970670n
,
1084 it('covers', function () {
1087 created: new Date('2023-12-07T17:36:47.000Z'),
1088 published: new Date('2023-12-07T17:37:50.000Z'),
1090 const result
= DB
._redeemedTicketToNative(redeemedData
);
1091 assert
.deepStrictEqual(result
, expected
);
1093 it('covers no published', function () {
1094 redeemedData
.published
= null;
1097 created: new Date('2023-12-07T17:36:47.000Z'),
1100 const result
= DB
._redeemedTicketToNative(redeemedData
);
1101 assert
.deepStrictEqual(result
, expected
);
1103 }); // _redeemedTicketToNative
1105 }); // DatabaseSQLite