rejigger schema version support, use shared logger stub in tests
authorJustin Wind <justin.wind+git@gmail.com>
Thu, 1 Aug 2024 23:23:01 +0000 (16:23 -0700)
committerJustin Wind <justin.wind+git@gmail.com>
Sat, 3 Aug 2024 17:51:09 +0000 (10:51 -0700)
18 files changed:
index.js
lib/abstract.js
lib/factory.js
lib/postgres-creator.js
lib/schema-version-helper.js
lib/sqlite-creator.js
test-integration/abstract.js
test-integration/index.js
test-integration/postgresql/index.js
test-integration/sqlite/index.js
test/helpers.js
test/integration.js
test/lib/abstract.js
test/lib/factory.js
test/lib/postgres-creator.js
test/lib/schema-version-helper.js
test/lib/sqlite-creator.js
test/stub.js

index c22d0a97f46d1d20d20c3318d876e2b5ac36383f..0f595ecafd3db745ddfa6c84cc2859e03dec5b7d 100644 (file)
--- a/index.js
+++ b/index.js
@@ -7,7 +7,7 @@ const SchemaVersionHelper = require('./lib/schema-version-helper');
 const PostgresCreator = require('./lib/postgres-creator');
 const SQLiteCreator = require('./lib/sqlite-creator');
 const { validate } = require('./lib/validation');
-const { stubCreator, stubPgp } = require('./test/stub');
+const { interfaceMethods, stubCreator, stubPgp } = require('./test/stub');
 const { itChecksImplementation } = require('./test/helpers');
 
 module.exports = {
@@ -20,6 +20,7 @@ module.exports = {
   validate,
   test: {
     itChecksImplementation,
+    interfaceMethods,
     stubCreator,
     stubPgp,
   },
index 7137342c1a3081e4425e5ce411dd1d39179bce8a..116b464b4ae03aa2a0718bd7051e3517d5776ff2 100644 (file)
@@ -3,7 +3,7 @@
 
 const { fileScope } = require('@squeep/log-helper');
 const DatabaseErrors = require('./errors');
-const { schemaVersionObjectToNumber } = require('./schema-version-helper');
+const { schemaVersionObjectCmp } = require('./schema-version-helper');
 
 const _fileScope = fileScope(__filename);
 
@@ -38,6 +38,31 @@ class Database {
    * @property {number} minor semver minor
    * @property {number} patch semver patch
    */
+
+  /**
+   * @returns {SchemaVersionObject} minimum supported schema version
+   */
+  // eslint-disable-next-line class-methods-use-this
+  get schemaVersionMin() {
+    return {
+      major: 0,
+      minor: 0,
+      patch: 0,
+    };
+  }
+
+  /**
+   * @returns {SchemaVersionObject} maximum supported schema version
+   */
+  // eslint-disable-next-line class-methods-use-this, sonarjs/no-identical-functions
+  get schemaVersionMax() {
+    return {
+      major: 0,
+      minor: 0,
+      patch: 0,
+    };
+  }
+
   /**
    * @typedef {object} VersionRange
    * @property {SchemaVersionObject} min schema minimum supported
@@ -47,7 +72,10 @@ class Database {
    * @returns {VersionRange} schema version range supported
    */
   get schemaVersionsSupported() {
-    return this._notImplemented('schemaVersionsSupported', arguments);
+    return {
+      min: this.schemaVersionMin,
+      max: this.schemaVersionMax,
+    };
   }
 
 
@@ -116,13 +144,19 @@ class Database {
     const _scope = _fileScope('initialize');
 
     this.currentSchema = await this._currentSchema();
-    const current = schemaVersionObjectToNumber(this.currentSchema);
-    const min = schemaVersionObjectToNumber(this.schemaVersionsSupported.min);
-    const max = schemaVersionObjectToNumber(this.schemaVersionsSupported.max);
-    if (current >= min && current <= max) {
-      this.logger.debug(_scope, 'schema supported', { currentSchema: this.currentSchema, schemaVersionsSupported: this.schemaVersionsSupported });
+    // Special case of version 0.0.0 interpreted as no installed schema.
+    if (!this.currentSchema || schemaVersionObjectCmp(this.currentSchema, { major: 0, minor: 0, patch: 0 }) == 0) {
+      this.logger.debug(_scope, 'no installed schema', { currentSchema: this.currentSchema, schemaVersionsSupported: this.schemaVersionsSupported });
+      return;
+    }
+    // Installed db schema must satisfy min >= db >= max, otherwise bail.
+    // Reasoning: we would expect engine implementation to have performed all migrations, so if db is outside implemented range, something is confused.
+    const currentSatisfiesMinimum = schemaVersionObjectCmp(this.currentSchema, this.schemaVersionsSupported.min) >= 0;
+    const currentSatisfiesMaxium = schemaVersionObjectCmp(this.currentSchema, this.schemaVersionsSupported.max) <= 0;
+    if (currentSatisfiesMinimum && currentSatisfiesMaxium) {
+      this.logger.debug(_scope, 'installed schema is supported', { currentSchema: this.currentSchema, schemaVersionsSupported: this.schemaVersionsSupported });
     } else {
-      this.logger.error(_scope, 'schema not supported', { currentSchema: this.currentSchema, schemaVersionsSupported: this.schemaVersionsSupported });
+      this.logger.error(_scope, 'installed schema not supported', { currentSchema: this.currentSchema, schemaVersionsSupported: this.schemaVersionsSupported });
       throw new DatabaseErrors.MigrationNeeded();
     }
   }
index e11cd66a6e4ba00be96868373fc82e3a42297ad5..e51cfcf60a5f02f46e32b47f401f0b192058e28a 100644 (file)
@@ -50,7 +50,7 @@ class DatabaseFactory {
   /**
    * @returns {string[]} supported engines for connection string
    */
-  get supportedProtocols() { // eslint-disable-line class-methods-use-this
+  static get supportedProtocols() {
     return [
       'postgresql',
       'sqlite',
@@ -64,7 +64,7 @@ class DatabaseFactory {
    * @returns {string} engine path
    */
   enginePath(enginePathPrefix, protocol) {
-    if (!this.supportedProtocols.includes(protocol)) {
+    if (!this.constructor.supportedProtocols.includes(protocol)) {
       throw new UnsupportedEngine(protocol);
     }
     return [enginePathPrefix, protocol].join(path.sep);
index 531702a52e5ef4c351788929f8c5679f9dbe1a9c..88fa66c82cf90ac3a5fba0ded7581dd90f4af817 100644 (file)
@@ -21,23 +21,6 @@ const PostgresCreator = (Abstract) => {
     };
 
 
-    // eslint-disable-next-line class-methods-use-this
-    get schemaVersionsSupported() {
-      return {
-        min: {
-          major: 0,
-          minor: 0,
-          patch: 0,
-        },
-        max: {
-          major: 0,
-          minor: 0,
-          patch: 0,
-        },
-      };
-    }
-
-
     /**
      * @typedef {object} ConsoleLike
      * @property {Function} error log error
index 17cd7c788c38f8bd62919f065165417155c4e727..fb38c1ca92a13540f0ecc92c1f96826617b3fac1 100644 (file)
@@ -11,48 +11,56 @@ const path = require('node:path');
 
 /**
  * @typedef {object} SchemaVersionObject
- * @property {number} major semver major
- * @property {number} minor semver minor
- * @property {number} patch semver patch
+ * @property {number|bigint} major semver major
+ * @property {number|bigint} minor semver minor
+ * @property {number|bigint} patch semver patch
  */
 
 
 /**
- * Split a dotted version string into parts.
- * @param {string} v version string
- * @returns {SchemaVersionObject} version object
+ * Integer-type-agnostic comparison helper.
+ * @param {number|bigint} a a
+ * @param {number|bigint} b b
+ * @returns {number} sort value
  */
-function schemaVersionStringToObject(v) {
-  const [ major, minor, patch ] = v.split('.', 3).map((x) => parseInt(x, 10));
-  return { major, minor, patch };
+function _intCmp(a, b) {
+  const diff = BigInt(a) - BigInt(b);
+  if (diff > 0n) {
+    return 1;
+  } else if (diff < 0n) {
+    return -1;
+  }
+  return 0;
 }
 
 
 /**
- * Render a version object numerically.
- * Version components must be <1000.
- * @param {SchemaVersionObject} v version object
- * @returns {number} numeric version
+ * Compare two version objects, returns numeric value suitable for sorting.
+ * @param {SchemaVersionObject} a version object
+ * @param {SchemaVersionObject} b version object
+ * @returns {number} version difference, 0 if a=b, 1 if a>b, -1 if a<b
  */
-function schemaVersionObjectToNumber(v) {
-  if (!v) {
-    return 0;
+function schemaVersionObjectCmp(a, b) {
+  const diffMajor = _intCmp(a.major, b.major);
+  if (diffMajor) {
+    return diffMajor;
   }
-  const vScale = 1000;
-  if (v.major >= vScale || v.minor >= vScale || v.patch >= vScale) {
-    throw new RangeError('version component too large');
+  const diffMinor = _intCmp(a.minor, b.minor);
+  if (diffMinor) {
+    return diffMinor;
   }
-  return parseInt(v.major) * vScale * vScale + parseInt(v.minor) * vScale + parseInt(v.patch);
+  return _intCmp(a.patch, b.patch);
 }
 
 
 /**
- * Convert dotted version string into number.
+ * Split a dotted version string into parts.
  * @param {string} v version string
- * @returns {number} numeric version
+ * @returns {SchemaVersionObject} version object
  */
-function schemaVersionStringToNumber(v) {
-  return schemaVersionObjectToNumber(schemaVersionStringToObject(v));
+function schemaVersionStringToObject(v) {
+  const [ major, minor, patch ] = v.split('.', 3).map((x) => parseInt(x, 10));
+  return { major, minor, patch };
 }
 
 
@@ -63,7 +71,7 @@ function schemaVersionStringToNumber(v) {
  * @returns {number} version difference
  */
 function schemaVersionStringCmp(a, b) {
-  return schemaVersionStringToNumber(a) - schemaVersionStringToNumber(b);
+  return schemaVersionObjectCmp(schemaVersionStringToObject(a), schemaVersionStringToObject(b));
 }
 
 
@@ -117,21 +125,21 @@ function allSchemaVersions(engineDir) {
  * @returns {string[]} list of un-applied migration versions
  */
 function unappliedSchemaVersions(engineDir, current, supported) {
-  const min = schemaVersionObjectToNumber(supported.min);
-  const max = schemaVersionObjectToNumber(supported.max);
-  const cur = schemaVersionObjectToNumber(current);
+  const { min, max } = supported;
   const available = allSchemaVersions(engineDir);
   return available.filter((a) => {
-    a = schemaVersionStringToNumber(a);
-    return a >= min && a <= max && a > cur;
+    a = schemaVersionStringToObject(a);
+    const satisfiesMinimum = schemaVersionObjectCmp(a, min) >= 0n;
+    const satisfiesMaximum = schemaVersionObjectCmp(a, max) <= 0n;
+    const isNeeded = (!current) || schemaVersionObjectCmp(a, current) > 0n;
+    return satisfiesMinimum && satisfiesMaximum && isNeeded;
   });
 }
 
 
 module.exports = {
+  schemaVersionObjectCmp,
   schemaVersionStringToObject,
-  schemaVersionObjectToNumber,
-  schemaVersionStringToNumber,
   schemaVersionStringCmp,
   isSchemaMigrationDirectory,
   allSchemaVersions,
index f1cc1710e5e3682de611e9512c7900cf3965fe37..32fbd762f36c47fda15a4cad8497419024e8d689 100644 (file)
@@ -14,21 +14,6 @@ const _fileScope = fileScope(__filename);
 const SQLiteCreator = (Abstract) => {
 
   class DatabaseSQLite extends Abstract {
-    // eslint-disable-next-line class-methods-use-this
-    get schemaVersionsSupported() {
-      return {
-        min: {
-          major: 0,
-          minor: 0,
-          patch: 0,
-        },
-        max: {
-          major: 0,
-          minor: 0,
-          patch: 0,
-        },
-      };
-    }
 
     /**
      * @typedef {object} ConsoleLike
@@ -275,12 +260,12 @@ COMMIT;`);
     }
 
 
-    context(fn) {
+    async context(fn) {
       return fn(this.db);
     }
 
 
-    transaction(dbCtx, fn) {
+    async transaction(dbCtx, fn) {
       dbCtx = dbCtx || this.db;
       return dbCtx.transaction(fn).immediate();
     }
index 26b110fdf696e0de164fdae3da521fe9c8acd637..82c9ac084316d43addc41402a440591ee31b8352 100644 (file)
@@ -1,3 +1,4 @@
+/* eslint-disable no-unused-vars */
 'use strict';
 
 const { Abstract } = require('../');
@@ -12,6 +13,24 @@ class AbstractIntegration extends Abstract {
     }
   }
 
+  // eslint-disable-next-line class-methods-use-this
+  get schemaVersionMin() {
+    return {
+      major: 1,
+      minor: 0,
+      patch: 0,
+    };
+  }
+
+  // eslint-disable-next-line class-methods-use-this, sonarjs/no-identical-functions
+  get schemaVersionMax() {
+    return {
+      major: 1,
+      minor: 0,
+      patch: 0,
+    };
+  }
+
   almanacGetAll(dbCtx) {
     this._notImplemented('almanacGetAll', arguments);
   }
index 14278234660c480b18ce59bc158721bea7bcd79b..8f5525ff9f1ebd0ad4a115e13e9d5d87cd3e4459 100644 (file)
@@ -6,7 +6,9 @@ class Factory extends BaseFactory {
   constructor(...args) {
     super('.', ...args);
   }
+  // eslint-disable-next-line class-methods-use-this
   localRequire(enginePath) {
+    // eslint-disable-next-line security/detect-non-literal-require
     return require(enginePath);
   }
 }
index 8af0c6044301118bfd733327570119d68a92ab01..fbc9d8a5fc9f42775c23a032108e0f3f552f0a5a 100644 (file)
@@ -11,22 +11,6 @@ const assert = require('node:assert');
  */
 class PostgresDB extends PostgresCreator(Abstract) {
 
-  // eslint-disable-next-line class-methods-use-this
-  get schemaVersionsSupported() {
-    return {
-      min: {
-        major: 0,
-        minor: 0,
-        patch: 0,
-      },
-      max: {
-        major: 1,
-        minor: 0,
-        patch: 0,
-      },
-    };
-  }
-
   constructor(...args) {
     super(...args);
     if (!this._isProduction) {
@@ -47,7 +31,7 @@ class PostgresDB extends PostgresCreator(Abstract) {
     assert.deepStrictEqual(result, expected);
   }
 
-  async almanacGetAll(dbCtx) {  // eslint-disable-line no-unused-vars
+  async almanacGetAll(dbCtx) {
     const _scope = 'almanacGetAll';
     this.logger.debug(_scope, 'called');
     try {
index 1379767b15f9dd58105859b5c4eb4ac051395b7c..daa998d5e242a1d58475bdbcd79f072ee4814770 100644 (file)
@@ -11,22 +11,6 @@ const DBErrors = require('../../lib/errors');
  */
 class SQLiteDB extends SQLiteCreator(Abstract) {
 
-  // eslint-disable-next-line class-methods-use-this
-  get schemaVersionsSupported() {
-    return {
-      min: {
-        major: 0,
-        minor: 0,
-        patch: 0,
-      },
-      max: {
-        major: 1,
-        minor: 0,
-        patch: 0,
-      },
-    };
-  }
-
   constructor(...args) {
     super(...args);
     if (!this._isProduction) {
@@ -40,7 +24,7 @@ class SQLiteDB extends SQLiteCreator(Abstract) {
 
   // eslint-disable-next-line class-methods-use-this
   _engineSpecificTests() {
-    // none
+    assert(true);
   }
 
   almanacGetAll(dbCtx) {  // eslint-disable-line no-unused-vars
index 8ad4f35bdde24bba5d04b1cf761376951ba00f99..9b9e6d197335e55ff20e4a756fd89e7462fd0d29 100644 (file)
@@ -1,64 +1,10 @@
 'use strict';
 
 const assert = require('node:assert');
-const sinon = require('sinon');
 const DBErrors = require('../lib/errors');
 
 const nop = () => undefined;
 
-class StubLogger {
-  constructor(backend) {
-    const logger = backend || (process.env['VERBOSE_TESTS'] && console) || StubLogger._nopLogger;
-    for (const level of StubLogger._levels) {
-      this[level] = logger[level]; // eslint-disable-line security/detect-object-injection
-    }
-  }
-
-  static get _levels() {
-    return ['error', 'warn', 'info', 'log', 'debug'];
-  }
-
-  static get _nopLogger() {
-    return Object.fromEntries(StubLogger._levels.map((level) => [level, nop]));
-  }
-
-  _spy() {
-    StubLogger._levels.forEach((level) => sinon.spy(this, level));
-  }
-}
-
-const stubPgp = () => {
-  const stub = {
-    result: () => ({ rows: [] }),
-    all: nop,
-    get: nop,
-    run: nop,
-    one: nop,
-    manyOrNone: nop,
-    oneOrNone: nop,
-    query: nop,
-    batch: nop,
-    multiResult: nop,
-    connect: nop,
-  };
-  stub.tx = (fn) => fn(stub);
-  stub.txIf = (fn) => fn(stub);
-  stub.task = (fn) => fn(stub);
-  return stub;
-};
-stubPgp.pg = {
-  types: {
-    getTypeParser: nop,
-    setTypeParser: nop,
-  },
-};
-stubPgp.QueryFile = class {};
-stubPgp.utils = {
-  enumSql: () => ({}),
-};
-stubPgp.end = nop;
-
-
 /**
  * Mocha function to validate all interface methods are implemented.
  * @param {*} db db instance
@@ -82,7 +28,5 @@ async function itChecksImplementation(db, interfaceMethods) {
 
 module.exports = {
   nop,
-  StubLogger,
-  stubPgp,
   itChecksImplementation,
 };
index 96c4dcf1149a1a1bd2ded9d60045e7fa82145b26..25847dcba61b70fb93f70d159161709660781fda 100644 (file)
@@ -15,8 +15,9 @@
  */
 
 const assert = require('node:assert');
+const sinon = require('sinon');
 const { step } = require('mocha-steps');
-const { StubLogger } = require('./helpers');
+const { StubLogger } = require('@squeep/test-helper');
 
 describe('Database Integration', function () {
   const implementations = [];
@@ -67,8 +68,7 @@ describe('Database Integration', function () {
 
       before(async function () {
         this.timeout(10 * 1000); // Allow some time for creating tables et cetera.
-        logger = new StubLogger();
-        logger._spy();
+        logger = new StubLogger(sinon);
         // eslint-disable-next-line security/detect-non-literal-require
         DB = require(i.module);
         db = new DB(logger, i.config);
index c6b32b37907b53ee3ea9526543d31fdc44b2cd16..e06adfe9fd7f2b723e842c4df31f5c0ade658734 100644 (file)
@@ -3,7 +3,7 @@
 
 const assert = require('node:assert');
 const sinon = require('sinon');
-const { StubLogger } = require('../helpers');
+const { StubLogger } = require('@squeep/test-helper');
 const Abstract = require('../../lib/abstract');
 const { MigrationNeeded, NotImplemented } = require('../../lib/errors');
 
@@ -14,11 +14,20 @@ describe('Abstract', function () {
     options = {
       db: {},
     };
-    stubLogger = new StubLogger();
-    stubLogger._spy();
+    stubLogger = new StubLogger(sinon);
     db = new Abstract(stubLogger, options);
   });
 
+  describe('schemaVersionsSupported', function () {
+    it('default', function () {
+      const result = db.schemaVersionsSupported;
+      assert.deepStrictEqual(result, {
+        min: { major: 0, minor: 0, patch: 0 },
+        max: { major: 0, minor: 0, patch: 0 },
+      });
+    });
+  }); // schemaVersionsSupported
+
   describe('initialize', function () {
     let schemaVersionsSupported, currentSchema;
     beforeEach(function () {
@@ -27,25 +36,26 @@ describe('Abstract', function () {
         max: { major: 2, minor: 0, patch: 0 },
       };
       currentSchema = { major: 1, minor: 5, patch: 0 };
-      sinon.stub(db, 'schemaVersionsSupported').get(() => schemaVersionsSupported);
+      sinon.stub(db, 'schemaVersionMin').get(() => schemaVersionsSupported.min);
+      sinon.stub(db, 'schemaVersionMax').get(() => schemaVersionsSupported.max);
       sinon.stub(db, '_currentSchema').callsFake(() => Promise.resolve(currentSchema));
     });
-
     it('validates supported schema version', async function () {
       await db.initialize();
       assert.deepStrictEqual(db.currentSchema, currentSchema);
     });
-
     it('reports unsupported low schema version', async function () {
       schemaVersionsSupported.min.major = 2;
       assert.rejects(() => db.initialize(), MigrationNeeded);
     });
-
     it('reports unsupported high schema version', async function () {
       schemaVersionsSupported.max.major = 1;
       assert.rejects(() => db.initialize(), MigrationNeeded);
     });
-
+    it('covers no current', async function () {
+      currentSchema = undefined;
+      await db.initialize();
+    });
   }); // initialize
 
   describe('_camelfy', function () {
@@ -97,16 +107,4 @@ describe('Abstract', function () {
     });
   }); // abstract methods
 
-  describe('abstract getters', function () {
-    const abstractGetters = [
-      'schemaVersionsSupported',
-    ];
-
-    it('need implementation', function () {
-      for (const m of abstractGetters) {
-        assert.throws(() => db[m], NotImplemented);
-      }
-    });
-  }); // abstract getters
-
 }); // Abstract
index 1e9187d0ddd7f9b7964de6ac151a5f8f3a54d7ef..d9775b88f5e5378b3ed8c22a7f1018d4beddb257 100644 (file)
@@ -5,14 +5,13 @@ const sinon = require('sinon');
 const Factory = require('../../lib/factory');
 const { UnsupportedEngine } = require('../../lib/errors');
 const pgp = require('pg-promise');
-
-const nop = () => undefined;
+const { StubLogger } = require('@squeep/test-helper');
 
 describe('Factory', function () {
   let logger, options, enginePathPrefix;
 
   beforeEach(function () {
-    logger = process.env['VERBOSE_TESTS'] ? console : { log: nop, debug: nop };
+    logger = new StubLogger(sinon);
     options = {
       db: {
         connectionString: '',
index 8db1f74896632c7cb13e61062a44a9e949ddcab7..76d394e7f300237fe7adc4a87397f4a3b5c188f1 100644 (file)
@@ -7,7 +7,8 @@ const Abstract = require('../../lib/abstract');
 const { interfaceMethods, stubPgp } = require('../stub');
 const PostgresCreator = require('../../lib/postgres-creator');
 const DBErrors = require('../../lib/errors');
-const { StubLogger, nop, itChecksImplementation } = require('../helpers');
+const { nop, itChecksImplementation } = require('../helpers');
+const { StubLogger } = require('@squeep/test-helper');
 
 describe('Postgres Creator', function () {
   let db, options, stubLogger;
@@ -22,8 +23,7 @@ describe('Postgres Creator', function () {
         noWarnings: true,
       },
     };
-    stubLogger = new StubLogger();
-    stubLogger._spy();
+    stubLogger = new StubLogger(sinon);
     db = new DatabasePostgres(stubLogger, options, stubPgp);
   });
 
index fff4cf49e925d3bd4f5a769f3a6b2e62e8fe215c..3b639e080ead07cc43b9f5e25d508c3ffc7aca7b 100644 (file)
@@ -30,42 +30,10 @@ describe('SchemaVersionHelper', function () {
     });
   }); // schemaVersionStringToObject
 
-  describe('schemaVersionObjectToNumber', function () {
-    it('covers', function () {
-      const expected = 1002003;
-      const result = svh.schemaVersionObjectToNumber({
-        major: 1,
-        minor: 2,
-        patch: 3,
-      });
-      assert.strictEqual(result, expected);
-    });
-    it('has numeric limitations', function () {
-      assert.throws(() => svh.schemaVersionObjectToNumber({
-        major: 1000,
-        minor: 1000,
-        patch: 1000,
-      }), RangeError);
-    });
-    it('returns zero for unknown', function () {
-      const expected = 0;
-      const result = svh.schemaVersionObjectToNumber();
-      assert.strictEqual(result, expected);
-    });
-  }); // schemaVersionObjectToNumber
-
-  describe('schemaVersionStringToNumber', function () {
-    it('covers', function () {
-      const expected = 1002003;
-      const result = svh.schemaVersionStringToNumber('1.2.3');
-      assert.strictEqual(result, expected);
-    });
-  }); // schemaVersionStringToNumber
-
   describe('schemaVersionStringCmp', function () {
     it('sorts', function () {
-      const expected = ['0.0.0', '1.0.0', '1.5.3', '64.123.998', '64.123.999'];
-      const source = ['1.5.3', '64.123.998', '1.0.0', '64.123.999', '0.0.0'];
+      const expected = ['0.0.0', '1.0.0', '1.5.3', '64.123.998', '64.123.999', '9999.9999.9999'];
+      const source = ['1.5.3', '64.123.998', '9999.9999.9999', '1.0.0', '64.123.999', '0.0.0'];
       source.sort(svh.schemaVersionStringCmp);
       assert.deepStrictEqual(source, expected);
     });
@@ -153,7 +121,25 @@ describe('SchemaVersionHelper', function () {
     it('covers', function () {
       const expected = ['1.1.0', '1.1.1'];
       fs.readdirSync.returns(['1.1.2', 'file.txt', '1.1.0', '1.1.1', 'init.sql', '1.0.1', '1.0.0']);
-      // cannot seem to stub isSchemaMigration, so here are the internals of it stubbed
+      // cannot seem to stub isSchemaMigrationDirectory, so here are the internals of it stubbed
+      let i = 0;
+      fs.statSync
+        .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.1.2'
+        .onCall(i++).returns(notDir) // 'file.txt'
+        .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.1.0'
+        .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.1.1'
+        .onCall(i++).returns(notDir) // 'init.sql'
+        .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.0.1'
+        .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.0.0'
+      ;
+      const result = svh.unappliedSchemaVersions('path', current, supported);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers no current version', function () {
+      current = undefined;
+      const expected = ['1.0.1', '1.1.0', '1.1.1'];
+      fs.readdirSync.returns(['1.1.2', 'file.txt', '1.1.0', '1.1.1', 'init.sql', '1.0.1', '1.0.0']);
+      // cannot seem to stub isSchemaMigrationDirectory, so here are the internals of it stubbed
       let i = 0;
       fs.statSync
         .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.1.2'
@@ -166,6 +152,7 @@ describe('SchemaVersionHelper', function () {
       ;
       const result = svh.unappliedSchemaVersions('path', current, supported);
       assert.deepStrictEqual(result, expected);
+
     });
   }); // unappliedSchemaVersions
 
index fd57f4929a9d1e155ebc20b441f12141158875e0..1c84aaa19274e01072c594cf9b3c831d295f9f02 100644 (file)
@@ -7,7 +7,8 @@ const Abstract = require('../../lib/abstract');
 const { interfaceMethods } = require('../stub');
 const SQLiteCreator = require('../../lib/sqlite-creator');
 const DBErrors = require('../../lib/errors');
-const { StubLogger, nop, itChecksImplementation } = require('../helpers');
+const { nop, itChecksImplementation } = require('../helpers');
+const { StubLogger } = require('@squeep/test-helper');
 
 describe('DatabaseSQLite', function () {
   let db, options, stubLogger;
@@ -21,8 +22,7 @@ describe('DatabaseSQLite', function () {
         queryLogLevel: 'debug',
       },
     };
-    stubLogger = new StubLogger();
-    stubLogger._spy();
+    stubLogger = new StubLogger(sinon);
     db = new DatabaseSQLite(stubLogger, options);
     sinon.stub(fs, 'readdirSync').returns([]);
     sinon.stub(fs, 'statSync').returns({
index 71c926380acfdcf8a0c4657e5f958c146aaaf649..aa8f54801191cb39a59714067471a7e9afb31e36 100644 (file)
@@ -105,8 +105,7 @@ function stubCreator(Abstract, metadata = stubMetadata()) {
     constructor(...args) {
       super(...args);
       this._stubMetadata.interfaceMethods = interfaceMethods(Abstract, metadata);
-      this._stubMetadata.spyMethods.forEach((m) => sinon.spy(this, m));
-      this._sinonReset();
+      this._sinonInit();
     }
 
     async context(fn) {
@@ -117,7 +116,7 @@ function stubCreator(Abstract, metadata = stubMetadata()) {
       await fn(dbCtx);
     }
 
-    _sinonReset() {
+    _sinonInit() {
       this._stubMetadata.abstractGetters
         .forEach((m) => sinon.stub(this, m).get())
       ;
@@ -129,7 +128,7 @@ function stubCreator(Abstract, metadata = stubMetadata()) {
         .forEach((m) => sinon.stub(this, m))
       ;
       this._stubMetadata.spyMethods
-        .forEach((m) => this[m].resetHistory()) // eslint-disable-line security/detect-object-injection
+        .forEach((m) => sinon.spy(this, m))
       ;
     }
   }