service-core/test/core.test.mjs
Jonatan Nilsson 4f4bc8cf6a
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
More core development, full integration started
2022-02-11 13:59:10 +00:00

824 lines
26 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 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, log, 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.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 stubWrite
t.beforeEach(function() {
testAppOneName = 'Tenshi'
testAppTwoName = 'no CLOVER'
db.data.core = {
[testAppOneName]: {
versions: []
},
[testAppTwoName]: {
versions: []
},
}
core = new Core(db, util, log, function() {})
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 app = {
name: name,
fresh: false,
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(log.error.callCount, 2)
assert.strictEqual(log.error.firstCall[0], assertFirstError)
assert.match(log.error.firstCall[1], new RegExp(testAppOneName))
assert.match(log.error.firstCall[1], /updat/)
assert.match(log.error.firstCall[1], new RegExp(assertFirstError.message))
assert.strictEqual(log.error.secondCall[0], assertSecondError)
assert.match(log.error.secondCall[1], new RegExp(testAppTwoName))
assert.match(log.error.secondCall[1], /updat/)
assert.match(log.error.secondCall[1], new RegExp(assertSecondError.message))
})
t.test('should call startAutoupdater on all applications', async function() {
stubRunApplication.rejects(new Error('not seen'))
await assert.isRejected(core.run())
assert.ok(core.applications[0].startAutoupdater.called)
assert.ok(core.applications[1].startAutoupdater.called)
})
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(log.error.callCount, 2)
assert.strictEqual(stubRunApplication.firstCall[0], core.applications[0])
assert.strictEqual(stubRunApplication.secondCall[0], core.applications[1])
assert.strictEqual(log.error.firstCall[0], assertFirstError)
assert.match(log.error.firstCall[1], new RegExp(testAppOneName))
assert.match(log.error.firstCall[1], /run/)
assert.match(log.error.firstCall[1], new RegExp(assertFirstError.message))
assert.strictEqual(log.error.secondCall[0], assertSecondError)
assert.match(log.error.secondCall[1], new RegExp(testAppTwoName))
assert.match(log.error.secondCall[1], /run/)
assert.match(log.error.secondCall[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(log.error.callCount, 1)
})
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, function() {})
db.write = stubWrite = stub().resolves()
log.info.reset()
log.warn.reset()
log.error.reset()
testApp = {
name: testAppName,
fresh: false,
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)
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.ok(log.error.called)
assert.strictEqual(log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName))
assert.match(log.error.firstCall[1], new RegExp(assertVersion))
assert.match(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)
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.ok(log.error.called)
assert.strictEqual(log.error.callCount, 1)
assert.strictEqual(log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName))
assert.match(log.error.firstCall[1], new RegExp('42'))
assert.match(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.rejects(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,
}, )
let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError)
assert.ok(log.error.called)
assert.strictEqual(log.error.callCount, 2)
assert.strictEqual(log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName))
assert.match(log.error.firstCall[1], new RegExp('31'))
assert.match(log.error.firstCall[1], new RegExp(assertError.message))
assert.strict(testApp.runVersion.firstCall[0], '31')
assert.strict(testApp.runVersion.firstCall[0], '32')
})
t.test('should skip version with stable of -1 if fresh is false', async function() {
const assertError = new Error('Daikichi to Rin')
testApp.runVersion.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.notStrictEqual(err, assertError)
assert.ok(log.error.called)
assert.strictEqual(log.error.callCount, 2)
assert.strictEqual(log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName))
assert.match(log.error.firstCall[1], new RegExp('30'))
assert.match(log.error.firstCall[1], new RegExp(assertError.message))
assert.strictEqual(log.error.secondCall[0], assertError)
assert.match(log.error.secondCall[1], new RegExp(testAppName))
assert.match(log.error.secondCall[1], new RegExp('32'))
assert.match(log.error.secondCall[1], new RegExp(assertError.message))
assert.strict(testApp.runVersion.firstCall[0], '30')
assert.strict(testApp.runVersion.firstCall[0], '32')
})
t.test('should change status accordingly when application is fresh', 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.ok(log.error.called)
assert.strictEqual(log.error.callCount, 2)
assert.strictEqual(log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName))
assert.match(log.error.firstCall[1], new RegExp('30'))
assert.match(log.error.firstCall[1], new RegExp(assertError.message))
assert.match(log.error.secondCall[1], new RegExp(testAppName))
assert.match(log.error.secondCall[1], new RegExp('31'))
assert.match(log.error.secondCall[1], new RegExp(assertError.message))
assert.strictEqual(db.data.core[testAppName].versions[0].stable, -1)
assert.strictEqual(db.data.core[testAppName].versions[1].stable, -2)
assert.ok(stubWrite.callCount, 2)
})
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: 0,
}, {
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)
})
})