update dependencies, fixes to support new authentication features
[websub-hub] / test / src / db / integration.js
index 3cb07f94c02c1b8bae89e8fd4620e0abf7a453f0..eed37e2c55f5070c4031f4c47d1a8491ace96588 100644 (file)
@@ -1,5 +1,3 @@
-/* eslint-env mocha */
-/* eslint-disable sonarjs/no-identical-functions */
 'use strict';
 
 /**
@@ -16,8 +14,8 @@
  * 
  */
 
-const assert = require('assert');
-const { step } = require('mocha-steps'); // eslint-disable-line node/no-unpublished-require
+const assert = require('node:assert');
+const { step } = require('mocha-steps');
 const stubLogger = require('../../stub-logger');
 const DBErrors = require('../../../src/db/errors');
 const testData = require('../../test-data/db-integration');
@@ -69,7 +67,7 @@ describe('Database Integration', function () {
         // eslint-disable-next-line security/detect-non-literal-require
         DB = require(i.module);
         db = new DB(stubLogger, i.config);
-        await db.schemaCheck();
+        await db.initialize();
         await db._purgeTables(true);
       });
       after(async function () {
@@ -79,34 +77,57 @@ describe('Database Integration', function () {
         assert(db);
       });
 
+      it('is healthy', async function () {
+        const result = await db.healthCheck();
+        assert(result);
+      });
+
       describe('Authentication', function () {
-        let identifier, credential;
+        let identifier, credential, otpKey;
         beforeEach(function () {
           identifier = 'username';
           credential = 'myEncryptedPassword';
+          otpKey = '1234567890123456789012';
         });
-        step('create auth entry', async function() {
+        step('create auth entry', async function () {
           await db.context(async (dbCtx) => {
             await db.authenticationUpsert(dbCtx, identifier, credential);
           });
         });
-        step('get auth entry', async function() {
+        step('get auth entry', async function () {
           await db.context(async (dbCtx) => {
             const authInfo = await db.authenticationGet(dbCtx, identifier);
             assert.strictEqual(authInfo.credential, credential);
           });
         });
-        step('valid auth event', async function() {
+        step('valid auth event', async function () {
           await db.context(async (dbCtx) => {
             await db.authenticationSuccess(dbCtx, identifier);
             const authInfo = await db.authenticationGet(dbCtx, identifier);
             assert.notStrictEqual(authInfo.lastAuthentication, undefined);
           });
         });
-        step('update auth entry', async function() {
+        step('update auth entry', async function () {
           await db.context(async (dbCtx) => {
             credential = 'myNewPassword';
-            await db.authenticationUpsert(dbCtx, identifier, credential);
+            await db.authenticationUpsert(dbCtx, identifier, credential, otpKey);
+            const authInfo = await db.authenticationGet(dbCtx, identifier);
+            assert.strictEqual(authInfo.credential, credential);
+            assert.strictEqual(authInfo.otpKey, otpKey);
+          });
+        });
+        step('update auth otp key', async function () {
+          await db.context(async (dbCtx) => {
+            const removedOTPKey = null;
+            await db.authenticationUpdateOTPKey(dbCtx, identifier, removedOTPKey);
+            const authInfo = await db.authenticationGet(dbCtx, identifier);
+            assert.strictEqual(authInfo.otpKey, removedOTPKey);
+          });
+        });
+        step('update credential', async function () {
+          await db.context(async (dbCtx) => {
+            credential = '$plain$anotherCredential';
+            await db.authenticationUpdateCredential(dbCtx, identifier, credential);
             const authInfo = await db.authenticationGet(dbCtx, identifier);
             assert.strictEqual(authInfo.credential, credential);
           });
@@ -114,6 +135,7 @@ describe('Database Integration', function () {
       }); // Authentication
 
       describe('Topic', function () {
+        let anotherTopicId;
         step('requires data', async function () {
           try {
             await db.context(async (dbCtx) => {
@@ -147,12 +169,15 @@ describe('Database Integration', function () {
           const data = {
             topicId,
             leaseSecondsMin: 60,
-          }
+          };
           await db.context(async(dbCtx) => {
-            let topic = await db.topicGetByUrl(dbCtx, testData.topicSet.url);
+            const expected = await db.topicGetByUrl(dbCtx, testData.topicSet.url, true);
+            expected.leaseSecondsMin = data.leaseSecondsMin;
+            let topic = await db.topicGetByUrl(dbCtx, testData.topicSet.url, false);
             await db.topicUpdate(dbCtx, { ...topic, ...data });
             topic = await db.topicGetByUrl(dbCtx, testData.topicSet.url);
             assert.strictEqual(Number(topic.leaseSecondsMin), data.leaseSecondsMin);
+            assert.deepEqual(topic, expected);
           });
         });
         step('gets topic by id', async function () {
@@ -219,10 +244,18 @@ describe('Database Integration', function () {
             assert.strictEqual(Number(topic.contentFetchAttemptsSinceSuccess), 0);
           });
         });
+        step('gets publish history', async function () {
+          await db.context(async (dbCtx) => {
+            const result = (await db.topicPublishHistory(dbCtx, topicId, 7))
+              .map((x) => Number(x));
+            const expected = [1, 0, 0, 0, 0, 0, 0];
+            assert.deepStrictEqual(result, expected);
+          });  
+        });
         step('deletes a topic', async function () {
           await db.context(async (dbCtx) => {
             const result = await db.topicSet(dbCtx, testData.anotherTopicSet);
-            const anotherTopicId = result.lastInsertRowid;
+            anotherTopicId = result.lastInsertRowid;
             await db.topicDeleted(dbCtx, anotherTopicId);
             const topic = await db.topicGetById(dbCtx, anotherTopicId);
             assert.strictEqual(topic.isDeleted, true);
@@ -231,7 +264,7 @@ describe('Database Integration', function () {
         step('update un-deletes a topic', async function () {
           await db.context(async (dbCtx) => {
             const result = await db.topicSet(dbCtx, testData.anotherTopicSet);
-            const anotherTopicId = result.lastInsertRowid;
+            assert.strictEqual(result.lastInsertRowid, anotherTopicId);
             const topic = await db.topicGetById(dbCtx, anotherTopicId);
             assert.strictEqual(topic.isDeleted, false);
           });
@@ -242,6 +275,15 @@ describe('Database Integration', function () {
             assert(topics.length);
           });
         });
+        // pending delete of deleted topic with no subscriptions
+        step('really deletes unsubscribed deleted topic', async function() {
+          await db.context(async (dbCtx) => {
+            await db.topicDeleted(dbCtx, anotherTopicId);
+            await db.topicPendingDelete(dbCtx, anotherTopicId);
+            const topic = await db.topicGetById(dbCtx, anotherTopicId);
+            assert(!topic);
+          });
+        });
       }); // Topic
 
       describe('Subscription', function () {
@@ -259,7 +301,7 @@ describe('Database Integration', function () {
           const data = {
             ...testData.subscriptionUpsert,
             topicId,
-          }
+          };
           await db.context(async (dbCtx) => {
             const result = await db.subscriptionUpsert(dbCtx, data);
             assert(result.lastInsertRowid);
@@ -318,7 +360,8 @@ describe('Database Integration', function () {
         step('complete subscription', async function () {
           const { callback } = testData.subscriptionUpsert;
           await db.context(async (dbCtx) => {
-            await db.subscriptionDeliveryComplete(dbCtx, callback, topicId);
+            const topic = await db.topicGetById(dbCtx, topicId);
+            await db.subscriptionDeliveryComplete(dbCtx, callback, topicId, topic.contentUpdated);
             const subscription = await db.subscriptionGetById(dbCtx, subscriptionId);
             assert.strictEqual(Number(subscription.deliveryAttemptsSinceSuccess), 0);
           });
@@ -337,7 +380,7 @@ describe('Database Integration', function () {
             ...testData.subscriptionUpsert,
             secret: 'newSecret',
             topicId,
-          }
+          };
           await db.context(async (dbCtx) => {
             const result = await db.subscriptionUpsert(dbCtx, data);
             assert(result.lastInsertRowid);
@@ -372,6 +415,28 @@ describe('Database Integration', function () {
             assert(!subscription);
           });
         });
+        step('create expired subscription', async function () {
+          const data = {
+            ...testData.subscriptionUpsert,
+            secret: 'newSecret',
+            topicId,
+            leaseSeconds: -1,
+          };
+          await db.context(async (dbCtx) => {
+            const result = await db.subscriptionUpsert(dbCtx, data);
+            assert(result.lastInsertRowid);
+            assert.notStrictEqual(result.lastInsertRowid, subscriptionId);
+            subscriptionId = result.lastInsertRowid;
+            assert.strictEqual(result.changes, 1);
+          });
+        });
+        step('delete expired subscriptions', async function() {
+          await db.context(async (dbCtx) => {
+            await db.subscriptionDeleteExpired(dbCtx, topicId);
+            const subscription = await db.subscriptionGet(dbCtx, testData.subscriptionUpsert.callback, topicId);
+            assert(!subscription);
+          });
+        });
       }); // Subscription
 
       describe('Verification', function () {