2 /* eslint-disable sonarjs/no-identical-functions */
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
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
15 * These tests are sequential, relying on the state created along the way.
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');
25 describe('Database Integration', function () {
26 const noExpectedException
= 'did not receive expected exception';
27 const implementations
= [];
29 if (!process
.env
.INTEGRATION_TESTS
) {
30 it
.skip('integration tests not requested');
34 if (process
.env
.POSTGRES_TEST_PATH
) {
35 implementations
.push({
37 module: '../../../src/db/postgres',
40 connectionString: `postgresql://${process.env.POSTGRES_TEST_PATH}`,
41 queryLogLevel: 'debug',
48 if (process
.env
.SQLITE_TEST_PATH
) {
49 implementations
.push({
51 module: '../../../src/db/sqlite',
54 connectionString: `sqlite://${process.env.SQLITE_TEST_PATH}`,
55 queryLogLevel: 'debug',
61 implementations
.forEach(function (i
) {
62 describe(i
.name
, function () {
64 let topicId
, subscriptionId
, verificationId
;
65 const claimant
= '96bff010-d9e6-11eb-b95d-0025905f714a';
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);
75 after(async
function () {
76 await db
._closeConnection();
78 it('instantiated', function () {
82 describe('Authentication', function () {
83 let identifier
, credential
;
84 beforeEach(function () {
85 identifier
= 'username';
86 credential
= 'myEncryptedPassword';
88 step('create auth entry', async
function() {
89 await db
.context(async (dbCtx
) => {
90 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
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
);
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);
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
);
114 }); // Authentication
116 describe('Topic', function () {
118 step('requires data', async
function () {
120 await db
.context(async (dbCtx
) => {
121 await db
.topicSet(dbCtx
);
123 assert
.fail(noExpectedException
);
125 assert(e
instanceof DBErrors
.DataValidation
);
128 step('creates topic', async
function () {
129 await db
.context(async (dbCtx
) => {
130 const result
= await db
.topicSet(dbCtx
, testData
.topicSet
);
131 topicId
= result
.lastInsertRowid
;
132 assert
.strictEqual(result
.changes
, 1);
135 step('gets topic by url', async
function () {
136 await db
.context(async (dbCtx
) => {
137 const topic
= await db
.topicGetByUrl(dbCtx
, testData
.topicSet
.url
);
138 assert
.strictEqual(topic
.url
, testData
.topicSet
.url
);
141 step('updates topic', async
function () {
142 await db
.context(async(dbCtx
) => {
143 const result
= await db
.topicSet(dbCtx
, testData
.topicUpdate
);
144 assert
.strictEqual(result
.changes
, 1);
147 step('also updates topic', async
function () {
152 await db
.context(async(dbCtx
) => {
153 let topic
= await db
.topicGetByUrl(dbCtx
, testData
.topicSet
.url
);
154 await db
.topicUpdate(dbCtx
, { ...topic
, ...data
});
155 topic
= await db
.topicGetByUrl(dbCtx
, testData
.topicSet
.url
);
156 assert
.strictEqual(Number(topic
.leaseSecondsMin
), data
.leaseSecondsMin
);
159 step('gets topic by id', async
function () {
160 await db
.context(async (dbCtx
) => {
161 const topic
= await db
.topicGetById(dbCtx
, topicId
);
162 assert
.strictEqual(topic
.url
, testData
.topicSet
.url
);
163 assert
.strictEqual(Number(topic
.leaseSecondsPreferred
), testData
.topicUpdate
.leaseSecondsPreferred
);
166 step('sets topic content', async
function () {
168 ...testData
.topicSetContent
,
171 await db
.context(async (dbCtx
) => {
172 const result
= await db
.topicSetContent(dbCtx
, data
);
173 assert
.strictEqual(result
.changes
, 1);
176 step('gets topic content', async
function () {
177 await db
.context(async (dbCtx
) => {
178 const topic
= await db
.topicGetContentById(dbCtx
, topicId
);
179 assert
.strictEqual(topic
.contentHash
, testData
.topicSetContent
.contentHash
);
182 step('sets publish request', async
function() {
183 await db
.context(async (dbCtx
) => {
184 const result
= await db
.topicFetchRequested(dbCtx
, topicId
);
185 assert
.strictEqual(result
.changes
, 1);
187 const topic
= await db
.topicGetById(dbCtx
, topicId
);
188 assert(topic
.lastPublish
);
191 step('claims topic fetch', async
function () {
192 const claimTimeoutSeconds
= 10;
195 await db
.context(async (dbCtx
) => {
196 topicIds
= await db
.topicFetchClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
198 assert(topicIds
.includes(topicId
));
200 step('incompletes topic fetch', async
function () {
201 await db
.context(async (dbCtx
) => {
202 const result
= await db
.topicFetchIncomplete(dbCtx
, topicId
);
203 assert
.strictEqual(result
.changes
, 1);
204 const topic
= await db
.topicGetById(dbCtx
, topicId
);
205 assert
.strictEqual(Number(topic
.contentFetchAttemptsSinceSuccess
), 1);
208 step('claims topic fetch by id', async
function () {
209 const claimTimeoutSeconds
= 10;
210 await db
.context(async (dbCtx
) => {
211 const result
= await db
.topicFetchClaimById(dbCtx
, topicId
, claimTimeoutSeconds
, claimant
);
212 assert
.strictEqual(result
.changes
, 1);
215 step('completes topic fetch', async
function () {
216 await db
.context(async (dbCtx
) => {
217 const result
= await db
.topicFetchComplete(dbCtx
, topicId
);
218 assert
.strictEqual(result
.changes
, 1);
219 const topic
= await db
.topicGetById(dbCtx
, topicId
);
220 assert
.strictEqual(Number(topic
.contentFetchAttemptsSinceSuccess
), 0);
223 step('deletes a topic', async
function () {
224 await db
.context(async (dbCtx
) => {
225 const result
= await db
.topicSet(dbCtx
, testData
.anotherTopicSet
);
226 anotherTopicId
= result
.lastInsertRowid
;
227 await db
.topicDeleted(dbCtx
, anotherTopicId
);
228 const topic
= await db
.topicGetById(dbCtx
, anotherTopicId
);
229 assert
.strictEqual(topic
.isDeleted
, true);
232 step('update un-deletes a topic', async
function () {
233 await db
.context(async (dbCtx
) => {
234 const result
= await db
.topicSet(dbCtx
, testData
.anotherTopicSet
);
235 assert
.strictEqual(result
.lastInsertRowid
, anotherTopicId
);
236 const topic
= await db
.topicGetById(dbCtx
, anotherTopicId
);
237 assert
.strictEqual(topic
.isDeleted
, false);
240 step('gets all topics', async
function() {
241 await db
.context(async (dbCtx
) => {
242 const topics
= await db
.topicGetAll(dbCtx
);
243 assert(topics
.length
);
246 // pending delete of deleted topic with no subscriptions
247 step('really deletes unsubscribed deleted topic', async
function() {
248 await db
.context(async (dbCtx
) => {
249 await db
.topicDeleted(dbCtx
, anotherTopicId
);
250 await db
.topicPendingDelete(dbCtx
, anotherTopicId
);
251 const topic
= await db
.topicGetById(dbCtx
, anotherTopicId
);
257 describe('Subscription', function () {
258 step('requires data', async
function () {
260 await db
.context(async (dbCtx
) => {
261 await db
.subscriptionUpsert(dbCtx
);
263 assert
.fail(noExpectedException
);
265 assert(e
instanceof DBErrors
.DataValidation
);
268 step('creates subscription', async
function () {
270 ...testData
.subscriptionUpsert
,
273 await db
.context(async (dbCtx
) => {
274 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
275 assert(result
.lastInsertRowid
);
276 subscriptionId
= result
.lastInsertRowid
;
277 assert
.strictEqual(result
.changes
, 1);
280 step('gets subscription', async
function () {
281 await db
.context(async (dbCtx
) => {
282 const subscription
= await db
.subscriptionGet(dbCtx
, testData
.subscriptionUpsert
.callback
, topicId
);
283 assert
.strictEqual(subscription
.secret
, testData
.subscriptionUpsert
.secret
);
286 step('gets subscription by id', async
function () {
287 await db
.context(async (dbCtx
) => {
288 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
289 assert
.strictEqual(subscription
.secret
, testData
.subscriptionUpsert
.secret
);
292 step('gets subscriptions by topic', async
function() {
293 await db
.context(async (dbCtx
) => {
294 const subscriptions
= await db
.subscriptionsByTopicId(dbCtx
, topicId
);
295 assert(subscriptions
.length
);
298 step('count subscriptions', async
function () {
299 await db
.context(async (dbCtx
) => {
300 const count
= await db
.subscriptionCountByTopicUrl(dbCtx
, testData
.topicSet
.url
);
301 assert
.strictEqual(Number(count
.count
), 1);
304 step('claim subscription', async
function () {
305 const claimTimeoutSeconds
= 10;
308 await db
.context(async (dbCtx
) => {
309 subscriptionIds
= await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
311 assert(subscriptionIds
.includes(subscriptionId
));
313 step('incompletes subscription', async
function () {
314 const { callback
} = testData
.subscriptionUpsert
;
315 await db
.context(async (dbCtx
) => {
316 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
);
317 const topic
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
318 assert
.strictEqual(Number(topic
.deliveryAttemptsSinceSuccess
), 1);
321 step('claim subscription by id', async
function () {
322 const claimTimeoutSeconds
= 10;
323 await db
.context(async (dbCtx
) => {
324 const result
= await db
.subscriptionDeliveryClaimById(dbCtx
, subscriptionId
, claimTimeoutSeconds
, claimant
);
325 assert
.strictEqual(result
.changes
, 1);
328 step('complete subscription', async
function () {
329 const { callback
} = testData
.subscriptionUpsert
;
330 await db
.context(async (dbCtx
) => {
331 const topic
= await db
.topicGetById(dbCtx
, topicId
);
332 await db
.subscriptionDeliveryComplete(dbCtx
, callback
, topicId
, topic
.contentUpdated
);
333 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
334 assert
.strictEqual(Number(subscription
.deliveryAttemptsSinceSuccess
), 0);
337 step('subscription delete', async
function () {
338 const { callback
} = testData
.subscriptionUpsert
;
339 await db
.context(async (dbCtx
) => {
340 const result
= await db
.subscriptionDelete(dbCtx
, callback
, topicId
);
341 assert
.strictEqual(result
.changes
, 1);
342 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
343 assert(!subscription
);
346 step('create subscription', async
function () {
348 ...testData
.subscriptionUpsert
,
352 await db
.context(async (dbCtx
) => {
353 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
354 assert(result
.lastInsertRowid
);
355 assert
.notStrictEqual(result
.lastInsertRowid
, subscriptionId
);
356 subscriptionId
= result
.lastInsertRowid
;
357 assert
.strictEqual(result
.changes
, 1);
360 step('update subscription', async
function () {
363 signatureAlgorithm: 'sha256',
365 await db
.context(async (dbCtx
) => {
366 await db
.subscriptionUpdate(dbCtx
, data
);
369 step('claim subscription', async
function () {
370 const claimTimeoutSeconds
= 10;
373 await db
.context(async (dbCtx
) => {
374 subscriptionIds
= await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
376 assert(subscriptionIds
.includes(subscriptionId
));
378 step('subscription gone', async
function () {
379 const { callback
} = testData
.subscriptionUpsert
;
380 await db
.context(async (dbCtx
) => {
381 await db
.subscriptionDeliveryGone(dbCtx
, callback
, topicId
);
382 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
383 assert(!subscription
);
386 step('create expired subscription', async
function () {
388 ...testData
.subscriptionUpsert
,
393 await db
.context(async (dbCtx
) => {
394 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
395 assert(result
.lastInsertRowid
);
396 assert
.notStrictEqual(result
.lastInsertRowid
, subscriptionId
);
397 subscriptionId
= result
.lastInsertRowid
;
398 assert
.strictEqual(result
.changes
, 1);
401 step('delete expired subscriptions', async
function() {
402 await db
.context(async (dbCtx
) => {
403 await db
.subscriptionDeleteExpired(dbCtx
, topicId
)
404 const subscription
= await db
.subscriptionGet(dbCtx
, testData
.subscriptionUpsert
.callback
, topicId
);
405 assert(!subscription
);
410 describe('Verification', function () {
411 step('requires data', async
function() {
413 await db
.context(async (dbCtx
) => {
414 await db
.verificationInsert(dbCtx
);
416 assert
.fail(noExpectedException
);
418 assert(e
instanceof DBErrors
.DataValidation
);
421 step('creates verification', async
function() {
422 const verificationData
= {
423 ...testData
.verificationInsert
,
426 await db
.context(async (dbCtx
) => {
427 verificationId
= await db
.verificationInsert(dbCtx
, verificationData
);
428 assert(verificationId
);
431 step('gets verification', async
function() {
432 await db
.context(async (dbCtx
) => {
433 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
434 assert
.strictEqual(verification
.mode
, testData
.verificationInsert
.mode
);
437 step('validates verification', async
function() {
438 await db
.context(async (dbCtx
) => {
439 await db
.verificationValidated(dbCtx
, verificationId
);
440 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
441 assert
.strictEqual(verification
.isPublisherValidated
, true);
444 step('claims verification', async
function() {
445 const claimTimeoutSeconds
= 10;
448 await db
.context(async (dbCtx
) => {
449 verificationIds
= await db
.verificationClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
451 assert(verificationIds
.includes(verificationId
));
453 step('releases verification', async
function() {
454 await db
.context(async (dbCtx
) => {
455 await db
.verificationRelease(dbCtx
, verificationId
);
458 step('updates verification', async
function() {
459 const verificationData
= {
460 ...testData
.verificationUpdate
,
462 await db
.context(async (dbCtx
) => {
463 db
.verificationUpdate(dbCtx
, verificationId
, verificationData
);
464 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
465 assert
.strictEqual(verification
.isPublisherValidated
, testData
.verificationUpdate
.isPublisherValidated
);
468 step('claims verification by id', async
function() {
469 const claimTimeoutSeconds
= 10;
470 await db
.context(async (dbCtx
) => {
471 const result
= await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
472 assert
.strictEqual(result
.changes
, 1);
475 step('incompletes verification', async
function() {
476 await db
.context(async (dbCtx
) => {
477 await db
.verificationIncomplete(dbCtx
, verificationId
);
480 step('claims verification by id', async
function() {
481 const claimTimeoutSeconds
= 10;
482 await db
.context(async (dbCtx
) => {
483 const result
= await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
484 assert
.strictEqual(result
.changes
, 1);
487 step('completes verification', async
function() {
488 await db
.context(async (dbCtx
) => {
489 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
490 await db
.subscriptionUpsert(dbCtx
, verification
);
491 await db
.verificationComplete(dbCtx
, verificationId
, testData
.verificationInsert
.callback
, topicId
);
492 const count
= await db
.subscriptionCountByTopicUrl(dbCtx
, testData
.topicSet
.url
);
493 assert
.strictEqual(Number(count
.count
), 1);
499 }); // specific implementation
502 }); // Database Integration