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('authenticationGet', function () {
220 let identifier
, credential
;
221 beforeEach(function () {
222 identifier
= 'username';
223 credential
= '$z$foo';
224 sinon
.stub(db
.statement
.authenticationGet
, 'get');
226 it('success', function() {
231 db
.statement
.authenticationGet
.get.returns(expected
);
232 const result
= db
.authenticationGet(dbCtx
, identifier
);
233 assert
.deepStrictEqual(result
, expected
);
235 it('failure', function () {
236 db
.statement
.authenticationGet
.get.throws(expectedException
);
237 assert
.throws(() => db
.authenticationGet(dbCtx
, identifier
), expectedException
);
239 }); // authenticationGet
241 describe('authenticationSuccess', function () {
242 let dbResult
, identifier
;
243 beforeEach(function () {
244 identifier
= 'username';
245 sinon
.stub(db
.statement
.authenticationSuccess
, 'run');
248 lastInsertRowid: undefined,
251 it('success', function() {
252 db
.statement
.authenticationSuccess
.run
.returns(dbResult
);
253 db
.authenticationSuccess(dbCtx
, identifier
);
255 it('failure', function () {
256 dbResult
.changes
= 0;
257 db
.statement
.authenticationSuccess
.run
.returns(dbResult
);
258 assert
.throws(() => db
.authenticationSuccess(dbCtx
, identifier
), DBErrors
.UnexpectedResult
);
260 }); // authenticationSuccess
262 describe('authenticationUpsert', function () {
263 let identifier
, credential
;
264 beforeEach(function () {
265 identifier
= 'username';
266 credential
= '$z$foo';
268 it('success', function() {
271 lastInsertRowid: undefined,
273 sinon
.stub(db
.statement
.authenticationUpsert
, 'run').returns(dbResult
);
274 db
.authenticationUpsert(dbCtx
, identifier
, credential
);
276 it('failure', function () {
279 lastInsertRowid: undefined,
281 sinon
.stub(db
.statement
.authenticationUpsert
, 'run').returns(dbResult
);
282 assert
.throws(() => db
.authenticationUpsert(dbCtx
, identifier
, credential
), DBErrors
.UnexpectedResult
);
284 }); // authenticationUpsert
286 describe('profileIdentifierInsert', function () {
287 let profile
, identifier
;
288 beforeEach(function () {
289 profile
= 'https://profile.example.com/';
290 identifier
= 'identifier';
291 sinon
.stub(db
.statement
.profileIdentifierInsert
, 'run');
293 it('success', function () {
294 db
.statement
.profileIdentifierInsert
.run
.returns({ changes: 1 });
295 db
.profileIdentifierInsert(dbCtx
, profile
, identifier
);
297 it('failure', function () {
298 db
.statement
.profileIdentifierInsert
.run
.returns({ changes: 0 });
299 assert
.throws(() => db
.profileIdentifierInsert(dbCtx
, profile
, identifier
), DBErrors
.UnexpectedResult
);
301 }); // profileIdentifierInsert
303 describe('profileScopeInsert', function () {
305 beforeEach(function () {
306 profile
= 'https://profile.example.com/';
308 sinon
.stub(db
.statement
.profileScopeInsert
, 'run');
310 it('success', function () {
311 db
.statement
.profileScopeInsert
.run
.returns({ changes: 1 });
312 db
.profileScopeInsert(dbCtx
, profile
, scope
);
314 it('failure', function () {
315 db
.statement
.profileScopeInsert
.run
.returns({ changes: 2 });
316 assert
.throws(() => db
.profileScopeInsert(dbCtx
, profile
, scope
), DBErrors
.UnexpectedResult
);
318 }); // profileScopeInsert
320 describe('profileIsValid', function () {
322 beforeEach(function () {
323 profile
= 'https://profile.exmaple.com';
325 it('valid profile', function () {
326 sinon
.stub(db
.statement
.profileGet
, 'get').returns({ profile
});
327 const result
= db
.profileIsValid(dbCtx
, profile
);
328 assert
.deepStrictEqual(result
, true);
330 it('invalid profile', function () {
331 sinon
.stub(db
.statement
.profileGet
, 'get').returns();
332 const result
= db
.profileIsValid(dbCtx
, profile
);
333 assert
.deepStrictEqual(result
, false);
335 it('failure', function() {
336 sinon
.stub(db
.statement
.profileGet
, 'get').throws(expectedException
);
337 assert
.throws(() => db
.profileIsValid(dbCtx
, profile
), expectedException
);
339 }); // profileIsValid
341 describe('profilesScopesByIdentifier', function () {
342 let identifier
, scopeIndex
, profileScopes
, profiles
;
343 beforeEach(function () {
344 identifier
= 'identifier';
347 description: 'A scope.',
350 isManuallyAdded: false,
351 profiles: ['https://first.example.com/', 'https://second.example.com/'],
354 description: 'Another scope.',
355 application: 'another test',
357 isManuallyAdded: false,
358 profiles: ['https://first.example.com/'],
362 'https://first.example.com/': {
363 'scope': scopeIndex
['scope'],
364 'another_scope': scopeIndex
['another_scope'],
366 'https://second.example.com/': {
367 'scope': scopeIndex
['scope'],
370 profiles
= ['https://first.example.com/', 'https://second.example.com/'];
372 it('success', function () {
374 { profile: 'https://first.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: false },
375 { profile: 'https://first.example.com/', scope: 'another_scope', application: 'another test', description: 'Another scope.', isPermanent: false, isManuallyAdded: false },
376 { profile: 'https://second.example.com/', scope: 'scope', application: 'test', description: 'A scope.', isPermanent: false, isManuallyAdded: false },
383 sinon
.stub(db
.statement
.profilesScopesByIdentifier
, 'all').returns(dbResult
);
384 const result
= db
.profilesScopesByIdentifier(dbCtx
, identifier
);
385 assert
.deepStrictEqual(result
, expected
);
387 it('failure', function() {
388 sinon
.stub(db
.statement
.profilesScopesByIdentifier
, 'all').throws(expectedException
);
389 assert
.throws(() => db
.profilesScopesByIdentifier(dbCtx
, identifier
), expectedException
);
391 }); // profilesScopesByIdentifier
393 describe('profileScopesSetAll', function () {
395 beforeEach(function () {
396 profile
= 'https://example.com/';
397 scopes
= ['scope1', 'scope2'];
398 sinon
.stub(db
.statement
.profileScopesClear
, 'run').returns();
399 sinon
.stub(db
.statement
.profileScopeInsert
, 'run');
401 it('success, no scopes', function () {
402 db
.statement
.profileScopeInsert
.run
.returns();
404 db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
406 it('success, scopes', function () {
407 db
.statement
.profileScopeInsert
.run
.returns();
408 scopes
.push('profile', 'email', 'create');
409 db
.profileScopesSetAll(dbCtx
, profile
, scopes
);
411 it('failure', function () {
412 db
.statement
.profileScopeInsert
.run
.throws(expectedException
);
413 assert
.throws(() => db
.profileScopesSetAll(dbCtx
, profile
, scopes
), expectedException
);
416 }); // profileScopesSetAll
418 describe('redeemCode', function () {
419 let codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
;
420 beforeEach(function () {
421 codeId
= '2f226616-3e79-11ec-ad0f-0025905f714a';
423 clientId
= 'https://app.exmaple.com/';
424 profile
= 'https://profile.example.com/';
425 identifier
= 'username';
426 scopes
= ['scope1', 'scope2'];
427 lifespanSeconds
= 600;
428 profileData
= undefined;
429 created
= new Date();
431 sinon
.stub(db
.statement
.scopeInsert
, 'run');
432 sinon
.stub(db
.statement
.tokenScopeSet
, 'run');
433 sinon
.stub(db
.statement
.redeemCode
, 'get');
435 it('success', function() {
438 lastInsertRowid: undefined,
443 db
.statement
.scopeInsert
.run
.returns(dbResult
);
444 db
.statement
.tokenScopeSet
.run
.returns(dbResult
);
445 db
.statement
.redeemCode
.get.returns(dbGet
);
449 const result
= db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
});
450 assert
.strictEqual(result
, true);
452 it('success (revoked)', function() {
455 lastInsertRowid: undefined,
460 db
.statement
.scopeInsert
.run
.returns(dbResult
);
461 db
.statement
.tokenScopeSet
.run
.returns(dbResult
);
462 db
.statement
.redeemCode
.get.returns(dbGet
);
463 const result
= db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
, profileData
});
464 assert
.strictEqual(result
, false);
466 it('failure', function () {
467 db
.statement
.scopeInsert
.run
.throws();
468 db
.statement
.tokenScopeSet
.run
.throws();
469 db
.statement
.redeemCode
.get.returns();
470 assert
.throws(() => db
.redeemCode(dbCtx
, { codeId
, created
, isToken
, clientId
, profile
, identifier
, scopes
, lifespanSeconds
}), DBErrors
.UnexpectedResult
);
474 describe('refreshCode', function () {
475 let refreshResponse
, removeResponse
, scopesResponse
, codeId
, refreshed
, removeScopes
;
476 beforeEach(function () {
477 sinon
.stub(db
.statement
.refreshCode
, 'get');
478 sinon
.stub(db
.statement
.tokenScopeRemove
, 'run');
479 sinon
.stub(db
.statement
.tokenScopesGetByCodeId
, 'all');
480 codeId
= '73db7b18-27bb-11ed-8edd-0025905f714a';
481 refreshed
= new Date();
482 removeScopes
= ['foop'];
483 const refreshedEpoch
= Math
.ceil(refreshed
.getTime() / 1000);
485 expires: refreshedEpoch
+ 86400,
486 refreshExpires: refreshedEpoch
+ 172800,
489 changes: removeScopes
.length
,
495 it('success', function () {
496 db
.statement
.refreshCode
.get.returns(refreshResponse
);
497 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
498 db
.statement
.tokenScopesGetByCodeId
.all
.returns(scopesResponse
);
499 const expectedResponse
= {
500 expires: new Date(refreshResponse
.expires
* 1000),
501 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
504 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
505 assert
.deepStrictEqual(response
, expectedResponse
);
507 it('success without scope removal', function () {
508 db
.statement
.refreshCode
.get.returns(refreshResponse
);
509 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
510 const expectedResponse
= {
511 expires: new Date(refreshResponse
.expires
* 1000),
512 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
515 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
516 assert
.deepStrictEqual(response
, expectedResponse
);
518 it('success with no scopes left', function () {
519 db
.statement
.refreshCode
.get.returns(refreshResponse
);
520 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
521 const expectedResponse
= {
522 expires: new Date(refreshResponse
.expires
* 1000),
523 refreshExpires: new Date(refreshResponse
.refreshExpires
* 1000),
526 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
527 assert
.deepStrictEqual(response
, expectedResponse
);
529 it('no code', function () {
530 db
.statement
.refreshCode
.get.returns();
531 removeResponse
.changes
= 0;
532 db
.statement
.tokenScopeRemove
.run
.returns();
533 const expectedResponse
= undefined;
534 const response
= db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
);
535 assert
.deepStrictEqual(response
, expectedResponse
);
537 it('failure', function () {
538 db
.statement
.refreshCode
.get.throws(expectedException
);
539 assert
.throws(() => db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
), expectedException
);
541 it('scope removal failure', function () {
542 removeResponse
.changes
= 0;
543 db
.statement
.tokenScopeRemove
.run
.returns(removeResponse
);
544 db
.statement
.refreshCode
.get.returns(refreshResponse
);
545 assert
.throws(() => db
.refreshCode(dbCtx
, codeId
, refreshed
, removeScopes
), DBErrors
.UnexpectedResult
);
548 describe('_refreshCodeResponseToNative', function () {
549 it('coverage', function () {
550 const expected
= { foo: 'bar' };
551 const result
= DB
._refreshCodeResponseToNative(expected
);
552 assert
.deepStrictEqual(result
, expected
);
554 it('coverage', function () {
555 const result
= DB
._refreshCodeResponseToNative();
556 assert
.strictEqual(result
, undefined);
561 describe('resourceGet', function () {
563 beforeEach(function () {
564 sinon
.stub(db
.statement
.resourceGet
, 'get');
565 identifier
= '05b81112-b224-11ec-a9c6-0025905f714a';
567 it('success', function () {
572 db
.statement
.resourceGet
.get.returns(dbResult
);
573 const result
= db
.resourceGet(dbCtx
, identifier
);
574 assert
.deepStrictEqual(result
, dbResult
);
576 it('failure', function() {
577 db
.statement
.resourceGet
.get.throws(expectedException
);
578 assert
.throws(() => db
.resourceGet(dbCtx
, identifier
), expectedException
);
582 describe('resourceUpsert', function () {
583 let resourceId
, secret
, description
;
584 beforeEach(function () {
585 resourceId
= '4086661a-f980-11ec-ba19-0025905f714a';
587 description
= 'some application';
589 it('success', function() {
592 lastInsertRowid: undefined,
594 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
595 db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
597 it('creates id if not provided', function () {
598 resourceId
= undefined;
601 lastInsertRowid: undefined,
603 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
604 db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
);
606 it('failure', function () {
609 lastInsertRowid: undefined,
611 sinon
.stub(db
.statement
.resourceUpsert
, 'run').returns(dbResult
);
612 assert
.throws(() => db
.resourceUpsert(dbCtx
, resourceId
, secret
, description
), DBErrors
.UnexpectedResult
);
614 }); // resourceUpsert
616 describe('scopeCleanup', function () {
617 let atLeastMsSinceLast
;
618 beforeEach(function () {
619 atLeastMsSinceLast
= 86400000;
620 sinon
.stub(db
.statement
.scopeCleanup
, 'run');
621 sinon
.stub(db
.statement
.almanacGet
, 'get');
622 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
624 it('success, empty almanac', function () {
626 db
.statement
.almanacGet
.get.returns();
627 db
.statement
.scopeCleanup
.run
.returns({ changes: cleaned
});
628 db
.statement
.almanacUpsert
.run
.returns({ changes: 1 });
629 const result
= db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
630 assert
.strictEqual(result
, cleaned
);
632 it('success, too soon', function () {
633 db
.statement
.almanacGet
.get.returns({ epoch: BigInt(Math
.ceil(Date
.now() / 1000) - 4) });
634 const result
= db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
);
635 assert
.strictEqual(result
, undefined);
636 assert(db
.statement
.scopeCleanup
.run
.notCalled
);
638 it('failure', function () {
639 db
.statement
.almanacGet
.get.returns({ epoch: 0n
});
640 db
.statement
.scopeCleanup
.run
.returns({ changes: 1 });
641 db
.statement
.almanacUpsert
.run
.returns({ changes: 0 });
642 assert
.throws(() => db
.scopeCleanup(dbCtx
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
646 describe('scopeDelete', function () {
647 let dbGetResult
, dbRunResult
, scope
;
648 beforeEach(function () {
649 sinon
.stub(db
.statement
.scopeInUse
, 'get');
653 sinon
.stub(db
.statement
.scopeDelete
, 'run');
657 scope
= 'some_scope';
659 it('success', function () {
660 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
661 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
662 const result
= db
.scopeDelete(dbCtx
, scope
);
663 assert
.strictEqual(result
, true);
665 it('in use', function () {
666 dbGetResult
.inUse
= true;
667 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
668 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
669 const result
= db
.scopeDelete(dbCtx
, scope
);
670 assert
.strictEqual(result
, false);
672 it('no scope', function () {
673 dbRunResult
.changes
= 0;
674 db
.statement
.scopeInUse
.get.returns(dbGetResult
);
675 db
.statement
.scopeDelete
.run
.returns(dbRunResult
);
676 const result
= db
.scopeDelete(dbCtx
, scope
);
677 assert
.strictEqual(result
, true);
679 it('failure', function () {
680 db
.statement
.scopeInUse
.get.throws(expectedException
);
681 assert
.throws(() => db
.scopeDelete(dbCtx
, scope
), expectedException
);
685 describe('scopeUpsert', function () {
686 let dbResult
, scope
, application
, description
;
687 beforeEach(function () {
689 application
= undefined;
690 description
= 'description';
691 sinon
.stub(db
.statement
.scopeUpsert
, 'run');
694 lastInsertRowid: undefined,
697 it('success', function() {
698 db
.statement
.scopeUpsert
.run
.returns(dbResult
);
699 db
.scopeUpsert(dbCtx
, scope
, application
, description
);
701 it('failure', function () {
702 dbResult
.changes
= 0;
703 db
.statement
.scopeUpsert
.run
.returns(dbResult
);
704 assert
.throws(() => db
.scopeUpsert(dbCtx
, scope
, application
, description
), DBErrors
.UnexpectedResult
);
706 it('failure, error', function () {
707 db
.statement
.scopeUpsert
.run
.throws(expectedException
);
708 assert
.throws(() => db
.scopeUpsert(dbCtx
, scope
, application
, description
), expectedException
);
712 describe('tokenCleanup', function () {
713 let codeLifespanSeconds
, atLeastMsSinceLast
;
714 beforeEach(function () {
715 codeLifespanSeconds
= 600;
716 atLeastMsSinceLast
= 86400000;
717 sinon
.stub(db
.statement
.tokenCleanup
, 'run');
718 sinon
.stub(db
.statement
.almanacGet
, 'get');
719 sinon
.stub(db
.statement
.almanacUpsert
, 'run');
721 it('success, empty almanac', function() {
723 db
.statement
.almanacGet
.get.returns();
724 db
.statement
.tokenCleanup
.run
.returns({ changes: cleaned
});
725 db
.statement
.almanacUpsert
.run
.returns({ changes: 1 });
726 const result
= db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
727 assert
.strictEqual(result
, cleaned
);
729 it('success, too soon', function () {
730 db
.statement
.almanacGet
.get.returns({ epoch: BigInt(Math
.ceil(Date
.now() / 1000) - 4) });
731 const result
= db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
);
732 assert
.strictEqual(result
, undefined);
733 assert(db
.statement
.tokenCleanup
.run
.notCalled
);
735 it('failure', function () {
736 db
.statement
.almanacGet
.get.returns({ epoch: 0n
});
737 db
.statement
.tokenCleanup
.run
.returns({ changes: 10 });
738 db
.statement
.almanacUpsert
.run
.returns({ changes: 0 });
739 assert
.throws(() => db
.tokenCleanup(dbCtx
, codeLifespanSeconds
, atLeastMsSinceLast
), DBErrors
.UnexpectedResult
);
743 describe('tokenGetByCodeId', function () {
745 beforeEach(function () {
746 codeId
= '184a26f6-2612-11ec-9e88-0025905f714a';
747 token
= 'TokenTokenTokenToken';
748 sinon
.stub(db
.statement
.tokenGetByCodeId
, 'get');
749 sinon
.stub(db
.statement
.tokenScopesGetByCodeId
, 'all');
751 it('success', function() {
752 const now
= new Date();
753 const nowEpoch
= Math
.ceil(now
/ 1000);
755 created: new Date(nowEpoch
* 1000),
757 refreshExpires: null,
769 created: Math
.ceil(nowEpoch
),
771 refreshExpires: null,
776 profileData: '{"name":"Some Name"}',
778 db
.statement
.tokenGetByCodeId
.get.returns(dbResult
);
779 const result
= db
.tokenGetByCodeId(dbCtx
, codeId
);
780 assert
.deepStrictEqual(result
, expected
);
782 it('success without profile data', function () {
783 const now
= new Date();
784 const nowEpoch
= Math
.ceil(now
/ 1000);
786 created: new Date(nowEpoch
* 1000),
788 refreshExpires: null,
794 scopes: ['foop', 'baa'],
797 created: Math
.ceil(nowEpoch
),
799 refreshExpires: null,
805 db
.statement
.tokenGetByCodeId
.get.returns(dbResult
);
806 db
.statement
.tokenScopesGetByCodeId
.all
.returns([{ scope: 'foop' }, { scope: 'baa' }]);
807 const result
= db
.tokenGetByCodeId(dbCtx
, codeId
);
808 assert
.deepStrictEqual(result
, expected
);
810 it('failure', function () {
811 db
.statement
.tokenGetByCodeId
.get.throws(expectedException
);
812 assert
.throws(() => db
.tokenGetByCodeId(dbCtx
, codeId
), expectedException
);
815 describe('_tokenToNative', function () {
816 it('covers', function () {
817 const result
= DB
._tokenToNative();
818 assert
.strictEqual(result
, undefined);
820 }); // _tokenToNative
821 }); // tokenGetByCodeId
823 describe('tokenRevokeByCodeId', function () {
824 let dbResult
, codeId
;
825 beforeEach(function () {
826 codeId
= '2f226616-3e79-11ec-ad0f-0025905f714a';
827 sinon
.stub(db
.statement
.tokenRevokeByCodeId
, 'run')
830 lastInsertRowid: undefined,
833 it('success', function() {
834 db
.statement
.tokenRevokeByCodeId
.run
.returns(dbResult
);
835 db
.tokenRevokeByCodeId(dbCtx
, codeId
);
837 it('failure', function () {
838 dbResult
.changes
= 0;
839 db
.statement
.tokenRevokeByCodeId
.run
.returns(dbResult
);
840 assert
.throws(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
842 it('failure, error', function () {
843 db
.statement
.tokenRevokeByCodeId
.run
.throws(expectedException
);
844 assert
.throws(() => db
.tokenRevokeByCodeId(dbCtx
, codeId
), expectedException
);
846 }); // tokenRevokeByCodeId
848 describe('tokenRefreshRevokeByCodeId', function () {
849 let dbResult
, codeId
;
850 beforeEach(function () {
853 lastInsertRowid: undefined,
855 codeId
= 'eabba58e-2633-11ed-bbad-0025905f714a';
856 sinon
.stub(db
.statement
.tokenRefreshRevokeByCodeId
, 'run');
858 it('success', function () {
859 db
.statement
.tokenRefreshRevokeByCodeId
.run
.returns(dbResult
);
860 db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
);
862 it('failure', function () {
863 dbResult
.changes
= 0;
864 db
.statement
.tokenRefreshRevokeByCodeId
.run
.returns(dbResult
);
865 assert
.throws(() => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), DBErrors
.UnexpectedResult
);
867 it('failure, error', function () {
868 const expected
= new Error('oh no');
869 db
.statement
.tokenRefreshRevokeByCodeId
.run
.throws(expected
);
870 assert
.throws(() => db
.tokenRefreshRevokeByCodeId(dbCtx
, codeId
), expected
);
872 }); // tokenRefreshRevokeByCodeId
874 describe('tokensGetByIdentifier', function () {
876 beforeEach(function () {
877 identifier
= 'identifier';
878 sinon
.stub(db
.statement
.tokensGetByIdentifier
, 'all');
880 it('success', function () {
881 const nowEpoch
= Math
.ceil(Date
.now() / 1000);
885 expires: nowEpoch
+ 86400,
887 refreshed: nowEpoch
+ 600,
888 refreshExpires: nowEpoch
+ 172800,
891 codeId: 'c0a7cef4-2637-11ed-a830-0025905f714a',
892 profile: 'https://profile.example.com/',
893 profileData: '{"name":"Some Name"}',
894 identifier: 'username',
898 Object
.assign({}, dbResult
[0], {
899 created: new Date(dbResult
[0].created
* 1000),
900 expires: new Date(dbResult
[0].expires
* 1000),
901 refreshed: new Date(dbResult
[0].refreshed
* 1000),
902 refreshExpires: new Date(dbResult
[0].refreshExpires
* 1000),
908 db
.statement
.tokensGetByIdentifier
.all
.returns(dbResult
);
909 const result
= db
.tokensGetByIdentifier(dbCtx
, identifier
);
910 assert
.deepStrictEqual(result
, expected
);
912 it('failure', function() {
913 db
.statement
.tokensGetByIdentifier
.all
.throws(expectedException
);
914 assert
.throws(() => db
.tokensGetByIdentifier(dbCtx
, identifier
), expectedException
);
916 }); // tokensGetByIdentifier
918 }); // DatabaseSQLite