Jonatan Nilsson
47344c5e7a
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
Fixed some minor bugs. Will now no longer travel through history but instead stop at last stable version.
1142 lines
No EOL
37 KiB
JavaScript
1142 lines
No EOL
37 KiB
JavaScript
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)
|
|
})
|
|
})
|
|
|
|
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.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)
|
|
})
|
|
}) |