service-core/test/core.test.mjs

1244 lines
40 KiB
JavaScript

import cluster from 'cluster'
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 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(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(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 throw if restart 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 accept log, util and close function', function() {
const assertLog = log
const assertRestarter = function() { }
let core = new Core(db, util, assertLog, assertRestarter)
assert.strictEqual(core.db, db)
assert.strictEqual(core.util, util)
assert.strictEqual(core.log, assertLog)
assert.strictEqual(core.restart, assertRestarter)
assert.deepStrictEqual(core.applications, [])
assert.ok(core.applicationMap)
assert.strictEqual(core.isSlave, false)
})
})
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)
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() {
log.error.reset()
core = new Core(db, util, log)
core.util = fakeUtil = {
verifyConfig: stub(),
getAppNames: stub().returns([]),
}
fakeProvider = stub()
fakeProviderConfig = stub()
Core.providers.set(assertProviderName, FakeProvider)
})
t.after(function() {
return Promise.all([
fs.rm('./log_test_1.log', { recursive: true, force: true }),
])
})
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.notStrictEqual(err, assertError)
assert.strictEqual(log.error.firstCall[0], assertError)
assert.match(err.message, /successful/i)
assert.match(err.message, /none/i)
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(log.error.firstCall[0], assertError)
assert.match(err.message, /successful/i)
assert.match(err.message, /none/i)
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.notStrictEqual(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)
})
t.test('should continue even if one application fails', async function() {
log.error.reset()
const assertProviderFailName = 'Knee Socks'
let stubFailCheckConfig = stub()
function FakeFailProvider(config) {
fakeProvider(config)
this.static = true
this.checkConfig = stubFailCheckConfig
}
Core.providers.set(assertProviderFailName, FakeFailProvider)
const assertError = new Error('Justice of light')
const assertPathLog = 'log_test_1.log'
const assertFirstAppName = 'Kaeshite'
const assertSecondAppName = 'Motteke'
const assertTestFirstString = 'Magia'
const assertTestSecondString = 'Snow Falling'
const assertConfig = {
[assertFirstAppName]: {
provider: assertProviderName,
teststring: assertTestFirstString,
log: [
{ path: assertPathLog, level: 'info' },
{ stream: 'process.stdout', level: 'warn' },
]
},
[assertSecondAppName]: {
provider: assertProviderFailName,
teststring: assertTestSecondString,
},
}
stubFailCheckConfig.throws(assertError)
db.config = assertConfig
fakeUtil.getAppNames.returns([assertSecondAppName, assertFirstAppName])
assert.strictEqual(core.applications.length, 0)
await core.init()
let application = core.getApplication(assertFirstAppName)
assert.ok(application)
assert.strictEqual(core.applications.length, 1)
assert.strictEqual(core.applications[0], application)
assert.strictEqual(application.name, assertFirstAppName)
assert.strictEqual(application.ctx.db, core.db)
assert.strictEqual(application.ctx.util, core.util)
assert.notStrictEqual(application.ctx.log, core.log) //
assert.strictEqual(application.ctx.core, core)
assert.strictEqual(application.config.teststring, assertTestFirstString)
assert.ok(application.fresh)
assert.ok(application.provider instanceof FakeProvider)
assert.ok(log.error.called)
assert.strictEqual(log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(assertProviderFailName))
assert.match(log.error.firstCall[1], new RegExp(assertSecondAppName))
assert.match(log.error.firstCall[1], new RegExp(assertError.message))
assert.ok(application.ctx.log.streams.length >= 4)
assert.strictEqual(application.ctx.log.streams[0].path, assertPathLog)
assert.strictEqual(application.ctx.log.streams[0].level, 30)
assert.strictEqual(application.ctx.log.streams[0].type, 'file')
assert.strictEqual(application.ctx.log.streams[1].stream, process.stdout)
assert.strictEqual(application.ctx.log.streams[1].level, 40)
assert.strictEqual(application.ctx.log.streams[1].type, 'stream')
})
t.test('should listen on cluster messages if one or more are cluster on', async function() {
const assertAppName1 = 'Dai Sagara Yoshiharu'
const assertAppName2 = 'Kuryo'
const assertCoreName = 'Ichuu'
const assertPayload1 = { a: 1 }
const assertPayload2 = { b: 2 }
const assertConfig = {
name: assertCoreName,
[assertAppName1]: {
provider: assertProviderName,
cluster: 2,
},
[assertAppName2]: {
provider: assertProviderName,
cluster: 1,
},
}
db.config = assertConfig
fakeUtil.getAppNames.returns([assertAppName1, assertAppName2])
assert.strictEqual(core.applications.length, 0)
await core.init()
core.log.emit = stub()
assert.strictEqual(core.applications.length, 2)
let app1 = core.getApplication(assertAppName1)
let app2 = core.getApplication(assertAppName2)
app1.ctx.log.emit = stub()
app2.ctx.log.emit = stub()
cluster.emit('message', null, {
apptarget: app1.name,
type: 'newlog',
payload: assertPayload1
})
assert.notOk(core.log.emit.called)
assert.ok(app1.ctx.log.emit.called)
assert.ok(app1.ctx.log.emit.firstCall[0], 'newlog')
assert.ok(app1.ctx.log.emit.firstCall[1], assertPayload1)
assert.notOk(app2.ctx.log.emit.called)
app1.ctx.log.emit.reset()
cluster.emit('message', null, {
apptarget: app2.name,
type: 'newlog',
payload: assertPayload2
})
assert.notOk(core.log.emit.called)
assert.notOk(app1.ctx.log.emit.called)
assert.ok(app2.ctx.log.emit.called)
assert.ok(app2.ctx.log.emit.firstCall[0], 'newlog')
assert.ok(app2.ctx.log.emit.firstCall[1], assertPayload2)
app2.ctx.log.emit.reset()
let tests = [
null,
undefined,
12412,
'asdfag',
{},
{ apptarget: 12421, type: 'newlog', payload: {}},
{ apptarget: {}, type: 'newlog', payload: {}},
{ apptarget: null, type: 'newlog', payload: {}},
{ type: 'newlog', payload: {}},
{ apptarget: app1.name, type: 12421, payload: {}},
{ apptarget: app1.name, type: {}, payload: {}},
{ apptarget: app1.name, type: null, payload: {}},
{ apptarget: app1.name, payload: {}},
{ apptarget: app1.name, type: 'newlog', payload: 12421},
{ apptarget: app1.name, type: 'newlog', payload: null},
{ apptarget: app1.name, type: 'newlog', payload: 'test'},
]
tests.forEach(function(test) {
cluster.emit('message', null, test)
assert.notOk(core.log.emit.called)
assert.notOk(app1.ctx.log.emit.called)
assert.notOk(app2.ctx.log.emit.called)
})
cluster.emit('message', null, {
apptarget: assertCoreName,
type: 'newlog',
payload: assertPayload1
})
assert.notOk(app1.ctx.log.emit.called)
assert.notOk(app2.ctx.log.emit.called)
assert.ok(core.log.emit.called)
assert.ok(core.log.emit.called)
assert.ok(core.log.emit.firstCall[0], 'newlog')
assert.ok(core.log.emit.firstCall[1], assertPayload1)
})
})
t.describe('#run()', function() {
let core
let testAppOneName
let testAppTwoName
let stubRunApplication
let stubOnOrOnce
let stubWrite
t.beforeEach(function() {
testAppOneName = 'Tenshi'
testAppTwoName = 'no CLOVER'
db.data.core = {
[testAppOneName]: {
versions: []
},
[testAppTwoName]: {
versions: []
},
}
core = new Core(db, util, log)
core.runApplication = stubRunApplication = stub().resolves()
db.write = stubWrite = stub().resolves()
log.info.reset()
log.warn.reset()
log.error.reset()
for (let name of [testAppOneName, testAppTwoName]) {
let onOrOnce = stub()
let app = {
name: name,
fresh: false,
on: onOrOnce,
once: onOrOnce,
ctx: {
log: {
info: stub(),
warn: stub(),
error: stub()
},
},
update: stub().resolves(),
startAutoupdater: stub(),
}
core.applicationMap.set(name, app)
core.applications.push(app)
}
})
t.test('should call update on all applications', async function() {
const assertFirstError = new Error('Manatsu')
const assertSecondError = new Error('no Photograph')
core.applicationMap.get(testAppOneName).update.rejects(assertFirstError)
core.applicationMap.get(testAppTwoName).update.rejects(assertSecondError)
await core.run()
assert.strictEqual(core.applications[0].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[0].ctx.log.error.firstCall[0], assertFirstError)
assert.match(core.applications[0].ctx.log.error.firstCall[1], /updat/)
assert.match(core.applications[0].ctx.log.error.firstCall[1], new RegExp(assertFirstError.message))
assert.strictEqual(core.applications[1].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[1].ctx.log.error.firstCall[0], assertSecondError)
assert.match(core.applications[1].ctx.log.error.firstCall[1], /updat/)
assert.match(core.applications[1].ctx.log.error.firstCall[1], new RegExp(assertSecondError.message))
})
t.test('app.on updated should be hooked eventually and should call runApplication', async function() {
assert.notOk(core.applicationMap.get(testAppOneName).once.called)
assert.notOk(core.applicationMap.get(testAppTwoName).once.called)
assert.notOk(core.applicationMap.get(testAppOneName).on.called)
assert.notOk(core.applicationMap.get(testAppTwoName).on.called)
const assertFirstError = new Error('Manatsu')
const assertSecondError = new Error('no Photograph')
core.applicationMap.get(testAppOneName).update.returnWith(function() {
assert.notOk(core.applicationMap.get(testAppOneName).once.called)
assert.notOk(core.applicationMap.get(testAppOneName).on.called)
return Promise.reject(assertFirstError)
})
core.applicationMap.get(testAppTwoName).update.returnWith(function() {
assert.notOk(core.applicationMap.get(testAppTwoName).once.called)
assert.notOk(core.applicationMap.get(testAppOneName).on.called)
return Promise.reject(assertSecondError)
})
core.runApplication.returnWith(function(app) {
assert.notOk(app.once.called)
assert.notOk(app.on.called)
return Promise.resolve()
})
await core.run()
assert.ok(core.applicationMap.get(testAppOneName).on.called)
assert.ok(core.applicationMap.get(testAppTwoName).on.called)
assert.strictEqual(core.applicationMap.get(testAppOneName).on.firstCall[0], 'updated')
assert.strictEqual(core.applicationMap.get(testAppTwoName).on.firstCall[0], 'updated')
assert.strictEqual(core.applications[0].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[0].ctx.log.error.firstCall[0], assertFirstError)
assert.match(core.applications[0].ctx.log.error.firstCall[1], /updat/)
assert.match(core.applications[0].ctx.log.error.firstCall[1], new RegExp(assertFirstError.message))
assert.strictEqual(core.applications[1].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[1].ctx.log.error.firstCall[0], assertSecondError)
assert.match(core.applications[1].ctx.log.error.firstCall[1], /updat/)
assert.match(core.applications[1].ctx.log.error.firstCall[1], new RegExp(assertSecondError.message))
assert.strictEqual(stubRunApplication.callCount, 2)
assert.strictEqual(stubRunApplication.getCallN(1)[0], core.applications[0])
assert.strictEqual(stubRunApplication.getCallN(2)[0], core.applications[1])
core.runApplication.resolves()
core.applications[0].on.firstCall[1]()
assert.strictEqual(stubRunApplication.callCount, 3)
assert.strictEqual(stubRunApplication.getCallN(3)[0], core.applications[0])
core.applications[0].on.firstCall[1]()
assert.strictEqual(stubRunApplication.callCount, 4)
assert.strictEqual(stubRunApplication.getCallN(4)[0], core.applications[0])
core.applications[1].on.firstCall[1]()
assert.strictEqual(stubRunApplication.callCount, 5)
assert.strictEqual(stubRunApplication.getCallN(5)[0], core.applications[1])
})
t.test('should call startAutoupdater on all applications', async function() {
stubRunApplication.rejects(new Error('not seen'))
assert.notOk(core.applications[0].startAutoupdater.called)
assert.notOk(core.applications[1].startAutoupdater.called)
await assert.isRejected(core.run())
assert.ok(core.applications[0].startAutoupdater.called)
assert.ok(core.applications[1].startAutoupdater.called)
})
t.test('should be safe to call multiple times', async function() {
await core.run()
assert.strictEqual(stubRunApplication.callCount, 2)
await core.run()
await core.run()
await core.run()
await core.run()
assert.strictEqual(stubRunApplication.callCount, 2)
})
t.test('should call runApplication on all applications', async function() {
const assertFirstError = new Error('Manatsu')
const assertSecondError = new Error('no Photograph')
const assertNoError = new Error('unseen')
stubRunApplication.onCallN(1).rejects(assertFirstError)
stubRunApplication.onCallN(2).rejects(assertSecondError)
stubRunApplication.onCallN(3).rejects(assertNoError)
await assert.isRejected(core.run())
assert.strictEqual(core.applications[0].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[0].ctx.log.error.firstCall[0], assertFirstError)
assert.match(core.applications[0].ctx.log.error.firstCall[1], /run/)
assert.match(core.applications[0].ctx.log.error.firstCall[1], new RegExp(assertFirstError.message))
assert.strictEqual(core.applications[1].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[1].ctx.log.error.firstCall[0], assertSecondError)
assert.match(core.applications[1].ctx.log.error.firstCall[1], /run/)
assert.match(core.applications[1].ctx.log.error.firstCall[1], new RegExp(assertSecondError.message))
})
t.test('should throw if all applications fail', async function() {
stubRunApplication.rejects(new Error('bla'))
let err = await assert.isRejected(core.run())
assert.match(err.message, /no/i)
assert.match(err.message, /application/i)
})
t.test('should not throw if at least one application is run', async function() {
stubRunApplication.onCallN(1).rejects(new Error('Scramble'))
await core.run()
assert.strictEqual(core.applications[0].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[1].ctx.log.error.callCount, 0)
})
t.test('should not throw if all applications are running', async function() {
await core.run()
assert.strictEqual(log.error.callCount, 0)
})
})
t.describe('#runApplication()', function() {
let core
let testApp
let testAppName
let stubWrite
t.beforeEach(function() {
testAppName = 'Precure'
db.data.core = {
[testAppName]: {
versions: []
}
}
core = new Core(db, util, log)
core.restart = stub()
db.write = stubWrite = stub().resolves()
log.info.reset()
log.warn.reset()
log.error.reset()
testApp = {
name: testAppName,
fresh: false,
ctx: {
log: {
info: stub(),
warn: stub(),
error: stub(),
},
},
closeServer: stub(),
runVersion: stub(),
}
core.applicationMap.set(testAppName, testApp)
})
t.test('Should always attempt to close application first', async function() {
const assertAppName = 'Pyramid Collapse'
const assertError = new Error('Pyramid Collapse')
const stubClose = stub().rejects(assertError)
db.data.core[assertAppName] = {
versions: [{installed: true, stable: 0}]
}
core.applicationMap.set(assertAppName, {
name: assertAppName,
closeServer: stubClose,
})
let err = await assert.isRejected(core.runApplication(core.applicationMap.get(assertAppName)))
assert.strictEqual(err, assertError)
})
t.test('should select first valid version', async function() {
const assertVersion = 'v50'
const assertError = new Error('jellyfish')
testApp.runVersion.rejects(assertError)
testApp.fresh = true
db.data.core[testAppName].versions.push({
id: assertVersion,
version: assertVersion + 'asdf',
installed: true,
stable: 0,
})
let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError)
assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertError)
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertVersion))
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertError.message))
assert.strict(testApp.runVersion.firstCall[0], assertVersion)
assert.match(err.message, /no/i)
assert.match(err.message, /found/i)
})
t.test('should skip versions that have not been installed or high error', async function() {
const assertError = new Error('Daikichi to Rin')
testApp.runVersion.rejects(assertError)
testApp.fresh = true
db.data.core[testAppName].versions.push({
id: '40',
version: 'v40',
installed: false,
stable: 0,
}, {
id: '41',
version: 'v41',
installed: true,
stable: -2,
}, {
id: '42',
version: 'v42',
installed: true,
stable: 0,
}, )
let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError)
assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(testApp.ctx.log.error.callCount, 1)
assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertError)
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp('42'))
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertError.message))
assert.strict(testApp.runVersion.firstCall[0], '42')
})
t.test('should attempt to run version with stable of -1 if fresh is true', async function() {
const assertError = new Error('Daikichi to Rin')
testApp.runVersion.returnWith(function() {
testApp.fresh = false
return Promise.reject(assertError)
})
testApp.fresh = true
db.data.core[testAppName].versions.push({
id: '30',
version: 'v30',
installed: false,
stable: 0,
}, {
id: '31',
version: 'v31',
installed: true,
stable: -1,
}, {
id: '32',
version: 'v32',
installed: true,
stable: 0,
}, )
await core.runApplication(testApp)
assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(testApp.ctx.log.error.callCount, 2)
assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertError)
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp('31'))
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertError.message))
assert.strict(testApp.runVersion.firstCall[0], '31')
assert.strict(testApp.runVersion.firstCall[0], '32')
assert.strictEqual(db.data.core[testAppName].versions[1].stable, -2)
assert.strictEqual(db.data.core[testAppName].versions[2].stable, -1)
})
t.test('should call restart if program crashes and fresh is false', async function() {
const assertErrorMessage = new Error('Daikichi to Rin')
const assertError = new Error('Country Lane')
testApp.runVersion.rejects(assertErrorMessage)
core.restart.rejects(assertError)
testApp.fresh = false
db.data.core[testAppName].versions.push({
id: '30',
version: 'v30',
installed: true,
stable: 0,
}, {
id: '31',
version: 'v31',
installed: true,
stable: -1,
}, {
id: '32',
version: 'v32',
installed: true,
stable: 0,
}, )
let err = await assert.isRejected(core.runApplication(testApp))
assert.strictEqual(err, assertError)
assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(testApp.ctx.log.error.callCount, 1)
assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertErrorMessage)
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp('30'))
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertErrorMessage.message))
assert.strictEqual(db.data.core[testAppName].versions[0].stable, -1)
assert.ok(core.restart.called)
assert.match(core.restart.firstCall[0], new RegExp(testAppName))
assert.match(core.restart.firstCall[0], /v30/)
assert.match(core.restart.firstCall[0], /dirty/)
})
t.test('should attempt next non-tested version if fresh is true', async function() {
const assertError = new Error('Daikichi to Rin')
testApp.runVersion.rejects(assertError)
testApp.fresh = true
db.data.core[testAppName].versions.push({
id: '30',
version: 'v30',
installed: true,
stable: 0,
}, {
id: '31',
version: 'v31',
installed: true,
stable: -1,
}, {
id: '32',
version: 'v32',
installed: true,
stable: -2,
})
let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError)
assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(testApp.ctx.log.error.callCount, 2)
assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertError)
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp('30'))
assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertError.message))
assert.match(testApp.ctx.log.error.secondCall[1], new RegExp('31'))
assert.match(testApp.ctx.log.error.secondCall[1], new RegExp(assertError.message))
assert.strictEqual(db.data.core[testAppName].versions[0].stable, -2)
assert.strictEqual(db.data.core[testAppName].versions[1].stable, -2)
assert.ok(stubWrite.callCount, 2)
})
t.test('should exit immediately if next version is -1 and fresh is false', async function() {
const assertError = new Error('Daikichi to Rin')
core.restart.rejects(assertError)
testApp.fresh = false
db.data.core[testAppName].versions.push({
id: '30',
version: 'v30',
installed: true,
stable: -1,
}, {
id: '31',
version: 'v31',
installed: true,
stable: 0,
}, {
id: '32',
version: 'v32',
installed: true,
stable: -2,
})
let err = await assert.isRejected(core.runApplication(testApp))
assert.strictEqual(err, assertError)
assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.warn.called)
assert.strictEqual(testApp.ctx.log.warn.callCount, 1)
assert.match(testApp.ctx.log.warn.firstCall[0], /restart/i)
assert.match(testApp.ctx.log.warn.firstCall[0], /fresh/i)
assert.match(testApp.ctx.log.warn.firstCall[0], /v30/i)
assert.match(core.restart.firstCall[0], /v30/i)
assert.match(core.restart.firstCall[0], /fresh/i)
assert.match(core.restart.firstCall[0], /-1/i)
assert.match(core.restart.firstCall[0], new RegExp(testAppName))
assert.strictEqual(db.data.core[testAppName].versions[0].stable, -1)
assert.strictEqual(db.data.core[testAppName].versions[1].stable, 0)
})
t.test('should stop on first stable and call core.restart if crash occurs', async function() {
const assertError = new Error('Daikichi to Rin')
testApp.runVersion.rejects(new Error('empty message'))
core.restart.rejects(assertError)
testApp.fresh = false
db.data.core[testAppName].versions.push({
id: '28',
version: 'v28',
installed: true,
stable: 5,
}, {
id: '29',
version: 'v29',
installed: true,
stable: 1,
}, {
id: '30',
version: 'v30',
installed: true,
stable: 0,
}, {
id: '31',
version: 'v31',
installed: true,
stable: -1,
})
let err = await assert.isRejected(core.runApplication(testApp))
assert.strictEqual(err, assertError)
assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(testApp.ctx.log.error.callCount, 1)
assert.strictEqual(db.data.core[testAppName].versions[0].stable, 5)
assert.strictEqual(stubWrite.callCount, 1)
assert.ok(core.restart.called)
assert.match(core.restart.firstCall[0], new RegExp(testAppName))
assert.match(core.restart.firstCall[0], /v28/)
assert.match(core.restart.firstCall[0], /stable/)
})
t.test('should throw if no stable version is found', async function() {
const assertError = new Error('Daikichi to Rin')
testApp.runVersion.rejects(assertError)
testApp.fresh = true
db.data.core[testAppName].versions.push({
id: '20',
version: 'v20',
installed: true,
stable: 0,
}, {
id: '21',
version: 'v21',
installed: true,
stable: 0,
}, {
id: '22',
version: 'v22',
installed: true,
stable: 0,
})
let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError)
assert.match(err.message, /found/)
})
t.test('should throw if no version are found', async function() {
const assertError = new Error('Daikichi to Rin')
testApp.runVersion.rejects(assertError)
testApp.fresh = true
db.data.core[testAppName].versions = []
let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError)
assert.match(err.message, /found/)
assert.notMatch(err.message, /stable/i)
})
t.test('should otherwise succeed if one is found', async function() {
const assertError = new Error('Daikichi to Rin')
let count = 0
testApp.runVersion.returnWith(function() {
if (count > 0) return Promise.reject(assertError)
count++
return Promise.resolve()
})
testApp.fresh = true
db.data.core[testAppName].versions.push({
id: '70',
version: 'v70',
installed: true,
stable: 100,
}, {
id: '71',
version: 'v71',
installed: true,
stable: 0,
}, {
id: '72',
version: 'v72',
installed: true,
stable: 0,
})
await core.runApplication(testApp)
assert.notOk(log.error.called)
assert.notOk(testApp.ctx.log.error.called)
assert.strictEqual(testApp.runVersion.callCount, 1)
assert.strictEqual(testApp.runVersion.firstCall[0], 'v70')
assert.strictEqual(db.data.core[testAppName].versions[0].stable, 1)
assert.strictEqual(db.data.core[testAppName].versions[1].stable, 0)
assert.strictEqual(db.data.core[testAppName].versions[2].stable, 0)
assert.ok(stubWrite.called)
})
t.test('should succeed if running a minus one on fresh', async function() {
const assertError = new Error('Daikichi to Rin')
let count = 0
testApp.fresh = true
testApp.runVersion.returnWith(function() {
if (count > 0) return Promise.reject(assertError)
count++
return Promise.resolve()
})
testApp.fresh = true
db.data.core[testAppName].versions.push({
id: '70',
version: 'v70',
installed: true,
stable: -1,
}, {
id: '71',
version: 'v71',
installed: true,
stable: 0,
}, {
id: '72',
version: 'v72',
installed: true,
stable: 0,
})
await core.runApplication(testApp)
assert.notOk(log.error.called)
assert.notOk(testApp.ctx.log.error.called)
assert.strictEqual(testApp.runVersion.callCount, 1)
assert.strictEqual(testApp.runVersion.firstCall[0], 'v70')
assert.strictEqual(db.data.core[testAppName].versions[0].stable, 1)
assert.strictEqual(db.data.core[testAppName].versions[1].stable, 0)
assert.strictEqual(db.data.core[testAppName].versions[2].stable, 0)
assert.ok(stubWrite.called)
})
})
t.describe('#criticalError()', function() {
let core
let testApp
let testAppName
let stubWrite
t.beforeEach(function() {
testAppName = 'nano.RIPE'
core = new Core(db, util, log)
db.writeSync = stubWrite = stub()
log.info.reset()
log.warn.reset()
log.error.reset()
testApp = {
name: testAppName,
fresh: false,
ctx: {
log: {
info: stub(),
warn: stub(),
error: stub(),
fatal: stub(),
},
},
closeServer: stub(),
runVersion: stub(),
}
})
t.test('should log to fatal', function() {
const assertVersion = {
version: 'Dai kirai! Aishiteru',
stable: 0,
}
assert.notOk(testApp.ctx.log.fatal.called)
core.criticalError(testApp, assertVersion)
assert.ok(testApp.ctx.log.fatal.called)
assert.match(testApp.ctx.log.fatal.firstCall[0], /critical/i)
assert.match(testApp.ctx.log.fatal.firstCall[0], new RegExp(assertVersion.version))
})
t.test('should always change to stable -2 regardless of fresh', function() {
const assertVersion = {
version: 'Dai kirai! Aishiteru',
stable: 0,
}
testApp.fresh = false
assertVersion.stable = 5
core.criticalError(testApp, assertVersion)
assert.strictEqual(assertVersion.stable, -2)
assertVersion.stable = -1
core.criticalError(testApp, assertVersion)
assert.strictEqual(assertVersion.stable, -2)
testApp.fresh = true
assertVersion.stable = 5
core.criticalError(testApp, assertVersion)
assert.strictEqual(assertVersion.stable, -2)
assertVersion.stable = -1
core.criticalError(testApp, assertVersion)
assert.strictEqual(assertVersion.stable, -2)
})
t.test('should call db.writeSync afterwards', function() {
let checkStable = 0
const assertVersion = {
version: 'Dai kirai! Aishiteru',
stable: 0,
}
stubWrite.returnWith(function() {
checkStable = assertVersion.stable
})
assert.notOk(stubWrite.called)
core.criticalError(testApp, assertVersion)
assert.ok(stubWrite.called)
assert.strictEqual(checkStable, -2)
})
})