9d7dbf9dbe3ae8cff770008b7afb42425c7a3362
[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 ].map((m) => {
45 try {
46 // eslint-disable-next-line security/detect-object-injection
47 db[m]();
48 } catch (e) {
49 assert(e instanceof DBErrors.NotImplemented, `${m}: unexpected exception ${e.name}`);
50 }
51 });
52 });
53 }); // Interface
54
55 describe('_isUUID', function () {
56 it('is a uuid', function () {
57 const result = DB._isUUID('8fde351e-2d63-11ed-8b0c-0025905f714a');
58 assert.strictEqual(result, true);
59 });
60 it('is not a uuid', function () {
61 const result = DB._isUUID('not a uuid');
62 assert.strictEqual(result, false);
63 });
64 });
65
66 describe('_isInfinites', function () {
67 it('is true for Infinity', function () {
68 const result = DB._isInfinites(Infinity);
69 assert.strictEqual(result, true);
70 });
71 it('is true for negative Infinity', function () {
72 const result = DB._isInfinites(-Infinity);
73 assert.strictEqual(result, true);
74 });
75 it('is false for finite value', function () {
76 const result = DB._isInfinites(5);
77 assert.strictEqual(result, false);
78 });
79 it('is false for NaN', function () {
80 const result = DB._isInfinites(NaN);
81 assert.strictEqual(result, false);
82 });
83 });
84
85 describe('_ensureTypes', function () {
86 let object;
87 beforeEach(function () {
88 object = {
89 array: ['foo', 'bar'],
90 bignum: BigInt(456),
91 buf: Buffer.from('foop'),
92 date: new Date(),
93 num: 123,
94 obj: {},
95 str: 'some words',
96 uuid: 'a4dd5106-2d64-11ed-a2ba-0025905f714a',
97 veryNull: null,
98 };
99 });
100 it('succeeds', function () {
101 db._ensureTypes(object, ['array'], ['array']);
102 db._ensureTypes(object, ['bignum', 'num'], ['number']);
103 db._ensureTypes(object, ['buf'], ['buffer']);
104 db._ensureTypes(object, ['date'], ['date']);
105 db._ensureTypes(object, ['str', 'veryNull'], ['string', 'null']);
106 });
107 it('data failure', function () {
108 try {
109 db._ensureTypes(object, ['missingField'], ['string', 'null']);
110 assert.fail('validation should have failed');
111 } catch (e) {
112 assert(e instanceof DBErrors.DataValidation);
113 }
114 });
115 it('failure covers singular', function () {
116 try {
117 db._ensureTypes(object, ['missingField'], ['string']);
118 assert.fail('validation should have failed');
119 } catch (e) {
120 assert(e instanceof DBErrors.DataValidation);
121 }
122 });
123 it('parameter failure', function () {
124 try {
125 db._ensureTypes(object, ['missingField'], undefined);
126 assert.fail('validation should have failed');
127 } catch (e) {
128 assert(e instanceof DBErrors.DataValidation);
129 }
130 });
131 }); // _ensureTypes
132
133 describe('_validateAuthentication', function () {
134 let authentication;
135 beforeEach(function () {
136 authentication = {
137 identifier: 'username',
138 credential: '$plain$secret',
139 created: new Date(),
140 lastAuthenticated: -Infinity,
141 };
142 });
143 it('covers', function () {
144 db._validateAuthentication(authentication);
145 });
146 it('covers failure', function () {
147 assert.throws(() => db._validateAuthentication(undefined), DBErrors.DataValidation);
148 });
149 }); // _validateAuthentication
150
151 describe('_validateResource', function () {
152 let resource;
153 beforeEach(function () {
154 resource = {
155 resourceId: '42016c1e-2d66-11ed-9e10-0025905f714a',
156 secret: 'secretSecret',
157 description: 'Some other service',
158 created: new Date(),
159 };
160 });
161 it('covers', function () {
162 db._validateResource(resource);
163 });
164 it('covers failure', function () {
165 assert.throws(() => db._validateResource(undefined), DBErrors.DataValidation);
166 });
167 }); // _validateResource
168
169 describe('_validateToken', function () {
170 let token;
171 beforeEach(function () {
172 token = {
173 codeId: '9efc7882-2d66-11ed-b03c-0025905f714a',
174 profile: 'https://profile.example.com/',
175 resource: null,
176 clientId: 'https://app.example.com/',
177 created: new Date(),
178 expires: new Date(),
179 refreshExpires: null,
180 refreshed: null,
181 isToken: true,
182 isRevoked: false,
183 scopes: ['scope'],
184 profileData: {
185 name: 'User von Namey',
186 },
187 };
188 });
189 it('covers', function () {
190 db._validateToken(token);
191 });
192 it('covers failure', function () {
193 assert.throws(() => db._validateToken(undefined), DBErrors.DataValidation);
194 });
195 }); // _validateToken
196
197 describe('_profilesScopesBuilder', function () {
198 it('covers empty', function () {
199 const result = DB._profilesScopesBuilder();
200 assert.deepStrictEqual(result, {
201 profileScopes: {},
202 scopeIndex: {},
203 profiles: [],
204 });
205 });
206 it('builds expected structure', function () {
207 const profileScopesRows = [
208 { profile: 'https://scopeless.example.com/', scope: null, description: null, application: null, isPermanent: null, isManuallyAdded: null },
209 { profile: 'https://profile.example.com/', scope: 'role:private', description: 'level', application: '', isPermanent: false, isManuallyAdded: true },
210 { profile: null, scope: 'profile', description: 'profile', application: 'IndieAuth', isPermanent: true, isManuallyAdded: false },
211 { profile: null, scope: 'role:private', description: 'level', application: '', isPermanent: false, isManuallyAdded: true },
212 { profile: null, scope: 'read', description: 'read', application: 'MicroPub', isPermanent: true, isManuallyAdded: false },
213 { profile: 'https://profile.example.com/', scope: 'profile', description: 'profile', application: 'IndieAuth', isPermanent: true, isManuallyAdded: false },
214 { profile: 'https://another.example.com/', scope: 'profile', description: 'profile', application: 'IndieAuth', isPermanent: true, isManuallyAdded: false },
215 ];
216 const expected = {
217 profileScopes: {
218 'https://scopeless.example.com/': {},
219 'https://profile.example.com/': {},
220 'https://another.example.com/': {},
221 },
222 scopeIndex: {
223 'role:private': {
224 description: 'level',
225 application: '',
226 isPermanent: false,
227 isManuallyAdded: true,
228 profiles: ['https://profile.example.com/'],
229 },
230 'profile': {
231 description: 'profile',
232 application: 'IndieAuth',
233 isPermanent: true,
234 isManuallyAdded: false,
235 profiles: ['https://profile.example.com/', 'https://another.example.com/'],
236 },
237 'read': {
238 description: 'read',
239 application: 'MicroPub',
240 isPermanent: true,
241 isManuallyAdded: false,
242 profiles: [],
243 },
244 },
245 profiles: ['https://scopeless.example.com/', 'https://profile.example.com/', 'https://another.example.com/'],
246 };
247 expected.profileScopes['https://profile.example.com/']['role:private'] = expected.scopeIndex['role:private'];
248 expected.profileScopes['https://profile.example.com/']['profile'] = expected.scopeIndex['profile'];
249 expected.profileScopes['https://another.example.com/']['profile'] = expected.scopeIndex['profile'];
250
251 const result = DB._profilesScopesBuilder(profileScopesRows);
252 assert.deepStrictEqual(result, expected);
253 });
254 }); // _profilesScopesBuilder
255
256 describe('initialize', function () {
257 let currentSchema;
258 beforeEach(function () {
259 currentSchema = {
260 major: 1,
261 minor: 0,
262 patch: 0,
263 };
264 db.schemaVersionsSupported = {
265 min: { ...currentSchema },
266 max: { ...currentSchema },
267 };
268 sinon.stub(db, '_currentSchema').resolves(currentSchema);
269 });
270 it('covers success', async function () {
271 await db.initialize();
272 });
273 it('covers failure', async function() {
274 db.schemaVersionsSupported = {
275 min: {
276 major: 3,
277 minor: 2,
278 patch: 1,
279 },
280 max: {
281 major: 5,
282 minor: 0,
283 patch: 0,
284 },
285 };
286 try {
287 await db.initialize();
288 assert.fail('did not get expected exception');
289 } catch (e) {
290 assert(e instanceof DBErrors.MigrationNeeded);
291 }
292 });
293 }); // initialize
294
295 }); // DatabaseBase