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('profileIdentifierInsert', function () {
313 let profile
, identifier
;
314 beforeEach(function () {
315 profile
= 'https://profile.example.com/';
316 identifier
= 'identifier';
317 sinon
.stub(db
.statement
.profileIdentifierInsert
, 'run');
319 it('success', function () {
320 db
.statement
.profileIdentifierInsert
.run
.returns({ changes: 1 });
321 db
.profileIdentifierInsert(dbCtx
, profile
, identifier
);
323 it('failure', function () {
324 db
.statement
.profileIdentifierInsert
.run
.returns({ changes: 0 });
325 assert
.throws(() => db
.profileIdentifierInsert(dbCtx
, profile
, identifier
), DBErrors
.UnexpectedResult
);
327 }); // profileIdentifierInsert
329 describe('profileScopeInsert', function () {
331 beforeEach(function () {
332 profile
= 'https://profile.example.com/';
334 sinon
.stub(db
.statement
.profileScopeInsert
, 'run');
336 it('success', function () {
337 db
.statement
.profileScopeInsert
.run
.returns({ changes: 1 });
338 db
.profileScopeInsert(dbCtx
, profile
, scope
);
340 it('failure', function () {
341 db
.statement
.profileScopeInsert
.run
.returns({ changes: 2 });
342 assert
.throws(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), DBErrors
.UnexpectedResult
);
344 }); // profileScopeInsert
346 describe('profileIsValid', function () {
348 beforeEach(function () {
349 profile
= 'https://profile.exmaple.com';
351 it('valid profile', function () {
352 sinon
.stub(db
.statement
.profileGet
, 'get').returns({ profile
});
353 const result
= db
.profileIsValid(dbCtx
, profile
);
354 assert
.deepStrictEqual(result
, true);
356 it('invalid profile', function () {
357 sinon
.stub(db
.statement
.profileGet
, 'get').returns();
358 const result
= db
.profileIsValid(dbCtx
, profile
);
359 assert
.deepStrictEqual(result
, false);
361 it('failure', function() {
362 sinon
.stub(db
.statement
.profileGet
, 'get').throws(expectedException
);
363 assert
.throws(() => db
.profileIsValid(dbCtx
, profile
), expectedException
);
365 }); // profileIsValid
367 describe('profilesScopesByIdentifier', function () {
368 let identifier
, scopeIndex
, profileScopes
, profiles
;
369 beforeEach(function () {
370 identifier
= 'identifier';
373 description: 'A scope.',
376 isManuallyAdded: false,
377 profiles: ['https://first.example.com/', 'https://second.example.com/'],
380 description: 'Another scope.',
381 application: 'another test',
383 isManuallyAdded: false,
384 profiles: ['https://first.example.com/'],
388 'https://first.example.com/': {
389 'scope': scopeIndex
['scope'],
390 'another_scope': scopeIndex
['another_scope'],
392 'https://second.example.com/': {
393 'scope': scopeIndex
['scope'],
396 profiles
= ['https://first.example.com/', 'https://second.example.com/'];
398 it('success', function () {
400 { profile: 'https://first.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: false },
401 { profile: 'https://first.example.com/', scope: 'another_scope', application: 'another test', description: 'Another scope.', isPermanent: false, isManuallyAdded: false },
402 { profile: 'https://second.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: false },
409 sinon
.stub(db
.statement
.profilesScopesByIdentifier
, 'all').returns(dbResult
);
410 const result
= db
.profilesScopesByIdentifier(dbCtx
, identifier
);
411 assert
.deepStrictEqual(result
, expected
);
413 it('failure', function() {
414 sinon
.stub(db
.statement
.profilesScopesByIdentifier
, 'all').throws(expectedException
);
415 assert
.throws(() => db
.profilesScopesByIdentifier(dbCtx
, identifier
), expectedException
);
417 }); // profilesScopesByIdentifier
419 describe('profileScopesSetAll', function () {
421 beforeEach(function () {
422 profile
= 'https://example.com/';
423 scopes
= ['scope1', 'scope2'];
424 sinon
.stub(db
.statement
.profileScopesClear
, 'run').returns();
425 sinon
.stub(db
.statement
.profileScopeInsert
, 'run');
427 it('success, no scopes', function () {
428 db
.statement
.profileScopeInsert
.run
.returns();
430 db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
432 it('success, scopes', function () {
433 db
.statement
.profileScopeInsert
.run
.returns();
434 scopes
.push('profile', 'email', 'create');
435 db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
437 it('failure', function () {
438 db
.statement
.profileScopeInsert
.run
.throws(expectedException
);
439 assert
.throws(() => db
.profileScopesSetAll(dbCtx
, profile
, scopes
), expectedException
);
442 }); // profileScopesSetAll
444 describe('redeemCode', function () {
445 let codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
;
446 beforeEach(function () {
447 codeId
= '2f226616-3e79-11ec-ad0f-0025905f714a';
449 clientId
= 'https://app.exmaple.com/';
450 profile
= 'https://profile.example.com/';
451 identifier
= 'username';
452 scopes
= ['scope1', 'scope2'];
453 lifespanSeconds
= 600;
454 profileData
= undefined;
455 created
= new Date();
457 sinon
.stub(db
.statement
.scopeInsert
, 'run');
458 sinon
.stub(db
.statement
.tokenScopeSet
, 'run');
459 sinon
.stub(db
.statement
.redeemCode
, 'get');
461 it('success', function() {
464 lastInsertRowid: undefined,
469 db
.statement
.scopeInsert
.run
.returns(dbResult
);
470 db
.statement
.tokenScopeSet
.run
.returns(dbResult
);
471 db
.statement
.redeemCode
.get.returns(dbGet
);
475 const result
= db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
});
476 assert
.strictEqual(result
, true);
478 it('success (revoked)', function() {
481 lastInsertRowid: undefined,
486 db
.statement
.scopeInsert
.run
.returns(dbResult
);
487 db
.statement
.tokenScopeSet
.run
.returns(dbResult
);
488 db
.statement
.redeemCode
.get.returns(dbGet
);
489 const result
= db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
});
490 assert
.strictEqual(result
, false);
492 it('failure', function () {
493 db
.statement
.scopeInsert
.run
.throws();
494 db
.statement
.tokenScopeSet
.run
.throws();
495 db
.statement
.redeemCode
.get.returns();
496 assert
.throws(() => db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
}), DBErrors
.UnexpectedResult
);
500 describe('refreshCode', function () {
501 let refreshResponse
, removeResponse
, scopesResponse
, codeId
, refreshed
, removeScopes
;
502 beforeEach(function () {
503 sinon
.stub(db
.statement
.refreshCode
, 'get');
504 sinon
.stub(db
.statement
.tokenScopeRemove
, 'run');
505 sinon
.stub(db
.statement
.tokenScopesGetByCodeId
, 'all');
506 codeId
= '73db7b18-27bb-11ed-8edd-0025905f714a';
507 refreshed
= new Date();
508 removeScopes
= ['foop'];
509 const refreshedEpoch
= Math
.ceil(refreshed
.getTime() / 1000);
511 expires: refreshedEpoch
+ 86400,
512 refreshExpires: refreshedEpoch
+ 172800,
515 changes: removeScopes
.length
,
521 it('success', function () {
522 db
.statement
.refreshCode
.get.returns(refreshResponse
);
523 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
524 db
.statement
.tokenScopesGetByCodeId
.all
.returns(scopesResponse
);
525 const expectedResponse
= {
526 expires: new Date(refreshResponse
.expires
* 1000),
527 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
530 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
531 assert
.deepStrictEqual(response
, expectedResponse
);
533 it('success without scope removal', function () {
534 db
.statement
.refreshCode
.get.returns(refreshResponse
);
535 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
536 const expectedResponse
= {
537 expires: new Date(refreshResponse
.expires
* 1000),
538 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
541 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
542 assert
.deepStrictEqual(response
, expectedResponse
);
544 it('success with no scopes left', function () {
545 db
.statement
.refreshCode
.get.returns(refreshResponse
);
546 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
547 const expectedResponse
= {
548 expires: new Date(refreshResponse
.expires
* 1000),
549 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
552 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
553 assert
.deepStrictEqual(response
, expectedResponse
);
555 it('no code', function () {
556 db
.statement
.refreshCode
.get.returns();
557 removeResponse
.changes
= 0;
558 db
.statement
.tokenScopeRemove
.run
.returns();
559 const expectedResponse
= undefined;
560 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
561 assert
.deepStrictEqual(response
, expectedResponse
);
563 it('failure', function () {
564 db
.statement
.refreshCode
.get.throws(expectedException
);
565 assert
.throws(() => db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
), expectedException
);
567 it('scope removal failure', function () {
568 removeResponse
.changes
= 0;
569 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
570 db
.statement
.refreshCode
.get.returns(refreshResponse
);
571 assert
.throws(() => db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
), DBErrors
.UnexpectedResult
);
574 describe('_refreshCodeResponseToNative', function () {
575 it('coverage', function () {
576 const expected
= { foo: 'bar' };
577 const result
= DB
._refreshCodeResponseToNative(expected
);
578 assert
.deepStrictEqual(result
, expected
);
580 it('coverage', function () {
581 const result
= DB
._refreshCodeResponseToNative();
582 assert
.strictEqual(result
, undefined);
587 describe('resourceGet', function () {
589 beforeEach(function () {
590 sinon
.stub(db
.statement
.resourceGet
, 'get');
591 identifier
= '05b81112-b224-11ec-a9c6-0025905f714a';
593 it('success', function () {
598 db
.statement
.resourceGet
.get.returns(dbResult
);
599 const result
= db
.resourceGet(dbCtx
, identifier
);
600 assert
.deepStrictEqual(result
, dbResult
);
602 it('failure', function() {
603 db
.statement
.resourceGet
.get.throws(expectedException
);
604 assert
.throws(() => db
.resourceGet(dbCtx
, identifier
), expectedException
);
608 describe('resourceUpsert', function () {
609 let resourceId
, secret
, description
;
610 beforeEach(function () {
611 resourceId
= '4086661a-f980-11ec-ba19-0025905f714a';
613 description
= 'some application';
615 it('success', function() {
618 lastInsertRowid: undefined,
620 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
621 db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
623 it('creates id if not provided', function () {
624 resourceId
= undefined;
627 lastInsertRowid: undefined,
629 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
630 db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
632 it('failure', function () {
635 lastInsertRowid: undefined,
637 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
638 assert
.throws(() => db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
), DBErrors
.UnexpectedResult
);
640 }); // resourceUpsert
642 describe('scopeCleanup', function () {
643 let atLeastMsSinceLast
;
644 beforeEach(function () {
645 atLeastMsSinceLast
= 86400000;
646 sinon
.stub(db
.statement
.scopeCleanup
, 'run');
647 sinon
.stub(db
.statement
.almanacGet
, 'get');
648 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
650 it('success, empty almanac', function () {
652 db
.statement
.almanacGet
.get.returns();
653 db
.statement
.scopeCleanup
.run
.returns({ changes: cleaned
});
654 db
.statement
.almanacUpsert
.run
.returns({ changes: 1 });
655 const result
= db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
656 assert
.strictEqual(result
, cleaned
);
658 it('success, too soon', function () {
659 db
.statement
.almanacGet
.get.returns({ epoch: BigInt(Math
.ceil(Date
.now() / 1000) - 4) });
660 const result
= db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
661 assert
.strictEqual(result
, undefined);
662 assert(db
.statement
.scopeCleanup
.run
.notCalled
);
664 it('failure', function () {
665 db
.statement
.almanacGet
.get.returns({ epoch: 0n
});
666 db
.statement
.scopeCleanup
.run
.returns({ changes: 1 });
667 db
.statement
.almanacUpsert
.run
.returns({ changes: 0 });
668 assert
.throws(() => db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
672 describe('scopeDelete', function () {
673 let dbGetResult
, dbRunResult
, scope
;
674 beforeEach(function () {
675 sinon
.stub(db
.statement
.scopeInUse
, 'get');
679 sinon
.stub(db
.statement
.scopeDelete
, 'run');
683 scope
= 'some_scope';
685 it('success', function () {
686 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
687 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
688 const result
= db
.scopeDelete(dbCtx
, scope
);
689 assert
.strictEqual(result
, true);
691 it('in use', function () {
692 dbGetResult
.inUse
= true;
693 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
694 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
695 const result
= db
.scopeDelete(dbCtx
, scope
);
696 assert
.strictEqual(result
, false);
698 it('no scope', function () {
699 dbRunResult
.changes
= 0;
700 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
701 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
702 const result
= db
.scopeDelete(dbCtx
, scope
);
703 assert
.strictEqual(result
, true);
705 it('failure', function () {
706 db
.statement
.scopeInUse
.get.throws(expectedException
);
707 assert
.throws(() => db
.scopeDelete(dbCtx
, scope
), expectedException
);
711 describe('scopeUpsert', function () {
712 let dbResult
, scope
, application
, description
;
713 beforeEach(function () {
715 application
= undefined;
716 description
= 'description';
717 sinon
.stub(db
.statement
.scopeUpsert
, 'run');
720 lastInsertRowid: undefined,
723 it('success', function() {
724 db
.statement
.scopeUpsert
.run
.returns(dbResult
);
725 db
.scopeUpsert(dbCtx
, scope
, application
, description
);
727 it('failure', function () {
728 dbResult
.changes
= 0;
729 db
.statement
.scopeUpsert
.run
.returns(dbResult
);
730 assert
.throws(() => db
.scopeUpsert(dbCtx
, scope
, application
, description
), DBErrors
.UnexpectedResult
);
732 it('failure, error', function () {
733 db
.statement
.scopeUpsert
.run
.throws(expectedException
);
734 assert
.throws(() => db
.scopeUpsert(dbCtx
, scope
, application
, description
), expectedException
);
738 describe('tokenCleanup', function () {
739 let codeLifespanSeconds
, atLeastMsSinceLast
;
740 beforeEach(function () {
741 codeLifespanSeconds
= 600;
742 atLeastMsSinceLast
= 86400000;
743 sinon
.stub(db
.statement
.tokenCleanup
, 'run');
744 sinon
.stub(db
.statement
.almanacGet
, 'get');
745 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
747 it('success, empty almanac', function() {
749 db
.statement
.almanacGet
.get.returns();
750 db
.statement
.tokenCleanup
.run
.returns({ changes: cleaned
});
751 db
.statement
.almanacUpsert
.run
.returns({ changes: 1 });
752 const result
= db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
753 assert
.strictEqual(result
, cleaned
);
755 it('success, too soon', function () {
756 db
.statement
.almanacGet
.get.returns({ epoch: BigInt(Math
.ceil(Date
.now() / 1000) - 4) });
757 const result
= db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
758 assert
.strictEqual(result
, undefined);
759 assert(db
.statement
.tokenCleanup
.run
.notCalled
);
761 it('failure', function () {
762 db
.statement
.almanacGet
.get.returns({ epoch: 0n
});
763 db
.statement
.tokenCleanup
.run
.returns({ changes: 10 });
764 db
.statement
.almanacUpsert
.run
.returns({ changes: 0 });
765 assert
.throws(() => db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
769 describe('tokenGetByCodeId', function () {
771 beforeEach(function () {
772 codeId
= '184a26f6-2612-11ec-9e88-0025905f714a';
773 token
= 'TokenTokenTokenToken';
774 sinon
.stub(db
.statement
.tokenGetByCodeId
, 'get');
775 sinon
.stub(db
.statement
.tokenScopesGetByCodeId
, 'all');
777 it('success', function() {
778 const now
= new Date();
779 const nowEpoch
= Math
.ceil(now
/ 1000);
781 created: new Date(nowEpoch
* 1000),
783 refreshExpires: null,
795 created: Math
.ceil(nowEpoch
),
797 refreshExpires: null,
802 profileData: '{"name":"Some Name"}',
804 db
.statement
.tokenGetByCodeId
.get.returns(dbResult
);
805 const result
= db
.tokenGetByCodeId(dbCtx
, codeId
);
806 assert
.deepStrictEqual(result
, expected
);
808 it('success without profile data', function () {
809 const now
= new Date();
810 const nowEpoch
= Math
.ceil(now
/ 1000);
812 created: new Date(nowEpoch
* 1000),
814 refreshExpires: null,
820 scopes: ['foop', 'baa'],
823 created: Math
.ceil(nowEpoch
),
825 refreshExpires: null,
831 db
.statement
.tokenGetByCodeId
.get.returns(dbResult
);
832 db
.statement
.tokenScopesGetByCodeId
.all
.returns([{ scope: 'foop' }, { scope: 'baa' }]);
833 const result
= db
.tokenGetByCodeId(dbCtx
, codeId
);
834 assert
.deepStrictEqual(result
, expected
);
836 it('failure', function () {
837 db
.statement
.tokenGetByCodeId
.get.throws(expectedException
);
838 assert
.throws(() => db
.tokenGetByCodeId(dbCtx
, codeId
), expectedException
);
841 describe('_tokenToNative', function () {
842 it('covers', function () {
843 const result
= DB
._tokenToNative();
844 assert
.strictEqual(result
, undefined);
846 }); // _tokenToNative
847 }); // tokenGetByCodeId
849 describe('tokenRevokeByCodeId', function () {
850 let dbResult
, codeId
;
851 beforeEach(function () {
852 codeId
= '2f226616-3e79-11ec-ad0f-0025905f714a';
853 sinon
.stub(db
.statement
.tokenRevokeByCodeId
, 'run')
856 lastInsertRowid: undefined,
859 it('success', function() {
860 db
.statement
.tokenRevokeByCodeId
.run
.returns(dbResult
);
861 db
.tokenRevokeByCodeId(dbCtx
, codeId
);
863 it('failure', function () {
864 dbResult
.changes
= 0;
865 db
.statement
.tokenRevokeByCodeId
.run
.returns(dbResult
);
866 assert
.throws(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
868 it('failure, error', function () {
869 db
.statement
.tokenRevokeByCodeId
.run
.throws(expectedException
);
870 assert
.throws(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), expectedException
);
872 }); // tokenRevokeByCodeId
874 describe('tokenRefreshRevokeByCodeId', function () {
875 let dbResult
, codeId
;
876 beforeEach(function () {
879 lastInsertRowid: undefined,
881 codeId
= 'eabba58e-2633-11ed-bbad-0025905f714a';
882 sinon
.stub(db
.statement
.tokenRefreshRevokeByCodeId
, 'run');
884 it('success', function () {
885 db
.statement
.tokenRefreshRevokeByCodeId
.run
.returns(dbResult
);
886 db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
);
888 it('failure', function () {
889 dbResult
.changes
= 0;
890 db
.statement
.tokenRefreshRevokeByCodeId
.run
.returns(dbResult
);
891 assert
.throws(() => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
893 it('failure, error', function () {
894 const expected
= new Error('oh no');
895 db
.statement
.tokenRefreshRevokeByCodeId
.run
.throws(expected
);
896 assert
.throws(() => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), expected
);
898 }); // tokenRefreshRevokeByCodeId
900 describe('tokensGetByIdentifier', function () {
902 beforeEach(function () {
903 identifier
= 'identifier';
904 sinon
.stub(db
.statement
.tokensGetByIdentifier
, 'all');
906 it('success', function () {
907 const nowEpoch
= Math
.ceil(Date
.now() / 1000);
911 expires: nowEpoch
+ 86400,
913 refreshed: nowEpoch
+ 600,
914 refreshExpires: nowEpoch
+ 172800,
917 codeId: 'c0a7cef4-2637-11ed-a830-0025905f714a',
918 profile: 'https://profile.example.com/',
919 profileData: '{"name":"Some Name"}',
920 identifier: 'username',
924 Object
.assign({}, dbResult
[0], {
925 created: new Date(dbResult
[0].created
* 1000),
926 expires: new Date(dbResult
[0].expires
* 1000),
927 refreshed: new Date(dbResult
[0].refreshed
* 1000),
928 refreshExpires: new Date(dbResult
[0].refreshExpires
* 1000),
934 db
.statement
.tokensGetByIdentifier
.all
.returns(dbResult
);
935 const result
= db
.tokensGetByIdentifier(dbCtx
, identifier
);
936 assert
.deepStrictEqual(result
, expected
);
938 it('failure', function() {
939 db
.statement
.tokensGetByIdentifier
.all
.throws(expectedException
);
940 assert
.throws(() => db
.tokensGetByIdentifier(dbCtx
, identifier
), expectedException
);
942 }); // tokensGetByIdentifier
944 describe('ticketRedeemed', function () {
945 let redeemedData
, dbResult
;
946 beforeEach(function () {
948 resource: 'https://resource.example.com/',
949 subject: 'https://subject.example.com/',
950 iss: 'https://idp.example.com/',
951 ticket: 'xxxTICKETxxx',
952 token: 'xxxTOKENxxx',
954 sinon
.stub(db
.statement
.ticketRedeemed
, 'run');
957 lastInsertRowid: undefined,
960 it('success', function () {
961 db
.statement
.ticketRedeemed
.run
.returns(dbResult
);
962 db
.ticketRedeemed(dbCtx
, redeemedData
);
964 it('failure', function () {
965 dbResult
.changes
= 0;
966 db
.statement
.ticketRedeemed
.run
.returns(dbResult
);
967 assert
.throws(() => db
.ticketRedeemed(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
969 }); // ticketRedeemed
971 describe('ticketTokenPublished', function () {
972 let redeemedData
, dbResult
;
973 beforeEach(function () {
975 resource: 'https://resource.example.com/',
976 subject: 'https://subject.example.com/',
977 iss: 'https://idp.example.com/',
978 ticket: 'xxxTICKETxxx',
979 token: 'xxxTOKENxxx',
981 sinon
.stub(db
.statement
.ticketTokenPublished
, 'run');
982 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
985 lastInsertRowid: undefined,
988 it('success', function () {
989 db
.statement
.ticketTokenPublished
.run
.returns(dbResult
);
990 db
.statement
.almanacUpsert
.run
.returns(dbResult
);
991 db
.ticketTokenPublished(dbCtx
, redeemedData
);
992 assert(db
.statement
.ticketTokenPublished
.run
.called
);
993 assert(db
.statement
.almanacUpsert
.run
.called
);
995 it('failure', function () {
996 dbResult
.changes
= 0;
997 db
.statement
.ticketTokenPublished
.run
.returns(dbResult
);
998 assert
.throws(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1000 it('failure of almanac', function () {
1001 const dbResultAlmanac
= {
1005 db
.statement
.ticketTokenPublished
.run
.returns(dbResult
);
1006 db
.statement
.almanacUpsert
.run
.returns(dbResultAlmanac
);
1007 assert
.throws(() => db
.ticketTokenPublished(dbCtx
, redeemedData
), DBErrors
.UnexpectedResult
);
1009 }); // ticketTokenPublished
1011 describe('ticketTokenGetUnpublished', function () {
1012 beforeEach(function () {
1013 sinon
.stub(db
.statement
.ticketTokenGetUnpublished
, 'all');
1015 it('success', function () {
1016 db
.statement
.ticketTokenGetUnpublished
.all
.returns([]);
1017 const result
= db
.ticketTokenGetUnpublished();
1018 assert
.deepStrictEqual(result
, []);
1020 it('failure', function () {
1021 db
.statement
.ticketTokenGetUnpublished
.all
.throws(expectedException
);
1022 assert
.throws(() => db
.ticketTokenGetUnpublished(), expectedException
);
1024 }); // ticketTokenGetUnpublished
1026 describe('_redeemedTicketToNative', function () {
1028 beforeEach(function () {
1030 resource: 'https://resource.example.com/',
1031 subject: 'https://subject.example.com/',
1032 iss: 'https://idp.example.com/',
1033 ticket: 'xxxTICKETxxx',
1034 token: 'xxxTOKENxxx',
1035 created: 1701970607n
,
1036 published: 1701970670n
,
1039 it('covers', function () {
1042 created: new Date('2023-12-07T17:36:47.000Z'),
1043 published: new Date('2023-12-07T17:37:50.000Z'),
1045 const result
= DB
._redeemedTicketToNative(redeemedData
);
1046 assert
.deepStrictEqual(result
, expected
);
1048 it('covers no published', function () {
1049 redeemedData
.published
= null;
1052 created: new Date('2023-12-07T17:36:47.000Z'),
1055 const result
= DB
._redeemedTicketToNative(redeemedData
);
1056 assert
.deepStrictEqual(result
, expected
);
1058 }); // _redeemedTicketToNative
1060 }); // DatabaseSQLite