bf74e973033ae70d3f734c8284a5861bdb9c9c55
[squeep-indie-auther] / test / src / db / abstract.js
1 /* eslint-env mocha */
2 'use strict';
3
4 const assert = require('assert');
5 const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
6
7 const StubDatabase = require('../../stub-db');
8 const StubLogger = require('../../stub-logger');
9 const DB = require('../../../src/db/abstract');
10 const DBErrors = require('../../../src/db/errors');
11
12 describe('DatabaseBase', function () {
13 let db, logger, stubDb;
14 before(function () {
15 logger = new StubLogger();
16 logger._reset();
17 stubDb = new StubDatabase();
18 });
19 beforeEach(function () {
20 db = new DB(logger, {});
21 });
22 afterEach(function () {
23 sinon.restore();
24 });
25
26 it('covers no options', function () {
27 db = new DB();
28 });
29
30 describe('Interface', function () {
31 it('covers abstract methods', async function () {
32 await Promise.all(stubDb._implementation.map(async (m) => {
33 try {
34 // eslint-disable-next-line security/detect-object-injection
35 await db[m]();
36 assert.fail(`${m}: did not catch NotImplemented exception`);
37 } catch (e) {
38 assert(e instanceof DBErrors.NotImplemented, `${m}: unexpected exception ${e.name}`);
39 }
40 }));
41 }); // covers abstract methods
42 it('covers private abstract methods', async function () {
43 [
44 '_currentSchema',
45 ].forEach((m) => {
46 try {
47 // eslint-disable-next-line security/detect-object-injection
48 db[m]();
49 } catch (e) {
50 assert(e instanceof DBErrors.NotImplemented, `${m}: unexpected exception ${e.name}`);
51 }
52 });
53 });
54 }); // Interface
55
56 describe('_ensureTypes', function () {
57 let object;
58 beforeEach(function () {
59 object = {
60 array: ['foo', 'bar'],
61 bignum: BigInt(456),
62 buf: Buffer.from('foop'),
63 date: new Date(),
64 infP: Infinity,
65 infN: -Infinity,
66 num: 123,
67 obj: {},
68 str: 'some words',
69 uuid: 'a4dd5106-2d64-11ed-a2ba-0025905f714a',
70 veryNull: null,
71 };
72 });
73 it('succeeds', function () {
74 db._ensureTypes(object, ['array'], ['array']);
75 db._ensureTypes(object, ['bignum'], ['bigint']);
76 db._ensureTypes(object, ['bignum', 'num'], ['number']);
77 db._ensureTypes(object, ['buf'], ['buffer']);
78 db._ensureTypes(object, ['date'], ['date']);
79 db._ensureTypes(object, ['infP', 'infN'], ['infinites']);
80 db._ensureTypes(object, ['str', 'veryNull'], ['string', 'null']);
81 });
82 it('data failure', function () {
83 assert.throws(() => db._ensureTypes(object, ['missingField'], ['string', 'null']), DBErrors.DataValidation);
84 });
85 it('failure covers singular', function () {
86 try {
87 db._ensureTypes(object, ['missingField'], ['string']);
88 assert.fail('validation should have failed');
89 } catch (e) {
90 assert(e instanceof DBErrors.DataValidation);
91 }
92 });
93 it('parameter failure', function () {
94 try {
95 db._ensureTypes(object, ['missingField'], undefined);
96 assert.fail('validation should have failed');
97 } catch (e) {
98 assert(e instanceof DBErrors.DataValidation);
99 }
100 });
101 it('covers unknown type', function () {
102 assert.throws(() => db._ensureTypes(object, ['field'], ['not a type']));
103 });
104 }); // _ensureTypes
105
106 describe('_validateAuthentication', function () {
107 let authentication;
108 beforeEach(function () {
109 authentication = {
110 identifier: 'username',
111 credential: '$plain$secret',
112 created: new Date(),
113 lastAuthentication: -Infinity,
114 };
115 });
116 it('covers', function () {
117 db._validateAuthentication(authentication);
118 });
119 it('covers failure', function () {
120 assert.throws(() => db._validateAuthentication(undefined), DBErrors.DataValidation);
121 });
122 }); // _validateAuthentication
123
124 describe('_validateResource', function () {
125 let resource;
126 beforeEach(function () {
127 resource = {
128 resourceId: '42016c1e-2d66-11ed-9e10-0025905f714a',
129 secret: 'secretSecret',
130 description: 'Some other service',
131 created: new Date(),
132 };
133 });
134 it('covers', function () {
135 db._validateResource(resource);
136 });
137 it('covers failure', function () {
138 assert.throws(() => db._validateResource(undefined), DBErrors.DataValidation);
139 });
140 }); // _validateResource
141
142 describe('_validateToken', function () {
143 let token;
144 beforeEach(function () {
145 token = {
146 codeId: '9efc7882-2d66-11ed-b03c-0025905f714a',
147 profile: 'https://profile.example.com/',
148 resource: null,
149 clientId: 'https://app.example.com/',
150 created: new Date(),
151 expires: new Date(),
152 refreshExpires: null,
153 refreshed: null,
154 isToken: true,
155 isRevoked: false,
156 scopes: ['scope'],
157 profileData: {
158 name: 'User von Namey',
159 },
160 };
161 });
162 it('covers', function () {
163 db._validateToken(token);
164 });
165 it('covers failure', function () {
166 assert.throws(() => db._validateToken(undefined), DBErrors.DataValidation);
167 });
168 }); // _validateToken
169
170 describe('_profilesScopesBuilder', function () {
171 it('covers empty', function () {
172 const result = DB._profilesScopesBuilder();
173 assert.deepStrictEqual(result, {
174 profileScopes: {},
175 scopeIndex: {},
176 profiles: [],
177 });
178 });
179 it('builds expected structure', function () {
180 const profileScopesRows = [
181 { profile: 'https://scopeless.example.com/', scope: null, description: null, application: null, isPermanent: null, isManuallyAdded: null },
182 { profile: 'https://profile.example.com/', scope: 'role:private', description: 'level', application: '', isPermanent: false, isManuallyAdded: true },
183 { profile: null, scope: 'profile', description: 'profile', application: 'IndieAuth', isPermanent: true, isManuallyAdded: false },
184 { profile: null, scope: 'role:private', description: 'level', application: '', isPermanent: false, isManuallyAdded: true },
185 { profile: null, scope: 'read', description: 'read', application: 'MicroPub', isPermanent: true, isManuallyAdded: false },
186 { profile: 'https://profile.example.com/', scope: 'profile', description: 'profile', application: 'IndieAuth', isPermanent: true, isManuallyAdded: false },
187 { profile: 'https://another.example.com/', scope: 'profile', description: 'profile', application: 'IndieAuth', isPermanent: true, isManuallyAdded: false },
188 ];
189 const expected = {
190 profileScopes: {
191 'https://scopeless.example.com/': {},
192 'https://profile.example.com/': {},
193 'https://another.example.com/': {},
194 },
195 scopeIndex: {
196 'role:private': {
197 description: 'level',
198 application: '',
199 isPermanent: false,
200 isManuallyAdded: true,
201 profiles: ['https://profile.example.com/'],
202 },
203 'profile': {
204 description: 'profile',
205 application: 'IndieAuth',
206 isPermanent: true,
207 isManuallyAdded: false,
208 profiles: ['https://profile.example.com/', 'https://another.example.com/'],
209 },
210 'read': {
211 description: 'read',
212 application: 'MicroPub',
213 isPermanent: true,
214 isManuallyAdded: false,
215 profiles: [],
216 },
217 },
218 profiles: ['https://scopeless.example.com/', 'https://profile.example.com/', 'https://another.example.com/'],
219 };
220 expected.profileScopes['https://profile.example.com/']['role:private'] = expected.scopeIndex['role:private'];
221 expected.profileScopes['https://profile.example.com/']['profile'] = expected.scopeIndex['profile'];
222 expected.profileScopes['https://another.example.com/']['profile'] = expected.scopeIndex['profile'];
223
224 const result = DB._profilesScopesBuilder(profileScopesRows);
225 assert.deepStrictEqual(result, expected);
226 });
227 }); // _profilesScopesBuilder
228
229 describe('initialize', function () {
230 let currentSchema;
231 beforeEach(function () {
232 currentSchema = {
233 major: 1,
234 minor: 0,
235 patch: 0,
236 };
237 db.schemaVersionsSupported = {
238 min: { ...currentSchema },
239 max: { ...currentSchema },
240 };
241 sinon.stub(db, '_currentSchema').resolves(currentSchema);
242 });
243 it('covers success', async function () {
244 await db.initialize();
245 });
246 it('covers failure', async function() {
247 db.schemaVersionsSupported = {
248 min: {
249 major: 3,
250 minor: 2,
251 patch: 1,
252 },
253 max: {
254 major: 5,
255 minor: 0,
256 patch: 0,
257 },
258 };
259 try {
260 await db.initialize();
261 assert.fail('did not get expected exception');
262 } catch (e) {
263 assert(e instanceof DBErrors.MigrationNeeded);
264 }
265 });
266 }); // initialize
267
268 }); // DatabaseBase