renamed database schemaCheck method to initialize
[websub-hub] / test / src / db / integration.js
1 /* eslint-env mocha */
2 /* eslint-disable sonarjs/no-identical-functions */
3 'use strict';
4
5 /**
6 * These are LIVE FIRE tests to exercise actual database operations.
7 * They should be configured to use local test databases, as they
8 * perform DESTRUCTIVE ACTIONS on all tables, beginning with a COMPLETE
9 * DATA WIPE.
10 *
11 * They will only run if all the appropriate environmental settings exist:
12 * - INTEGRATION_TESTS must be set
13 * - <ENGINE>_TEST_PATH must point to the endpoint/db
14 *
15 * These tests are sequential, relying on the state created along the way.
16 *
17 */
18
19 const assert = require('assert');
20 const { step } = require('mocha-steps'); // eslint-disable-line node/no-unpublished-require
21 const stubLogger = require('../../stub-logger');
22 const DBErrors = require('../../../src/db/errors');
23 const testData = require('../../test-data/db-integration');
24
25 describe('Database Integration', function () {
26 const noExpectedException = 'did not receive expected exception';
27 const implementations = [];
28
29 if (!process.env.INTEGRATION_TESTS) {
30 it.skip('integration tests not requested');
31 return;
32 }
33
34 if (process.env.POSTGRES_TEST_PATH) {
35 implementations.push({
36 name: 'PostgreSQL',
37 module: '../../../src/db/postgres',
38 config: {
39 db: {
40 connectionString: `postgresql://${process.env.POSTGRES_TEST_PATH}`,
41 queryLogLevel: 'debug',
42 noWarnings: true,
43 },
44 },
45 });
46 }
47
48 if (process.env.SQLITE_TEST_PATH) {
49 implementations.push({
50 name: 'SQLite',
51 module: '../../../src/db/sqlite',
52 config: {
53 db: {
54 connectionString: `sqlite://${process.env.SQLITE_TEST_PATH}`,
55 queryLogLevel: 'debug',
56 },
57 },
58 });
59 }
60
61 implementations.forEach(function (i) {
62 describe(i.name, function () {
63 let DB, db;
64 let topicId, subscriptionId, verificationId;
65 const claimant = '96bff010-d9e6-11eb-b95d-0025905f714a';
66
67 before(async function () {
68 this.timeout(10 * 1000); // Allow some time for creating tables et cetera.
69 // eslint-disable-next-line security/detect-non-literal-require
70 DB = require(i.module);
71 db = new DB(stubLogger, i.config);
72 await db.initialize();
73 await db._purgeTables(true);
74 });
75 after(async function () {
76 await db._closeConnection();
77 });
78 it('instantiated', function () {
79 assert(db);
80 });
81
82 describe('Authentication', function () {
83 let identifier, credential;
84 beforeEach(function () {
85 identifier = 'username';
86 credential = 'myEncryptedPassword';
87 });
88 step('create auth entry', async function() {
89 await db.context(async (dbCtx) => {
90 await db.authenticationUpsert(dbCtx, identifier, credential);
91 });
92 });
93 step('get auth entry', async function() {
94 await db.context(async (dbCtx) => {
95 const authInfo = await db.authenticationGet(dbCtx, identifier);
96 assert.strictEqual(authInfo.credential, credential);
97 });
98 });
99 step('valid auth event', async function() {
100 await db.context(async (dbCtx) => {
101 await db.authenticationSuccess(dbCtx, identifier);
102 const authInfo = await db.authenticationGet(dbCtx, identifier);
103 assert.notStrictEqual(authInfo.lastAuthentication, undefined);
104 });
105 });
106 step('update auth entry', async function() {
107 await db.context(async (dbCtx) => {
108 credential = 'myNewPassword';
109 await db.authenticationUpsert(dbCtx, identifier, credential);
110 const authInfo = await db.authenticationGet(dbCtx, identifier);
111 assert.strictEqual(authInfo.credential, credential);
112 });
113 });
114 }); // Authentication
115
116 describe('Topic', function () {
117 step('requires data', async function () {
118 try {
119 await db.context(async (dbCtx) => {
120 await db.topicSet(dbCtx);
121 });
122 assert.fail(noExpectedException);
123 } catch (e) {
124 assert(e instanceof DBErrors.DataValidation);
125 }
126 });
127 step('creates topic', async function () {
128 await db.context(async (dbCtx) => {
129 const result = await db.topicSet(dbCtx, testData.topicSet);
130 topicId = result.lastInsertRowid;
131 assert.strictEqual(result.changes, 1);
132 });
133 });
134 step('gets topic by url', async function () {
135 await db.context(async (dbCtx) => {
136 const topic = await db.topicGetByUrl(dbCtx, testData.topicSet.url);
137 assert.strictEqual(topic.url, testData.topicSet.url);
138 });
139 });
140 step('updates topic', async function () {
141 await db.context(async(dbCtx) => {
142 const result = await db.topicSet(dbCtx, testData.topicUpdate);
143 assert.strictEqual(result.changes, 1);
144 });
145 });
146 step('also updates topic', async function () {
147 const data = {
148 topicId,
149 leaseSecondsMin: 60,
150 }
151 await db.context(async(dbCtx) => {
152 let topic = await db.topicGetByUrl(dbCtx, testData.topicSet.url);
153 await db.topicUpdate(dbCtx, { ...topic, ...data });
154 topic = await db.topicGetByUrl(dbCtx, testData.topicSet.url);
155 assert.strictEqual(Number(topic.leaseSecondsMin), data.leaseSecondsMin);
156 });
157 });
158 step('gets topic by id', async function () {
159 await db.context(async (dbCtx) => {
160 const topic = await db.topicGetById(dbCtx, topicId);
161 assert.strictEqual(topic.url, testData.topicSet.url);
162 assert.strictEqual(Number(topic.leaseSecondsPreferred), testData.topicUpdate.leaseSecondsPreferred);
163 });
164 });
165 step('sets topic content', async function () {
166 const data = {
167 ...testData.topicSetContent,
168 topicId,
169 };
170 await db.context(async (dbCtx) => {
171 const result = await db.topicSetContent(dbCtx, data);
172 assert.strictEqual(result.changes, 1);
173 });
174 });
175 step('gets topic content', async function () {
176 await db.context(async (dbCtx) => {
177 const topic = await db.topicGetContentById(dbCtx, topicId);
178 assert.strictEqual(topic.contentHash, testData.topicSetContent.contentHash);
179 });
180 });
181 step('sets publish request', async function() {
182 await db.context(async (dbCtx) => {
183 const result = await db.topicFetchRequested(dbCtx, topicId);
184 assert.strictEqual(result.changes, 1);
185
186 const topic = await db.topicGetById(dbCtx, topicId);
187 assert(topic.lastPublish);
188 });
189 });
190 step('claims topic fetch', async function () {
191 const claimTimeoutSeconds = 10;
192 const wanted = 5;
193 let topicIds;
194 await db.context(async (dbCtx) => {
195 topicIds = await db.topicFetchClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
196 });
197 assert(topicIds.includes(topicId));
198 });
199 step('incompletes topic fetch', async function () {
200 await db.context(async (dbCtx) => {
201 const result = await db.topicFetchIncomplete(dbCtx, topicId);
202 assert.strictEqual(result.changes, 1);
203 const topic = await db.topicGetById(dbCtx, topicId);
204 assert.strictEqual(Number(topic.contentFetchAttemptsSinceSuccess), 1);
205 });
206 });
207 step('claims topic fetch by id', async function () {
208 const claimTimeoutSeconds = 10;
209 await db.context(async (dbCtx) => {
210 const result = await db.topicFetchClaimById(dbCtx, topicId, claimTimeoutSeconds, claimant);
211 assert.strictEqual(result.changes, 1);
212 });
213 });
214 step('completes topic fetch', async function () {
215 await db.context(async (dbCtx) => {
216 const result = await db.topicFetchComplete(dbCtx, topicId);
217 assert.strictEqual(result.changes, 1);
218 const topic = await db.topicGetById(dbCtx, topicId);
219 assert.strictEqual(Number(topic.contentFetchAttemptsSinceSuccess), 0);
220 });
221 });
222 step('deletes a topic', async function () {
223 await db.context(async (dbCtx) => {
224 const result = await db.topicSet(dbCtx, testData.anotherTopicSet);
225 const anotherTopicId = result.lastInsertRowid;
226 await db.topicDeleted(dbCtx, anotherTopicId);
227 const topic = await db.topicGetById(dbCtx, anotherTopicId);
228 assert.strictEqual(topic.isDeleted, true);
229 });
230 });
231 step('update un-deletes a topic', async function () {
232 await db.context(async (dbCtx) => {
233 const result = await db.topicSet(dbCtx, testData.anotherTopicSet);
234 const anotherTopicId = result.lastInsertRowid;
235 const topic = await db.topicGetById(dbCtx, anotherTopicId);
236 assert.strictEqual(topic.isDeleted, false);
237 });
238 });
239 step('gets all topics', async function() {
240 await db.context(async (dbCtx) => {
241 const topics = await db.topicGetAll(dbCtx);
242 assert(topics.length);
243 });
244 });
245 }); // Topic
246
247 describe('Subscription', function () {
248 step('requires data', async function () {
249 try {
250 await db.context(async (dbCtx) => {
251 await db.subscriptionUpsert(dbCtx);
252 });
253 assert.fail(noExpectedException);
254 } catch (e) {
255 assert(e instanceof DBErrors.DataValidation);
256 }
257 });
258 step('creates subscription', async function () {
259 const data = {
260 ...testData.subscriptionUpsert,
261 topicId,
262 }
263 await db.context(async (dbCtx) => {
264 const result = await db.subscriptionUpsert(dbCtx, data);
265 assert(result.lastInsertRowid);
266 subscriptionId = result.lastInsertRowid;
267 assert.strictEqual(result.changes, 1);
268 });
269 });
270 step('gets subscription', async function () {
271 await db.context(async (dbCtx) => {
272 const subscription = await db.subscriptionGet(dbCtx, testData.subscriptionUpsert.callback, topicId);
273 assert.strictEqual(subscription.secret, testData.subscriptionUpsert.secret);
274 });
275 });
276 step('gets subscription by id', async function () {
277 await db.context(async (dbCtx) => {
278 const subscription = await db.subscriptionGetById(dbCtx, subscriptionId);
279 assert.strictEqual(subscription.secret, testData.subscriptionUpsert.secret);
280 });
281 });
282 step('gets subscriptions by topic', async function() {
283 await db.context(async (dbCtx) => {
284 const subscriptions = await db.subscriptionsByTopicId(dbCtx, topicId);
285 assert(subscriptions.length);
286 });
287 });
288 step('count subscriptions', async function () {
289 await db.context(async (dbCtx) => {
290 const count = await db.subscriptionCountByTopicUrl(dbCtx, testData.topicSet.url);
291 assert.strictEqual(Number(count.count), 1);
292 });
293 });
294 step('claim subscription', async function () {
295 const claimTimeoutSeconds = 10;
296 const wanted = 5;
297 let subscriptionIds;
298 await db.context(async (dbCtx) => {
299 subscriptionIds = await db.subscriptionDeliveryClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
300 });
301 assert(subscriptionIds.includes(subscriptionId));
302 });
303 step('incompletes subscription', async function () {
304 const { callback } = testData.subscriptionUpsert;
305 await db.context(async (dbCtx) => {
306 await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId);
307 const topic = await db.subscriptionGetById(dbCtx, subscriptionId);
308 assert.strictEqual(Number(topic.deliveryAttemptsSinceSuccess), 1);
309 });
310 });
311 step('claim subscription by id', async function () {
312 const claimTimeoutSeconds = 10;
313 await db.context(async (dbCtx) => {
314 const result = await db.subscriptionDeliveryClaimById(dbCtx, subscriptionId, claimTimeoutSeconds, claimant);
315 assert.strictEqual(result.changes, 1);
316 });
317 });
318 step('complete subscription', async function () {
319 const { callback } = testData.subscriptionUpsert;
320 await db.context(async (dbCtx) => {
321 await db.subscriptionDeliveryComplete(dbCtx, callback, topicId);
322 const subscription = await db.subscriptionGetById(dbCtx, subscriptionId);
323 assert.strictEqual(Number(subscription.deliveryAttemptsSinceSuccess), 0);
324 });
325 });
326 step('subscription delete', async function () {
327 const { callback } = testData.subscriptionUpsert;
328 await db.context(async (dbCtx) => {
329 const result = await db.subscriptionDelete(dbCtx, callback, topicId);
330 assert.strictEqual(result.changes, 1);
331 const subscription = await db.subscriptionGetById(dbCtx, subscriptionId);
332 assert(!subscription);
333 });
334 });
335 step('create subscription', async function () {
336 const data = {
337 ...testData.subscriptionUpsert,
338 secret: 'newSecret',
339 topicId,
340 }
341 await db.context(async (dbCtx) => {
342 const result = await db.subscriptionUpsert(dbCtx, data);
343 assert(result.lastInsertRowid);
344 assert.notStrictEqual(result.lastInsertRowid, subscriptionId);
345 subscriptionId = result.lastInsertRowid;
346 assert.strictEqual(result.changes, 1);
347 });
348 });
349 step('update subscription', async function () {
350 const data = {
351 subscriptionId,
352 signatureAlgorithm: 'sha256',
353 };
354 await db.context(async (dbCtx) => {
355 await db.subscriptionUpdate(dbCtx, data);
356 });
357 });
358 step('claim subscription', async function () {
359 const claimTimeoutSeconds = 10;
360 const wanted = 5;
361 let subscriptionIds;
362 await db.context(async (dbCtx) => {
363 subscriptionIds = await db.subscriptionDeliveryClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
364 });
365 assert(subscriptionIds.includes(subscriptionId));
366 });
367 step('subscription gone', async function () {
368 const { callback } = testData.subscriptionUpsert;
369 await db.context(async (dbCtx) => {
370 await db.subscriptionDeliveryGone(dbCtx, callback, topicId);
371 const subscription = await db.subscriptionGetById(dbCtx, subscriptionId);
372 assert(!subscription);
373 });
374 });
375 }); // Subscription
376
377 describe('Verification', function () {
378 step('requires data', async function() {
379 try {
380 await db.context(async (dbCtx) => {
381 await db.verificationInsert(dbCtx);
382 });
383 assert.fail(noExpectedException);
384 } catch (e) {
385 assert(e instanceof DBErrors.DataValidation);
386 }
387 });
388 step('creates verification', async function() {
389 const verificationData = {
390 ...testData.verificationInsert,
391 topicId,
392 };
393 await db.context(async (dbCtx) => {
394 verificationId = await db.verificationInsert(dbCtx, verificationData);
395 assert(verificationId);
396 });
397 });
398 step('gets verification', async function() {
399 await db.context(async (dbCtx) => {
400 const verification = await db.verificationGetById(dbCtx, verificationId);
401 assert.strictEqual(verification.mode, testData.verificationInsert.mode);
402 });
403 });
404 step('validates verification', async function() {
405 await db.context(async (dbCtx) => {
406 await db.verificationValidated(dbCtx, verificationId);
407 const verification = await db.verificationGetById(dbCtx, verificationId);
408 assert.strictEqual(verification.isPublisherValidated, true);
409 });
410 });
411 step('claims verification', async function() {
412 const claimTimeoutSeconds = 10;
413 const wanted = 5;
414 let verificationIds;
415 await db.context(async (dbCtx) => {
416 verificationIds = await db.verificationClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
417 });
418 assert(verificationIds.includes(verificationId));
419 });
420 step('releases verification', async function() {
421 await db.context(async (dbCtx) => {
422 await db.verificationRelease(dbCtx, verificationId);
423 });
424 });
425 step('updates verification', async function() {
426 const verificationData = {
427 ...testData.verificationUpdate,
428 };
429 await db.context(async (dbCtx) => {
430 db.verificationUpdate(dbCtx, verificationId, verificationData);
431 const verification = await db.verificationGetById(dbCtx, verificationId);
432 assert.strictEqual(verification.isPublisherValidated, testData.verificationUpdate.isPublisherValidated);
433 });
434 });
435 step('claims verification by id', async function() {
436 const claimTimeoutSeconds = 10;
437 await db.context(async (dbCtx) => {
438 const result = await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
439 assert.strictEqual(result.changes, 1);
440 });
441 });
442 step('incompletes verification', async function() {
443 await db.context(async (dbCtx) => {
444 await db.verificationIncomplete(dbCtx, verificationId);
445 });
446 });
447 step('claims verification by id', async function() {
448 const claimTimeoutSeconds = 10;
449 await db.context(async (dbCtx) => {
450 const result = await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
451 assert.strictEqual(result.changes, 1);
452 });
453 });
454 step('completes verification', async function() {
455 await db.context(async (dbCtx) => {
456 const verification = await db.verificationGetById(dbCtx, verificationId);
457 await db.subscriptionUpsert(dbCtx, verification);
458 await db.verificationComplete(dbCtx, verificationId, testData.verificationInsert.callback, topicId);
459 const count = await db.subscriptionCountByTopicUrl(dbCtx, testData.topicSet.url);
460 assert.strictEqual(Number(count.count), 1);
461 });
462 });
463
464 }); // Verification
465
466 }); // specific implementation
467 }); // foreach
468
469 }); // Database Integration