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 () {
117 step('requires data', async
function () {
119 await db
.context(async (dbCtx
) => {
120 await db
.topicSet(dbCtx
);
122 assert
.fail(noExpectedException
);
124 assert(e
instanceof DBErrors
.DataValidation
);
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);
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
);
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);
146 step('also updates topic', async
function () {
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
);
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
);
165 step('sets topic content', async
function () {
167 ...testData
.topicSetContent
,
170 await db
.context(async (dbCtx
) => {
171 const result
= await db
.topicSetContent(dbCtx
, data
);
172 assert
.strictEqual(result
.changes
, 1);
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
);
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);
186 const topic
= await db
.topicGetById(dbCtx
, topicId
);
187 assert(topic
.lastPublish
);
190 step('claims topic fetch', async
function () {
191 const claimTimeoutSeconds
= 10;
194 await db
.context(async (dbCtx
) => {
195 topicIds
= await db
.topicFetchClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
197 assert(topicIds
.includes(topicId
));
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);
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);
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);
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);
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);
239 step('gets all topics', async
function() {
240 await db
.context(async (dbCtx
) => {
241 const topics
= await db
.topicGetAll(dbCtx
);
242 assert(topics
.length
);
247 describe('Subscription', function () {
248 step('requires data', async
function () {
250 await db
.context(async (dbCtx
) => {
251 await db
.subscriptionUpsert(dbCtx
);
253 assert
.fail(noExpectedException
);
255 assert(e
instanceof DBErrors
.DataValidation
);
258 step('creates subscription', async
function () {
260 ...testData
.subscriptionUpsert
,
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);
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
);
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
);
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
);
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);
294 step('claim subscription', async
function () {
295 const claimTimeoutSeconds
= 10;
298 await db
.context(async (dbCtx
) => {
299 subscriptionIds
= await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
301 assert(subscriptionIds
.includes(subscriptionId
));
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);
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);
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);
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
);
335 step('create subscription', async
function () {
337 ...testData
.subscriptionUpsert
,
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);
349 step('update subscription', async
function () {
352 signatureAlgorithm: 'sha256',
354 await db
.context(async (dbCtx
) => {
355 await db
.subscriptionUpdate(dbCtx
, data
);
358 step('claim subscription', async
function () {
359 const claimTimeoutSeconds
= 10;
362 await db
.context(async (dbCtx
) => {
363 subscriptionIds
= await db
.subscriptionDeliveryClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
365 assert(subscriptionIds
.includes(subscriptionId
));
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
);
377 describe('Verification', function () {
378 step('requires data', async
function() {
380 await db
.context(async (dbCtx
) => {
381 await db
.verificationInsert(dbCtx
);
383 assert
.fail(noExpectedException
);
385 assert(e
instanceof DBErrors
.DataValidation
);
388 step('creates verification', async
function() {
389 const verificationData
= {
390 ...testData
.verificationInsert
,
393 await db
.context(async (dbCtx
) => {
394 verificationId
= await db
.verificationInsert(dbCtx
, verificationData
);
395 assert(verificationId
);
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
);
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);
411 step('claims verification', async
function() {
412 const claimTimeoutSeconds
= 10;
415 await db
.context(async (dbCtx
) => {
416 verificationIds
= await db
.verificationClaim(dbCtx
, wanted
, claimTimeoutSeconds
, claimant
);
418 assert(verificationIds
.includes(verificationId
));
420 step('releases verification', async
function() {
421 await db
.context(async (dbCtx
) => {
422 await db
.verificationRelease(dbCtx
, verificationId
);
425 step('updates verification', async
function() {
426 const verificationData
= {
427 ...testData
.verificationUpdate
,
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
);
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);
442 step('incompletes verification', async
function() {
443 await db
.context(async (dbCtx
) => {
444 await db
.verificationIncomplete(dbCtx
, verificationId
);
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);
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);
466 }); // specific implementation
469 }); // Database Integration