From 09488856715b8097621eb9d2040cc4b1a0e74027 Mon Sep 17 00:00:00 2001 From: Jonatan Nilsson Date: Tue, 18 Jan 2022 11:48:35 +0000 Subject: [PATCH] More development --- core/db.mjs | 33 ++++++++++---------- core/defaults.mjs | 25 +++++++++++++++ test/db.test.mjs | 71 ++++++++++++++++++++++++++++++++++++++++-- test/defaults.test.mjs | 64 +++++++++++++++++++++++++++++++++++++ test/updater.test.mjs | 0 5 files changed, 174 insertions(+), 19 deletions(-) create mode 100644 core/defaults.mjs create mode 100644 test/defaults.test.mjs create mode 100644 test/updater.test.mjs diff --git a/core/db.mjs b/core/db.mjs index 975eb4d..df48eea 100644 --- a/core/db.mjs +++ b/core/db.mjs @@ -1,5 +1,6 @@ import { Low, JSONFile } from 'lowdb' import { type } from 'os' +import { defaults, isObject } from './defaults.mjs' export default function GetDB(util, log, filename = 'db.json') { let fullpath = util.getPathFromRoot('./' + filename) @@ -60,33 +61,31 @@ export default function GetDB(util, log, filename = 'db.json') { } return false } + + db.addApplication = function(name) { + db.data.core[name] ||= {} + defaults(db.data.core[name], { + active: null, + latestInstalled: null, + latestVersion: null, + versions: [], + }) + } return db.read() .then(function() { - db.data ||= { - core: { - app: { - active: null, - latestInstalled: null, - latestVersion: null, - versions: [], - }, - manager: { - active: null, - latestInstalled: null, - latestVersion: null, - versions: [], - }, - version: 1, - } + if (!isObject(db.data)) { + log.warn(`File ${fullpath} was empty or not a json object, clearing it.`) + db.data = {} } + defaults(db.data, { core: { version: 1 } }) return db.write() }) .then( function() { return db }, function(err) { - let wrapped = new Error(`Error writing to ${fullpath}: ${err.code}`) + let wrapped = new Error(`Error writing to ${fullpath}: ${err.message} (${err.code})`) wrapped.code = err.code throw wrapped } diff --git a/core/defaults.mjs b/core/defaults.mjs new file mode 100644 index 0000000..fb701bc --- /dev/null +++ b/core/defaults.mjs @@ -0,0 +1,25 @@ + +// taken from isobject npm library +export function isObject(val) { + return val != null && typeof val === 'object' && Array.isArray(val) === false +} + +export function defaults(original, def) { + if (!isObject(original)) throw new Error('defaults called with non-object type') + + Object.keys(original).forEach(key => { + if (isObject(original[key]) && def && isObject(def[key])) { + defaults(original[key], def[key]) + } + }) + + if (def) { + Object.keys(def).forEach(function(key) { + if (typeof original[key] === 'undefined') { + original[key] = def[key] + } + }) + } + + return original +} diff --git a/test/db.test.mjs b/test/db.test.mjs index a556043..0e3d16e 100644 --- a/test/db.test.mjs +++ b/test/db.test.mjs @@ -6,6 +6,7 @@ import Util from '../core/util.mjs' var util = new Util(import.meta.url) var logger = { info: stub(), + warn: stub(), error: stub(), } @@ -28,10 +29,76 @@ t.test('Should auto create file with some defaults', async function() { assert.ok(stat.size > 0) assert.ok(db.data.core) + assert.ok(db.data.core.version) + assert.notOk(db.data.core.app) + assert.notOk(db.data.core.manager) +}) + +t.test('Should apply defaults to existing file', async function() { + await assert.isRejected(fs.stat('./test/db_test.json')) + await fs.writeFile('./test/db_test.json', '{"test":true}') + + let db = await lowdb(util, logger, 'db_test.json') + + let stat = await fs.stat('./test/db_test.json') + assert.ok(stat.size > 0) + + assert.ok(db.data.core) + assert.ok(db.data.core.version) + assert.strictEqual(db.data.test, true) + assert.notOk(db.data.core.app) + assert.notOk(db.data.core.manager) +}) + +t.test('Should not fail if db is invalid', async function() { + logger.warn.reset() + await assert.isRejected(fs.stat('./test/db_test.json')) + await fs.writeFile('./test/db_test.json', '[]') + + let db = await lowdb(util, logger, 'db_test.json') + + let stat = await fs.stat('./test/db_test.json') + assert.ok(stat.size > 0) + + assert.ok(db.data.core) + assert.ok(db.data.core.version) + assert.notOk(db.data.core.app) + assert.notOk(db.data.core.manager) + assert.ok(logger.warn.called) + assert.match(logger.warn.firstCall[0], /db_test.json/i) + assert.match(logger.warn.firstCall[0], /clear/i) +}) + +t.test('Should support adding an application with defaults', async function() { + await assert.isRejected(fs.stat('./test/db_test.json')) + + let db = await lowdb(util, logger, 'db_test.json') + + let stat = await fs.stat('./test/db_test.json') + assert.ok(stat.size > 0) + + assert.ok(db.data.core) + assert.ok(db.data.core.version) + assert.notOk(db.data.core.app) + assert.notOk(db.data.core.manager) + + db.addApplication('app') + assert.ok(db.data.core.app) assert.ok(db.data.core.app.versions) - assert.ok(db.data.core.manager) - assert.ok(db.data.core.manager.versions) + assert.strictEqual(db.data.core.app.active, null) + assert.strictEqual(db.data.core.app.latestInstalled, null) + assert.strictEqual(db.data.core.app.latestVersion, null) + + assert.notOk(db.data.core.herpderp) + + db.addApplication('herpderp') + + assert.ok(db.data.core.herpderp) + assert.ok(db.data.core.herpderp.versions) + assert.strictEqual(db.data.core.herpderp.active, null) + assert.strictEqual(db.data.core.herpderp.latestInstalled, null) + assert.strictEqual(db.data.core.herpderp.latestVersion, null) }) t.test('Should support reading from db', async function() { diff --git a/test/defaults.test.mjs b/test/defaults.test.mjs new file mode 100644 index 0000000..84e8146 --- /dev/null +++ b/test/defaults.test.mjs @@ -0,0 +1,64 @@ +import { Eltro as t, assert} from 'eltro' +import { isObject, defaults } from '../core/defaults.mjs' + +t.describe('#isObject()', () => { + t.test('should return false for invalid objects', function() { + let tests = [ + [], + '', + 'asdf', + 12341, + 0, + ] + + tests.forEach(function(check) { + assert.strictEqual(isObject(check), false) + }) + }) + t.test('should return true for valid objects', function() { + let tests = [ + {}, + new Object(), + ] + + tests.forEach(function(check) { + assert.strictEqual(isObject(check), true) + }) + }) +}) + +t.describe('#defaults()', () => { + t.test('should throw if original is missing or invalid', () => { + assert.throws(function() { defaults() }) + assert.throws(function() { defaults('asdf') }) + assert.throws(function() { defaults('') }) + assert.throws(function() { defaults(null) }) + assert.throws(function() { defaults(1234) }) + assert.throws(function() { defaults([]) }) + }) + t.test('should not override existing variables', () => { + let assertOutput = { a: 2 } + defaults(assertOutput, { a: 1 }) + + assert.deepStrictEqual(assertOutput, { a: 2 }) + }) + + t.test('should allow nesting through objects', () => { + let def = { a: { b: 2 } } + let inside = { a: { c: 3} } + defaults(inside, def) + + assert.deepStrictEqual(inside.a, { + b: 2, + c: 3, + }) + }) + + t.test('should not return new reference', () => { + let assertInput = { a: 1 } + let existing = assertInput + defaults(assertInput, { b: 2 }) + + assert.strictEqual(assertInput, existing) + }) +}) diff --git a/test/updater.test.mjs b/test/updater.test.mjs new file mode 100644 index 0000000..e69de29