X-Git-Url: http://git.squeep.com/?p=squeep-indie-auther;a=blobdiff_plain;f=test%2Fsrc%2Fdb%2Fabstract.js;fp=test%2Fsrc%2Fdb%2Fabstract.js;h=9d7dbf9dbe3ae8cff770008b7afb42425c7a3362;hp=0000000000000000000000000000000000000000;hb=b0103b0d496262c438b40bc20304081dbfe41e73;hpb=8ed81748bce7cea7904cac7225b20a60cafdfc16 diff --git a/test/src/db/abstract.js b/test/src/db/abstract.js new file mode 100644 index 0000000..9d7dbf9 --- /dev/null +++ b/test/src/db/abstract.js @@ -0,0 +1,295 @@ +/* eslint-env mocha */ +'use strict'; + +const assert = require('assert'); +const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require + +const StubDatabase = require('../../stub-db'); +const StubLogger = require('../../stub-logger'); +const DB = require('../../../src/db/abstract'); +const DBErrors = require('../../../src/db/errors'); + +describe('DatabaseBase', function () { + let db, logger, stubDb; + before(function () { + logger = new StubLogger(); + logger._reset(); + stubDb = new StubDatabase(); + }); + beforeEach(function () { + db = new DB(logger, {}); + }); + afterEach(function () { + sinon.restore(); + }); + + it('covers no options', function () { + db = new DB(); + }); + + describe('Interface', function () { + it('covers abstract methods', async function () { + await Promise.all(stubDb._implementation.map(async (m) => { + try { + // eslint-disable-next-line security/detect-object-injection + await db[m](); + assert.fail(`${m}: did not catch NotImplemented exception`); + } catch (e) { + assert(e instanceof DBErrors.NotImplemented, `${m}: unexpected exception ${e.name}`); + } + })); + }); // covers abstract methods + it('covers private abstract methods', async function () { + [ + ].map((m) => { + try { + // eslint-disable-next-line security/detect-object-injection + db[m](); + } catch (e) { + assert(e instanceof DBErrors.NotImplemented, `${m}: unexpected exception ${e.name}`); + } + }); + }); + }); // Interface + + describe('_isUUID', function () { + it('is a uuid', function () { + const result = DB._isUUID('8fde351e-2d63-11ed-8b0c-0025905f714a'); + assert.strictEqual(result, true); + }); + it('is not a uuid', function () { + const result = DB._isUUID('not a uuid'); + assert.strictEqual(result, false); + }); + }); + + describe('_isInfinites', function () { + it('is true for Infinity', function () { + const result = DB._isInfinites(Infinity); + assert.strictEqual(result, true); + }); + it('is true for negative Infinity', function () { + const result = DB._isInfinites(-Infinity); + assert.strictEqual(result, true); + }); + it('is false for finite value', function () { + const result = DB._isInfinites(5); + assert.strictEqual(result, false); + }); + it('is false for NaN', function () { + const result = DB._isInfinites(NaN); + assert.strictEqual(result, false); + }); + }); + + describe('_ensureTypes', function () { + let object; + beforeEach(function () { + object = { + array: ['foo', 'bar'], + bignum: BigInt(456), + buf: Buffer.from('foop'), + date: new Date(), + num: 123, + obj: {}, + str: 'some words', + uuid: 'a4dd5106-2d64-11ed-a2ba-0025905f714a', + veryNull: null, + }; + }); + it('succeeds', function () { + db._ensureTypes(object, ['array'], ['array']); + db._ensureTypes(object, ['bignum', 'num'], ['number']); + db._ensureTypes(object, ['buf'], ['buffer']); + db._ensureTypes(object, ['date'], ['date']); + db._ensureTypes(object, ['str', 'veryNull'], ['string', 'null']); + }); + it('data failure', function () { + try { + db._ensureTypes(object, ['missingField'], ['string', 'null']); + assert.fail('validation should have failed'); + } catch (e) { + assert(e instanceof DBErrors.DataValidation); + } + }); + it('failure covers singular', function () { + try { + db._ensureTypes(object, ['missingField'], ['string']); + assert.fail('validation should have failed'); + } catch (e) { + assert(e instanceof DBErrors.DataValidation); + } + }); + it('parameter failure', function () { + try { + db._ensureTypes(object, ['missingField'], undefined); + assert.fail('validation should have failed'); + } catch (e) { + assert(e instanceof DBErrors.DataValidation); + } + }); + }); // _ensureTypes + + describe('_validateAuthentication', function () { + let authentication; + beforeEach(function () { + authentication = { + identifier: 'username', + credential: '$plain$secret', + created: new Date(), + lastAuthenticated: -Infinity, + }; + }); + it('covers', function () { + db._validateAuthentication(authentication); + }); + it('covers failure', function () { + assert.throws(() => db._validateAuthentication(undefined), DBErrors.DataValidation); + }); + }); // _validateAuthentication + + describe('_validateResource', function () { + let resource; + beforeEach(function () { + resource = { + resourceId: '42016c1e-2d66-11ed-9e10-0025905f714a', + secret: 'secretSecret', + description: 'Some other service', + created: new Date(), + }; + }); + it('covers', function () { + db._validateResource(resource); + }); + it('covers failure', function () { + assert.throws(() => db._validateResource(undefined), DBErrors.DataValidation); + }); + }); // _validateResource + + describe('_validateToken', function () { + let token; + beforeEach(function () { + token = { + codeId: '9efc7882-2d66-11ed-b03c-0025905f714a', + profile: 'https://profile.example.com/', + resource: null, + clientId: 'https://app.example.com/', + created: new Date(), + expires: new Date(), + refreshExpires: null, + refreshed: null, + isToken: true, + isRevoked: false, + scopes: ['scope'], + profileData: { + name: 'User von Namey', + }, + }; + }); + it('covers', function () { + db._validateToken(token); + }); + it('covers failure', function () { + assert.throws(() => db._validateToken(undefined), DBErrors.DataValidation); + }); + }); // _validateToken + + describe('_profilesScopesBuilder', function () { + it('covers empty', function () { + const result = DB._profilesScopesBuilder(); + assert.deepStrictEqual(result, { + profileScopes: {}, + scopeIndex: {}, + profiles: [], + }); + }); + it('builds expected structure', function () { + const profileScopesRows = [ + { profile: 'https://scopeless.example.com/', scope: null, description: null, application: null, isPermanent: null, isManuallyAdded: null }, + { profile: 'https://profile.example.com/', scope: 'role:private', description: 'level', application: '', isPermanent: false, isManuallyAdded: true }, + { profile: null, scope: 'profile', description: 'profile', application: 'IndieAuth', isPermanent: true, isManuallyAdded: false }, + { profile: null, scope: 'role:private', description: 'level', application: '', isPermanent: false, isManuallyAdded: true }, + { profile: null, scope: 'read', description: 'read', application: 'MicroPub', isPermanent: true, isManuallyAdded: false }, + { profile: 'https://profile.example.com/', scope: 'profile', description: 'profile', application: 'IndieAuth', isPermanent: true, isManuallyAdded: false }, + { profile: 'https://another.example.com/', scope: 'profile', description: 'profile', application: 'IndieAuth', isPermanent: true, isManuallyAdded: false }, + ]; + const expected = { + profileScopes: { + 'https://scopeless.example.com/': {}, + 'https://profile.example.com/': {}, + 'https://another.example.com/': {}, + }, + scopeIndex: { + 'role:private': { + description: 'level', + application: '', + isPermanent: false, + isManuallyAdded: true, + profiles: ['https://profile.example.com/'], + }, + 'profile': { + description: 'profile', + application: 'IndieAuth', + isPermanent: true, + isManuallyAdded: false, + profiles: ['https://profile.example.com/', 'https://another.example.com/'], + }, + 'read': { + description: 'read', + application: 'MicroPub', + isPermanent: true, + isManuallyAdded: false, + profiles: [], + }, + }, + profiles: ['https://scopeless.example.com/', 'https://profile.example.com/', 'https://another.example.com/'], + }; + expected.profileScopes['https://profile.example.com/']['role:private'] = expected.scopeIndex['role:private']; + expected.profileScopes['https://profile.example.com/']['profile'] = expected.scopeIndex['profile']; + expected.profileScopes['https://another.example.com/']['profile'] = expected.scopeIndex['profile']; + + const result = DB._profilesScopesBuilder(profileScopesRows); + assert.deepStrictEqual(result, expected); + }); + }); // _profilesScopesBuilder + + describe('initialize', function () { + let currentSchema; + beforeEach(function () { + currentSchema = { + major: 1, + minor: 0, + patch: 0, + }; + db.schemaVersionsSupported = { + min: { ...currentSchema }, + max: { ...currentSchema }, + }; + sinon.stub(db, '_currentSchema').resolves(currentSchema); + }); + it('covers success', async function () { + await db.initialize(); + }); + it('covers failure', async function() { + db.schemaVersionsSupported = { + min: { + major: 3, + minor: 2, + patch: 1, + }, + max: { + major: 5, + minor: 0, + patch: 0, + }, + }; + try { + await db.initialize(); + assert.fail('did not get expected exception'); + } catch (e) { + assert(e instanceof DBErrors.MigrationNeeded); + } + }); + }); // initialize + +}); // DatabaseBase