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