Massive development. Application finished. Core started

master
Jonatan Nilsson 2022-02-04 09:33:03 +00:00
parent df7e1e5509
commit 758e61b8b1
21 changed files with 2117 additions and 831 deletions

View File

@ -1,13 +1,20 @@
import { EventEmitter } from 'events'
import fs from 'fs/promises'
import { request } from './client.mjs'
import HttpServer from './http.mjs'
import { defaults } from './defaults.mjs'
export default class Application extends EventEmitter {
constructor(util, db, provider, name, opts = {}) {
constructor(ctx, provider, name, opts = {}) {
super()
this.util = util
this.db = db
this.config = db.config[name] || { }
this.ctx = {
db: ctx.db,
util: ctx.util,
log: ctx.log,
core: ctx.core,
app: this,
}
this.config = defaults({}, this.ctx.db.config[name])
this.provider = provider
this.name = name
this.updating = false
@ -26,14 +33,18 @@ export default class Application extends EventEmitter {
// Apply defaults to config
this.config.updateEvery = this.config.updateEvery || 180
this.config.waitUntilFail = this.config.waitUntilFail || (60 * 1000)
this.config.startWaitUntilFail = this.config.startWaitUntilFail || (60 * 1000)
this.config.heartbeatTimeout = this.config.heartbeatTimeout || (3 * 1000)
this.config.heartbeatAttempts = this.config.heartbeatAttempts || 5
this.config.heartbeatAttemptsWait = this.config.heartbeatAttemptsWait || (2 * 1000)
this.config.heartbeatPath = this.config.heartbeatPath || '/'
Object.assign(this, {
setInterval: opts.setInterval || setInterval,
fs: opts.fs || fs,
})
this.db.addApplication(name)
this.ctx.db.addApplication(name)
}
startAutoupdater() {
@ -42,10 +53,10 @@ export default class Application extends EventEmitter {
let timer = this.setInterval(() => {
this.update().then(
() => {
this.db.data.core[this.name].updater += 'Automatic update finished successfully. '
this.ctx.db.data.core[this.name].updater += 'Automatic update finished successfully. '
},
(err) => {
this.db.data.core[this.name].updater += 'Error while running automatic update: ' + err.message + '. '
this.ctx.db.data.core[this.name].updater += 'Error while running automatic update: ' + err.message + '. '
}
)
}, this.config.updateEvery * 60 * 1000)
@ -53,8 +64,8 @@ export default class Application extends EventEmitter {
}
updateLog(message) {
this.db.data.core[this.name].updater += message
this.db.log.info(message)
this.ctx.db.data.core[this.name].updater += message
this.ctx.db.log.info(message)
return message
}
@ -62,10 +73,10 @@ export default class Application extends EventEmitter {
update() {
if (this.provider.static) {
if (this.db.data.core[this.name].updater !== this.msgStatic) {
this.db.data.core[this.name].updater = ''
if (this.ctx.db.data.core[this.name].updater !== this.msgStatic) {
this.ctx.db.data.core[this.name].updater = ''
this.updateLog(this.msgStatic)
return this.db.write()
return this.ctx.db.write()
}
return Promise.resolve()
}
@ -76,11 +87,11 @@ export default class Application extends EventEmitter {
return this._update()
.then(() => {
this.updating = false
return this.db.write()
return this.ctx.db.write()
})
.catch((err) => {
this.updating = false
return this.db.write()
return this.ctx.db.write()
.then(function() { return Promise.reject(err) })
})
}
@ -96,7 +107,7 @@ export default class Application extends EventEmitter {
}
async _update() {
this.db.data.core[this.name].updater = ''
this.ctx.db.data.core[this.name].updater = ''
let cleanup = true
let folder = ''
let log = ''
@ -111,7 +122,7 @@ export default class Application extends EventEmitter {
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) {
if (this.ctx.db.data.core[this.name].latestInstalled === latest.version) {
this.updateLog('Already up to date, nothing to do. ')
return
}
@ -121,7 +132,7 @@ export default class Application extends EventEmitter {
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)
var found = this.ctx.db.get(this.ctx.db.data.core[this.name].versions, latest.id)
if (found) {
// Check if the existing version found was already installed.
if (found.installed) {
@ -155,18 +166,18 @@ export default class Application extends EventEmitter {
} else {
// 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)
this.ctx.db.upsertFirst(this.ctx.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}`)
let target = this.ctx.util.getPathFromRoot(`./${this.name}/${latest.version}/file${this.ctx.util.getExtension(latest.filename)}`)
folder = this.ctx.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()
await this.ctx.db.write()
// Download the latest version using the provider in question.
await this.provider.downloadVersion(latest, target)
@ -176,10 +187,10 @@ export default class Application extends EventEmitter {
})
log += '\n' + this.updateLog(`Extracting ${target}. `) + '\n'
await this.db.write()
await this.ctx.db.write()
// Download was successful, extract the archived file that we downloaded
await this.util.extractFile(target, function(msg) {
await this.ctx.util.extractFile(target, function(msg) {
log += msg
}).catch(function(err) {
latest.failtodownload = (latest.failtodownload || 0) + 1
@ -196,7 +207,7 @@ export default class Application extends EventEmitter {
// 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`))
await this.fs.stat(this.ctx.util.getPathFromRoot(`./${this.name}/${latest.version}/index.mjs`))
.catch((err) => {
latest.failtodownload = (latest.failtodownload || 0) + 1
log += this.updateLog('Version did not include or was missing index.mjs. ') + '\n'
@ -212,16 +223,16 @@ export default class Application extends EventEmitter {
// 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`))
let packageStat = await this.fs.stat(this.ctx.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()
await this.ctx.db.write()
// For some weird reason, --loglevel=notice is required otherwise
// we get practically zero log output.
await this.util.runCommand(
await this.ctx.util.runCommand(
'npm.cmd',
['install', '--production', '--no-optional', '--no-package-lock', '--no-audit', '--loglevel=notice'],
folder,
@ -255,33 +266,45 @@ export default class Application extends EventEmitter {
// 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
this.ctx.db.data.core[this.name].latestInstalled = latest.version
latest.installed = true
latest.log = log
}
registerModule(module) {
registerModule(module, version = '') {
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`)
throw new Error(`Application ${this.name}${version ? ' version ' + version : '' } registerModule was called with a non module missing start function`)
}
this.module = module
}
async runVersion(version) {
let module = this.module
this.ctx.db.data.core[this.name].active = version
await this.ctx.db.write()
let errTimeout = new Error(`Application ${this.name} version ${version} timed out (took over ${this.config.waitUntilFail}ms) while running start()`)
if (version !== 'static') {
let indexPath = this.ctx.util.getPathFromRoot(`./${this.name}/${version}/index.mjs`)
await this.fs.stat(indexPath).catch((err) => {
return Promise.reject(new Error(`Application ${this.name} version ${version} was missing index.mjs: ${err.message}`))
})
let module = await import(this.ctx.util.getUrlFromRoot(`./${this.name}/${version}/index.mjs`)).catch((err) => {
return Promise.reject(new Error(`Application ${this.name} version ${version} failed to load index.mjs: ${err.message}`))
})
this.registerModule(module, version)
}
let errTimeout = new Error(`Application ${this.name} version ${version} timed out (took over ${this.config.startWaitUntilFail}ms) while running start()`)
await new Promise((res, rej) => {
setTimeout(() => {
rej(errTimeout)
}, this.config.waitUntilFail)
}, this.config.startWaitUntilFail)
let startRes = module.start(this.db, this.db.log, this.http, this.config.port)
let startRes = this.module.start(this.http, this.config.port, this.ctx)
if (startRes && startRes.then) {
return startRes.then(res, rej)
}
@ -291,5 +314,22 @@ export default class Application extends EventEmitter {
if (!this.http.active) {
return Promise.reject(new Error(`Application ${this.name} version ${version} did not call http.createServer()`))
}
let lastErr = null
for (let i = 0; i < this.config.heartbeatAttempts; i++) {
try {
await request({ timeout: this.config.heartbeatAttemptsWait }, `http://localhost:${this.config.port}` + this.config.heartbeatPath, null, 0, true)
return
} catch (err) {
lastErr = err
}
}
return Promise.reject(new Error(`Application ${this.name} version ${version} failed to start properly: ${lastErr.message}`))
}
}
closeServer() {
return this.http.closeServer()
}
}

View File

@ -46,13 +46,13 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
if (config.token) {
headers['Authorization'] = `token ${config.token}`
}
let timeout = fastRaw ? 5000 : config.timeout || 10000
let timeout = config.timeout || 10000
let timedout = false
let timer = setTimeout(function() {
timedout = true
if (req) { req.destroy() }
reject(new Error(`Request ${path} timed out out after ${timeout}`))
reject(new Error(`Request ${path} timed out after ${timeout}ms`))
}, timeout)
req = h.request({

View File

@ -1,499 +1,68 @@
import fs from 'fs'
import { EventEmitter } from 'events'
import { request } from './client.mjs'
import HttpServer from './http.mjs'
import Application from './application.mjs'
import Util from './util.mjs'
import { Low } from 'lowdb'
const fsp = fs.promises
export default class Core {
static providers = new Map()
static addProvider(name, provider) {
if (!name || typeof(name) !== 'string')
throw new Error('addProvider name must be a string')
if (typeof(provider) !== 'function')
throw new Error(`addProvider ${name} provider must be a class`)
let test = new provider({})
if (typeof(test.checkConfig) !== 'function')
throw new Error(`addProvider ${name} provider class missing checkConfig`)
if (typeof(test.getLatestVersion) !== 'function')
throw new Error(`addProvider ${name} provider class missing getLatestVersion`)
Core.providers.set(name, provider)
}
constructor(db, util, log, restart) {
// some sanity checks
if (typeof(restart) !== 'function') throw new Error('restart parameter was not a function')
if (!log || typeof(log) !== 'object') throw new Error('log parameter was invalid')
if (typeof(log.event) !== 'object') throw new Error('log parameter was invalid')
if (typeof(log.info) !== 'function'
|| typeof(log.warn) !== 'function'
|| typeof(log.error) !== 'function'
|| typeof(log.event.info) !== 'function'
|| typeof(log.event.warn) !== 'function'
|| typeof(log.event.error) !== 'function') throw new Error('log parameter was invalid')
if (!util || !(util instanceof Util)) throw new Error('util not instance of Util')
if (!db || !(db instanceof Low)) throw new Error('db not instance of Low')
export default class Core extends EventEmitter{
constructor(util, config, db, log, closeCb) {
super()
process.stdin.resume()
this.http = new HttpServer()
this.util = util
this.config = config
this.db = db
this.util = util
this.log = log
this._close = closeCb
this._activeCrashHandler = null
this.appRunning = false
this.manageRunning = false
this.monitoring = false
this._appUpdating = {
fresh: true,
updating: false,
starting: false,
logs: '',
}
this._manageUpdating = {
fresh: true,
updating: false,
starting: false,
logs: '',
}
this.db.set('core.manageActive', null)
.set('core.appActive', null)
.write().then()
this.restart = restart
this.applications = []
this.applicationMap = new Map()
}
startMonitor() {
if (this.monitoring) return
this.log.info('[Scheduler] Automatic updater has been turned on. Will check for updates every 3 hours')
let updating = false
this.monitoring = setInterval(async () => {
if (updating) return
updating = true
this.log.info('[Scheduler] Starting automatic check for latest version of app and manage')
try {
await this.installLatestVersion('app')
await this.installLatestVersion('manage')
} catch(err) {
this.log.error(err, 'Error checking for latest versions')
this.log.event.error('Error checking for latest versions: ' + err.message)
updating = false
return
}
try {
if (this.hasNewVersionAvailable('app') || !this.appRunning) {
await this.tryStartProgram('app')
}
} catch(err) {
this.log.error(err, 'Unknown error occured attempting to app')
this.log.event.error('Unknown error starting app: ' + err.message)
}
try {
if (this.hasNewVersionAvailable('manage') || !this.manageRunning) {
await this.tryStartProgram('manage')
}
} catch(err) {
this.log.error(err, 'Unknown error occured attempting to start manage')
this.log.event.error('Unknown error starting manage: ' + err.message)
}
updating = false
}, 1000 * 60 * 60 * 3) // every 3 hours
getApplication(name) {
return this.applicationMap.get(name)
}
restart() {
this._close()
}
async init() {
this.util.verifyConfig(this.db.config)
status() {
return {
app: this.appRunning,
manage: this.manageRunning,
appUpdating: this._appUpdating.updating,
manageUpdating: this._manageUpdating.updating,
appStarting: this._appUpdating.starting,
manageStarting: this._manageUpdating.starting,
}
}
let names = this.util.getAppNames(this.db.config)
async getLatestVersion(active, name) {
// Example: 'https://api.github.com/repos/thething/sc-helloworld/releases'
this.logActive(name, active, `Updater: Fetching release info from: https://api.github.com/repos/${this.config[name + 'Repository']}/releases\n`)
for (let name of names) {
let provConstructor = Core.providers.get(this.db.config[name].provider)
let provider = new provConstructor(this.db.config[name])
await provider.checkConfig(this.db.config[name])
let result = await request(this.config, `https://api.github.com/repos/${this.config[name + 'Repository']}/releases`)
let items = result.body.filter(function(item) {
if (!item.assets.length) return false
for (let i = 0; i < item.assets.length; i++) {
if (item.assets[i].name.endsWith('-sc.zip')) return true
}
})
if (items && items.length) {
for (let x = 0; x < items.length; x++) {
let item = items[x]
for (let i = 0; i < item.assets.length; i++) {
if (item.assets[i].name.endsWith('-sc.zip')) {
if (this.db.get('core.' + name + 'LatestInstalled').value() === item.name) {
this.logActive(name, active, `Updater: Latest version already installed, exiting early\n`)
return null
}
this.logActive(name, active, `Updater: Found version ${item.name} with file ${item.assets[i].name}\n`)
await this.db.set(`core.${name}LatestVersion`, item.name)
.write()
this.emit('dbupdated', {})
return {
name: item.name,
filename: item.assets[i].name,
url: item.assets[i].browser_download_url,
description: item.body,
}
}
}
}
} else {
return null
}
}
logActive(name, active, logline, doNotPrint = false) {
if (!doNotPrint) {
this.log.info(`[${name}] ` + logline.replace(/\n/g, ''))
}
active.logs += logline
this.emit(name + 'log', active)
}
getProgramLogs(name) {
if (name === 'app' && this._appUpdating.logs) {
return this._appUpdating.logs
} else if (name === 'manage' && this._manageUpdating.logs) {
return this._manageUpdating.logs
}
let latestInstalled = this.db.get('core.' + name + 'LatestInstalled').value()
let latestVersion = this.db.get('core.' + name + 'LatestVersion').value()
if (latestVersion) {
let value = this.db.get(`core_${name}History`).getById(latestVersion).value()
if (value) return value.logs
}
if (latestInstalled) {
let value = this.db.get(`core_${name}History`).getById(latestInstalled).value()
if (value) return value.logs
}
return '< no logs found >'
}
async installVersion(name, active, version) {
if (fs.existsSync(this.util.getPathFromRoot(`./${name}/` + version.name))) {
await this.util.runCommand('rmdir', ['/S', '/Q', `"${this.util.getPathFromRoot(`./${name}/` + version.name)}"`])
}
if (!fs.existsSync(this.util.getPathFromRoot(`./${name}/`))) {
await fsp.mkdir(this.util.getPathFromRoot(`./${name}/`))
}
try {
await fsp.mkdir(this.util.getPathFromRoot(`./${name}/` + version.name))
} catch(err) {
if (err.code !== 'EEXIST') {
throw err
}
}
// await fsp.mkdir(this.util.getPathFromRoot(`./${name}/` + version.name + '/node_modules'))
this.logActive(name, active, `Installer: Downloading ${version.name} (${version.url}) to ${version.name + '/' + version.name + '.zip'}\n`)
let filePath = this.util.getPathFromRoot(`./${name}/` + version.name + '/' + version.name + '.zip')
await request(this.config, version.url, filePath)
this.logActive(name, active, `Installer: Downloading finished, starting extraction\n`)
await this.util.runCommand(
'"C:\\Program Files\\7-Zip\\7z.exe"',
['x', `"${filePath}"`],
this.util.getPathFromRoot(`./${name}/` + version.name + '/'),
this.logActive.bind(this, name, active)
)
if (!fs.existsSync(this.util.getPathFromRoot(`./${name}/` + version.name + '/index.mjs'))) {
this.logActive(name, active, `\nInstaller: ERROR: Missing index.mjs in the folder, exiting\n`)
throw new Error(`Missing index.mjs in ${this.util.getPathFromRoot(`./${name}/` + version.name + '/index.mjs')}`)
}
this.logActive(name, active, `\nInstaller: Starting npm install\n`)
await this.util.runCommand(
'npm.cmd',
['install', '--production', '--no-optional', '--no-package-lock', '--no-audit'],
this.util.getPathFromRoot(`./${name}/` + version.name + '/'),
this.logActive.bind(this, name, active)
)
await this.db.set(`core.${name}LatestInstalled`, version.name)
.write()
this.emit('dbupdated', {})
this.logActive(name, active, `\nInstaller: Successfully installed ${version.name}\n`)
}
getActive(name) {
if (name === 'app') {
return this._appUpdating
} else if (name === 'manage') {
return this._manageUpdating
} else {
throw new Error('Invalid name: ' + name)
}
}
async startModule(module, port) {
let out = await module.start(this.config, this.db, this.log, this, this.http, port)
if (out && out.then) {
await out
}
if (!this.http.getCurrentServer()) {
this.log.warn('Module did not call http.createServer')
}
}
hasNewVersionAvailable(name) {
let newestVersion = this.db.get(`core.${name}LatestInstalled`).value()
if (!newestVersion) return false
let history = this.db.get(`core_${name}History`).getById(newestVersion).value()
if (history.installed && history.stable === 0) {
return true
}
return false
}
async tryStartProgram(name) {
let active = this.getActive(name)
if (this[name + 'Running'] && !this.hasNewVersionAvailable(name)) {
this.log.event.warn('Attempting to start ' + name + ' which is already running')
this.log.warn('Attempting to start ' + name + ' which is already running')
this.logActive(name, active, `Runner: Attempting to start it but it is already running\n`, true)
return
}
active.starting = true
if (this[name + 'Running']) {
let success = await this.http.closeServer(name)
if (!success) {
if (process.env.NODE_ENV === 'production') {
this.logActive(name, active, `Runner: Found new version but server could not be shut down, restarting service core\n`)
await new Promise(() => {
this.log.event.warn('Found new version of ' + name + ' but server could not be shut down gracefully, restarting...', null, () => {
process.exit(100)
})
})
} else {
this.logActive(name, active, `Runner: Found new version but server could not be shut down\n`)
return
}
}
this[name + 'Running'] = false
this.emit('statusupdated', {})
}
let history = this.db.get(`core_${name}History`)
.filter('installed')
.orderBy('installed', 'desc')
.value()
this.logActive(name, active, `Runner: Finding available version\n`)
for (let i = 0; i < history.length; i++) {
if ((history[i].stable === -1 && !active.fresh)
|| (history[i].stable < -1)) {
this.logActive(name, active, `Runner: Skipping version ${history[i].name} due to marked as unstable\n`)
continue
}
await this.db.set(`core.${name}Active`, history[i].name)
.write()
this.emit('dbupdated', {})
let running = await this.tryStartProgramVersion(name, active, history[i].name)
if (running) {
history[i].stable = 1
} else {
if (active.fresh || history[i].stable === -1) {
history[i].stable = -2
} else {
history[i].stable = -1
}
await this.db.set(`core.${name}Active`, null)
.write()
this.emit('dbupdated', {})
}
active.fresh = false
await this.db.get(`core_${name}History`).updateById(history[i].id, history[i].stable).write()
if (history[i].stable > 0) break
}
if (!this.db.get(`core.${name}Active`).value()) {
this.logActive(name, active, `Runner: Could not find any available stable version of ${name}\n`)
this.log.error('Unable to start ' + name)
this.log.event.error('Unable to start ' + name)
}
active.starting = false
}
programCrashed(name, version, active, oldStable) {
let newStable = -2
console.log('EXITING:', oldStable, active)
if (oldStable === 0 && !active.fresh) {
newStable = -1
}
let temp = this.db.get(`core_${name}History`).getById(version).set('stable', newStable )
temp.value() // Trigger update on __wrapped__
fs.writeFileSync(this.db.adapterFilePath, JSON.stringify(temp.__wrapped__, null, 2))
}
async tryStartProgramVersion(name, active, version) {
if (!version) return false
this.logActive(name, active, `Runner: Attempting to start ${version}\n`)
let indexPath = this.util.getUrlFromRoot(`./${name}/` + version + '/index.mjs')
let module
try {
this.logActive(name, active, `Runner: Loading ${indexPath}\n`)
module = await import(indexPath)
} catch (err) {
this.logActive(name, active, `Runner: Error importing module\n`, true)
this.logActive(name, active, `${err.stack}\n`, true)
this.log.error(err, `Failed to load ${indexPath}`)
return false
}
let checkTimeout = null
let oldStable = this.db.get(`core_${name}History`).getById(version).value().stable
this._activeCrashHandler = this.programCrashed.bind(this, name, version, active, oldStable)
process.once('exit', this._activeCrashHandler)
try {
let port = name === 'app' ? this.config.port : this.config.managePort
await new Promise((res, rej) => {
checkTimeout = setTimeout(function() {
rej(new Error('Program took longer than 60 seconds to resolve promise'))
}, 60 * 1000)
this.logActive(name, active, `Runner: Starting module\n`)
try {
this.http.setContext(name)
this.startModule(module, port)
.then(res, rej)
} catch (err) {
rej(err)
}
})
clearTimeout(checkTimeout)
await this.checkProgramRunning(name, active, port)
process.off('exit', this._activeCrashHandler)
} catch (err) {
clearTimeout(checkTimeout)
process.off('exit', this._activeCrashHandler)
await this.http.closeServer(name)
this.logActive(name, active, `Runner: Error starting\n`, true)
this.logActive(name, active, `${err.stack}\n`, true)
this.log.error(err, `Failed to start ${name}`)
return false
}
this._activeCrashHandler = null
this.logActive(name, active, `Runner: Successfully started version ${version}\n`)
await this.db.set(`core.${name}Active`, version)
.write()
if (name === 'app') {
this.appRunning = true
} else {
this.manageRunning = true
}
this.emit('statusupdated', {})
this.logActive(name, active, `Runner: Module is running successfully\n`)
return true
}
async checkProgramRunning(name, active, port) {
this.logActive(name, active, `Checker: Testing out module port ${port}\n`)
let start = new Date()
let error = null
let success = false
while (new Date() - start < 10 * 1000) {
try {
let check = await request(this.config, `http://localhost:${port}`, null, 0, true)
success = true
break
} catch(err) {
this.logActive(name, active, `Checker: ${err.message}, retrying in 3 seconds\n`)
error = err
await new Promise(function(res) { setTimeout(res, 3000)})
}
}
if (success) return true
throw error || new Error('Checking server failed')
}
async installLatestVersion(name) {
if (!this.config[name + 'Repository']) {
if (name === 'app') {
this.log.error(name + ' Repository was missing from config')
this.log.event.error(name + ' Repository was missing from config')
} else {
this.log.warn(name + ' Repository was missing from config')
this.log.event.warn(name + ' Repository was missing from config')
}
return
}
let active = this.getActive(name)
let oldLogs = active.logs || ''
if (oldLogs) {
oldLogs += '\n'
}
active.logs = ''
active.updating = true
this.emit('statusupdated', {})
this.logActive(name, active, `Installer: Checking for updates at time: ${new Date().toISOString().replace('T', ' ').split('.')[0]}\n`)
let version = null
let installed = false
let found = false
try {
version = await this.getLatestVersion(active, name)
if (version) {
let core = this.db.get('core').value()
let fromDb = this.db.get(`core_${name}History`).getById(version.name).value()
if (!fromDb || !fromDb.installed) {
let oldVersion = core[name + 'Current'] || '<none>'
this.logActive(name, active, `Installer: Updating from ${oldVersion} to ${version.name}\n`)
await this.installVersion(name, active, version)
this.logActive(name, active, `Installer: Finished: ${new Date().toISOString().replace('T', ' ').split('.')[0]}\n`)
installed = new Date()
} else {
found = true
this.logActive(name, active, `Installer: Version ${version.name} already installed\n`)
}
}
} catch(err) {
this.logActive(name, active, '\n', true)
this.logActive(name, active, `Installer: Exception occured while updating ${name}\n`, true)
this.logActive(name, active, err.stack, true)
this.log.error('Error while updating ' + name, err)
}
active.updating = false
if (version && !found) {
await this.db.get(`core_${name}History`).upsert({
id: version.name,
name: version.name,
filename: version.filename,
url: version.url,
description: version.description,
logs: active.logs,
stable: 0,
installed: installed && installed.toISOString(),
}).write()
}
active.logs = oldLogs + active.logs
this.emit(name + 'log', active)
this.emit('statusupdated', {})
}
async start(name) {
var version = this.db.get('core.' + name + 'LatestInstalled').value()
if (version) {
await this.tryStartProgram(name)
}
await this.installLatestVersion(name)
if (version !== this.db.get('core.' + name + 'LatestInstalled').value()) {
if (!this[name + 'Running'] || this.hasNewVersionAvailable(name)) {
await this.tryStartProgram(name)
}
let application = new Application({
db: this.db,
util: this.util,
log: this.log,
core: this,
}, provider, name)
this.applications.push(application)
this.applicationMap.set(name, application)
}
}
}

View File

499
core/core_old.mjs Normal file
View File

@ -0,0 +1,499 @@
import fs from 'fs'
import { EventEmitter } from 'events'
import { request } from './client.mjs'
import HttpServer from './http.mjs'
const fsp = fs.promises
export default class Core extends EventEmitter{
constructor(util, config, db, log, closeCb) {
super()
process.stdin.resume()
this.http = new HttpServer()
this.util = util
this.config = config
this.db = db
this.log = log
this._close = closeCb
this._activeCrashHandler = null
this.appRunning = false
this.manageRunning = false
this.monitoring = false
this._appUpdating = {
fresh: true,
updating: false,
starting: false,
logs: '',
}
this._manageUpdating = {
fresh: true,
updating: false,
starting: false,
logs: '',
}
this.db.set('core.manageActive', null)
.set('core.appActive', null)
.write().then()
}
startMonitor() {
if (this.monitoring) return
this.log.info('[Scheduler] Automatic updater has been turned on. Will check for updates every 3 hours')
let updating = false
this.monitoring = setInterval(async () => {
if (updating) return
updating = true
this.log.info('[Scheduler] Starting automatic check for latest version of app and manage')
try {
await this.installLatestVersion('app')
await this.installLatestVersion('manage')
} catch(err) {
this.log.error(err, 'Error checking for latest versions')
this.log.event.error('Error checking for latest versions: ' + err.message)
updating = false
return
}
try {
if (this.hasNewVersionAvailable('app') || !this.appRunning) {
await this.tryStartProgram('app')
}
} catch(err) {
this.log.error(err, 'Unknown error occured attempting to app')
this.log.event.error('Unknown error starting app: ' + err.message)
}
try {
if (this.hasNewVersionAvailable('manage') || !this.manageRunning) {
await this.tryStartProgram('manage')
}
} catch(err) {
this.log.error(err, 'Unknown error occured attempting to start manage')
this.log.event.error('Unknown error starting manage: ' + err.message)
}
updating = false
}, 1000 * 60 * 60 * 3) // every 3 hours
}
restart() {
this._close()
}
status() {
return {
app: this.appRunning,
manage: this.manageRunning,
appUpdating: this._appUpdating.updating,
manageUpdating: this._manageUpdating.updating,
appStarting: this._appUpdating.starting,
manageStarting: this._manageUpdating.starting,
}
}
async getLatestVersion(active, name) {
// Example: 'https://api.github.com/repos/thething/sc-helloworld/releases'
this.logActive(name, active, `Updater: Fetching release info from: https://api.github.com/repos/${this.config[name + 'Repository']}/releases\n`)
let result = await request(this.config, `https://api.github.com/repos/${this.config[name + 'Repository']}/releases`)
let items = result.body.filter(function(item) {
if (!item.assets.length) return false
for (let i = 0; i < item.assets.length; i++) {
if (item.assets[i].name.endsWith('-sc.zip')) return true
}
})
if (items && items.length) {
for (let x = 0; x < items.length; x++) {
let item = items[x]
for (let i = 0; i < item.assets.length; i++) {
if (item.assets[i].name.endsWith('-sc.zip')) {
if (this.db.get('core.' + name + 'LatestInstalled').value() === item.name) {
this.logActive(name, active, `Updater: Latest version already installed, exiting early\n`)
return null
}
this.logActive(name, active, `Updater: Found version ${item.name} with file ${item.assets[i].name}\n`)
await this.db.set(`core.${name}LatestVersion`, item.name)
.write()
this.emit('dbupdated', {})
return {
name: item.name,
filename: item.assets[i].name,
url: item.assets[i].browser_download_url,
description: item.body,
}
}
}
}
} else {
return null
}
}
logActive(name, active, logline, doNotPrint = false) {
if (!doNotPrint) {
this.log.info(`[${name}] ` + logline.replace(/\n/g, ''))
}
active.logs += logline
this.emit(name + 'log', active)
}
getProgramLogs(name) {
if (name === 'app' && this._appUpdating.logs) {
return this._appUpdating.logs
} else if (name === 'manage' && this._manageUpdating.logs) {
return this._manageUpdating.logs
}
let latestInstalled = this.db.get('core.' + name + 'LatestInstalled').value()
let latestVersion = this.db.get('core.' + name + 'LatestVersion').value()
if (latestVersion) {
let value = this.db.get(`core_${name}History`).getById(latestVersion).value()
if (value) return value.logs
}
if (latestInstalled) {
let value = this.db.get(`core_${name}History`).getById(latestInstalled).value()
if (value) return value.logs
}
return '< no logs found >'
}
async installVersion(name, active, version) {
if (fs.existsSync(this.util.getPathFromRoot(`./${name}/` + version.name))) {
await this.util.runCommand('rmdir', ['/S', '/Q', `"${this.util.getPathFromRoot(`./${name}/` + version.name)}"`])
}
if (!fs.existsSync(this.util.getPathFromRoot(`./${name}/`))) {
await fsp.mkdir(this.util.getPathFromRoot(`./${name}/`))
}
try {
await fsp.mkdir(this.util.getPathFromRoot(`./${name}/` + version.name))
} catch(err) {
if (err.code !== 'EEXIST') {
throw err
}
}
// await fsp.mkdir(this.util.getPathFromRoot(`./${name}/` + version.name + '/node_modules'))
this.logActive(name, active, `Installer: Downloading ${version.name} (${version.url}) to ${version.name + '/' + version.name + '.zip'}\n`)
let filePath = this.util.getPathFromRoot(`./${name}/` + version.name + '/' + version.name + '.zip')
await request(this.config, version.url, filePath)
this.logActive(name, active, `Installer: Downloading finished, starting extraction\n`)
await this.util.runCommand(
'"C:\\Program Files\\7-Zip\\7z.exe"',
['x', `"${filePath}"`],
this.util.getPathFromRoot(`./${name}/` + version.name + '/'),
this.logActive.bind(this, name, active)
)
if (!fs.existsSync(this.util.getPathFromRoot(`./${name}/` + version.name + '/index.mjs'))) {
this.logActive(name, active, `\nInstaller: ERROR: Missing index.mjs in the folder, exiting\n`)
throw new Error(`Missing index.mjs in ${this.util.getPathFromRoot(`./${name}/` + version.name + '/index.mjs')}`)
}
this.logActive(name, active, `\nInstaller: Starting npm install\n`)
await this.util.runCommand(
'npm.cmd',
['install', '--production', '--no-optional', '--no-package-lock', '--no-audit'],
this.util.getPathFromRoot(`./${name}/` + version.name + '/'),
this.logActive.bind(this, name, active)
)
await this.db.set(`core.${name}LatestInstalled`, version.name)
.write()
this.emit('dbupdated', {})
this.logActive(name, active, `\nInstaller: Successfully installed ${version.name}\n`)
}
getActive(name) {
if (name === 'app') {
return this._appUpdating
} else if (name === 'manage') {
return this._manageUpdating
} else {
throw new Error('Invalid name: ' + name)
}
}
async startModule(module, port) {
let out = await module.start(this.config, this.db, this.log, this, this.http, port)
if (out && out.then) {
await out
}
if (!this.http.getCurrentServer()) {
this.log.warn('Module did not call http.createServer')
}
}
hasNewVersionAvailable(name) {
let newestVersion = this.db.get(`core.${name}LatestInstalled`).value()
if (!newestVersion) return false
let history = this.db.get(`core_${name}History`).getById(newestVersion).value()
if (history.installed && history.stable === 0) {
return true
}
return false
}
async tryStartProgram(name) {
let active = this.getActive(name)
if (this[name + 'Running'] && !this.hasNewVersionAvailable(name)) {
this.log.event.warn('Attempting to start ' + name + ' which is already running')
this.log.warn('Attempting to start ' + name + ' which is already running')
this.logActive(name, active, `Runner: Attempting to start it but it is already running\n`, true)
return
}
active.starting = true
if (this[name + 'Running']) {
let success = await this.http.closeServer(name)
if (!success) {
if (process.env.NODE_ENV === 'production') {
this.logActive(name, active, `Runner: Found new version but server could not be shut down, restarting service core\n`)
await new Promise(() => {
this.log.event.warn('Found new version of ' + name + ' but server could not be shut down gracefully, restarting...', null, () => {
process.exit(100)
})
})
} else {
this.logActive(name, active, `Runner: Found new version but server could not be shut down\n`)
return
}
}
this[name + 'Running'] = false
this.emit('statusupdated', {})
}
let history = this.db.get(`core_${name}History`)
.filter('installed')
.orderBy('installed', 'desc')
.value()
this.logActive(name, active, `Runner: Finding available version\n`)
for (let i = 0; i < history.length; i++) {
if ((history[i].stable === -1 && !active.fresh)
|| (history[i].stable < -1)) {
this.logActive(name, active, `Runner: Skipping version ${history[i].name} due to marked as unstable\n`)
continue
}
await this.db.set(`core.${name}Active`, history[i].name)
.write()
this.emit('dbupdated', {})
let running = await this.tryStartProgramVersion(name, active, history[i].name)
if (running) {
history[i].stable = 1
} else {
if (active.fresh || history[i].stable === -1) {
history[i].stable = -2
} else {
history[i].stable = -1
}
await this.db.set(`core.${name}Active`, null)
.write()
this.emit('dbupdated', {})
}
active.fresh = false
await this.db.get(`core_${name}History`).updateById(history[i].id, history[i].stable).write()
if (history[i].stable > 0) break
}
if (!this.db.get(`core.${name}Active`).value()) {
this.logActive(name, active, `Runner: Could not find any available stable version of ${name}\n`)
this.log.error('Unable to start ' + name)
this.log.event.error('Unable to start ' + name)
}
active.starting = false
}
programCrashed(name, version, active, oldStable) {
let newStable = -2
console.log('EXITING:', oldStable, active)
if (oldStable === 0 && !active.fresh) {
newStable = -1
}
let temp = this.db.get(`core_${name}History`).getById(version).set('stable', newStable )
temp.value() // Trigger update on __wrapped__
fs.writeFileSync(this.db.adapterFilePath, JSON.stringify(temp.__wrapped__, null, 2))
}
async tryStartProgramVersion(name, active, version) {
if (!version) return false
this.logActive(name, active, `Runner: Attempting to start ${version}\n`)
let indexPath = this.util.getUrlFromRoot(`./${name}/` + version + '/index.mjs')
let module
try {
this.logActive(name, active, `Runner: Loading ${indexPath}\n`)
module = await import(indexPath)
} catch (err) {
this.logActive(name, active, `Runner: Error importing module\n`, true)
this.logActive(name, active, `${err.stack}\n`, true)
this.log.error(err, `Failed to load ${indexPath}`)
return false
}
let checkTimeout = null
let oldStable = this.db.get(`core_${name}History`).getById(version).value().stable
this._activeCrashHandler = this.programCrashed.bind(this, name, version, active, oldStable)
process.once('exit', this._activeCrashHandler)
try {
let port = name === 'app' ? this.config.port : this.config.managePort
await new Promise((res, rej) => {
checkTimeout = setTimeout(function() {
rej(new Error('Program took longer than 60 seconds to resolve promise'))
}, 60 * 1000)
this.logActive(name, active, `Runner: Starting module\n`)
try {
this.http.setContext(name)
this.startModule(module, port)
.then(res, rej)
} catch (err) {
rej(err)
}
})
clearTimeout(checkTimeout)
await this.checkProgramRunning(name, active, port)
process.off('exit', this._activeCrashHandler)
} catch (err) {
clearTimeout(checkTimeout)
process.off('exit', this._activeCrashHandler)
await this.http.closeServer(name)
this.logActive(name, active, `Runner: Error starting\n`, true)
this.logActive(name, active, `${err.stack}\n`, true)
this.log.error(err, `Failed to start ${name}`)
return false
}
this._activeCrashHandler = null
this.logActive(name, active, `Runner: Successfully started version ${version}\n`)
await this.db.set(`core.${name}Active`, version)
.write()
if (name === 'app') {
this.appRunning = true
} else {
this.manageRunning = true
}
this.emit('statusupdated', {})
this.logActive(name, active, `Runner: Module is running successfully\n`)
return true
}
async checkProgramRunning(name, active, port) {
this.logActive(name, active, `Checker: Testing out module port ${port}\n`)
let start = new Date()
let error = null
let success = false
while (new Date() - start < 10 * 1000) {
try {
let check = await request(this.config, `http://localhost:${port}`, null, 0, true)
success = true
break
} catch(err) {
this.logActive(name, active, `Checker: ${err.message}, retrying in 3 seconds\n`)
error = err
await new Promise(function(res) { setTimeout(res, 3000)})
}
}
if (success) return true
throw error || new Error('Checking server failed')
}
async installLatestVersion(name) {
if (!this.config[name + 'Repository']) {
if (name === 'app') {
this.log.error(name + ' Repository was missing from config')
this.log.event.error(name + ' Repository was missing from config')
} else {
this.log.warn(name + ' Repository was missing from config')
this.log.event.warn(name + ' Repository was missing from config')
}
return
}
let active = this.getActive(name)
let oldLogs = active.logs || ''
if (oldLogs) {
oldLogs += '\n'
}
active.logs = ''
active.updating = true
this.emit('statusupdated', {})
this.logActive(name, active, `Installer: Checking for updates at time: ${new Date().toISOString().replace('T', ' ').split('.')[0]}\n`)
let version = null
let installed = false
let found = false
try {
version = await this.getLatestVersion(active, name)
if (version) {
let core = this.db.get('core').value()
let fromDb = this.db.get(`core_${name}History`).getById(version.name).value()
if (!fromDb || !fromDb.installed) {
let oldVersion = core[name + 'Current'] || '<none>'
this.logActive(name, active, `Installer: Updating from ${oldVersion} to ${version.name}\n`)
await this.installVersion(name, active, version)
this.logActive(name, active, `Installer: Finished: ${new Date().toISOString().replace('T', ' ').split('.')[0]}\n`)
installed = new Date()
} else {
found = true
this.logActive(name, active, `Installer: Version ${version.name} already installed\n`)
}
}
} catch(err) {
this.logActive(name, active, '\n', true)
this.logActive(name, active, `Installer: Exception occured while updating ${name}\n`, true)
this.logActive(name, active, err.stack, true)
this.log.error('Error while updating ' + name, err)
}
active.updating = false
if (version && !found) {
await this.db.get(`core_${name}History`).upsert({
id: version.name,
name: version.name,
filename: version.filename,
url: version.url,
description: version.description,
logs: active.logs,
stable: 0,
installed: installed && installed.toISOString(),
}).write()
}
active.logs = oldLogs + active.logs
this.emit(name + 'log', active)
this.emit('statusupdated', {})
}
async start(name) {
var version = this.db.get('core.' + name + 'LatestInstalled').value()
if (version) {
await this.tryStartProgram(name)
}
await this.installLatestVersion(name)
if (version !== this.db.get('core.' + name + 'LatestInstalled').value()) {
if (!this[name + 'Running'] || this.hasNewVersionAvailable(name)) {
await this.tryStartProgram(name)
}
}
}
}

View File

@ -1,6 +1,5 @@
import { setTimeout } from 'timers/promises'
import { Low, JSONFile, Memory } from 'lowdb'
import { type } from 'os'
import { defaults, isObject } from './defaults.mjs'
export default function GetDB(config, log, orgFilename = 'db.json') {
@ -87,9 +86,9 @@ export default function GetDB(config, log, orgFilename = 'db.json') {
db.addApplication = function(name) {
db.data.core[name] ||= {}
defaults(db.data.core[name], {
active: null,
latestInstalled: null,
latestVersion: null,
active: '',
latestInstalled: '',
latestVersion: '',
updater: '',
versions: [],
})

View File

@ -24,6 +24,17 @@ export default class HttpServer {
})
})
server.listenAsync = (port, host) => {
return new Promise((res, rej) => {
server.once('error', rej)
server.listen(port, host || '0.0.0.0', () => {
server.off('error', rej)
res()
})
})
}
this.active = server
return server
}
@ -38,11 +49,13 @@ export default class HttpServer {
this.sockets.clear()
this.active.close(err => {
if (err) return rej(err)
if (err) {
if (err.code !== 'ERR_SERVER_NOT_RUNNING') return rej(err)
}
this.active = null
// Waiting 1 second for it to close down
setTimeout(function() {res() }, 1000)
setTimeout(function() {res() }, 100)
})
})
}

View File

@ -1,15 +1,24 @@
import nodewindows from 'node-windows'
// import nodewindows from 'node-windows'
import bunyan from 'bunyan-lite'
import { setTimeout } from 'timers/promises'
export default function getLog(name) {
export default function getLog(name, streams = null, opts = {}) {
let settings
let ringbuffer = new bunyan.RingBuffer({ limit: 100 })
let ringbufferwarn = new bunyan.RingBuffer({ limit: 100 })
if (streams) {
streams.forEach(function(stream) {
if (stream.stream === 'process.stdout') {
stream.stream = process.stdout
}
})
}
if (process.env.NODE_ENV === 'production') {
settings = {
"name": "service-core",
"streams": [{
"name": name,
"streams": streams || [{
path: 'log.log',
level: 'info',
}
@ -17,8 +26,8 @@ export default function getLog(name) {
}
} else {
settings = {
"name": "service-core",
"streams": [{
"name": name,
"streams": streams || [{
"stream": process.stdout,
"level": "debug"
}
@ -53,16 +62,64 @@ export default function getLog(name) {
level: 'info',
})
let eventManager = null
let eventManagerLoading = false
async function safeLoadEvent(level, message, code) {
if (eventManager === false) {
return Promise.resolve()
}
if (!eventManager) {
if (eventManagerLoading) {
for (let i = 0; i < 10 && eventManagerLoading; i++) {
await setTimeout(50)
}
if (eventManagerLoading) {
eventManager = false
}
return safeLoadEvent(level, message, code)
}
eventManagerLoading = true
let prom
if (opts.import) {
prom = opts.import('node-windows')
} else {
prom = import('node-windows')
}
await prom.then(
function(res) { eventManager = new res.default.EventLogger(name) },
function() { eventManager = false },
)
eventManagerLoading = false
return safeLoadEvent(level, message, code)
}
return new Promise(function(res) {
try {
eventManager[level](message, code, function() { res() })
} catch {
res()
}
})
}
// Create our logger.
logger = bunyan.createLogger(settings)
if (process.env.NODE_ENV === 'production') {
logger.event = new nodewindows.EventLogger(name)
logger.event = {
info: safeLoadEvent.bind(this, 'info'),
warn: safeLoadEvent.bind(this, 'warn'),
error: safeLoadEvent.bind(this, 'error'),
}
} else {
logger.event = {
info: function() {},
warn: function() {},
error: function() {},
info: function() { return Promise.resolve() },
warn: function() { return Promise.resolve() },
error: function() { return Promise.resolve() },
}
}
logger.ringbuffer = ringbuffer

View File

@ -26,15 +26,58 @@ export default class Util {
}
getAppNames(config) {
const validLevels = [
'fatal',
'error',
'warn',
'info',
'debug',
'trace',
]
let out = []
let keys = Object.keys(config)
for (let key of keys) {
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
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 < 0))
continue
if (config[key].startWaitUntilFail != null && (typeof(config[key].startWaitUntilFail) !== 'number' || config[key].startWaitUntilFail < 10))
continue
if (config[key].heartbeatTimeout != null && (typeof(config[key].heartbeatTimeout) !== 'number' || config[key].heartbeatTimeout < 10))
continue
if (config[key].heartbeatAttempts != null && (typeof(config[key].heartbeatAttempts) !== 'number' || config[key].heartbeatAttempts < 1))
continue
if (config[key].heartbeatAttemptsWait != null && (typeof(config[key].heartbeatAttemptsWait) !== 'number' || config[key].heartbeatAttemptsWait < 10))
continue
if (config[key].heartbeatPath != null && (typeof(config[key].heartbeatPath) !== 'string' || config[key].heartbeatPath[0] !== '/'))
continue
if (config[key].log != null) {
if (!Array.isArray(config[key].log))
continue
let valid = true
for (let stream of config[key].log) {
if (!stream || typeof(stream) !== 'object' || Array.isArray(stream)) {
valid = false
break
}
if (typeof(stream.level) !== 'string' || !stream.level || !validLevels.includes(stream.level)) {
valid = false
break
}
if ((typeof(stream.path) !== 'string' || !stream.path) && stream.stream !== 'process.stdout') {
valid = false
break
}
}
if (!valid)
continue
}
out.push(key)
}

View File

@ -6,6 +6,7 @@
"scripts": {
"dev": "nodemon --watch dev/api --watch core --watch runner.mjs --watch db.mjs --watch log.mjs runner.mjs | bunyan",
"test": "eltro \"test/**/*.test.mjs\" -r dot",
"test:spec": "eltro \"test/**/*.test.mjs\" -r list",
"test:watch": "npm-watch test"
},
"watch": {
@ -13,7 +14,10 @@
"patterns": [
"{core,test}/*"
],
"ignore": "test/testapp",
"ignore": [
"test/testapp",
"test/testnoexisting"
],
"extensions": "js,mjs",
"quiet": true,
"inherit": true

View File

@ -1,38 +1,38 @@
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 GitProvider from '../core/providers/git.mjs'
import Util from '../core/util.mjs'
import { createFakeContext } from './helpers.mjs'
const util = new Util(import.meta.url)
const logger = {
info: stub(),
warn: stub(),
error: stub(),
}
t.skip().timeout(10000).describe('Application update integration test', function() {
let db
let ctx
let app
let provider
t.before(function() {
return lowdb({ test: { } }, logger, util.getPathFromRoot('./db_test.json')).then(function(res) {
db = res
provider = new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/sc-helloworld/releases' })
app = new Application(util, db, provider, 'testapp')
return provider.getLatestVersion()
}).then(function(version) {
return fs.rm(`./test/testapp/${version.version}`, { force: true, recursive: true })
})
return createFakeContext({ }, util, util.getPathFromRoot('./db_test.json'))
.then(function(res) {
ctx = res
provider = new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/sc-helloworld/releases' })
app = new Application(ctx, provider, 'testapp')
return provider.getLatestVersion()
}).then(function(version) {
return fs.rm(`./test/testapp/${version.version}`, { force: true, recursive: true })
})
})
t.after(function() {
if (db.data.core.testapp.versions.length) {
return fs.rm(`./test/testapp/${db.data.core.testapp.versions[0].id}`, { force: true, recursive: true })
}
return fs.rm(util.getPathFromRoot('./db_test.json'))
.then(function() {
if (ctx.db.data.core.testapp.versions.length) {
return fs.rm(`./test/testapp/${ctx.db.data.core.testapp.versions[0].id}`, { force: true, recursive: true })
}
})
})
t.test('should run update and install correctly', async function(){
@ -40,15 +40,15 @@ t.skip().timeout(10000).describe('Application update integration test', function
await app.update()
} catch (err) {
console.log(err)
if (db.data.core.testapp.versions.length) {
console.log(db.data.core.testapp.versions[0].log)
if (ctx.db.data.core.testapp.versions.length) {
console.log(ctx.db.data.core.testapp.versions[0].log)
}
throw err
}
assert.ok(db.data.core.testapp.latestInstalled)
await fs.stat(util.getPathFromRoot(`./testapp/${db.data.core.testapp.latestInstalled}/index.mjs`))
await fs.stat(util.getPathFromRoot(`./testapp/${db.data.core.testapp.latestInstalled}/package.json`))
await fs.stat(util.getPathFromRoot(`./testapp/${db.data.core.testapp.latestInstalled}/node_modules`))
assert.ok(ctx.db.data.core.testapp.latestInstalled)
await fs.stat(util.getPathFromRoot(`./testapp/${ctx.db.data.core.testapp.latestInstalled}/index.mjs`))
await fs.stat(util.getPathFromRoot(`./testapp/${ctx.db.data.core.testapp.latestInstalled}/package.json`))
await fs.stat(util.getPathFromRoot(`./testapp/${ctx.db.data.core.testapp.latestInstalled}/node_modules`))
})
})

View File

@ -1,58 +1,59 @@
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 lowdb from '../core/db.mjs'
import StaticProvider from '../core/providers/static.mjs'
import { createFakeContext } from './helpers.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() {
t.describe('#runVersion("static")', function() {
const assertPort = 22345
let db
let ctx
let app
const defaultHandler = function(db, log, http, port) {
const server = http.createServer(function (req, res) {
const defaultHandler = function(orgHandler) {
let handler = orgHandler || function (req, res) {
res.writeHead(204); res.end(JSON.stringify({ a: 1 }))
})
}
return function(http, port, ctx) {
const server = http.createServer(handler)
return new Promise(function(res, rej) {
server.listen(port, '0.0.0.0', function(err) {
if (err) return rej(err)
res()
})
})
return server.listenAsync(port)
}
}
t.before(function() {
return fs.mkdir(util.getPathFromRoot('./testnoexisting'), { recursive: true })
})
t.after(function() {
return fs.rm(util.getPathFromRoot('./testnoexisting'), { force: true, recursive: true })
})
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)
})
return createFakeContext()
.then(function(res) {
ctx = res
let provider = new StaticProvider()
app = new Application(ctx, provider, 'testapp')
app.config.port = assertPort
app.registerModule(defaultHandler())
})
})
t.afterEach(function() {
return app.http.closeServer()
})
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)
app.registerModule(function(checkHttp, checkPort, checkCtx) {
assert.strictEqual(checkHttp, app.http)
assert.strictEqual(checkPort, assertPort)
assert.strictEqual(checkCtx.db, ctx.db)
assert.strictEqual(checkCtx.log, ctx.log)
assert.strictEqual(checkCtx.app, app)
})
let err = await assert.isRejected(app.runVersion('static'))
@ -62,10 +63,11 @@ t.timeout(250).describe('#runVersion()', function() {
assert.match(err.message, /static/)
assert.match(err.message, new RegExp(app.name))
assert.match(err.message, /call/i)
assert.strictEqual(ctx.db.data.core.testapp.active, 'static')
})
t.test('should throw if it timeouts waiting for promise to succeed', async function() {
app.config.waitUntilFail = 50
app.config.startWaitUntilFail = 50
app.registerModule(function() { return new Promise(function() {}) })
let err = await assert.isRejected(app.runVersion('static'))
@ -75,18 +77,203 @@ t.timeout(250).describe('#runVersion()', function() {
assert.match(err.message, /static/)
assert.match(err.message, /50ms/)
assert.match(err.message, new RegExp(app.name))
assert.strictEqual(ctx.db.data.core.testapp.active, 'static')
})
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) {
const handler = defaultHandler()
app.config.startWaitUntilFail = 250
app.registerModule(function(http, port, ctx) {
return new Promise(function(res) {
setTimeout(res, 25)
}).then(function() {
return defaultHandler(db, log, http, port)
return handler(http, port, ctx)
})
})
await app.runVersion('static')
assert.strictEqual(ctx.db.data.core.testapp.active, 'static')
})
t.test('should fail if run succeeds but heartbeat errors', async function() {
let called = 0
const handler = function(req, res) {
called++
res.statusCode = 400
res.end(JSON.stringify({ a: 1 }))
}
app.config.heartbeatAttempts = 3
app.registerModule(defaultHandler(handler))
let err = await assert.isRejected(app.runVersion('static'))
assert.match(err.message, /app/i)
assert.match(err.message, /failed/i)
assert.match(err.message, /static/i)
assert.match(err.message, /testapp/i)
assert.match(err.message, /400/i)
assert.strictEqual(called, 3)
assert.strictEqual(ctx.db.data.core.testapp.active, 'static')
})
t.test('should fail if run succeeds but heartbeat times out', async function() {
let called = 0
const handler = function(req, res) {
called++
}
app.config.heartbeatAttempts = 2
app.config.heartbeatAttemptsWait = 30
app.registerModule(defaultHandler(handler))
let start = performance.now()
let err = await assert.isRejected(app.runVersion('static'))
let end = performance.now()
assert.match(err.message, /app/i)
assert.match(err.message, /failed/i)
assert.match(err.message, /static/i)
assert.match(err.message, /testapp/i)
assert.match(err.message, /time/i)
assert.match(err.message, /out/i)
assert.match(err.message, /30ms/i)
assert.ok(end - start > app.config.heartbeatAttempts * app.config.heartbeatAttemptsWait)
assert.strictEqual(called, 2)
assert.strictEqual(ctx.db.data.core.testapp.active, 'static')
})
t.test('should call with correct path', async function() {
const assertPath = '/test/something'
const handler = function(req, res) {
if (req.url === assertPath) {
res.writeHead(204); res.end(JSON.stringify({ a: 1 }))
} else {
res.statusCode = 400
res.end(JSON.stringify({ a: 1 }))
}
}
app.config.heartbeatAttempts = 3
app.registerModule(defaultHandler(handler))
let err = await assert.isRejected(app.runVersion('static'))
assert.match(err.message, /app/i)
assert.match(err.message, /failed/i)
assert.match(err.message, /static/i)
assert.match(err.message, /testapp/i)
assert.match(err.message, /400/i)
await app.http.closeServer()
app.registerModule(defaultHandler(handler))
app.config.heartbeatPath = assertPath
await app.runVersion('static')
assert.strictEqual(ctx.db.data.core.testapp.active, 'static')
})
})
t.skip().describe('#runVersion("version")', function() {
const assertConfig = util.getPathFromRoot('./db_test_applicationrun.json')
const assertPort = 22345
let ctx
let app
t.before(function() {
return fs.rm(util.getPathFromRoot('./testnoexisting'), { force: true, recursive: true })
.then(function() {
return fs.mkdir(util.getPathFromRoot('./testnoexisting'), { recursive: true })
})
})
t.after(function() {
return fs.rm(util.getPathFromRoot('./testnoexisting'), { force: true, recursive: true })
})
t.beforeEach(function() {
return createFakeContext({ }, util, assertConfig)
.then(function(res) {
ctx = res
let provider = new StaticProvider()
app = new Application(ctx, provider, 'testnoexisting')
app.config.port = assertPort
return app.ctx.db.write()
})
})
t.afterEach(function() {
return Promise.all([
fs.rm(assertConfig),
app.http.closeServer(),
])
})
t.test('when version is specified, should check if index.mjs exists', async function() {
const assertNotError = new Error('AI DO')
const assertTarget = util.getPathFromRoot('./testnoexisting/v100/index.mjs')
let stubFsStat = stub()
let provider = new StaticProvider()
app = new Application(ctx, provider, 'testnoexisting', {
fs: { stat: stubFsStat }
})
app.config.port = assertPort
stubFsStat.rejects(assertNotError)
let err = await assert.isRejected(app.runVersion('v100'))
assert.notStrictEqual(err, assertNotError)
assert.match(err.message, new RegExp(assertNotError.message))
assert.match(err.message, /index\.mjs/i)
assert.match(err.message, /testnoexisting/i)
assert.match(err.message, /v100/i)
assert.match(err.message, /missing/i)
assert.strictEqual(stubFsStat.firstCall[0], assertTarget)
assert.strictEqual(app.ctx.db.data.core.testnoexisting.active, 'v100')
let checkDb = await lowdb({}, ctx.log, assertConfig)
assert.strictEqual(checkDb.data.core.testnoexisting.active, 'v100')
})
t.test('when version is specified and file exists, should attempt to load module', async function() {
const assertError = new Error('Parallel Days')
await fs.mkdir(util.getPathFromRoot('./testnoexisting/v99'), { recursive: true })
await fs.writeFile(util.getPathFromRoot('./testnoexisting/v99/index.mjs'), `throw new Error('${assertError.message}')`)
let err = await assert.isRejected(app.runVersion('v99'))
assert.notStrictEqual(err, assertError)
assert.match(err.message, new RegExp(assertError.message))
assert.match(err.message, /testnoexisting/i)
assert.match(err.message, /v99/i)
assert.match(err.message, /index\.mjs/i)
assert.strictEqual(app.ctx.db.data.core.testnoexisting.active, 'v99')
let checkDb = await lowdb({}, ctx.log, assertConfig)
assert.strictEqual(checkDb.data.core.testnoexisting.active, 'v99')
})
t.test('when version is specified and file exists, should check if it has start', async function() {
await fs.mkdir(util.getPathFromRoot('./testnoexisting/v98'), { recursive: true })
await fs.writeFile(util.getPathFromRoot('./testnoexisting/v98/index.mjs'), ``)
let err = await assert.isRejected(app.runVersion('v98'))
assert.match(err.message, /testnoexisting/i)
assert.match(err.message, /v98/i)
assert.match(err.message, /start/i)
assert.strictEqual(app.ctx.db.data.core.testnoexisting.active, 'v98')
let checkDb = await lowdb({}, ctx.log, assertConfig)
assert.strictEqual(checkDb.data.core.testnoexisting.active, 'v98')
})
t.test('when version is specified and file exists and everything is okay, should work normally', async function() {
await fs.mkdir(util.getPathFromRoot('./testnoexisting/v97'), { recursive: true })
await fs.copyFile(
util.getPathFromRoot('./exampleindex.mjs'),
util.getPathFromRoot('./testnoexisting/v97/index.mjs')
)
app.ctx.log.info.reset()
app.ctx.log.event.info.reset()
await app.runVersion('v97')
assert.ok(app.ctx.log.info.called)
assert.ok(app.ctx.log.event.info.called)
})
})

View File

@ -1,10 +1,10 @@
import { setTimeout, setImmediate } from 'timers/promises'
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'
import { createFakeContext } from './helpers.mjs'
const util = new Util(import.meta.url)
@ -21,41 +21,46 @@ function createProvider() {
}
t.describe('constructor()', function() {
let db
let ctx
t.beforeEach(function() {
return lowdb({ test: { } }, logger, null).then(function(res) {
db = res
})
return createFakeContext()
.then(function(res) { ctx = res })
})
t.test('should auto-create application', function() {
assert.notOk(db.data.core.test)
assert.notOk(ctx.db.data.core.test)
new Application(util, db, {}, 'test')
new Application(ctx, {}, 'test')
assert.ok(db.data.core.test)
assert.ok(db.data.core.test.versions)
assert.strictEqual(db.data.core.test.active, null)
assert.strictEqual(db.data.core.test.latestInstalled, null)
assert.strictEqual(db.data.core.test.latestVersion, null)
assert.ok(ctx.db.data.core.test)
assert.ok(ctx.db.data.core.test.versions)
assert.strictEqual(ctx.db.data.core.test.active, '')
assert.strictEqual(ctx.db.data.core.test.latestInstalled, '')
assert.strictEqual(ctx.db.data.core.test.latestVersion, '')
})
t.test('should keep config and other of itself', function() {
const assertTest = { a: 1 }
const assertName = 'test'
db.config = {
ctx.db.config = {
test: assertTest,
app: { b: 2},
manage: { c: 3 },
}
let app = new Application(util, db, {}, assertName)
assert.strictEqual(app.config, assertTest)
let app = new Application(ctx, {}, assertName)
assert.notStrictEqual(app.config, assertTest)
assert.strictEqual(app.config.a, assertTest.a)
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.config.startWaitUntilFail, 60 * 1000)
assert.strictEqual(app.config.heartbeatTimeout, 3 * 1000)
assert.strictEqual(app.config.heartbeatAttempts, 5)
assert.strictEqual(app.config.heartbeatAttemptsWait, 2 * 1000)
assert.strictEqual(app.config.heartbeatPath, '/')
assert.strictEqual(app.ctx.db, ctx.db)
assert.strictEqual(app.ctx.app, app)
assert.strictEqual(app.ctx.util, ctx.util)
assert.strictEqual(app.name, assertName)
assert.strictEqual(app.fresh, true)
assert.strictEqual(app.running, false)
@ -66,38 +71,37 @@ t.describe('constructor()', function() {
})
t.test('should create http instance correctly', function() {
db.config = {
ctx.db.config = {
testapp: { a: 1, https: true },
app: { b: 2},
manage: { c: 3 },
}
let app = new Application(util, db, {}, 'testapp')
let app = new Application(ctx, {}, 'testapp')
assert.ok(app.http)
assert.ok(app.http.ishttps)
})
t.test('should keep provider', function() {
const assertProvider = { a: 1 }
let app = new Application(util, db, assertProvider, 'test')
let app = new Application(ctx, assertProvider, 'test')
assert.strictEqual(app.provider, assertProvider)
})
})
t.timeout(250).describe('#startAutoupdater()', function() {
let db
let ctx
t.beforeEach(function() {
return lowdb({ test: { }, testapp: { } }, logger, null).then(function(res) {
db = res
})
return createFakeContext()
.then(function(res) { ctx = res })
})
t.test('should do nothing if provider is static', async function() {
const stubInterval = stub()
stubInterval.throws(new Error('should not be seen'))
let provider = new StaticProvider()
let app = new Application(util, db, provider, 'teststatic', { setInterval: stubInterval })
let app = new Application(ctx, provider, 'teststatic', { setInterval: stubInterval })
app.startAutoupdater()
})
@ -108,9 +112,11 @@ t.timeout(250).describe('#startAutoupdater()', function() {
const stubUnref = stub()
stubInterval.returns({ unref: stubUnref })
db.config.test.updateEvery = assertTimeMinutes
ctx.db.config.test = {
updateEvery: assertTimeMinutes,
}
let app = new Application(util, db, {}, 'test', { setInterval: stubInterval })
let app = new Application(ctx, {}, 'test', { setInterval: stubInterval })
assert.strictEqual(stubInterval.called, false)
assert.strictEqual(stubUnref.called, false)
@ -127,7 +133,7 @@ t.timeout(250).describe('#startAutoupdater()', function() {
const stubInterval = stub()
stubInterval.returns({ unref: function() {} })
let app = new Application(util, db, {}, 'test', { setInterval: stubInterval })
let app = new Application(ctx, {}, 'test', { setInterval: stubInterval })
assert.strictEqual(stubInterval.called, false)
@ -147,7 +153,7 @@ t.timeout(250).describe('#startAutoupdater()', function() {
return Promise.resolve()
})
let app = new Application(util, db, {}, 'test', { setInterval: stubInterval })
let app = new Application(ctx, {}, 'test', { setInterval: stubInterval })
app.update = stubUpdate
app.startAutoupdater()
@ -156,12 +162,12 @@ t.timeout(250).describe('#startAutoupdater()', function() {
stubInterval.firstCall[0]()
while (db.data.core.test.updater === '') {
while (ctx.db.data.core.test.updater === '') {
await setTimeout(10)
}
assert.match(db.data.core.test.updater, /auto/i)
assert.match(db.data.core.test.updater, /update/i)
assert.match(ctx.db.data.core.test.updater, /auto/i)
assert.match(ctx.db.data.core.test.updater, /update/i)
})
t.test('should add any errors to last in db update check on errors when updating', async function() {
@ -169,28 +175,55 @@ t.timeout(250).describe('#startAutoupdater()', function() {
const assertErrorMessage = 'Ai Do'
stubInterval.returns({ unref: function() {} })
let app = new Application(util, db, {}, 'test', { setInterval: stubInterval })
let app = new Application(ctx, {}, 'test', { setInterval: stubInterval })
app.update = function() {
return Promise.reject(new Error(assertErrorMessage))
}
app.startAutoupdater()
assert.strictEqual(db.data.core.test.updater, '')
assert.strictEqual(ctx.db.data.core.test.updater, '')
stubInterval.firstCall[0]()
while (db.data.core.test.updater === '') {
while (ctx.db.data.core.test.updater === '') {
await setTimeout(10)
}
assert.match(db.data.core.test.updater, /auto/i)
assert.match(db.data.core.test.updater, /update/i)
assert.match(db.data.core.test.updater, new RegExp(assertErrorMessage))
assert.match(ctx.db.data.core.test.updater, /auto/i)
assert.match(ctx.db.data.core.test.updater, /update/i)
assert.match(ctx.db.data.core.test.updater, new RegExp(assertErrorMessage))
})
})
t.timeout(250).describe('#closeServer()', function() {
let app
let stubCloseServer
t.beforeEach(function() {
return createFakeContext()
.then(function(res) {
let provider = createProvider()
app = new Application(res, provider, 'testapp')
app.http.closeServer = stubCloseServer = stub().resolves()
})
})
t.test('should call closeServer correctly', async function() {
const assertError = new Error('Moonlight Fiesta')
stubCloseServer.rejects(assertError)
let err = await assert.isRejected(app.closeServer())
assert.strictEqual(err, assertError)
})
t.test('otherwise should work fine', async function() {
await app.closeServer()
})
})
t.timeout(250).describe('#update()', function() {
let db
let ctx
let app
let provider
let stubExtract
@ -201,30 +234,27 @@ t.timeout(250).describe('#update()', function() {
let stubFsStat
t.beforeEach(function() {
util.extractFile = stubExtract = stub()
util.runCommand = stubRunCommand = stub()
return createFakeContext()
.then(function(res) {
ctx = res
ctx.util.extractFile = stubExtract = stub().resolves()
ctx.util.runCommand = stubRunCommand = stub().resolves()
ctx.db.write = stubWrite = stub().resolves()
stubExtract.resolves()
stubRunCommand.resolves()
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: {
mkdir: stubFsMkdir = stub(),
rm: stubFsRm = stub(),
stat: stubFsStat = stub(),
},
provider = createProvider()
app = new Application(ctx, provider, 'testapp', {
fs: {
mkdir: stubFsMkdir = stub(),
rm: stubFsRm = stub(),
stat: stubFsStat = stub(),
},
})
stubFsMkdir.resolves()
stubFsRm.resolves()
stubFsStat.resolves({})
provider.downloadVersion.resolves()
provider.getLatestVersion.resolves({ version: '123456789', link: 'httplinkhere', filename: 'test.7z' })
})
stubFsMkdir.resolves()
stubFsRm.resolves()
stubFsStat.resolves({})
provider.downloadVersion.resolves()
provider.getLatestVersion.resolves({ version: '123456789', link: 'httplinkhere', filename: 'test.7z' })
})
})
t.afterEach(function() {
@ -233,33 +263,35 @@ t.timeout(250).describe('#update()', function() {
t.test('should do nothing if provider is static', async function() {
provider = new StaticProvider()
app = new Application(util, db, provider, 'teststatic')
app = new Application(ctx, provider, 'teststatic')
stubWrite.reset()
await app.update()
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.match(ctx.db.data.core.teststatic.updater, /static/i)
assert.match(ctx.db.data.core.teststatic.updater, /nothing/i)
let old = ctx.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(ctx.db.data.core.teststatic.updater, old)
assert.strictEqual(stubWrite.callCount, 1)
db.data.core.teststatic.updater = 'asdf'
ctx.db.data.core.teststatic.updater = 'asdf'
await app.update()
assert.strictEqual(db.data.core.teststatic.updater, old)
assert.strictEqual(ctx.db.data.core.teststatic.updater, old)
assert.strictEqual(stubWrite.callCount, 2)
await app.update()
assert.strictEqual(db.data.core.teststatic.updater, old)
assert.strictEqual(ctx.db.data.core.teststatic.updater, old)
assert.strictEqual(stubWrite.callCount, 2)
})
t.test('multiple calls should be safe', async function() {
db.data.core.testapp.updater = ''
ctx.db.data.core.testapp.updater = ''
provider.getLatestVersion.returnWith(function() {
return new Promise(function() {})
@ -282,7 +314,7 @@ t.timeout(250).describe('#update()', function() {
t.test('should check for latest version', async function() {
const assertError = new Error('Ore wa Subete wo Shihaisuru')
provider.getLatestVersion.rejects(assertError)
db.data.core.testapp.updater = ''
ctx.db.data.core.testapp.updater = ''
let err = await assert.isRejected(app.update())
@ -291,10 +323,10 @@ t.timeout(250).describe('#update()', function() {
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]))
assert.match(db.data.core.testapp.updater, new RegExp(assertError.message))
assert.match(ctx.db.data.core.testapp.updater, /check/i)
assert.match(ctx.db.data.core.testapp.updater, /version/i)
assert.match(ctx.db.data.core.testapp.updater, new RegExp(new Date().toISOString().split('T')[0]))
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertError.message))
})
t.test('should call provider download latest correctly if new 7zip version', async function() {
@ -302,27 +334,27 @@ t.timeout(250).describe('#update()', function() {
const assertLink = 'All of you'
const assertVersion = { version: '123456789', link: assertLink, filename: 'test.7z' }
const assertTarget = util.getPathFromRoot('./testapp/123456789/file.7z')
assert.strictEqual(db.data.core.testapp.versions.length, 0)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
provider.getLatestVersion.resolves(assertVersion)
provider.downloadVersion.rejects(assertError)
db.data.core.testapp.updater = ''
ctx.db.data.core.testapp.updater = ''
let err = await assert.isRejected(app.update())
assert.strictEqual(app.updating, false)
assert.strictEqual(err, assertError)
assert.match(db.data.core.testapp.updater, /found/i)
assert.match(db.data.core.testapp.updater, new RegExp(assertVersion.version))
assert.match(db.data.core.testapp.updater, /downloading/i)
assert.match(db.data.core.testapp.updater, new RegExp(assertLink))
assert.match(db.data.core.testapp.updater, new RegExp(assertTarget.replace(/\\/g, '\\\\')))
assert.match(ctx.db.data.core.testapp.updater, /found/i)
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertVersion.version))
assert.match(ctx.db.data.core.testapp.updater, /downloading/i)
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertLink))
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertTarget.replace(/\\/g, '\\\\')))
assert.strictEqual(provider.downloadVersion.firstCall[0], assertVersion)
assert.strictEqual(provider.downloadVersion.firstCall[1], assertTarget)
assert.match(db.data.core.testapp.updater, new RegExp(assertError.message))
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertError.message))
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.ok(assertVersion.log)
assert.match(assertVersion.log, /\. \n/)
assert.match(assertVersion.log, new RegExp(assertError.message))
@ -341,8 +373,8 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(app.updating, false)
assert.strictEqual(err, assertError)
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(assertVersion.failtodownload, 2)
})
@ -351,27 +383,27 @@ t.timeout(250).describe('#update()', function() {
const assertLink = 'All of you'
const assertVersion = { version: '123456789', link: assertLink, filename: 'test.7z' }
const assertTarget = util.getPathFromRoot('./testapp/123456789/file.7z')
assert.strictEqual(db.data.core.testapp.versions.length, 0)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
provider.getLatestVersion.resolves(assertVersion)
provider.downloadVersion.rejects(assertError)
db.data.core.testapp.updater = ''
ctx.db.data.core.testapp.updater = ''
let err = await assert.isRejected(app.update())
assert.strictEqual(app.updating, false)
assert.strictEqual(err, assertError)
assert.match(db.data.core.testapp.updater, /found/i)
assert.match(db.data.core.testapp.updater, new RegExp(assertVersion.version))
assert.match(db.data.core.testapp.updater, /downloading/i)
assert.match(db.data.core.testapp.updater, new RegExp(assertLink))
assert.match(db.data.core.testapp.updater, new RegExp(assertTarget.replace(/\\/g, '\\\\')))
assert.match(ctx.db.data.core.testapp.updater, /found/i)
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertVersion.version))
assert.match(ctx.db.data.core.testapp.updater, /downloading/i)
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertLink))
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertTarget.replace(/\\/g, '\\\\')))
assert.strictEqual(provider.downloadVersion.firstCall[0], assertVersion)
assert.strictEqual(provider.downloadVersion.firstCall[1], assertTarget)
assert.match(db.data.core.testapp.updater, new RegExp(assertError.message))
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertError.message))
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.ok(assertVersion.log)
assert.match(assertVersion.log, /\. \n/)
assert.match(assertVersion.log, new RegExp(assertError.message))
@ -389,8 +421,8 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(app.updating, false)
assert.strictEqual(err, assertError)
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(assertVersion.failtodownload, 2)
})
@ -404,7 +436,7 @@ t.timeout(250).describe('#update()', function() {
stream(assertExtractText)
return Promise.reject(assertError)
})
assert.strictEqual(db.data.core.testapp.versions.length, 0)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
let err = await assert.isRejected(app.update())
@ -413,8 +445,8 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(app.updating, false)
assert.strictEqual(stubExtract.firstCall[0], assertTarget)
assert.match(db.data.core.testapp.updater, new RegExp('extracting[^.]+file\.7z', 'i'))
assert.match(db.data.core.testapp.updater, new RegExp(assertError.message))
assert.match(ctx.db.data.core.testapp.updater, new RegExp('extracting[^.]+file\.7z', 'i'))
assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertError.message))
assert.ok(stubFsRm.called)
assert.strictEqual(stubFsRm.firstCall[0], assertFolder)
@ -423,8 +455,8 @@ t.timeout(250).describe('#update()', function() {
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.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
let version = ctx.db.data.core.testapp.versions[0]
assert.ok(version.log)
assert.match(version.log, /\. \n/)
assert.match(version.log, new RegExp('extracting[^.]+file\.7z', 'i'))
@ -439,15 +471,15 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(app.updating, false)
assert.strictEqual(err, assertError)
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(db.data.core.testapp.versions[0], version)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], version)
assert.strictEqual(version.failtodownload, 2)
})
t.test('should call fs remove the archieve file', async function() {
const assertError = new Error('Tiny Kizuna')
const assertTarget = util.getPathFromRoot('./testapp/123456789/file.7z')
assert.strictEqual(db.data.core.testapp.versions.length, 0)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
stubFsRm.rejects(assertError)
await app.update()
@ -464,7 +496,7 @@ t.timeout(250).describe('#update()', function() {
const assertTarget = util.getPathFromRoot('./testapp/123456789/index.mjs')
stubRunCommand.rejects(new Error('should not be seen'))
stubFsStat.rejects(assertError)
assert.strictEqual(db.data.core.testapp.versions.length, 0)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
let err = await assert.isRejected(app.update())
@ -472,11 +504,11 @@ t.timeout(250).describe('#update()', function() {
assert.ok(stubExtract.called)
assert.strictEqual(err, assertError)
assert.strictEqual(stubFsStat.firstCall[0], assertTarget)
assert.match(db.data.core.testapp.updater, /index\.mjs/i)
assert.match(db.data.core.testapp.updater, /missing/i)
assert.match(ctx.db.data.core.testapp.updater, /index\.mjs/i)
assert.match(ctx.db.data.core.testapp.updater, /missing/i)
assert.strictEqual(db.data.core.testapp.versions.length, 1)
let version = db.data.core.testapp.versions[0]
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
let version = ctx.db.data.core.testapp.versions[0]
assert.ok(version.log)
assert.match(version.log, /index\.mjs/i)
assert.match(version.log, /missing/i)
@ -489,8 +521,8 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(app.updating, false)
assert.strictEqual(err, assertError)
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(db.data.core.testapp.versions[0], version)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], version)
assert.strictEqual(version.failtodownload, 2)
})
@ -504,7 +536,7 @@ t.timeout(250).describe('#update()', function() {
}
return Promise.resolve({})
})
assert.strictEqual(db.data.core.testapp.versions.length, 0)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
await app.update()
@ -514,16 +546,16 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(stubFsStat.secondCall[0], assertTarget)
assert.ok(stubExtract.called)
assert.notOk(stubRunCommand.called)
assert.match(db.data.core.testapp.updater, /package\.json/i)
assert.match(db.data.core.testapp.updater, /contain/i)
assert.match(ctx.db.data.core.testapp.updater, /package\.json/i)
assert.match(ctx.db.data.core.testapp.updater, /contain/i)
assert.strictEqual(db.data.core.testapp.versions.length, 1)
let version = db.data.core.testapp.versions[0]
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
let version = ctx.db.data.core.testapp.versions[0]
assert.ok(version.log)
assert.match(version.log, /package\.json/i)
assert.match(version.log, /contain/i)
assert.ok(version.log.endsWith('\n'))
assert.ok(db.data.core.testapp.latestInstalled)
assert.ok(ctx.db.data.core.testapp.latestInstalled)
assert.match(version.log, /finished/i)
assert.match(version.log, /updating/i)
@ -533,10 +565,10 @@ t.timeout(250).describe('#update()', function() {
await app.update()
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.notOk(provider.downloadVersion.called)
assert.match(db.data.core.testapp.updater, /already/i)
assert.match(db.data.core.testapp.updater, /nothing/i)
assert.match(ctx.db.data.core.testapp.updater, /already/i)
assert.match(ctx.db.data.core.testapp.updater, /nothing/i)
})
t.test('should otherwise call npm install correctly', async function() {
@ -547,7 +579,7 @@ t.timeout(250).describe('#update()', function() {
const assertTarget = util.getPathFromRoot('./testapp/123456789')
const assertTargetCheck = util.getPathFromRoot('./testapp/123456789/')
provider.getLatestVersion.resolves(assertVersion)
assert.strictEqual(db.data.core.testapp.versions.length, 0)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
stubExtract.returnWith(function(target, stream) {
stream(assertExtractText)
@ -578,10 +610,10 @@ t.timeout(250).describe('#update()', function() {
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)
assert.strictEqual(db.data.core.testapp.versions[0], assertVersion)
assert.match(ctx.db.data.core.testapp.updater, /npm/i)
assert.match(ctx.db.data.core.testapp.updater, /install/i)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.ok(assertVersion.log)
assert.match(assertVersion.log, /\. \n/)
assert.match(assertVersion.log, new RegExp(assertExtractText))
@ -598,23 +630,23 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(app.updating, false)
assert.strictEqual(err, assertError)
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(assertVersion.failtoinstall, 2)
})
t.test('should update latest installed correctly', async function() {
const assertVersion = { version: '123456789', link: 'httplinkhere', filename: 'test.7z' }
provider.getLatestVersion.resolves(assertVersion)
assert.notStrictEqual(db.data.core.testapp.latestInstalled, assertVersion.version)
assert.strictEqual(db.data.core.testapp.versions.length, 0)
assert.notStrictEqual(ctx.db.data.core.testapp.latestInstalled, assertVersion.version)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
await app.update()
assert.strictEqual(app.updating, false)
assert.strictEqual(db.data.core.testapp.latestInstalled, assertVersion.version)
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(ctx.db.data.core.testapp.latestInstalled, assertVersion.version)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.ok(assertVersion.log)
assert.ok(stubWrite.callCount >= 4)
assert.strictEqual(assertVersion.installed, true)
@ -635,18 +667,18 @@ t.timeout(250).describe('#update()', function() {
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)
ctx.db.upsert(ctx.db.data.core.testapp.versions, assertVersion)
provider.getLatestVersion.resolves({ version: assertVersion.version, link: assertNewLink, filename: assertNewFilename })
assert.strictEqual(db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
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(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(assertVersion.stable, assertStable)
assert.ok(assertVersion.log)
assert.ok(assertVersion.log.startsWith(oldLog))
@ -664,16 +696,16 @@ t.timeout(250).describe('#update()', function() {
const assertVersion = { version: '999.888.777.666', filename: 'test.7z' }
provider.getLatestVersion.resolves(assertVersion)
provider.downloadVersion.rejects(assertError)
db.data.core.testapp.updater = ''
db.data.core.testapp.latestInstalled = assertVersion.version
ctx.db.data.core.testapp.updater = ''
ctx.db.data.core.testapp.latestInstalled = assertVersion.version
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)
assert.match(ctx.db.data.core.testapp.updater, /already/i)
assert.match(ctx.db.data.core.testapp.updater, /nothing/i)
})
t.test('should do nothing if installed version is found', async function() {
@ -681,20 +713,20 @@ t.timeout(250).describe('#update()', function() {
const assertVersion = { version: '999.888.777.666', filename: 'test.7z' }
provider.getLatestVersion.resolves(assertVersion)
provider.downloadVersion.rejects(assertError)
db.data.core.testapp.updater = ''
ctx.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 })
ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '111.111.111.111', installed: true })
ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '222.222.222.222', installed: true })
ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', installed: true })
ctx.db.upsert(ctx.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)
assert.match(db.data.core.testapp.updater, /nothing/i)
assert.match(ctx.db.data.core.testapp.updater, /already/i)
assert.match(ctx.db.data.core.testapp.updater, /nothing/i)
})
t.test('should do nothing it exists and failtodownload is higher than 3', async function() {
@ -702,17 +734,17 @@ t.timeout(250).describe('#update()', function() {
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: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtodownload: 4 })
ctx.db.data.core.testapp.updater = ''
ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtodownload: 4 })
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)
assert.match(db.data.core.testapp.updater, /fail/i)
assert.match(db.data.core.testapp.updater, /skip/i)
assert.match(ctx.db.data.core.testapp.updater, /many/i)
assert.match(ctx.db.data.core.testapp.updater, /fail/i)
assert.match(ctx.db.data.core.testapp.updater, /skip/i)
})
t.test('should do nothing it exists and failtoinstall is higher than 3', async function() {
@ -720,31 +752,32 @@ t.timeout(250).describe('#update()', function() {
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: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtoinstall: 4 })
ctx.db.data.core.testapp.updater = ''
ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtoinstall: 4 })
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)
assert.match(db.data.core.testapp.updater, /fail/i)
assert.match(db.data.core.testapp.updater, /skip/i)
assert.match(ctx.db.data.core.testapp.updater, /many/i)
assert.match(ctx.db.data.core.testapp.updater, /fail/i)
assert.match(ctx.db.data.core.testapp.updater, /skip/i)
})
})
t.timeout(250).describe('#registerModule()', function() {
const assertAppName = 'testappregister'
let db
let ctx
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)
})
return createFakeContext()
.then(function(res) {
ctx = res
let provider = new StaticProvider()
app = new Application(ctx, provider, assertAppName)
})
})
t.test('should fail if not an object with start', function() {

365
test/core.test.mjs Normal file
View File

@ -0,0 +1,365 @@
import { Eltro as t, assert, stub } from 'eltro'
import fs from 'fs/promises'
import Core from '../core/core.mjs'
import Util from '../core/util.mjs'
import { createFakeLog } from './helpers.mjs'
import StaticProvider from '../core/providers/static.mjs'
import lowdb from '../core/db.mjs'
const util = new Util(import.meta.url)
const log = createFakeLog()
let db
t.before(function() {
return lowdb({}, log, null).then(function(res) {
db = res
})
})
t.describe('Core.addProvider()', function() {
t.beforeEach(function() {
Core.providers.clear()
})
t.after(function() {
Core.providers.clear()
})
t.test('should fail if name is not a string', function() {
let tests = [
[1, 'number'],
[0, 'false number'],
['', 'false string'],
[[], 'array'],
[{}, 'object'],
]
tests.forEach(function(check) {
assert.throws(function() {
Core.addProvider(check[0], StaticProvider)
}, function(err) {
assert.match(err.message, /name/i)
assert.match(err.message, /string/i)
return true
}, `throw if name is ${check[1]}`)
})
})
t.test('should fail if provider not a function', function() {
let tests = [
[1, 'number'],
[0, 'false number'],
['asdf', 'string'],
['', 'false string'],
[[], 'array'],
[{}, 'object'],
]
tests.forEach(function(check) {
assert.throws(function() {
Core.addProvider('insertname', check[0])
}, function(err) {
assert.match(err.message, /provider/i)
assert.match(err.message, /class/i)
assert.match(err.message, /insertname/i)
return true
}, `throw if provider is ${check[1]}`)
})
})
t.test('should fail if provider instance is missing checkConfig', function() {
let tests = [
[1, 'number'],
[0, 'false number'],
['asdf', 'string'],
['', 'false string'],
[[], 'array'],
[{}, 'object'],
]
tests.forEach(function(check) {
assert.throws(function() {
let provider = function() { this.getLatestVersion = function() {}; this.checkConfig = check[0] }
Core.addProvider('somename', provider)
}, function(err) {
assert.match(err.message, /provider/i)
assert.match(err.message, /class/i)
assert.match(err.message, /missing/i)
assert.match(err.message, /checkConfig/i)
assert.match(err.message, /somename/i)
return true
}, `throw if provider checkConfig is ${check[1]}`)
})
})
t.test('should fail if provider instance is missing getLatestVersion', function() {
let tests = [
[1, 'number'],
[0, 'false number'],
['asdf', 'string'],
['', 'false string'],
[[], 'array'],
[{}, 'object'],
]
tests.forEach(function(check) {
assert.throws(function() {
let provider = function() { this.checkConfig = function() {}; this.getLatestVersion = check[0] }
Core.addProvider('somename', provider)
}, function(err) {
assert.match(err.message, /provider/i)
assert.match(err.message, /class/i)
assert.match(err.message, /missing/i)
assert.match(err.message, /getLatestVersion/i)
assert.match(err.message, /somename/i)
return true
}, `throw if provider getLatestVersion is ${check[1]}`)
})
})
t.test('should otherwise add provider to map', function() {
assert.strictEqual(Core.providers.size, 0)
Core.addProvider('testnamehere', StaticProvider)
assert.strictEqual(Core.providers.size, 1)
assert.strictEqual(Core.providers.get('testnamehere'), StaticProvider)
})
})
t.describe('#constructor()', function() {
t.test('should throw if close is not a function', function() {
let tests = [
[1, 'number'],
[0, 'false number'],
['asdf', 'string'],
['', 'false string'],
[[], 'array'],
[{}, 'object'],
]
tests.forEach(function(check) {
assert.throws(function() {
new Core(db, util, log, check[0])
}, function(err) {
assert.match(err.message, /restart/i)
assert.match(err.message, /function/i)
return true
}, `throw if restart is ${check[1]}`)
})
})
t.test('should throw if util is not util', function() {
let tests = [
[1, 'number'],
[0, 'false number'],
['asdf', 'string'],
['', 'false string'],
[[], 'array'],
[{}, 'object'],
[Util, 'not instance'],
]
tests.forEach(function(check) {
assert.throws(function() {
new Core(db, check[0], log, function() {})
}, function(err) {
assert.match(err.message, /util/i)
assert.match(err.message, /instance/i)
return true
}, `throw if util is ${check[1]}`)
})
})
t.test('should throw if db is not lowdb', function() {
let tests = [
[1, 'number'],
[0, 'false number'],
['asdf', 'string'],
['', 'false string'],
[[], 'array'],
[{}, 'object'],
[lowdb.Low, 'not instance'],
]
tests.forEach(function(check) {
assert.throws(function() {
new Core(check[0], util, log, function() {})
}, function(err) {
assert.match(err.message, /db/i)
assert.match(err.message, /instance/i)
return true
}, `throw if db is ${check[1]}`)
})
})
t.test('should throw if log is not an object with event', function() {
let func = function() {}
let validEvent = { info: func, warn: func, error: func }
let tests = [
[1, 'number'],
[0, 'false number'],
[null, 'null'],
[undefined, 'undefined'],
['asdf', 'string'],
['', 'false string'],
[[], 'array'],
[{}, 'object'],
[{warn: func, event: validEvent }, 'log only warn'],
[{error: func, event: validEvent }, 'log only error'],
[{info: func, event: validEvent }, 'log only info'],
[{warn: func, error: func, event: validEvent }, 'log only warn and error'],
[{warn: func, info: func, event: validEvent }, 'log only warn and info'],
[{error: func, info: func, event: validEvent }, 'log only error and info'],
[{ warn: func, error: func, info: func, event: { warn: func } }, 'event only warn'],
[{ warn: func, error: func, info: func, event: { error: func } }, 'event only error'],
[{ warn: func, error: func, info: func, event: { info: func } }, 'event only info'],
[{ warn: func, error: func, info: func, event: { warn: func, error: func } }, 'event only warn and error'],
[{ warn: func, error: func, info: func, event: { warn: func, info: func } }, 'event only warn and info'],
[{ warn: func, error: func, info: func, event: { error: func, info: func } }, 'event only error and info'],
]
tests.forEach(function(check) {
assert.throws(function() {
new Core(db, util, check[0], func)
}, function(err) {
assert.match(err.message, /log/i)
assert.match(err.message, /valid/i)
return true
}, `throw if log is ${check[1]}`)
})
})
t.test('should accept log, util and close function', function() {
const assertLog = log
const assertClose = function() {}
let core = new Core(db, util, assertLog, assertClose)
assert.strictEqual(core.db, db)
assert.strictEqual(core.util, util)
assert.strictEqual(core.log, assertLog)
assert.strictEqual(core.restart, assertClose)
assert.deepStrictEqual(core.applications, [])
assert.ok(core.applicationMap)
})
})
t.describe('#getApplication()', function() {
t.test('should return application based on the name', function() {
const assertName = 'Yami no Naka'
const assertApplication = { a: 1 }
let core = new Core(db, util, log, function() {})
core.applicationMap.set(assertName, assertApplication)
assert.strictEqual(core.getApplication(assertName), assertApplication)
})
})
t.describe('#init()', function() {
const assertProviderName = 'Kyousuu Gakku Gogyou Kikan'
let core
let fakeUtil
let fakeProvider
let fakeProviderConfig
function FakeProvider(config) {
fakeProvider(config)
this.static = true
this.checkConfig = fakeProviderConfig
}
t.beforeEach(function() {
core = new Core(db, util, createFakeLog(), function() {})
core.util = fakeUtil = {
verifyConfig: stub(),
getAppNames: stub().returns([]),
}
fakeProvider = stub()
fakeProviderConfig = stub()
Core.providers.set(assertProviderName, FakeProvider)
})
t.test('it should call util.verifyConfig correctly', async function() {
const assertError = new Error('Red faction IO drive mix')
const assertConfig = { a: 1 }
db.config = assertConfig
fakeUtil.verifyConfig.throws(assertError)
let err = await assert.isRejected(core.init())
assert.strictEqual(err, assertError)
assert.strictEqual(fakeUtil.verifyConfig.firstCall[0], assertConfig)
})
t.test('should call util.getNames correctly', async function() {
const assertError = new Error('Hero within')
const assertConfig = { a: 1 }
db.config = assertConfig
fakeUtil.getAppNames.throws(assertError)
let err = await assert.isRejected(core.init())
assert.strictEqual(err, assertError)
assert.strictEqual(fakeUtil.getAppNames.firstCall[0], assertConfig)
})
t.test('should call provider constructor correctly', async function() {
const assertError = new Error('Funny days')
const assertAppName = 'Tsuugakuro'
const assertConfig = {
[assertAppName]: {
provider: assertProviderName,
}
}
db.config = assertConfig
fakeProvider.throws(assertError)
fakeUtil.getAppNames.returns([assertAppName])
let err = await assert.isRejected(core.init())
assert.strictEqual(err, assertError)
assert.strictEqual(fakeProvider.firstCall[0], assertConfig[assertAppName])
})
t.test('should call provider checkConfig correctly', async function() {
const assertAppName = 'Zetsubou'
const assertConfig = {
[assertAppName]: {
provider: assertProviderName,
}
}
db.config = assertConfig
const assertError = new Error('Shousou')
fakeProviderConfig.rejects(assertError)
fakeUtil.getAppNames.returns([assertAppName])
let err = await assert.isRejected(core.init())
assert.strictEqual(err, assertError)
assert.strictEqual(fakeProviderConfig.firstCall[0], assertConfig[assertAppName])
})
t.test('should create an application with the provider and name and config', async function() {
const assertAppName = 'Yasashii Ketsumatsu'
const assertTestString = 'Serozore no Omoi'
const assertConfig = {
[assertAppName]: {
provider: assertProviderName,
teststring: assertTestString,
}
}
db.config = assertConfig
fakeUtil.getAppNames.returns([assertAppName])
assert.strictEqual(core.applications.length, 0)
await core.init()
let application = core.getApplication(assertAppName)
assert.ok(application)
assert.strictEqual(core.applications.length, 1)
assert.strictEqual(core.applications[0], application)
assert.strictEqual(application.name, assertAppName)
assert.strictEqual(application.ctx.db, core.db)
assert.strictEqual(application.ctx.util, core.util)
assert.strictEqual(application.ctx.log, core.log)
assert.strictEqual(application.ctx.core, core)
assert.strictEqual(application.config.teststring, assertTestString)
assert.ok(application.fresh)
assert.ok(application.provider instanceof FakeProvider)
})
})

View File

@ -118,9 +118,9 @@ t.test('Should support adding an application with defaults', async function() {
assert.ok(db.data.core.app)
assert.ok(db.data.core.app.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.strictEqual(db.data.core.app.active, '')
assert.strictEqual(db.data.core.app.latestInstalled, '')
assert.strictEqual(db.data.core.app.latestVersion, '')
assert.notOk(db.data.core.herpderp)
@ -128,9 +128,9 @@ t.test('Should support adding an application with defaults', async function() {
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)
assert.strictEqual(db.data.core.herpderp.active, '')
assert.strictEqual(db.data.core.herpderp.latestInstalled, '')
assert.strictEqual(db.data.core.herpderp.latestVersion, '')
})
t.test('Should support reading from db', async function() {

View File

@ -1,4 +1,4 @@
export function start(db, log, core, http, port) {
export function start(http, port, ctx) {
const server = http.createServer(function (req, res) {
res.writeHead(200);
res.end(JSON.stringify({ version: 'exampleindex' }))
@ -9,8 +9,8 @@ export function start(db, log, core, http, port) {
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}`)
ctx.log.event.info(`Server is listening on ${port} serving exampleindex`)
ctx.log.info(`Server is listening on ${port} serving exampleindex`)
res()
})
})

29
test/helpers.mjs Normal file
View File

@ -0,0 +1,29 @@
import { stub } from 'eltro'
import lowdb from '../core/db.mjs'
import Util from '../core/util.mjs'
export function createFakeLog() {
return {
info: stub(),
warn: stub(),
error: stub(),
event: {
info: stub(),
warn: stub(),
error: stub(),
}
}
}
export function createFakeContext(config = { }, util = new Util(import.meta.url), filename = null) {
const log = createFakeLog()
return lowdb(config, log, filename).then(function(res) {
return {
db: res,
util: util,
log: log,
core: { },
}
})
}

View File

@ -1,6 +1,7 @@
import { Eltro as t, assert, stub } from 'eltro'
import http from 'http'
import https from 'https'
import { setTimeout } from 'timers/promises'
import { request } from '../core/client.mjs'
import HttpServer from '../core/http.mjs'
@ -22,9 +23,7 @@ t.describe('Sockets', function() {
let http = new HttpServer()
t.after(function() {
http.closeServer().then(function() { }, function(err) {
console.error(err)
})
return http.closeServer()
})
t.test('should keep track of sockets through its lifetime', function(cb) {
@ -41,7 +40,7 @@ t.describe('Sockets', function() {
Promise.resolve()
.then(async function() {
await new Promise(function(res, rej) {
server.listen(port, function(err) { if (err) rej(err); res()})
server.listen(port, function() { res()})
})
assert.strictEqual(actives.length, 0)
@ -50,16 +49,18 @@ t.describe('Sockets', function() {
request({}, prefix).then(function() {}, cb)
request({}, prefix).then(async function() {
while (http.sockets.size > 0) {
await new Promise(function(res) { setTimeout(res, 10) })
await setTimeout(10)
}
assert.strictEqual(http.sockets.size, 0)
cb()
}, cb)
while (actives.length < 2) {
await new Promise(function(res) { setTimeout(res, 10) })
await setTimeout(10)
}
assert.strictEqual(http.sockets.size, 2)
assert.ok(http.active)
actives[0].statusCode = 200
actives[0].end('{}')
actives[1].statusCode = 200
@ -68,13 +69,17 @@ t.describe('Sockets', function() {
})
})
t.describe('Close', function() {
t.describe('closeServer()', function() {
let http = new HttpServer()
t.after(function() {
http.closeServer().then(function() { }, function(err) {
console.error(err)
})
return http.closeServer()
})
t.test('should not fail if server is not listening', function() {
http.createServer(function() { })
return http.closeServer()
})
t.test('should support forcefully closing them on server close', function(cb) {
@ -90,7 +95,7 @@ t.describe('Close', function() {
Promise.resolve()
.then(async function() {
await new Promise(function(res, rej) {
server.listen(port, function(err) { if (err) rej(err); res()})
server.listen(port, function() { res()})
})
assert.strictEqual(http.sockets.size, 0)
@ -105,13 +110,15 @@ t.describe('Close', function() {
)
while (http.sockets.size < 2) {
await new Promise(function(res) { setTimeout(res, 10) })
await setTimeout(10)
}
assert.ok(http.active)
http.closeServer().then(function() { }, cb)
while (requestErrors.length < 2) {
await new Promise(function(res) { setTimeout(res, 10) })
await setTimeout(10)
}
assert.strictEqual(http.sockets.size, 0)
assert.strictEqual(requestErrors.length, 2)
@ -122,12 +129,39 @@ t.describe('Close', function() {
assert.strictEqual(requestErrors[1].code, 'ECONNRESET')
while (requestErrors.length < 2) {
await new Promise(function(res) { setTimeout(res, 10) })
await setTimeout(10)
}
while (http.active) {
await new Promise(function(res) { setTimeout(res, 10) })
await setTimeout(10)
}
})
.then(function() { cb()}, cb)
})
})
t.describe('listenAsync()', function() {
let httpFirst = new HttpServer()
let httpSecond = new HttpServer()
t.after(function() {
return Promise.all([
httpFirst.closeServer(),
httpSecond.closeServer(),
])
})
t.test('should reject successfully if port is busy', async function() {
let serverFirst = httpFirst.createServer(function() { })
let serverSecond = httpSecond.createServer(function() { })
await serverFirst.listenAsync(port)
await setTimeout(10)
let err = await assert.isRejected(serverSecond.listenAsync(port))
assert.strictEqual(err.code, 'EADDRINUSE')
assert.ok(serverFirst.listening)
assert.notOk(serverSecond.listening)
})
})

286
test/log.test.mjs Normal file
View File

@ -0,0 +1,286 @@
import { Eltro as t, assert, stub } from 'eltro'
import getLog from '../core/log.mjs'
t.describe('#constructor', function() {
t.afterEach(function() {
process.env.NODE_ENV = null
})
t.test('should add name', function() {
const assertName = 'Stray Cat'
let logger = getLog(assertName)
assert.strictEqual(logger.fields.name, assertName)
process.env.NODE_ENV = 'production'
logger = getLog(assertName)
assert.strictEqual(logger.fields.name, assertName)
})
t.test('should add default stdout streams in normal environment', function() {
let logger = getLog('app', null)
assert.strictEqual(logger.streams.length, 4)
assert.strictEqual(logger.streams[0].stream, process.stdout)
assert.strictEqual(logger.streams[0].level, 20)
})
t.test('should add default file log stream in production environment', function() {
process.env.NODE_ENV = 'production'
let logger = getLog('app', null)
assert.strictEqual(logger.streams.length, 4)
assert.strictEqual(logger.streams[0].path, 'log.log')
assert.strictEqual(logger.streams[0].level, 30)
})
t.test('should not add default stream if empty array', function() {
let logger = getLog('app', [])
assert.strictEqual(logger.streams.length, 3)
process.env.NODE_ENV = 'production'
logger = getLog('app', [])
assert.strictEqual(logger.streams.length, 3)
})
t.test('should replace process.stdout with actual process', function() {
let logger = getLog('app', [{ stream: 'process.stdout', level: 'info' }])
assert.strictEqual(logger.streams.length, 4)
assert.strictEqual(logger.streams[0].stream, process.stdout)
process.env.NODE_ENV = 'production'
logger = getLog('app', [{ stream: 'process.stdout', level: 'info' }])
assert.strictEqual(logger.streams.length, 4)
assert.strictEqual(logger.streams[0].stream, process.stdout)
})
})
t.describe('ringbuffer', function() {
let logger
t.beforeEach(function() {
logger = getLog('app', [])
})
t.test('should have ringbuffer for info', function() {
const assertMessage = 'Oitachi'
assert.strictEqual(logger.ringbuffer.records.length, 0)
logger.info(assertMessage)
assert.strictEqual(logger.ringbuffer.records.length, 1)
assert.strictEqual(logger.ringbuffer.records[0].level, 30)
assert.strictEqual(logger.ringbuffer.records[0].msg, assertMessage)
logger.debug(assertMessage)
assert.strictEqual(logger.ringbuffer.records.length, 1)
logger.warn(assertMessage)
assert.strictEqual(logger.ringbuffer.records.length, 2)
assert.strictEqual(logger.ringbuffer.records[1].level, 40)
assert.strictEqual(logger.ringbuffer.records[1].msg, assertMessage)
})
t.test('should keep it limited to max 100 records', function() {
const assertPrefix = 'In memory of Keiten'
for (let i = 1; i <= 101; i++) {
logger.info(assertPrefix + i)
}
assert.strictEqual(logger.ringbuffer.records.length, 100)
assert.strictEqual(logger.ringbuffer.records[0].msg, assertPrefix + '2')
logger.info(assertPrefix)
assert.strictEqual(logger.ringbuffer.records.length, 100)
assert.strictEqual(logger.ringbuffer.records[0].msg, assertPrefix + '3')
})
})
t.describe('ringbufferwarn', function() {
let logger
t.beforeEach(function() {
logger = getLog('app', [])
})
t.test('should have ringbufferwarn for info', function() {
const assertMessage = 'Oitachi'
assert.strictEqual(logger.ringbufferwarn.records.length, 0)
logger.warn(assertMessage)
assert.strictEqual(logger.ringbufferwarn.records.length, 1)
assert.strictEqual(logger.ringbufferwarn.records[0].level, 40)
assert.strictEqual(logger.ringbufferwarn.records[0].msg, assertMessage)
logger.info(assertMessage)
assert.strictEqual(logger.ringbufferwarn.records.length, 1)
logger.error(assertMessage)
assert.strictEqual(logger.ringbufferwarn.records.length, 2)
assert.strictEqual(logger.ringbufferwarn.records[1].level, 50)
assert.strictEqual(logger.ringbufferwarn.records[1].msg, assertMessage)
})
t.test('should keep it limited to max 100 records', function() {
const assertPrefix = 'In memory of Keiten'
for (let i = 1; i <= 101; i++) {
logger.warn(assertPrefix + i)
}
assert.strictEqual(logger.ringbufferwarn.records.length, 100)
assert.strictEqual(logger.ringbufferwarn.records[0].msg, assertPrefix + '2')
logger.warn(assertPrefix)
assert.strictEqual(logger.ringbufferwarn.records.length, 100)
assert.strictEqual(logger.ringbufferwarn.records[0].msg, assertPrefix + '3')
})
})
t.describe('event', function() {
t.test('should call import if not in production', async function() {
let stubImport = stub()
stubImport.rejects(new Error('should not be seen'))
let logger = getLog('app', [], { import: stubImport })
let first = new Promise(function(res, rej) {
setImmediate(function() { logger.event.warn('text message here').then(res, rej) })
})
let second = new Promise(function(res, rej) {
setImmediate(function() { logger.event.warn('new message here').then(res, rej) })
})
await Promise.all([
first, second,
])
assert.notOk(stubImport.called)
})
t.test('should call import correctly if in production and fail only once', async function() {
process.env.NODE_ENV = 'production'
let stubImport = stub()
stubImport.rejects(new Error('should not be seen'))
let logger = getLog('app', [], { import: stubImport })
let first = new Promise(function(res, rej) {
setImmediate(function() { try { logger.event.warn('first').then(res, rej) } catch (err) { rej(err) } })
})
let second = new Promise(function(res, rej) {
setImmediate(function() { try { logger.event.warn('second').then(res, rej) } catch (err) { rej(err) } })
})
await Promise.all([
first, second,
])
assert.ok(stubImport.called)
assert.strictEqual(stubImport.callCount, 1)
assert.strictEqual(stubImport.firstCall[0], 'node-windows')
await new Promise(function(res, rej) {
setImmediate(function() { try { logger.event.warn('third').then(res, rej) } catch (err) { rej(err) } })
})
})
t.test('should call event on imported object correctly', async function() {
const assertName = 'It is going to be The Special'
let checkName = ''
let stubInfo = stub().returnWith(function(msg, code, cb) { setTimeout(cb, 20) })
let stubWarn = stub().returnWith(function(msg, code, cb) { setTimeout(cb, 20) })
let stubError = stub().returnWith(function(msg, code, cb) { setTimeout(cb, 20) })
process.env.NODE_ENV = 'production'
let stubImport = stub().resolves({
default: {
EventLogger: function(name) {
checkName = name
this.info = stubInfo
this.warn = stubWarn
this.error = stubError
},
},
})
let logger = getLog(assertName, [], { import: stubImport })
let first = new Promise(function(res, rej) {
setImmediate(function() { try { logger.event.info('first', 1010).then(res, rej) } catch (err) { rej(err) } })
})
let second = new Promise(function(res, rej) {
setImmediate(function() { try { logger.event.warn('second', 1020).then(res, rej) } catch (err) { rej(err) } })
})
await Promise.all([
first, second,
])
assert.strictEqual(checkName, assertName)
assert.ok(stubInfo.called)
assert.strictEqual(stubInfo.firstCall[0], 'first')
assert.strictEqual(stubInfo.firstCall[1], 1010)
assert.ok(stubWarn.called)
assert.strictEqual(stubWarn.firstCall[0], 'second')
assert.strictEqual(stubWarn.firstCall[1], 1020)
assert.notOk(stubError.called)
await new Promise(function(res, rej) {
setImmediate(function() { try { logger.event.error('third', 1030).then(res, rej) } catch (err) { rej(err) } })
})
assert.ok(stubError.called)
assert.strictEqual(stubError.firstCall[0], 'third')
assert.strictEqual(stubError.firstCall[1], 1030)
})
t.test('should work even if it were to throw', async function() {
const assertName = 'It is going to be The Special'
let checkName = ''
let stubInfo = stub().returnWith(function() { throw new Error('not to be seen') })
let stubWarn = stub().returnWith(function() { throw new Error('not to be seen') })
let stubError = stub().returnWith(function() { throw new Error('not to be seen') })
process.env.NODE_ENV = 'production'
let stubImport = stub().resolves({
default: {
EventLogger: function(name) {
checkName = name
this.info = stubInfo
this.warn = stubWarn
this.error = stubError
},
},
})
let logger = getLog(assertName, [], { import: stubImport })
let first = new Promise(function(res, rej) {
setImmediate(function() { try { logger.event.info().then(res, rej) } catch (err) { rej(err) } })
})
let second = new Promise(function(res, rej) {
setImmediate(function() { try { logger.event.warn().then(res, rej) } catch (err) { rej(err) } })
})
await Promise.all([
first, second,
])
assert.strictEqual(checkName, assertName)
assert.ok(stubInfo.called)
assert.ok(stubWarn.called)
assert.notOk(stubError.called)
await new Promise(function(res, rej) {
setImmediate(function() { try { logger.event.error().then(res, rej) } catch (err) { rej(err) } })
})
assert.ok(stubError.called)
})
t.test('should work without stub', async function() {
let res = await import('node-windows').catch(function() {})
if (!res) { return }
process.env.NODE_ENV = 'production'
let logger = getLog('service-core-unit-test', [])
await logger.event.info('Hello from service-core log.event unit test')
})
})

View File

View File

@ -1,6 +1,7 @@
import { Eltro as t, assert} from 'eltro'
import fs from 'fs/promises'
import Util from '../core/util.mjs'
import { defaults } from '../core/defaults.mjs'
const isWindows = process.platform === 'win32'
@ -108,43 +109,162 @@ t.describe('#getApplications()', function() {
assert.deepStrictEqual(util.getAppNames({ app: { provider: 1234, port: 1234 } }), [])
})
function getBase(extra = {}) {
return defaults({ app: extra }, { app: { provider: 'asdf', 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: {} } }), [])
assert.deepStrictEqual(util.getAppNames(getBase()), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ https: null })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ https: false })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ https: true })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ https: 'asdf' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ https: '1234' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ https: 0 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ https: [] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ 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: {} } }), [])
assert.deepStrictEqual(util.getAppNames(getBase()), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ updateEvery: null })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ updateEvery: 5 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ updateEvery: 1000 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ updateEvery: 'asdf' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ updateEvery: '1234' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ updateEvery: 0 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ updateEvery: -1 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ updateEvery: -5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ updateEvery: [] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ 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.test('should fail to find if startWaitUntilFail is defined but not a valid number', function() {
assert.deepStrictEqual(util.getAppNames(getBase()), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: null })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: 5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: 15 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: 1000 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: 'asdf' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: '1234' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: 0 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: -5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: [] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ startWaitUntilFail: {} })), [])
})
t.test('should fail to find if heartbeatTimeout is defined but not a valid number', function() {
assert.deepStrictEqual(util.getAppNames(getBase()), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: null })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: 5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: 15 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: 1000 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: 'asdf' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: '1234' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: 0 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: -5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: [] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatTimeout: {} })), [])
})
t.test('should fail to find if heartbeatAttempts is defined but not a valid number', function() {
assert.deepStrictEqual(util.getAppNames(getBase()), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: null })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: 1 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: 15 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: 1000 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: 'asdf' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: '1234' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: 0 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: -5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: [] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttempts: {} })), [])
})
t.test('should fail to find if heartbeatAttemptsWait is defined but not a valid number', function() {
assert.deepStrictEqual(util.getAppNames(getBase()), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: null })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: 5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: 15 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: 1000 })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: 'asdf' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: '1234' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: 0 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: -5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: [] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatAttemptsWait: {} })), [])
})
t.test('should fail to find if heartbeatPath is defined but not a valid string', function() {
assert.deepStrictEqual(util.getAppNames(getBase()), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: null })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: 5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: 15 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: 1000 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: 'asdf' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: '1234' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: '/asdf' })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: '/1234' })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: 0 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: -5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: [] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ heartbeatPath: {} })), [])
})
t.test('should fail to find if log is defined but not an array', function() {
assert.deepStrictEqual(util.getAppNames(getBase()), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: null })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: 5 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: 'asdf' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: '1234' })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: 0 })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [] })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: {} })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: { length:1 } })), [])
})
t.test('should fail to find if log has an item but level and either stream or path ', function() {
assert.deepStrictEqual(util.getAppNames(getBase()), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: null })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [null] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [5] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [15] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [1000] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: ['asdf'] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: ['1234'] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: ['/asdf'] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: ['/1234'] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [0] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [-5] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [[]] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{}] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: null, path: 'log' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 5, path: 'log' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 0, path: 'log' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: [], path: 'log' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: {}, path: 'log' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: '', path: 'log' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'asdf', path: 'log' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'fatal', path: 'log' }] })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'error', path: 'log' }] })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'warn', path: 'log' }] })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', path: 'log' }] })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'debug', path: 'log' }] })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'trace', path: 'log' }] })), ['app'])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', path: '' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', path: null }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', path: 5 }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', path: 0 }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', path: [] }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', path: {} }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', stream: '' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', stream: null }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', stream: 5 }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', stream: 0 }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', stream: [] }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', stream: {} }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', stream: 'asdf' }] })), [])
assert.deepStrictEqual(util.getAppNames(getBase({ log: [{ level: 'info', stream: 'process.stdout' }] })), ['app'])
})
})
@ -227,6 +347,14 @@ t.describe('#verifyConfig()', function() {
t.describe('#extractFile()', function() {
var util = new Util(import.meta.url)
t.beforeEach(function() {
return Promise.all([
fs.rm('./test/testapp/example.tar', { force: true }),
fs.rm('./test/testapp/file1.txt', { force: true }),
fs.rm('./test/testapp/file2.txt', { force: true }),
])
})
t.afterEach(function() {
return Promise.all([
fs.rm('./test/testapp/example.tar', { force: true }),