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 let topic
= await db
.topicGetByUrl(dbCtx
, testData
.topicSet
.url
);
159 await db
.topicUpdate(dbCtx
, { ...topic
, ...data
});
160 topic
= await db
.topicGetByUrl(dbCtx
, testData
.topicSet
.url
);
161 assert
.strictEqual(Number(topic
.leaseSecondsMin
), data
.leaseSecondsMin
);
164 step('gets topic by id', async
function () {
165 await db
.context(async (dbCtx
) => {
166 const topic
= await db
.topicGetById(dbCtx
, topicId
);
167 assert
.strictEqual(topic
.url
, testData
.topicSet
.url
);
168 assert
.strictEqual(Number(topic
.leaseSecondsPreferred
), testData
.topicUpdate
.leaseSecondsPreferred
);
171 step('sets topic content', async
function () {
173 ...testData
.topicSetContent
,
176 await db
.context(async (dbCtx
) => {
177 const result
= await db
.topicSetContent(dbCtx
, data
);
178 assert
.strictEqual(result
.changes
, 1);
181 step('gets topic content', async
function () {
182 await db
.context(async (dbCtx
) => {
183 const topic
= await db
.topicGetContentById(dbCtx
, topicId
);
184 assert
.strictEqual(topic
.contentHash
, testData
.topicSetContent
.contentHash
);
187 step('sets publish request', async
function() {
188 await db
.context(async (dbCtx
) => {
189 const result
= await db
.topicFetchRequested(dbCtx
, topicId
);
190 assert
.strictEqual(result
.changes
, 1);
192 const topic
= await db
.topicGetById(dbCtx
, topicId
);
193 assert(topic
.lastPublish
);
196 step('claims topic fetch', async
function () {
197 const claimTimeoutSeconds
= 10;
200 await db
.context(async (dbCtx
) => {
201 topicIds
= await db
.topicFetchClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
203 assert(topicIds
.includes(topicId
));
205 step('incompletes topic fetch', async
function () {
206 await db
.context(async (dbCtx
) => {
207 const result
= await db
.topicFetchIncomplete(dbCtx
, topicId
);
208 assert
.strictEqual(result
.changes
, 1);
209 const topic
= await db
.topicGetById(dbCtx
, topicId
);
210 assert
.strictEqual(Number(topic
.contentFetchAttemptsSinceSuccess
), 1);
213 step('claims topic fetch by id', async
function () {
214 const claimTimeoutSeconds
= 10;
215 await db
.context(async (dbCtx
) => {
216 const result
= await db
.topicFetchClaimById(dbCtx
, topicId
, claimTimeoutSeconds
, claimant
);
217 assert
.strictEqual(result
.changes
, 1);
220 step('completes topic fetch', async
function () {
221 await db
.context(async (dbCtx
) => {
222 const result
= await db
.topicFetchComplete(dbCtx
, topicId
);
223 assert
.strictEqual(result
.changes
, 1);
224 const topic
= await db
.topicGetById(dbCtx
, topicId
);
225 assert
.strictEqual(Number(topic
.contentFetchAttemptsSinceSuccess
), 0);
228 step('gets publish history', async
function () {
229 await db
.context(async (dbCtx
) => {
230 const result
= (await db
.topicPublishHistory(dbCtx
, topicId
, 7))
231 .map((x
) => Number(x
));
232 const expected
= [1, 0, 0, 0, 0, 0, 0];
233 assert
.deepStrictEqual(result
, expected
);
236 step('deletes a topic', async
function () {
237 await db
.context(async (dbCtx
) => {
238 const result
= await db
.topicSet(dbCtx
, testData
.anotherTopicSet
);
239 anotherTopicId
= result
.lastInsertRowid
;
240 await db
.topicDeleted(dbCtx
, anotherTopicId
);
241 const topic
= await db
.topicGetById(dbCtx
, anotherTopicId
);
242 assert
.strictEqual(topic
.isDeleted
, true);
245 step('update un-deletes a topic', async
function () {
246 await db
.context(async (dbCtx
) => {
247 const result
= await db
.topicSet(dbCtx
, testData
.anotherTopicSet
);
248 assert
.strictEqual(result
.lastInsertRowid
, anotherTopicId
);
249 const topic
= await db
.topicGetById(dbCtx
, anotherTopicId
);
250 assert
.strictEqual(topic
.isDeleted
, false);
253 step('gets all topics', async
function() {
254 await db
.context(async (dbCtx
) => {
255 const topics
= await db
.topicGetAll(dbCtx
);
256 assert(topics
.length
);
259 // pending delete of deleted topic with no subscriptions
260 step('really deletes unsubscribed deleted topic', async
function() {
261 await db
.context(async (dbCtx
) => {
262 await db
.topicDeleted(dbCtx
, anotherTopicId
);
263 await db
.topicPendingDelete(dbCtx
, anotherTopicId
);
264 const topic
= await db
.topicGetById(dbCtx
, anotherTopicId
);
270 describe('Subscription', function () {
271 step('requires data', async
function () {
273 await db
.context(async (dbCtx
) => {
274 await db
.subscriptionUpsert(dbCtx
);
276 assert
.fail(noExpectedException
);
278 assert(e
instanceof DBErrors
.DataValidation
);
281 step('creates subscription', async
function () {
283 ...testData
.subscriptionUpsert
,
286 await db
.context(async (dbCtx
) => {
287 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
288 assert(result
.lastInsertRowid
);
289 subscriptionId
= result
.lastInsertRowid
;
290 assert
.strictEqual(result
.changes
, 1);
293 step('gets subscription', async
function () {
294 await db
.context(async (dbCtx
) => {
295 const subscription
= await db
.subscriptionGet(dbCtx
, testData
.subscriptionUpsert
.callback
, topicId
);
296 assert
.strictEqual(subscription
.secret
, testData
.subscriptionUpsert
.secret
);
299 step('gets subscription by id', async
function () {
300 await db
.context(async (dbCtx
) => {
301 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
302 assert
.strictEqual(subscription
.secret
, testData
.subscriptionUpsert
.secret
);
305 step('gets subscriptions by topic', async
function() {
306 await db
.context(async (dbCtx
) => {
307 const subscriptions
= await db
.subscriptionsByTopicId(dbCtx
, topicId
);
308 assert(subscriptions
.length
);
311 step('count subscriptions', async
function () {
312 await db
.context(async (dbCtx
) => {
313 const count
= await db
.subscriptionCountByTopicUrl(dbCtx
, testData
.topicSet
.url
);
314 assert
.strictEqual(Number(count
.count
), 1);
317 step('claim subscription', async
function () {
318 const claimTimeoutSeconds
= 10;
321 await db
.context(async (dbCtx
) => {
322 subscriptionIds
= await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
324 assert(subscriptionIds
.includes(subscriptionId
));
326 step('incompletes subscription', async
function () {
327 const { callback
} = testData
.subscriptionUpsert
;
328 await db
.context(async (dbCtx
) => {
329 await db
.subscriptionDeliveryIncomplete(dbCtx
, callback
, topicId
);
330 const topic
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
331 assert
.strictEqual(Number(topic
.deliveryAttemptsSinceSuccess
), 1);
334 step('claim subscription by id', async
function () {
335 const claimTimeoutSeconds
= 10;
336 await db
.context(async (dbCtx
) => {
337 const result
= await db
.subscriptionDeliveryClaimById(dbCtx
, subscriptionId
, claimTimeoutSeconds
, claimant
);
338 assert
.strictEqual(result
.changes
, 1);
341 step('complete subscription', async
function () {
342 const { callback
} = testData
.subscriptionUpsert
;
343 await db
.context(async (dbCtx
) => {
344 const topic
= await db
.topicGetById(dbCtx
, topicId
);
345 await db
.subscriptionDeliveryComplete(dbCtx
, callback
, topicId
, topic
.contentUpdated
);
346 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
347 assert
.strictEqual(Number(subscription
.deliveryAttemptsSinceSuccess
), 0);
350 step('subscription delete', async
function () {
351 const { callback
} = testData
.subscriptionUpsert
;
352 await db
.context(async (dbCtx
) => {
353 const result
= await db
.subscriptionDelete(dbCtx
, callback
, topicId
);
354 assert
.strictEqual(result
.changes
, 1);
355 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
356 assert(!subscription
);
359 step('create subscription', async
function () {
361 ...testData
.subscriptionUpsert
,
365 await db
.context(async (dbCtx
) => {
366 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
367 assert(result
.lastInsertRowid
);
368 assert
.notStrictEqual(result
.lastInsertRowid
, subscriptionId
);
369 subscriptionId
= result
.lastInsertRowid
;
370 assert
.strictEqual(result
.changes
, 1);
373 step('update subscription', async
function () {
376 signatureAlgorithm: 'sha256',
378 await db
.context(async (dbCtx
) => {
379 await db
.subscriptionUpdate(dbCtx
, data
);
382 step('claim subscription', async
function () {
383 const claimTimeoutSeconds
= 10;
386 await db
.context(async (dbCtx
) => {
387 subscriptionIds
= await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
389 assert(subscriptionIds
.includes(subscriptionId
));
391 step('subscription gone', async
function () {
392 const { callback
} = testData
.subscriptionUpsert
;
393 await db
.context(async (dbCtx
) => {
394 await db
.subscriptionDeliveryGone(dbCtx
, callback
, topicId
);
395 const subscription
= await db
.subscriptionGetById(dbCtx
, subscriptionId
);
396 assert(!subscription
);
399 step('create expired subscription', async
function () {
401 ...testData
.subscriptionUpsert
,
406 await db
.context(async (dbCtx
) => {
407 const result
= await db
.subscriptionUpsert(dbCtx
, data
);
408 assert(result
.lastInsertRowid
);
409 assert
.notStrictEqual(result
.lastInsertRowid
, subscriptionId
);
410 subscriptionId
= result
.lastInsertRowid
;
411 assert
.strictEqual(result
.changes
, 1);
414 step('delete expired subscriptions', async
function() {
415 await db
.context(async (dbCtx
) => {
416 await db
.subscriptionDeleteExpired(dbCtx
, topicId
)
417 const subscription
= await db
.subscriptionGet(dbCtx
, testData
.subscriptionUpsert
.callback
, topicId
);
418 assert(!subscription
);
423 describe('Verification', function () {
424 step('requires data', async
function() {
426 await db
.context(async (dbCtx
) => {
427 await db
.verificationInsert(dbCtx
);
429 assert
.fail(noExpectedException
);
431 assert(e
instanceof DBErrors
.DataValidation
);
434 step('creates verification', async
function() {
435 const verificationData
= {
436 ...testData
.verificationInsert
,
439 await db
.context(async (dbCtx
) => {
440 verificationId
= await db
.verificationInsert(dbCtx
, verificationData
);
441 assert(verificationId
);
444 step('gets verification', async
function() {
445 await db
.context(async (dbCtx
) => {
446 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
447 assert
.strictEqual(verification
.mode
, testData
.verificationInsert
.mode
);
450 step('validates verification', async
function() {
451 await db
.context(async (dbCtx
) => {
452 await db
.verificationValidated(dbCtx
, verificationId
);
453 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
454 assert
.strictEqual(verification
.isPublisherValidated
, true);
457 step('claims verification', async
function() {
458 const claimTimeoutSeconds
= 10;
461 await db
.context(async (dbCtx
) => {
462 verificationIds
= await db
.verificationClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
464 assert(verificationIds
.includes(verificationId
));
466 step('releases verification', async
function() {
467 await db
.context(async (dbCtx
) => {
468 await db
.verificationRelease(dbCtx
, verificationId
);
471 step('updates verification', async
function() {
472 const verificationData
= {
473 ...testData
.verificationUpdate
,
475 await db
.context(async (dbCtx
) => {
476 db
.verificationUpdate(dbCtx
, verificationId
, verificationData
);
477 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
478 assert
.strictEqual(verification
.isPublisherValidated
, testData
.verificationUpdate
.isPublisherValidated
);
481 step('claims verification by id', async
function() {
482 const claimTimeoutSeconds
= 10;
483 await db
.context(async (dbCtx
) => {
484 const result
= await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
485 assert
.strictEqual(result
.changes
, 1);
488 step('incompletes verification', async
function() {
489 await db
.context(async (dbCtx
) => {
490 await db
.verificationIncomplete(dbCtx
, verificationId
);
493 step('claims verification by id', async
function() {
494 const claimTimeoutSeconds
= 10;
495 await db
.context(async (dbCtx
) => {
496 const result
= await db
.verificationClaimById(dbCtx
, verificationId
, claimTimeoutSeconds
, claimant
);
497 assert
.strictEqual(result
.changes
, 1);
500 step('completes verification', async
function() {
501 await db
.context(async (dbCtx
) => {
502 const verification
= await db
.verificationGetById(dbCtx
, verificationId
);
503 await db
.subscriptionUpsert(dbCtx
, verification
);
504 await db
.verificationComplete(dbCtx
, verificationId
, testData
.verificationInsert
.callback
, topicId
);
505 const count
= await db
.subscriptionCountByTopicUrl(dbCtx
, testData
.topicSet
.url
);
506 assert
.strictEqual(Number(count
.count
), 1);
512 }); // specific implementation
515 }); // Database Integration