From df7e1e550901986f86d15df9dc26993da3b43454 Mon Sep 17 00:00:00 2001 From: Jonatan Nilsson Date: Fri, 28 Jan 2022 11:47:55 +0000 Subject: [PATCH] Fix a bug in db test, finished application update. Fixed a few bugs and did some refactoring. Started on application.run() functionality --- core/application.mjs | 188 +++++++++++++++++++++++++++------- core/core_new.mjs | 0 core/db.mjs | 14 +++ core/util.mjs | 3 + test/application.run.test.mjs | 92 +++++++++++++++++ test/application.test.mjs | 135 +++++++++++++++++++++++- test/db.test.mjs | 21 +++- test/exampleindex.mjs | 17 +++ test/util.test.mjs | 39 +++++++ 9 files changed, 469 insertions(+), 40 deletions(-) create mode 100644 core/core_new.mjs create mode 100644 test/application.run.test.mjs create mode 100644 test/exampleindex.mjs diff --git a/core/application.mjs b/core/application.mjs index 6894561..5eb9ec7 100644 --- a/core/application.mjs +++ b/core/application.mjs @@ -1,5 +1,6 @@ import { EventEmitter } from 'events' import fs from 'fs/promises' +import HttpServer from './http.mjs' export default class Application extends EventEmitter { constructor(util, db, provider, name, opts = {}) { @@ -10,6 +11,22 @@ export default class Application extends EventEmitter { this.provider = provider this.name = name this.updating = false + this.http = new HttpServer(this.config) + this.module = null + this.running = false + + // Fresh is used to indicate that when we run the application and it fails, + // whether the environment we are in was fresh or not. An example would be + // if we had previously run an older version. In that case, that older version + // might have dirtied the runtime or left a port open or other stuff. + // In which case, running the new version might fail even though it should + // normally be fine. As such we have this flag here. to indicate we might + // need a full restart before making another attempt. + this.fresh = true + + // Apply defaults to config + this.config.updateEvery = this.config.updateEvery || 180 + this.config.waitUntilFail = this.config.waitUntilFail || (60 * 1000) Object.assign(this, { setInterval: opts.setInterval || setInterval, @@ -31,7 +48,7 @@ export default class Application extends EventEmitter { this.db.data.core[this.name].updater += 'Error while running automatic update: ' + err.message + '. ' } ) - }, (this.config.updateEvery || 180) * 60 * 1000) + }, this.config.updateEvery * 60 * 1000) timer.unref() } @@ -41,15 +58,44 @@ export default class Application extends EventEmitter { return message } - async update() { - if (this.updating) return + msgStatic = 'Provider in question is static and so no update required, nothing to do.' + update() { if (this.provider.static) { - this.updateLog('Provider in question is static and so no update required, nothing to do.') - return + if (this.db.data.core[this.name].updater !== this.msgStatic) { + this.db.data.core[this.name].updater = '' + this.updateLog(this.msgStatic) + return this.db.write() + } + return Promise.resolve() } + if (this.updating) return this.updating = true + + return this._update() + .then(() => { + this.updating = false + return this.db.write() + }) + .catch((err) => { + this.updating = false + return this.db.write() + .then(function() { return Promise.reject(err) }) + }) + } + + logAddSeperator(log) { + if (!log.endsWith('\n')) { + log += '\n' + } + if (!log.endsWith('\n\n')) { + log += '\n' + } + return log + } + + async _update() { this.db.data.core[this.name].updater = '' let cleanup = true let folder = '' @@ -59,45 +105,70 @@ export default class Application extends EventEmitter { try { log += this.updateLog(`Checking for latest version at ${new Date().toISOString().replace('T', ' ').split('.')[0]}. `) + '\n' + // Get the latest version from our provider latest = await this.provider.getLatestVersion() log += this.updateLog(`Found ${latest.version}. `) + '\n' + // If the versino matches the latest installed, then there's nothing to do if (this.db.data.core[this.name].latestInstalled === latest.version) { this.updateLog('Already up to date, nothing to do. ') - this.updating = false return } + // Make the id for the vesion the version number. Allows for easy lookup + // among other nice and simple structure. latest.id = latest.version + + // check to see if we already have this version in our database. var found = this.db.get(this.db.data.core[this.name].versions, latest.id) if (found) { + // Check if the existing version found was already installed. + if (found.installed) { + this.updateLog('Version was already installed, nothing to do. ') + return + } + + // We found existing on, update the keys of the one in the databse with + // the latest data we got from getLatestVersion(). This ensures that info + // like link, filename and such get updated if these have changed since. Object.keys(latest).forEach(function(key) { found[key] = latest[key] }) latest = found + + // Check to see if the existing one has failed too many times and + // if so, we should skip them and consider those versions as black + // listed and avoid at all cost. + if (latest.failtodownload && latest.failtodownload > 3) { + this.updateLog('Version failed to download too many times, skipping this version. ') + return + } + + if (latest.failtoinstall && latest.failtoinstall > 3) { + this.updateLog('Version failed to install too many times, skipping this version. ') + return + } + + // Combine the logs log = latest.log + log } else { - this.db.upsert(this.db.data.core[this.name].versions, latest) - } - - if (latest.failtodownload && latest.failtodownload > 3) { - this.updateLog('Version failed to download too many times, skipping this version. ') - this.updating = false - return - } - if (latest.failtoinstall && latest.failtoinstall > 3) { - this.updateLog('Version failed to install too many times, skipping this version. ') - this.updating = false - return + // This is a new version, mark it with stable tag of zero. + latest.stable = 0 + this.db.upsertFirst(this.db.data.core[this.name].versions, latest) } + // The target file for the archive and the target folder for new our version let target = this.util.getPathFromRoot(`./${this.name}/${latest.version}/file${this.util.getExtension(latest.filename)}`) folder = this.util.getPathFromRoot(`./${this.name}/${latest.version}`) + // Create it in case it does not exist. await this.fs.mkdir(folder, { recursive: true }) log += this.updateLog(`Downloading ${latest.link} to ${target}. `) + '\n' + await this.db.write() + + // Download the latest version using the provider in question. await this.provider.downloadVersion(latest, target) .catch(function(err) { latest.failtodownload = (latest.failtodownload || 0) + 1 @@ -105,6 +176,9 @@ export default class Application extends EventEmitter { }) log += '\n' + this.updateLog(`Extracting ${target}. `) + '\n' + await this.db.write() + + // Download was successful, extract the archived file that we downloaded await this.util.extractFile(target, function(msg) { log += msg }).catch(function(err) { @@ -112,15 +186,16 @@ export default class Application extends EventEmitter { return Promise.reject(err) }) + // Remove the archived file since we're done using it. await this.fs.rm(target, { force: true }).catch(function() {}) - if (!log.endsWith('\n')) { - log += '\n' - } - if (!log.endsWith('\n\n')) { - log += '\n' - } + // The extracting process might not leave enough newlines for our + // desired clean output for our logs so add them. + log = this.logAddSeperator(log) + // check if the version we downloaded had index.mjs. If this is + // missing then either the extracting or download failed without erroring + // or the archived is borked. await this.fs.stat(this.util.getPathFromRoot(`./${this.name}/${latest.version}/index.mjs`)) .catch((err) => { latest.failtodownload = (latest.failtodownload || 0) + 1 @@ -128,38 +203,44 @@ export default class Application extends EventEmitter { return Promise.reject(err) }) + // If we reach here, then we don't wanna cleanup or remove existing files + // in case more errors occured. The download was success and preliminary + // checks indicate the version is valid. As such, we are gonna skip + // clearing the folder even if something occurs later on. cleanup = false + // Check if we have a package.json file. If we do, we need to run + // npm install. If we don't then this application either has all the + // required packages or it doesn't need them to run let packageStat = await this.fs.stat(this.util.getPathFromRoot(`./${this.name}/${latest.version}/package.json`)) .catch(function() { return null }) if (packageStat) { log += this.updateLog(`running npm install --production. `) + '\n' + await this.db.write() + + // For some weird reason, --loglevel=notice is required otherwise + // we get practically zero log output. await this.util.runCommand( 'npm.cmd', ['install', '--production', '--no-optional', '--no-package-lock', '--no-audit', '--loglevel=notice'], folder, - function(msg) { - log += msg - } + function(msg) { log += msg } ).catch(function(err) { latest.failtoinstall = (latest.failtoinstall || 0) + 1 return Promise.reject(err) }) - if (!log.endsWith('\n')) { - log += '\n' - } - if (!log.endsWith('\n\n')) { - log += '\n' - } - + log = this.logAddSeperator(log) } else { log += this.updateLog('Release did not contain package.json, skipping npm install. ') + '\n' } } catch (err) { - this.updating = false log += this.updateLog(`Error: ${err.message}. `) + '\n' + + // Check if we have a folder and we need to do some cleanups. We do + // this if the download process failed so we can have a fresh clean + // tree for the next time update is run if (folder && cleanup) { await this.fs.rm(folder, { force: true, recursive: true }).catch((err) => { this.updateLog(`Error while cleaning up: ${err.message}. `) @@ -171,9 +252,44 @@ export default class Application extends EventEmitter { return Promise.reject(err) } + // If we reached here then everything went swimmingly. Mark the version + // as being installed and attach the install log to it. log += this.updateLog(`Finished updating ${this.name} to version ${latest.version}.`) + '\n' this.db.data.core[this.name].latestInstalled = latest.version + latest.installed = true latest.log = log - this.updating = false + } + + registerModule(module) { + if (module && typeof(module) === 'function') { + return this.registerModule({ start: module }) + } + if (!module || typeof(module) !== 'object' || typeof(module.start) !== 'function') { + throw new Error(`Application ${this.name} registerModule was called with a non module missing start function`) + } + + this.module = module + } + + async runVersion(version) { + let module = this.module + + let errTimeout = new Error(`Application ${this.name} version ${version} timed out (took over ${this.config.waitUntilFail}ms) while running start()`) + + await new Promise((res, rej) => { + setTimeout(() => { + rej(errTimeout) + }, this.config.waitUntilFail) + + let startRes = module.start(this.db, this.db.log, this.http, this.config.port) + if (startRes && startRes.then) { + return startRes.then(res, rej) + } + res() + }) + + if (!this.http.active) { + return Promise.reject(new Error(`Application ${this.name} version ${version} did not call http.createServer()`)) + } } } \ No newline at end of file diff --git a/core/core_new.mjs b/core/core_new.mjs new file mode 100644 index 0000000..e69de29 diff --git a/core/db.mjs b/core/db.mjs index 5c30b57..52670fc 100644 --- a/core/db.mjs +++ b/core/db.mjs @@ -55,6 +55,20 @@ export default function GetDB(config, log, orgFilename = 'db.json') { col.push(item) } + db.upsertFirst = function(collection, item) { + let col = db.getCollection(collection) + if (item[db.id]) { + let i = db.get(col, item[db.id], true) + if (i !== null) { + col[i] = item + return + } + } else { + item[db.id] = db.createId(col) + } + col.splice(0, 0, item) + } + db.remove = function(collection, itemOrId) { let col = db.getCollection(collection) let id = itemOrId diff --git a/core/util.mjs b/core/util.mjs index 9722f04..c427286 100644 --- a/core/util.mjs +++ b/core/util.mjs @@ -32,6 +32,9 @@ export default class Util { if (typeof(config[key]) !== 'object' || config[key] == null) continue if (typeof(config[key].port) !== 'number' || !config[key].port) continue if (typeof(config[key].provider) !== 'string' || !config[key].provider) continue + if (config[key].https != null && typeof(config[key].https) !== 'boolean') continue + if (config[key].updateEvery != null && (typeof(config[key].updateEvery) !== 'number' || config[key].updateEvery < 1)) continue + if (config[key].waitUntilFail != null && (typeof(config[key].waitUntilFail) !== 'number' || config[key].waitUntilFail < 10)) continue out.push(key) } diff --git a/test/application.run.test.mjs b/test/application.run.test.mjs new file mode 100644 index 0000000..5c517cb --- /dev/null +++ b/test/application.run.test.mjs @@ -0,0 +1,92 @@ +import { Eltro as t, assert, stub } from 'eltro' +import fs from 'fs/promises' +import lowdb from '../core/db.mjs' +import Application from '../core/application.mjs' +import Util from '../core/util.mjs' +import StaticProvider from '../core/providers/static.mjs' + +const util = new Util(import.meta.url) + +const logger = { + info: stub(), + warn: stub(), + error: stub(), +} +function createProvider() { + return { + getLatestVersion: stub(), + downloadVersion: stub(), + } +} + +t.timeout(250).describe('#runVersion()', function() { + const assertPort = 22345 + let db + let app + + const defaultHandler = function(db, log, http, port) { + const server = http.createServer(function (req, res) { + res.writeHead(204); res.end(JSON.stringify({ a: 1 })) + }) + + return new Promise(function(res, rej) { + server.listen(port, '0.0.0.0', function(err) { + if (err) return rej(err) + res() + }) + }) + } + + t.beforeEach(function() { + return lowdb({ test: { } }, logger, null).then(function(res) { + db = res + let provider = new StaticProvider() + app = new Application(util, db, provider, 'testapp') + app.config.port = assertPort + app.registerModule(defaultHandler) + }) + }) + + t.test('should throw if http is not called', async function() { + app.registerModule(function(checkDb, checkLog, checkHttp, checkPort) { + assert.strictEqual(checkDb, db) + assert.strictEqual(checkLog, db.log) + assert.strictEqual(checkHttp, app.http) + assert.strictEqual(checkPort, assertPort) + }) + + let err = await assert.isRejected(app.runVersion('static')) + + assert.match(err.message, /http/i) + assert.match(err.message, /createServer/i) + assert.match(err.message, /static/) + assert.match(err.message, new RegExp(app.name)) + assert.match(err.message, /call/i) + }) + + t.test('should throw if it timeouts waiting for promise to succeed', async function() { + app.config.waitUntilFail = 50 + app.registerModule(function() { return new Promise(function() {}) }) + + let err = await assert.isRejected(app.runVersion('static')) + + assert.match(err.message, /time/i) + assert.match(err.message, /out/i) + assert.match(err.message, /static/) + assert.match(err.message, /50ms/) + assert.match(err.message, new RegExp(app.name)) + }) + + t.test('should otherwise succeed if it finished within the time limit', async function() { + app.config.waitUntilFail = 250 + app.registerModule(function(db, log, http, port) { + return new Promise(function(res) { + setTimeout(res, 25) + }).then(function() { + return defaultHandler(db, log, http, port) + }) + }) + + await app.runVersion('static') + }) +}) diff --git a/test/application.test.mjs b/test/application.test.mjs index 4df3b6b..29bb2d1 100644 --- a/test/application.test.mjs +++ b/test/application.test.mjs @@ -52,9 +52,29 @@ t.describe('constructor()', function() { let app = new Application(util, db, {}, assertName) assert.strictEqual(app.config, assertTest) + assert.strictEqual(app.config.updateEvery, 180) + assert.strictEqual(app.config.waitUntilFail, 60 * 1000) assert.strictEqual(app.db, db) assert.strictEqual(app.util, util) assert.strictEqual(app.name, assertName) + assert.strictEqual(app.fresh, true) + assert.strictEqual(app.running, false) + assert.ok(app.http) + assert.ok(app.http.sockets) + assert.strictEqual(typeof(app.http.createServer), 'function') + assert.strictEqual(typeof(app.http.closeServer), 'function') + }) + + t.test('should create http instance correctly', function() { + db.config = { + testapp: { a: 1, https: true }, + app: { b: 2}, + manage: { c: 3 }, + } + + let app = new Application(util, db, {}, 'testapp') + assert.ok(app.http) + assert.ok(app.http.ishttps) }) t.test('should keep provider', function() { @@ -190,6 +210,7 @@ t.timeout(250).describe('#update()', function() { return lowdb({ test: { } }, logger, null).then(function(res) { db = res db.write = stubWrite = stub() + stubWrite.resolves() provider = createProvider() app = new Application(util, db, provider, 'testapp', { fs: { @@ -218,6 +239,23 @@ t.timeout(250).describe('#update()', function() { assert.match(db.data.core.teststatic.updater, /static/i) assert.match(db.data.core.teststatic.updater, /nothing/i) + let old = db.data.core.teststatic.updater + assert.ok(stubWrite.called) + assert.strictEqual(stubWrite.callCount, 1) + + await app.update() + assert.strictEqual(db.data.core.teststatic.updater, old) + assert.strictEqual(stubWrite.callCount, 1) + + db.data.core.teststatic.updater = 'asdf' + + await app.update() + assert.strictEqual(db.data.core.teststatic.updater, old) + assert.strictEqual(stubWrite.callCount, 2) + + await app.update() + assert.strictEqual(db.data.core.teststatic.updater, old) + assert.strictEqual(stubWrite.callCount, 2) }) t.test('multiple calls should be safe', async function() { @@ -250,6 +288,9 @@ t.timeout(250).describe('#update()', function() { assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) + + assert.ok(stubWrite.called) + assert.ok(stubWrite.callCount >= 1) assert.match(db.data.core.testapp.updater, /check/i) assert.match(db.data.core.testapp.updater, /version/i) assert.match(db.data.core.testapp.updater, new RegExp(new Date().toISOString().split('T')[0])) @@ -288,6 +329,8 @@ t.timeout(250).describe('#update()', function() { assert.ok(assertVersion.log.endsWith('\n')) assert.strictEqual(assertVersion.failtodownload, 1) + assert.ok(stubWrite.called) + assert.ok(stubWrite.callCount >= 2) assert.ok(stubFsMkdir.called) assert.match(stubFsMkdir.firstCall[0], /123456789/) assert.ok(stubFsMkdir.firstCall[1]?.recursive) @@ -370,7 +413,6 @@ t.timeout(250).describe('#update()', function() { assert.strictEqual(app.updating, false) assert.strictEqual(stubExtract.firstCall[0], assertTarget) - assert.notOk(stubWrite.called) assert.match(db.data.core.testapp.updater, new RegExp('extracting[^.]+file\.7z', 'i')) assert.match(db.data.core.testapp.updater, new RegExp(assertError.message)) @@ -379,6 +421,8 @@ t.timeout(250).describe('#update()', function() { assert.ok(stubFsRm.firstCall[1]?.recursive) assert.ok(stubFsRm.firstCall[1]?.force) + assert.ok(stubWrite.called) + assert.ok(stubWrite.callCount >= 3) assert.strictEqual(db.data.core.testapp.versions.length, 1) let version = db.data.core.testapp.versions[0] assert.ok(version.log) @@ -504,7 +548,6 @@ t.timeout(250).describe('#update()', function() { const assertTargetCheck = util.getPathFromRoot('./testapp/123456789/') provider.getLatestVersion.resolves(assertVersion) assert.strictEqual(db.data.core.testapp.versions.length, 0) - assert.notOk(stubWrite.called) stubExtract.returnWith(function(target, stream) { stream(assertExtractText) @@ -533,6 +576,8 @@ t.timeout(250).describe('#update()', function() { assert.notStrictEqual(stubFsRm.getCall(i)[0], assertTargetCheck) } + assert.ok(stubWrite.called) + assert.ok(stubWrite.callCount >= 4) assert.match(db.data.core.testapp.updater, /npm/i) assert.match(db.data.core.testapp.updater, /install/i) assert.strictEqual(db.data.core.testapp.versions.length, 1) @@ -571,6 +616,9 @@ t.timeout(250).describe('#update()', function() { assert.strictEqual(db.data.core.testapp.versions.length, 1) assert.strictEqual(db.data.core.testapp.versions[0], assertVersion) assert.ok(assertVersion.log) + assert.ok(stubWrite.callCount >= 4) + assert.strictEqual(assertVersion.installed, true) + assert.strictEqual(assertVersion.stable, 0) assert.match(assertVersion.log, /found/i) assert.match(assertVersion.log, new RegExp(assertVersion.version)) assert.match(assertVersion.log, /downloading/i) @@ -582,8 +630,9 @@ t.timeout(250).describe('#update()', function() { t.test('should update existing version if found', async function() { const assertNewLink = 'THE last pain' const assertNewFilename = 'The place of hope.7z' + const assertStable = 100 const oldLog = 'The Smell of Sea\n' - const assertVersion = { version: '123456789', link: 'httplinkhere', filename: 'test.7z', log: oldLog } + const assertVersion = { version: '123456789', link: 'httplinkhere', filename: 'test.7z', log: oldLog, stable: assertStable } assertVersion.id = assertVersion.version db.upsert(db.data.core.testapp.versions, assertVersion) @@ -593,11 +642,15 @@ t.timeout(250).describe('#update()', function() { await app.update() + assert.ok(assertVersion.log) + assert.ok(stubWrite.callCount >= 4) assert.strictEqual(app.updating, false) assert.strictEqual(db.data.core.testapp.versions.length, 1) assert.strictEqual(db.data.core.testapp.versions[0], assertVersion) + assert.strictEqual(assertVersion.stable, assertStable) assert.ok(assertVersion.log) assert.ok(assertVersion.log.startsWith(oldLog)) + assert.strictEqual(assertVersion.installed, true) assert.match(assertVersion.log, /found/i) assert.match(assertVersion.log, new RegExp(assertVersion.version)) assert.match(assertVersion.log, /downloading/i) @@ -616,6 +669,28 @@ t.timeout(250).describe('#update()', function() { await app.update() + assert.ok(stubWrite.callCount >= 1) + assert.strictEqual(app.updating, false) + assert.notOk(provider.downloadVersion.called) + assert.match(db.data.core.testapp.updater, /already/i) + assert.match(db.data.core.testapp.updater, /nothing/i) + }) + + t.test('should do nothing if installed version is found', async function() { + const assertError = new Error('should not be seen') + const assertVersion = { version: '999.888.777.666', filename: 'test.7z' } + provider.getLatestVersion.resolves(assertVersion) + provider.downloadVersion.rejects(assertError) + db.data.core.testapp.updater = '' + + db.upsert(db.data.core.testapp.versions, { id: '111.111.111.111', installed: true }) + db.upsert(db.data.core.testapp.versions, { id: '222.222.222.222', installed: true }) + db.upsert(db.data.core.testapp.versions, { id: '999.888.777.666', installed: true }) + db.upsert(db.data.core.testapp.versions, { id: '333.333.333.333', installed: true }) + + await app.update() + + assert.ok(stubWrite.callCount >= 1) assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(db.data.core.testapp.updater, /already/i) @@ -632,6 +707,7 @@ t.timeout(250).describe('#update()', function() { await app.update() + assert.ok(stubWrite.callCount >= 1) assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(db.data.core.testapp.updater, /many/i) @@ -649,6 +725,7 @@ t.timeout(250).describe('#update()', function() { await app.update() + assert.ok(stubWrite.callCount >= 1) assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(db.data.core.testapp.updater, /many/i) @@ -656,3 +733,55 @@ t.timeout(250).describe('#update()', function() { assert.match(db.data.core.testapp.updater, /skip/i) }) }) + +t.timeout(250).describe('#registerModule()', function() { + const assertAppName = 'testappregister' + let db + let app + + t.beforeEach(function() { + return lowdb({ test: { } }, logger, null).then(function(res) { + db = res + let provider = new StaticProvider() + app = new Application(util, db, provider, assertAppName) + }) + }) + + t.test('should fail if not an object with start', function() { + let tests = [ + [1, 'number'], + [0, 'empty number'], + ['text', 'string'], + ['', 'empty string'], + [{}, 'empty object'], + [[], 'array'], + [{ a: 1 }, 'non-empty object'], + ] + + tests.forEach(function(check) { + assert.throws(function() { + app.registerModule(check[0]) + }, function(err) { + assert.match(err.message, /registerModule/i) + assert.match(err.message, /function/i) + assert.match(err.message, /start/i) + assert.match(err.message, new RegExp(assertAppName, 'i')) + return true + }, `should fail if called with ${check[1]}`) + }) + }) + + t.test('should otherwise succeed if object has function start', function() { + const assertFunction = function() {} + const assertModule = { start: assertFunction } + + app.registerModule(assertModule) + + assert.strictEqual(app.module, assertModule) + + app.registerModule(assertFunction) + + assert.notStrictEqual(app.module, assertModule) + assert.deepStrictEqual(app.module, assertModule) + }) +}) diff --git a/test/db.test.mjs b/test/db.test.mjs index 3eea1f4..52de7dd 100644 --- a/test/db.test.mjs +++ b/test/db.test.mjs @@ -151,7 +151,7 @@ t.test('Should support reading from db', async function() { }) t.test('should throw if unable to write to file', function() { - return assert.isRejected(lowdb({}, logger, '../test')) + return assert.isRejected(lowdb({}, logger, util.getPathFromRoot('../test'))) }) t.test('should have basic database-like functions defined', async function() { @@ -285,3 +285,22 @@ t.test('#upsert() should work properly', async function() { assert.strictEqual(db.data.test.items[0].id, '1234') assert.strictEqual(db.data.test.items[0].text, '2') }) + + +t.test('#upsertFirst() should work properly', async function() { + let db = await lowdb({}, logger, null) + db.data.test = { + items: [] + } + + db.upsertFirst(db.data.test.items, { id: '1234', text: '1' }) + db.upsertFirst(db.data.test.items, { id: '1234', text: '2' }) + db.upsertFirst(db.data.test.items, { id: '1', text: '3' }) + db.upsertFirst(db.data.test.items, { id: '2', text: '4' }) + + assert.strictEqual(db.data.test.items.length, 3) + assert.strictEqual(db.data.test.items[2].id, '1234') + assert.strictEqual(db.data.test.items[2].text, '2') + assert.strictEqual(db.data.test.items[1].text, '3') + assert.strictEqual(db.data.test.items[0].text, '4') +}) diff --git a/test/exampleindex.mjs b/test/exampleindex.mjs new file mode 100644 index 0000000..45fc686 --- /dev/null +++ b/test/exampleindex.mjs @@ -0,0 +1,17 @@ +export function start(db, log, core, http, port) { + const server = http.createServer(function (req, res) { + res.writeHead(200); + res.end(JSON.stringify({ version: 'exampleindex' })) + }) + + return new Promise(function(res, rej) { + server.listen(port || 4000, '0.0.0.0', function(err) { + if (err) { + return rej(err) + } + log.event.info(`Server is listening on ${port} serving package ${staticPackage}`) + log.info(`Server is listening on ${port} serving package ${staticPackage}`) + res() + }) + }) +} \ No newline at end of file diff --git a/test/util.test.mjs b/test/util.test.mjs index c38478e..b43de75 100644 --- a/test/util.test.mjs +++ b/test/util.test.mjs @@ -107,6 +107,45 @@ t.describe('#getApplications()', function() { assert.deepStrictEqual(util.getAppNames({ app: { provider: {}, port: 1234 } }), []) assert.deepStrictEqual(util.getAppNames({ app: { provider: 1234, port: 1234 } }), []) }) + + t.test('should fail to find if https is defined but not a boolean', function() { + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, https: null } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, https: false } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, https: true } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, https: 'asdf' } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, https: '1234' } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, https: 0 } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, https: [] } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, https: {} } }), []) + }) + + t.test('should fail to find if updateEvery is defined but not a valid number', function() { + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, updateEvery: null } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, updateEvery: 5 } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, updateEvery: 1000 } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, updateEvery: 'asdf' } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, updateEvery: '1234' } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, updateEvery: 0 } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, updateEvery: -5 } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, updateEvery: [] } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, updateEvery: {} } }), []) + }) + + t.test('should fail to find if waitUntilFail is defined but not a valid number', function() { + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: null } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: 5 } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: 15 } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: 1000 } }), ['app']) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: 'asdf' } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: '1234' } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: 0 } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: -5 } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: [] } }), []) + assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 1234, waitUntilFail: {} } }), []) + }) }) t.describe('#verifyConfig()', function() {