--- /dev/null
+/* eslint-disable sonarjs/no-identical-functions */
+/* eslint-env mocha */
+/* eslint-disable sonarjs/no-duplicate-string */
+'use strict';
+
+/* This provides implementation coverage, stubbing pg-promise. */
+
+const assert = require('assert');
+// eslint-disable-next-line node/no-unpublished-require
+const sinon = require('sinon');
+const DBStub = require('../../stub-db');
+const stubLogger = require('../../stub-logger');
+const DB = require('../../../src/db/postgres');
+const DBErrors = require('../../../src/db/errors');
+const common = require('../../../src/common');
+const Config = require('../../../config');
+
+const noExpectedException = 'did not receive expected exception';
+
+describe('DatabasePostgres', function () {
+ let db, options, pgpStub;
+ let dbCtx, claimant, claimTimeoutSeconds, callback, subscriptionId, topicId, verificationId;
+ let topicUrl, leaseSeconds, secret, httpRemoteAddr, httpFrom, retryDelays, wanted;
+ before(function () {
+ pgpStub = () => {
+ const stub = {
+ result: () => ({ rows: [] }),
+ all: common.nop,
+ get: common.nop,
+ run: common.nop,
+ one: common.nop,
+ manyOrNone: common.nop,
+ oneOrNone: common.nop,
+ query: common.nop,
+ batch: common.nop,
+ multiResult: common.nop,
+ connect: common.nop,
+ };
+ stub.tx = (fn) => fn(stub);
+ stub.txIf = (fn) => fn(stub);
+ stub.task = (fn) => fn(stub);
+ return stub;
+ };
+ pgpStub.utils = {
+ enumSql: () => ({}),
+ };
+ pgpStub.QueryFile = class {};
+ pgpStub.end = common.nop,
+ options = new Config('test');
+ db = new DB(stubLogger, options, pgpStub);
+ });
+ beforeEach(function () {
+ stubLogger._reset();
+ dbCtx = db.db;
+ claimant = '19af19b8-6be3-4a6f-8946-65f5f1ccc5d7';
+ claimTimeoutSeconds = 300;
+ subscriptionId = 'fbaf8f19-ed9c-4a21-89ae-98b7005e3bf6';
+ topicUrl = 'https://example.com/blog';
+ callback = 'https://example.com/callback?id=123';
+ topicId = 'c59d4bda-10ad-41d9-99df-4ce8bc331424';
+ verificationId = '55cd7748-d2d5-11eb-b355-0025905f714a';
+ retryDelays = [60];
+ leaseSeconds = 86400;
+ secret = 'secret';
+ httpRemoteAddr = '127.0.0.1';
+ httpFrom = 'user@example.com';
+ wanted = 5;
+});
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ // Ensure all interface methods are implemented
+ describe('Implementation', function () {
+ it('implements interface', async function () {
+ const results = await Promise.allSettled(DBStub._implementation.map(async (fn) => {
+ try {
+ // eslint-disable-next-line security/detect-object-injection
+ await db[fn](db.db);
+ } catch (e) {
+ assert(!(e instanceof DBErrors.NotImplemented), `${fn} not implemented`);
+ }
+ }));
+ const failures = results.filter((x) => x.status === 'rejected');
+ assert(!failures.length, failures.map((x) => {
+ x = x.reason.toString();
+ return x.slice(x.indexOf(': '));
+ }));
+ });
+ }); // Implementation
+
+ describe('pgpInitOptions', function () {
+ describe('error', function () {
+ it('covers', function () {
+ const err = {};
+ const event = {};
+ db.pgpInitOptions.error(err, event);
+ assert(db.logger.error.called);
+ });
+ }); // error
+ describe('query', function () {
+ it('covers', function () {
+ const event = {};
+ db.pgpInitOptions.query(event);
+ assert(db.logger.debug.called);
+ });
+ }); // query
+ describe('receive', function () {
+ it('covers', function () {
+ const data = [
+ {
+ column_one: 'one', // eslint-disable-line camelcase
+ column_two: 2, // eslint-disable-line camelcase
+ },
+ {
+ column_one: 'foo', // eslint-disable-line camelcase
+ column_two: 4, // eslint-disable-line camelcase
+ },
+ ];
+ const result = {};
+ const event = {};
+ const expectedData = [
+ {
+ columnOne: 'one',
+ columnTwo: 2,
+ },
+ {
+ columnOne: 'foo',
+ columnTwo: 4,
+ },
+ ];
+ db.pgpInitOptions.receive(data, result, event)
+ assert(db.logger.debug.called);
+ assert.deepStrictEqual(data, expectedData);
+ });
+ }); // receive
+ }); // pgpInitOptions
+
+ describe('_initTables', function () {
+ beforeEach(function () {
+ sinon.stub(db.db, 'oneOrNone');
+ sinon.stub(db.db, 'multiResult');
+ sinon.stub(db, '_currentSchema');
+ });
+
+ it('covers apply', async function() {
+ db.db.oneOrNone.onCall(0).resolves(null).onCall(1).resolves({});
+ db._currentSchema.resolves({ major: 0, minor: 0, patch: 0 });
+ await db._initTables();
+ });
+ it('covers exists', async function() {
+ db.db.oneOrNone.resolves({});
+ db._currentSchema.resolves(db.schemaVersionsSupported.max);
+ await db._initTables();
+ });
+ }); // _initTables
+
+ describe('schemaCheck', function () {
+ it('passes supported version', async function () {
+ const version = { major: 1, minor: 0, patch: 0 };
+ sinon.stub(db.db, 'one').resolves(version);
+ await db.schemaCheck(false);
+ });
+ it('fails low version', async function () {
+ const version = { major: 0, minor: 0, patch: 0 };
+ sinon.stub(db.db, 'one').resolves(version);
+ try {
+ await db.schemaCheck(false);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.MigrationNeeded);
+ }
+ });
+ it('fails high version', async function () {
+ const version = { major: 100, minor: 100, patch: 100 };
+ sinon.stub(db.db, 'one').resolves(version);
+ try {
+ await db.schemaCheck(false);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.MigrationNeeded);
+ }
+ });
+ it('covers migration', async function() {
+ sinon.stub(db.db, 'oneOrNone').resolves({});
+ sinon.stub(db.db, 'multiResult');
+ sinon.stub(db, '_currentSchema').resolves(db.schemaVersionsSupported.max);
+ sinon.stub(db.db, 'one').resolves(db.schemaVersionsSupported.max);
+ await db.schemaCheck();
+ });
+ }); // schemaCheck
+
+ describe('healthCheck', function () {
+ beforeEach(function () {
+ sinon.stub(db.db, 'connect').resolves({
+ done: () => {},
+ client: {
+ serverVersion: '0.0',
+ },
+ });
+ });
+ it('covers', async function () {
+ const result = await db.healthCheck();
+ assert.deepStrictEqual(result, { serverVersion: '0.0' });
+ });
+ }); // healthCheck
+
+ describe('_queryFileHelper', function () {
+ it('covers success', function () {
+ const _queryFile = db._queryFileHelper(pgpStub);
+ _queryFile();
+ });
+ it('covers failure', function () {
+ const err = new Error();
+ pgpStub.QueryFile = class {
+ constructor() {
+ this.error = err;
+ }
+ };
+ const _queryFile = db._queryFileHelper(pgpStub);
+ try {
+ _queryFile();
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.strictEqual(e, err);
+ }
+ });
+ }); // _queryFileHelper
+
+ describe('_closeConnection', function () {
+ it('success', async function () {
+ sinon.stub(db._pgp, 'end');
+ await db._closeConnection();
+ assert(db._pgp.end.called);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db._pgp, 'end').throws(expected);
+ try {
+ await db._closeConnection();
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // _closeConnection
+
+ describe('_purgeTables', function () {
+ it('covers not really', async function () {
+ sinon.stub(db.db, 'tx');
+ await db._purgeTables(false);
+ assert(!db.db.tx.called);
+ });
+ it('success', async function () {
+ sinon.stub(db.db, 'batch');
+ await db._purgeTables(true);
+ assert(db.db.batch.called);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'tx').rejects(expected);
+ try {
+ await db._purgeTables(true);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // _purgeTables
+
+ describe('context', function () {
+ it('covers', async function () {
+ await db.context(common.nop);
+ });
+ }); // context
+
+ describe('transaction', function () {
+ it('covers', async function () {
+ await db.transaction(db.db, common.nop);
+ });
+ }); // transaction
+
+ describe('authenticationSuccess', function () {
+ let identifier;
+ beforeEach(function () {
+ identifier = 'username';
+ });
+ it('success', async function () {
+ const dbResult = {
+ rowCount: 1,
+ rows: undefined,
+ duration: 22,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.authenticationSuccess(dbCtx, identifier);
+ });
+ it('failure', async function() {
+ const dbResult = {
+ rowCount: 0,
+ rows: undefined,
+ duration: 22,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.authenticationSuccess(dbCtx, identifier);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // authenticationSuccess
+
+ describe('authenticationGet', function () {
+ let identifier, credential;
+ beforeEach(function () {
+ identifier = 'username';
+ credential = '$z$foo';
+ });
+ it('success', async function () {
+ const dbResult = { identifier, credential };
+ sinon.stub(db.db, 'oneOrNone').resolves(dbResult);
+ const result = await db.authenticationGet(dbCtx, identifier);
+ assert.deepStrictEqual(result, dbResult);
+ });
+ it('failure', async function() {
+ const expected = new Error('blah');
+ sinon.stub(db.db, 'oneOrNone').rejects(expected);
+ try {
+ await db.authenticationGet(dbCtx, identifier, credential);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // authenticationGet
+
+ describe('authenticationUpsert', function () {
+ let identifier, credential;
+ beforeEach(function () {
+ identifier = 'username';
+ credential = '$z$foo';
+ });
+ it('success', async function () {
+ const dbResult = {
+ rowCount: 1,
+ rows: undefined,
+ duration: 22,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.authenticationUpsert(dbCtx, identifier, credential);
+ });
+ it('failure', async function() {
+ credential = undefined;
+ const dbResult = {
+ rowCount: 0,
+ rows: undefined,
+ duration: 22,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.authenticationUpsert(dbCtx, identifier, credential);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // authenticationUpsert
+
+ describe('subscriptionsByTopicId', function () {
+ it('success', async function () {
+ const expected = [];
+ sinon.stub(db.db, 'manyOrNone').resolves(expected);
+ const result = await db.subscriptionsByTopicId(dbCtx, topicUrl);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'manyOrNone').throws(expected);
+ try {
+ await db.subscriptionsByTopicId(dbCtx, topicUrl);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // subscriptionsByTopicId
+
+ describe('subscriptionCountByTopicUrl', function () {
+ it('success', async function () {
+ const expected = { count: 3 };
+ sinon.stub(db.db, 'one').resolves(expected);
+ const result = await db.subscriptionCountByTopicUrl(dbCtx, topicUrl);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'one').throws(expected);
+ try {
+ await db.subscriptionCountByTopicUrl(dbCtx, topicUrl);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // subscriptionCountByTopicUrl
+
+ describe('subscriptionDelete', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [ {} ],
+ duration: 10,
+ };
+ const expected = {
+ changes: 1,
+ lastInsertRowid: undefined,
+ duration: 10,
+ }
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ const result = await db.subscriptionDelete(dbCtx, callback, topicId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'result').throws(expected);
+ try {
+ await db.subscriptionDelete(dbCtx, callback, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // subscriptionDelete
+
+ describe('subscriptionDeliveryClaim', function () {
+ it('success', async function() {
+ const dbResult = [
+ {
+ id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
+ },
+ ];
+ const expected = ['c2e254c5-aa6e-4a8f-b1a1-e474b07392bb'];
+ sinon.stub(db.db, 'manyOrNone').resolves(dbResult);
+ const result = await db.subscriptionDeliveryClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'manyOrNone').throws(expected);
+ try {
+ await db.subscriptionDeliveryClaim(dbCtx, wanted, claimTimeoutSeconds, claimant );
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // subscriptionDeliveryClaim
+
+ describe('subscriptionDeliveryClaimById', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [{ id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb' }],
+ duration: 11,
+ };
+ const expected = {
+ changes: 1,
+ lastInsertRowid: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
+ duration: 11,
+ }
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ const result = await db.subscriptionDeliveryClaimById(dbCtx, subscriptionId, claimTimeoutSeconds, claimant);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: undefined,
+ duration: 11,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.subscriptionDeliveryClaimById(dbCtx, callback, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // subscriptionDeliveryClaimById
+
+ describe('subscriptionDeliveryComplete', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.subscriptionDeliveryComplete(dbCtx, callback, topicId);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ };
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult);
+ try {
+ await db.subscriptionDeliveryComplete(dbCtx, callback, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ it('second failure', async function () {
+ const dbResult0 = {
+ rowCount: 1,
+ };
+ const dbResult1 = {
+ rowCount: 0,
+ };
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ try {
+ await db.subscriptionDeliveryComplete(dbCtx, callback, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // subscriptionDeliveryComplete
+
+ describe('subscriptionDeliveryGone', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.subscriptionDeliveryGone(dbCtx, callback, topicId);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.subscriptionDeliveryGone(dbCtx, callback, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // subscriptionDeliveryGone
+
+ describe('subscriptionDeliveryIncomplete', function () {
+ it('success', async function() {
+ const dbOne = { deliveryAttemptsSinceSuccess: 0 };
+ const dbResult = {
+ rowCount: 1,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId, retryDelays);
+ });
+ it('success covers default', async function() {
+ const dbOne = { deliveryAttemptsSinceSuccess: 0 };
+ const dbResult = {
+ rowCount: 1,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId);
+ });
+ it('failure', async function () {
+ const dbOne = { deliveryAttemptsSinceSuccess: 0 };
+ const dbResult = {
+ rowCount: 0,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId, retryDelays);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ it('second failure', async function () {
+ const dbOne = { deliveryAttemptsSinceSuccess: 0 };
+ const dbResult0 = {
+ rowCount: 1,
+ };
+ const dbResult1 = {
+ rowCount: 0,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ try {
+ await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId, retryDelays);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // subscriptionDeliveryIncomplete
+
+ describe('subscriptionGet', function () {
+ it('success', async function() {
+ const expected = {
+ id: subscriptionId,
+ };
+ sinon.stub(db.db, 'oneOrNone').resolves(expected);
+ const result = await db.subscriptionGet(dbCtx, callback, topicId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'oneOrNone').throws(expected);
+ try {
+ await db.subscriptionGet(dbCtx, callback, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // subscriptionGet
+
+ describe('subscriptionGetById', function () {
+ it('success', async function() {
+ const expected = {
+ id: subscriptionId,
+ };
+ sinon.stub(db.db, 'oneOrNone').resolves(expected);
+ const result = await db.subscriptionGetById(dbCtx, subscriptionId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'oneOrNone').throws(expected);
+ try {
+ await db.subscriptionGetById(dbCtx, subscriptionId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // subscriptionGetById
+
+ describe('subscriptionUpsert', function () {
+ let data;
+ beforeEach(function () {
+ data = {
+ callback,
+ topicId,
+ leaseSeconds,
+ secret,
+ httpRemoteAddr,
+ httpFrom,
+ };
+ });
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [{ id: subscriptionId }],
+ duration: 10,
+ };
+ const expected = {
+ changes: 1,
+ lastInsertRowid: subscriptionId,
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ const result = await db.subscriptionUpsert(dbCtx, data);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.subscriptionUpsert(dbCtx, data);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // subscriptionUpsert
+
+ describe('subscriptionUpdate', function () {
+ let data;
+ beforeEach(function () {
+ data = {
+ signatureAlgorithm: 'sha256',
+ };
+ });
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.subscriptionUpdate(dbCtx, data);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.subscriptionUpdate(dbCtx, data);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // subscriptionUpdate
+
+ describe('topicDeleted', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.topicDeleted(dbCtx, topicId);
+ });
+ it('failure', async function() {
+ const dbResult = {
+ rowCount: 0,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.topicDeleted(dbCtx, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // topicDeleted
+
+ describe('topicFetchClaim', function () {
+ it('success', async function() {
+ const dbResult = [{ id: topicId }];
+ const expected = [topicId];
+ sinon.stub(db.db, 'manyOrNone').resolves(dbResult);
+ const result = await db.topicFetchClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'manyOrNone').throws(expected);
+ try {
+ await db.topicFetchClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // topicFetchClaim
+
+ describe('topicFetchClaimById', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const expected = {
+ changes: 1,
+ lastInsertRowid: undefined,
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ const result = await db.topicFetchClaimById(dbCtx, topicId, claimTimeoutSeconds, claimant);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'result').throws(expected);
+ try {
+ await db.topicFetchClaimById(dbCtx, topicId, claimTimeoutSeconds, claimant);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // topicFetchClaimById
+
+ describe('topicFetchComplete', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.topicFetchComplete(dbCtx, topicId);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.topicFetchComplete(dbCtx, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ it('second failure', async function () {
+ const dbResult0 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const dbResult1 = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ try {
+ await db.topicFetchComplete(dbCtx, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // topicFetchComplete
+
+ describe('topicFetchIncomplete', function () {
+ it('success', async function() {
+ const dbOne = { currentAttempt: 0 };
+ const dbResult0 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const dbResult1 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ }
+ const expected = {
+ changes: 1,
+ lastInsertRowid: undefined,
+ duration: 10,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ const result = await db.topicFetchIncomplete(dbCtx, topicId, retryDelays);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('covers defaults', async function() {
+ const dbOne = { currentAttempt: 0 };
+ const dbResult0 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const dbResult1 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ }
+ const expected = {
+ changes: 1,
+ lastInsertRowid: undefined,
+ duration: 10,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ const result = await db.topicFetchIncomplete(dbCtx, topicId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const dbOne = { currentAttempt: 0 };
+ const dbResult0 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const dbResult1 = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ }
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ try {
+ await db.topicFetchIncomplete(dbCtx, topicId, retryDelays);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ it('second failure', async function () {
+ const dbOne = { currentAttempt: 0 };
+ const dbResult0 = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ const dbResult1 = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ }
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ try {
+ await db.topicFetchIncomplete(dbCtx, topicId, retryDelays);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // topicFetchIncomplete
+
+ describe('topicFetchRequested', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const expected = {
+ changes: 1,
+ lastInsertRowid: undefined,
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ const result = await db.topicFetchRequested(dbCtx, topicId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.topicFetchRequested(dbCtx, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // topicFetchRequested
+
+ describe('topicGetAll', function () {
+ it('success', async function() {
+ const expected = [{ id: topicId }];
+ sinon.stub(db.db, 'manyOrNone').resolves(expected);
+ const result = await db.topicGetAll(dbCtx);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('covers default', async function() {
+ const expected = undefined;
+ sinon.stub(db.db, 'manyOrNone').resolves(expected);
+ const result = await db.topicGetAll(dbCtx);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'manyOrNone').throws(expected);
+ try {
+ await db.topicGetAll(dbCtx);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // topicGetById
+
+ describe('topicGetById', function () {
+ it('success', async function() {
+ const expected = { id: topicId };
+ sinon.stub(db.db, 'oneOrNone').resolves(expected);
+ const result = await db.topicGetById(dbCtx, topicId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('covers none', async function() {
+ const expected = undefined;
+ sinon.stub(db.db, 'oneOrNone').resolves(expected);
+ const result = await db.topicGetById(dbCtx, topicId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('covers no defaults', async function () {
+ const expected = { id: topicId };
+ sinon.stub(db.db, 'oneOrNone').resolves(expected);
+ const result = await db.topicGetById(dbCtx, topicId, false);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'oneOrNone').throws(expected);
+ try {
+ await db.topicGetById(dbCtx, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // topicGetById
+
+ describe('topicGetByUrl', function () {
+ it('success', async function() {
+ const expected = [];
+ sinon.stub(db.db, 'oneOrNone').resolves(expected);
+ const result = await db.topicGetByUrl(dbCtx, topicUrl);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'oneOrNone').throws(expected);
+ try {
+ await db.topicGetByUrl(dbCtx, topicUrl);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // topicGetByUrl
+
+ describe('topicGetContentById', function () {
+ it('success', async function() {
+ const expected = { id: topicId };
+ sinon.stub(db.db, 'oneOrNone').resolves(expected);
+ const result = await db.topicGetContentById(dbCtx, topicId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('covers default', async function() {
+ const expected = undefined;
+ sinon.stub(db.db, 'oneOrNone').resolves(expected);
+ const result = await db.topicGetContentById(dbCtx, topicId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'oneOrNone').throws(expected);
+ try {
+ await db.topicGetContentById(dbCtx, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // topicGetContentById
+
+ describe('topicSet', function () {
+ let data;
+ beforeEach(function () {
+ data = {
+ url: topicUrl,
+ };
+ });
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [{ id: topicId }],
+ duration: 10,
+ };
+ const expected = {
+ changes: 1,
+ lastInsertRowid: topicId,
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ const result = await db.topicSet(dbCtx, data);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.topicSet(dbCtx, data);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ it('fails invalid value', async function () {
+ sinon.stub(db.db, 'result');
+ try {
+ data.leaseSecondsPreferred = -100;
+ await db.topicSet(dbCtx, data);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.DataValidation);
+ }
+ assert(!db.db.result.called);
+ });
+ it('fails invalid values', async function () {
+ sinon.stub(db.db, 'result');
+ try {
+ data.leaseSecondsPreferred = 10;
+ data.leaseSecondsMax = 100;
+ data.leaseSecondsMin = 50;
+ await db.topicSet(dbCtx, data);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.DataValidation);
+ }
+ assert(!db.db.result.called);
+ });
+ }); // topicSet
+
+ describe('topicSetContent', function () {
+ let data;
+ beforeEach(function () {
+ data = {
+ content: 'content',
+ contentType: 'text/plain',
+ contentHash: 'abc123',
+ };
+ });
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const expected = {
+ changes: 1,
+ lastInsertRowid: undefined,
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ const result = await db.topicSetContent(dbCtx, data);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.topicSetContent(dbCtx, data);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // topicSetContent
+
+ describe('topicUpdate', function () {
+ let data;
+ beforeEach(function () {
+ data = {
+ leaseSecondsPreferred: 123,
+ leaseSecondsMin: 100,
+ leaseSecondsMax: 1000,
+ publisherValidationUrl: null,
+ contentHashAlgorithm: 'sha256',
+ };
+ });
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.topicUpdate(dbCtx, data);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.topicUpdate(dbCtx, data);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+
+ });
+
+ describe('verificationClaim', function () {
+ it('success', async function() {
+ const dbManyOrNone = [{ id: verificationId }];
+ const expected = [verificationId];
+ sinon.stub(db.db, 'manyOrNone').resolves(dbManyOrNone);
+ const result = await db.verificationClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'manyOrNone').throws(expected);
+ try {
+ await db.verificationClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // verificationClaim
+
+ describe('verificationClaimById', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [ { id: verificationId } ],
+ duration: 10,
+ };
+ const expected = {
+ changes: 1,
+ lastInsertRowid: verificationId,
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ const result = await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'result').throws(expected);
+ try {
+ await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // verificationClaimById
+
+ describe('verificationComplete', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.verificationComplete(dbCtx, verificationId, callback, topicId);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.verificationComplete(dbCtx, verificationId, callback, topicId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // verificationComplete
+
+ describe('verificationGetById', function () {
+ it('success', async function() {
+ const dbOneOrNone = { id: verificationId };
+ const expected = { id: verificationId };
+ sinon.stub(db.db, 'oneOrNone').resolves(dbOneOrNone);
+ const result = await db.verificationGetById(dbCtx, verificationId);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const expected = new Error();
+ sinon.stub(db.db, 'oneOrNone').throws(expected);
+ try {
+ await db.verificationGetById(dbCtx, verificationId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.deepStrictEqual(e, expected);
+ }
+ });
+ }); // verificationGetById
+
+ describe('verificationIncomplete', function () {
+ it('success', async function() {
+ const dbOne = { attempts: 0 };
+ const dbResult0 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const dbResult1 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
+ });
+ it('covers defaults', async function() {
+ const dbOne = { attempts: 0 };
+ const dbResult0 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const dbResult1 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ await db.verificationIncomplete(dbCtx, verificationId);
+ });
+ it('failure', async function () {
+ const dbOne = { attempts: 0 };
+ const dbResult0 = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ const dbResult1 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ try {
+ await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ it('second failure', async function () {
+ const dbOne = { attempts: 0 };
+ const dbResult0 = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ const dbResult1 = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'one').resolves(dbOne);
+ sinon.stub(db.db, 'result').onCall(0).resolves(dbResult0).onCall(1).resolves(dbResult1);
+ try {
+ await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // verificationIncomplete
+
+ describe('verificationInsert', function () {
+ let verification;
+ beforeEach(function () {
+ verification = {
+ topicId,
+ callback,
+ mode: 'subscribe',
+ isPublisherValidated: true,
+ leaseSeconds: 86400,
+ };
+ });
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [{ id: verificationId }],
+ duration: 10,
+ };
+ const expected = verificationId;
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ const result = await db.verificationInsert(dbCtx, verification);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.verificationInsert(dbCtx, verification);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ it('fails validation', async function () {
+ delete verification.leaseSeconds;
+ try {
+ await db.verificationInsert(dbCtx, verification);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.DataValidation);
+ }
+ });
+ }); // verificationInsert
+
+ describe('verificationRelease', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.verificationRelease(dbCtx, verificationId);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.verificationRelease(dbCtx, verificationId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // verificationRelease
+
+ describe('verificationUpdate', function () {
+ let data;
+ beforeEach(function () {
+ data = {
+ mode: 'subscribe',
+ isPublisherValidated: true,
+ };
+ });
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ };
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.verificationUpdate(dbCtx, verificationId, data);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ }
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.verificationUpdate(dbCtx, verificationId, data);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult, e.name);
+ }
+ });
+ it('fails validation', async function () {
+ delete data.mode;
+ try {
+ await db.verificationUpdate(dbCtx, verificationId, data);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.DataValidation);
+ }
+ });
+ }); // verificationUpdate
+
+ describe('verificationValidated', function () {
+ it('success', async function() {
+ const dbResult = {
+ rowCount: 1,
+ rows: [],
+ duration: 10,
+ }
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ await db.verificationValidated(dbCtx, verificationId);
+ });
+ it('failure', async function () {
+ const dbResult = {
+ rowCount: 0,
+ rows: [],
+ duration: 10,
+ }
+ sinon.stub(db.db, 'result').resolves(dbResult);
+ try {
+ await db.verificationValidated(dbCtx, verificationId);
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert(e instanceof DBErrors.UnexpectedResult);
+ }
+ });
+ }); // verificationValidated
+
+}); // DatabasePostgres