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 it('is healthy', async
function () {
83 const result
= await db
.healthCheck();
87 describe('Authentication', function () {
88 let identifier
, credential
;
89 beforeEach(function () {
90 identifier
= 'username';
91 credential
= 'myEncryptedPassword';
93 step('create auth entry', async
function() {
94 await db
.context(async (dbCtx
) => {
95 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
98 step('get auth entry', async
function() {
99 await db
.context(async (dbCtx
) => {
100 const authInfo
= await db
.authenticationGet(dbCtx
, identifier
);
101 assert
.strictEqual(authInfo
.credential
, credential
);
104 step('valid auth event', async
function() {
105 await db
.context(async (dbCtx
) => {
106 await db
.authenticationSuccess(dbCtx
, identifier
);
107 const authInfo
= await db
.authenticationGet(dbCtx
, identifier
);
108 assert
.notStrictEqual(authInfo
.lastAuthentication
, undefined);
111 step('update auth entry', async
function() {
112 await db
.context(async (dbCtx
) => {
113 credential
= 'myNewPassword';
114 await db
.authenticationUpsert(dbCtx
, identifier
, credential
);
115 const authInfo
= await db
.authenticationGet(dbCtx
, identifier
);
116 assert
.strictEqual(authInfo
.credential
, credential
);
119 }); // Authentication
121 describe('Topic', function () {
123 step('requires data', async
function () {
125 await db
.context(async (dbCtx
) => {
126 await db
.topicSet(dbCtx
);
128 assert
.fail(noExpectedException
);
130 assert(e
instanceof DBErrors
.DataValidation
);
133 step('creates topic', async
function () {
134 await db
.context(async (dbCtx
) => {
135 const result
= await db
.topicSet(dbCtx
, testData
.topicSet
);
136 topicId
= result
.lastInsertRowid
;
137 assert
.strictEqual(result
.changes
, 1);
140 step('gets topic by url', async
function () {
141 await db
.context(async (dbCtx
) => {
142 const topic
= await db
.topicGetByUrl(dbCtx
, testData
.topicSet
.url
);
143 assert
.strictEqual(topic
.url
, testData
.topicSet
.url
);
146 step('updates topic', async
function () {
147 await db
.context(async(dbCtx
) => {
148 const result
= await db
.topicSet(dbCtx
, testData
.topicUpdate
);
149 assert
.strictEqual(result
.changes
, 1);
152 step('also updates topic', async
function () {
157 await db
.context(async(dbCtx
) => {
158 const expected
= await db
.topicGetByUrl(dbCtx
, testData
.topicSet
.url
, true);
159 expected
.leaseSecondsMin
= data
.leaseSecondsMin
;
160 let topic
= await db
.topicGetByUrl(dbCtx
, testData
.topicSet
.url
, false);
161 await db
.topicUpdate(dbCtx
, { ...topic
, ...data
});
162 topic
= await db
.topicGetByUrl(dbCtx
, testData
.topicSet
.url
);
163 assert
.strictEqual(Number(topic
.leaseSecondsMin
), data
.leaseSecondsMin
);
164 assert
.deepEqual(topic
, expected
);
167 step('gets topic by id', async
function () {
168 await db
.context(async (dbCtx
) => {
169 const topic
= await db
.topicGetById(dbCtx
, topicId
);
170 assert
.strictEqual(topic
.url
, testData
.topicSet
.url
);
171 assert
.strictEqual(Number(topic
.leaseSecondsPreferred
), testData
.topicUpdate
.leaseSecondsPreferred
);
174 step('sets topic content', async
function () {
176 ...testData
.topicSetContent
,
179 await db
.context(async (dbCtx
) => {
180 const result
= await db
.topicSetContent(dbCtx
, data
);
181 assert
.strictEqual(result
.changes
, 1);
184 step('gets topic content', async
function () {
185 await db
.context(async (dbCtx
) => {
186 const topic
= await db
.topicGetContentById(dbCtx
, topicId
);
187 assert
.strictEqual(topic
.contentHash
, testData
.topicSetContent
.contentHash
);
190 step('sets publish request', async
function() {
191 await db
.context(async (dbCtx
) => {
192 const result
= await db
.topicFetchRequested(dbCtx
, topicId
);
193 assert
.strictEqual(result
.changes
, 1);
195 const topic
= await db
.topicGetById(dbCtx
, topicId
);
196 assert(topic
.lastPublish
);
199 step('claims topic fetch', async
function () {
200 const claimTimeoutSeconds
= 10;
203 await db
.context(async (dbCtx
) => {
204 topicIds
= await db
.topicFetchClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
206 assert(topicIds
.includes(topicId
));
208 step('incompletes topic fetch', async
function () {
209 await db
.context(async (dbCtx
) => {
210 const result
= await db
.topicFetchIncomplete(dbCtx
, topicId
);
211 assert
.strictEqual(result
.changes
, 1);
212 const topic
= await db
.topicGetById(dbCtx
, topicId
);
213 assert
.strictEqual(Number(topic
.contentFetchAttemptsSinceSuccess
), 1);
216 step('claims topic fetch by id', async
function () {
217 const claimTimeoutSeconds
= 10;
218 await db
.context(async (dbCtx
) => {
219 const result
= await db
.topicFetchClaimById(dbCtx
, topicId
, claimTimeoutSeconds
, claimant
);
220 assert
.strictEqual(result
.changes
, 1);
223 step('completes topic fetch', async
function () {
224 await db
.context(async (dbCtx
) => {
225 const result
= await db
.topicFetchComplete(dbCtx
, topicId
);
226 assert
.strictEqual(result
.changes
, 1);
227 const topic
= await db
.topicGetById(dbCtx
, topicId
);
228 assert
.strictEqual(Number(topic
.contentFetchAttemptsSinceSuccess
), 0);
231 step('gets publish history', async
function () {
232 await db
.context(async (dbCtx
) => {
233 const result
= (await db
.topicPublishHistory(dbCtx
, topicId
, 7))
234 .map((x
) => Number(x
));
235 const expected
= [1, 0, 0, 0, 0, 0, 0];
236 assert
.deepStrictEqual(result
, expected
);
239 step('deletes a topic', async
function () {
240 await db
.context(async (dbCtx
) => {
241 const result
= await db
.topicSet(dbCtx
, testData
.anotherTopicSet
);
242 anotherTopicId
= result
.lastInsertRowid
;
243 await db
.topicDeleted(dbCtx
, anotherTopicId
);
244 const topic
= await db
.topicGetById(dbCtx
, anotherTopicId
);
245 assert
.strictEqual(topic
.isDeleted
, true);
248 step('update un-deletes a topic', async
function () {
249 await db
.context(async (dbCtx
) => {
250 const result
= await db
.topicSet(dbCtx
, testData
.anotherTopicSet
);
251 assert
.strictEqual(result
.lastInsertRowid
, anotherTopicId
);
252 const topic
= await db
.topicGetById(dbCtx
, anotherTopicId
);
253 assert
.strictEqual(topic
.isDeleted
, false);
256 step('gets all topics', async
function() {
257 await db
.context(async (dbCtx
) => {
258 const topics
= await db
.topicGetAll(dbCtx
);
259 assert(topics
.length
);
262 // pending delete of deleted topic with no subscriptions
263 step('really deletes unsubscribed deleted topic', async
function() {
264 await db
.context(async (dbCtx
) => {
265 await db
.topicDeleted(dbCtx
, anotherTopicId
);
266 await db
.topicPendingDelete(dbCtx
, anotherTopicId
);
267 const topic
= await db
.topicGetById(dbCtx
, anotherTopicId
);
273 describe('Subscription', function () {
274 step('requires data', async
function () {
276 await db
.context(async (dbCtx
) => {
277 await db
.subscriptionUpsert(dbCtx
);
279 assert
.fail(noExpectedException
);
281 assert(e
instanceof DBErrors
.DataValidation
);
284 step('creates subscription', async
function () {
286 ...testData
.subscriptionUpsert
,
289 await db
.context(async (dbCtx
) => {
290 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
291 assert(result
.lastInsertRowid
);
292 subscriptionId
= result
.lastInsertRowid
;
293 assert
.strictEqual(result
.changes
, 1);
296 step('gets subscription', async
function () {
297 await db
.context(async (dbCtx
) => {
298 const subscription
= await db
.subscriptionGet(dbCtx
, testData
.subscriptionUpsert
.callback
, topicId
);
299 assert
.strictEqual(subscription
.secret
, testData
.subscriptionUpsert
.secret
);
302 step('gets subscription by id', async
function () {
303 await db
.context(async (dbCtx
) => {
304 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
305 assert
.strictEqual(subscription
.secret
, testData
.subscriptionUpsert
.secret
);
308 step('gets subscriptions by topic', async
function() {
309 await db
.context(async (dbCtx
) => {
310 const subscriptions
= await db
.subscriptionsByTopicId(dbCtx
, topicId
);
311 assert(subscriptions
.length
);
314 step('count subscriptions', async
function () {
315 await db
.context(async (dbCtx
) => {
316 const count
= await db
.subscriptionCountByTopicUrl(dbCtx
, testData
.topicSet
.url
);
317 assert
.strictEqual(Number(count
.count
), 1);
320 step('claim subscription', async
function () {
321 const claimTimeoutSeconds
= 10;
324 await db
.context(async (dbCtx
) => {
325 subscriptionIds
= await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
327 assert(subscriptionIds
.includes(subscriptionId
));
329 step('incompletes subscription', async
function () {
330 const { callback
} = testData
.subscriptionUpsert
;
331 await db
.context(async (dbCtx
) => {
332 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
);
333 const topic
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
334 assert
.strictEqual(Number(topic
.deliveryAttemptsSinceSuccess
), 1);
337 step('claim subscription by id', async
function () {
338 const claimTimeoutSeconds
= 10;
339 await db
.context(async (dbCtx
) => {
340 const result
= await db
.subscriptionDeliveryClaimById(dbCtx
, subscriptionId
, claimTimeoutSeconds
, claimant
);
341 assert
.strictEqual(result
.changes
, 1);
344 step('complete subscription', async
function () {
345 const { callback
} = testData
.subscriptionUpsert
;
346 await db
.context(async (dbCtx
) => {
347 const topic
= await db
.topicGetById(dbCtx
, topicId
);
348 await db
.subscriptionDeliveryComplete(dbCtx
, callback
, topicId
, topic
.contentUpdated
);
349 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
350 assert
.strictEqual(Number(subscription
.deliveryAttemptsSinceSuccess
), 0);
353 step('subscription delete', async
function () {
354 const { callback
} = testData
.subscriptionUpsert
;
355 await db
.context(async (dbCtx
) => {
356 const result
= await db
.subscriptionDelete(dbCtx
, callback
, topicId
);
357 assert
.strictEqual(result
.changes
, 1);
358 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
359 assert(!subscription
);
362 step('create subscription', async
function () {
364 ...testData
.subscriptionUpsert
,
368 await db
.context(async (dbCtx
) => {
369 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
370 assert(result
.lastInsertRowid
);
371 assert
.notStrictEqual(result
.lastInsertRowid
, subscriptionId
);
372 subscriptionId
= result
.lastInsertRowid
;
373 assert
.strictEqual(result
.changes
, 1);
376 step('update subscription', async
function () {
379 signatureAlgorithm: 'sha256',
381 await db
.context(async (dbCtx
) => {
382 await db
.subscriptionUpdate(dbCtx
, data
);
385 step('claim subscription', async
function () {
386 const claimTimeoutSeconds
= 10;
389 await db
.context(async (dbCtx
) => {
390 subscriptionIds
= await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
392 assert(subscriptionIds
.includes(subscriptionId
));
394 step('subscription gone', async
function () {
395 const { callback
} = testData
.subscriptionUpsert
;
396 await db
.context(async (dbCtx
) => {
397 await db
.subscriptionDeliveryGone(dbCtx
, callback
, topicId
);
398 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
399 assert(!subscription
);
402 step('create expired subscription', async
function () {
404 ...testData
.subscriptionUpsert
,
409 await db
.context(async (dbCtx
) => {
410 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
411 assert(result
.lastInsertRowid
);
412 assert
.notStrictEqual(result
.lastInsertRowid
, subscriptionId
);
413 subscriptionId
= result
.lastInsertRowid
;
414 assert
.strictEqual(result
.changes
, 1);
417 step('delete expired subscriptions', async
function() {
418 await db
.context(async (dbCtx
) => {
419 await db
.subscriptionDeleteExpired(dbCtx
, topicId
)
420 const subscription
= await db
.subscriptionGet(dbCtx
, testData
.subscriptionUpsert
.callback
, topicId
);
421 assert(!subscription
);
426 describe('Verification', function () {
427 step('requires data', async
function() {
429 await db
.context(async (dbCtx
) => {
430 await db
.verificationInsert(dbCtx
);
432 assert
.fail(noExpectedException
);
434 assert(e
instanceof DBErrors
.DataValidation
);
437 step('creates verification', async
function() {
438 const verificationData
= {
439 ...testData
.verificationInsert
,
442 await db
.context(async (dbCtx
) => {
443 verificationId
= await db
.verificationInsert(dbCtx
, verificationData
);
444 assert(verificationId
);
447 step('gets verification', async
function() {
448 await db
.context(async (dbCtx
) => {
449 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
450 assert
.strictEqual(verification
.mode
, testData
.verificationInsert
.mode
);
453 step('validates verification', async
function() {
454 await db
.context(async (dbCtx
) => {
455 await db
.verificationValidated(dbCtx
, verificationId
);
456 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
457 assert
.strictEqual(verification
.isPublisherValidated
, true);
460 step('claims verification', async
function() {
461 const claimTimeoutSeconds
= 10;
464 await db
.context(async (dbCtx
) => {
465 verificationIds
= await db
.verificationClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
467 assert(verificationIds
.includes(verificationId
));
469 step('releases verification', async
function() {
470 await db
.context(async (dbCtx
) => {
471 await db
.verificationRelease(dbCtx
, verificationId
);
474 step('updates verification', async
function() {
475 const verificationData
= {
476 ...testData
.verificationUpdate
,
478 await db
.context(async (dbCtx
) => {
479 db
.verificationUpdate(dbCtx
, verificationId
, verificationData
);
480 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
481 assert
.strictEqual(verification
.isPublisherValidated
, testData
.verificationUpdate
.isPublisherValidated
);
484 step('claims verification by id', async
function() {
485 const claimTimeoutSeconds
= 10;
486 await db
.context(async (dbCtx
) => {
487 const result
= await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
488 assert
.strictEqual(result
.changes
, 1);
491 step('incompletes verification', async
function() {
492 await db
.context(async (dbCtx
) => {
493 await db
.verificationIncomplete(dbCtx
, verificationId
);
496 step('claims verification by id', async
function() {
497 const claimTimeoutSeconds
= 10;
498 await db
.context(async (dbCtx
) => {
499 const result
= await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
500 assert
.strictEqual(result
.changes
, 1);
503 step('completes verification', async
function() {
504 await db
.context(async (dbCtx
) => {
505 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
506 await db
.subscriptionUpsert(dbCtx
, verification
);
507 await db
.verificationComplete(dbCtx
, verificationId
, testData
.verificationInsert
.callback
, topicId
);
508 const count
= await db
.subscriptionCountByTopicUrl(dbCtx
, testData
.topicSet
.url
);
509 assert
.strictEqual(Number(count
.count
), 1);
515 }); // specific implementation
518 }); // Database Integration