More core development, full integration started
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded

This commit is contained in:
Jonatan Nilsson 2022-02-11 13:59:10 +00:00
parent 3f8e3cdc16
commit 4f4bc8cf6a
11 changed files with 631 additions and 92 deletions

View file

@ -291,8 +291,12 @@ export default class Application extends EventEmitter {
await this.fs.stat(indexPath).catch((err) => { await this.fs.stat(indexPath).catch((err) => {
return Promise.reject(new Error(`Version was missing index.mjs: ${err.message}`)) return Promise.reject(new Error(`Version was missing index.mjs: ${err.message}`))
}) })
this.fresh = false
let module = await import(this.ctx.util.getUrlFromRoot(`./${this.name}/${version}/index.mjs`)) let module = await import(this.ctx.util.getUrlFromRoot(`./${this.name}/${version}/index.mjs`))
this.registerModule(module, version) this.registerModule(module, version)
} else {
this.fresh = false
} }
let errTimeout = new Error(`Version timed out (took over ${this.config.startWaitUntilFail}ms) while running start()`) let errTimeout = new Error(`Version timed out (took over ${this.config.startWaitUntilFail}ms) while running start()`)

View file

@ -1,6 +1,9 @@
import Application from './application.mjs' import Application from './application.mjs'
import Util from './util.mjs' import Util from './util.mjs'
import getLog from './log.mjs'
import { Low } from 'lowdb' import { Low } from 'lowdb'
import StaticProvider from './providers/static.mjs'
import GitProvider from './providers/git.mjs'
export default class Core { export default class Core {
static providers = new Map() static providers = new Map()
@ -46,10 +49,13 @@ export default class Core {
} }
async init() { async init() {
this.util.verifyConfig(this.db.config) this.log.info(`Verifying config`)
this.util.verifyConfig(this.db.config)
let names = this.util.getAppNames(this.db.config) let names = this.util.getAppNames(this.db.config)
this.log.info(`Found applications: ${names.join(', ')}.`)
let lastError = null let lastError = null
for (let name of names) { for (let name of names) {
@ -61,7 +67,7 @@ export default class Core {
let application = new Application({ let application = new Application({
db: this.db, db: this.db,
util: this.util, util: this.util,
log: this.log, log: getLog(name, this.db.config[name].log || null),
core: this, core: this,
}, provider, name) }, provider, name)
this.applications.push(application) this.applications.push(application)
@ -77,26 +83,65 @@ export default class Core {
} }
} }
async runApplication(name) { async run() {
let application = this.applicationMap.get(name) this.log.info(`Running updater on ${this.applications.length} apps`)
await Promise.all(this.applications.map((app) => {
if (!application) { return app.update().catch(err => {
return Promise.reject(new Error(`Core.runApplication was called on a non-existing application name ${name}`)) this.log.error(err, `Error updating ${app.name}: ${err.message}`)
} })
}))
let found = false let found = false
for (let app of this.applications) {
app.startAutoupdater()
await this.runApplication(app).then(
() => {
found = true
},
err => {
this.log.error(err, `Error running application ${app.name}: ${err.message}`)
}
)
}
if (!found) {
throw new Error('No stable application was found')
}
}
async runApplication(application) {
let name = application.name
let found = false
if (!this.db.data.core[name].versions.length) {
return Promise.reject(new Error(`No versions were found`))
}
for (let i = 0; i < this.db.data.core[name].versions.length; i++) { for (let i = 0; i < this.db.data.core[name].versions.length; i++) {
let version = this.db.data.core[name].versions[i] let version = this.db.data.core[name].versions[i]
if (!version.installed || version.stable < -1) continue
if (version.stable < 0 && !application.fresh) continue
await application.closeServer() await application.closeServer()
try { try {
await application.runVersion() this.log.info(`Attempting to run application ${name} version ${version.version}`)
await application.runVersion(version.version)
found = true
break
} catch(err) { } catch(err) {
this.log.error(err, `Error starting ${name} ${version.id}: ${err.message}`) version.stable--
await this.db.write()
this.log.error(err, `Error starting ${name} ${version.version}: ${err.message}`)
} }
} }
throw new Error(``) if (!found) {
return Promise.reject(Error(`No stable versions were found`))
} }
} }
}
Core.addProvider('static', StaticProvider)
Core.addProvider('git', GitProvider)

View file

@ -6,6 +6,8 @@
"scripts": { "scripts": {
"dev": "nodemon --watch dev/api --watch core --watch runner.mjs --watch db.mjs --watch log.mjs runner.mjs | bunyan", "dev": "nodemon --watch dev/api --watch core --watch runner.mjs --watch db.mjs --watch log.mjs runner.mjs | bunyan",
"test": "eltro \"test/**/*.test.mjs\" -r dot", "test": "eltro \"test/**/*.test.mjs\" -r dot",
"test:integration": "eltro \"test/**/*.test.integration.mjs\" -r list",
"test:test": "eltro \"test/application.test.integration.mjs\" -r list",
"test:spec": "eltro \"test/**/*.test.mjs\" -r list", "test:spec": "eltro \"test/**/*.test.mjs\" -r list",
"test:watch": "npm-watch test" "test:watch": "npm-watch test"
}, },

View file

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

View file

@ -48,7 +48,9 @@ t.describe('#runVersion("static")', function() {
assert.strictEqual(checkCtx.app, app) assert.strictEqual(checkCtx.app, app)
}) })
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('static')) let err = await assert.isRejected(app.runVersion('static'))
assert.strictEqual(app.fresh, false)
assert.match(err.message, /http/i) assert.match(err.message, /http/i)
assert.match(err.message, /createServer/i) assert.match(err.message, /createServer/i)
@ -60,7 +62,9 @@ t.describe('#runVersion("static")', function() {
app.config.startWaitUntilFail = 50 app.config.startWaitUntilFail = 50
app.registerModule(function() { return new Promise(function() {}) }) app.registerModule(function() { return new Promise(function() {}) })
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('static')) let err = await assert.isRejected(app.runVersion('static'))
assert.strictEqual(app.fresh, false)
assert.match(err.message, /time/i) assert.match(err.message, /time/i)
assert.match(err.message, /out/i) assert.match(err.message, /out/i)
@ -79,7 +83,9 @@ t.describe('#runVersion("static")', function() {
}) })
}) })
assert.strictEqual(app.fresh, true)
await app.runVersion('static') await app.runVersion('static')
assert.strictEqual(app.fresh, false)
assert.strictEqual(ctx.db.data.core.testapp.active, 'static') assert.strictEqual(ctx.db.data.core.testapp.active, 'static')
}) })
@ -94,7 +100,10 @@ t.describe('#runVersion("static")', function() {
app.config.heartbeatAttempts = 3 app.config.heartbeatAttempts = 3
app.registerModule(defaultHandler(handler)) app.registerModule(defaultHandler(handler))
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('static')) let err = await assert.isRejected(app.runVersion('static'))
assert.strictEqual(app.fresh, false)
assert.match(err.message, /failed/i) assert.match(err.message, /failed/i)
assert.match(err.message, /400/i) assert.match(err.message, /400/i)
assert.strictEqual(called, 3) assert.strictEqual(called, 3)
@ -110,9 +119,12 @@ t.describe('#runVersion("static")', function() {
app.config.heartbeatAttemptsWait = 30 app.config.heartbeatAttemptsWait = 30
app.registerModule(defaultHandler(handler)) app.registerModule(defaultHandler(handler))
assert.strictEqual(app.fresh, true)
let start = performance.now() let start = performance.now()
let err = await assert.isRejected(app.runVersion('static')) let err = await assert.isRejected(app.runVersion('static'))
let end = performance.now() let end = performance.now()
assert.strictEqual(app.fresh, false)
assert.match(err.message, /failed/i) assert.match(err.message, /failed/i)
assert.match(err.message, /time/i) assert.match(err.message, /time/i)
assert.match(err.message, /out/i) assert.match(err.message, /out/i)
@ -193,7 +205,10 @@ t.describe('#runVersion("version")', function() {
app.config.port = assertPort app.config.port = assertPort
stubFsStat.rejects(assertNotError) stubFsStat.rejects(assertNotError)
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('v100')) let err = await assert.isRejected(app.runVersion('v100'))
assert.strictEqual(app.fresh, true)
assert.notStrictEqual(err, assertNotError) assert.notStrictEqual(err, assertNotError)
assert.match(err.message, new RegExp(assertNotError.message)) assert.match(err.message, new RegExp(assertNotError.message))
assert.match(err.message, /index\.mjs/i) assert.match(err.message, /index\.mjs/i)
@ -210,7 +225,10 @@ t.describe('#runVersion("version")', function() {
await fs.mkdir(util.getPathFromRoot('./testnoexisting/v99'), { recursive: true }) await fs.mkdir(util.getPathFromRoot('./testnoexisting/v99'), { recursive: true })
await fs.writeFile(util.getPathFromRoot('./testnoexisting/v99/index.mjs'), `throw new Error('${assertError.message}')`) await fs.writeFile(util.getPathFromRoot('./testnoexisting/v99/index.mjs'), `throw new Error('${assertError.message}')`)
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('v99')) let err = await assert.isRejected(app.runVersion('v99'))
assert.strictEqual(app.fresh, false)
assert.notStrictEqual(err, assertError) assert.notStrictEqual(err, assertError)
assert.strictEqual(err.message, assertError.message) assert.strictEqual(err.message, assertError.message)
@ -223,7 +241,9 @@ t.describe('#runVersion("version")', function() {
await fs.mkdir(util.getPathFromRoot('./testnoexisting/v98'), { recursive: true }) await fs.mkdir(util.getPathFromRoot('./testnoexisting/v98'), { recursive: true })
await fs.writeFile(util.getPathFromRoot('./testnoexisting/v98/index.mjs'), ``) await fs.writeFile(util.getPathFromRoot('./testnoexisting/v98/index.mjs'), ``)
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('v98')) let err = await assert.isRejected(app.runVersion('v98'))
assert.strictEqual(app.fresh, false)
assert.match(err.message, /start/i) assert.match(err.message, /start/i)
assert.strictEqual(app.ctx.db.data.core.testnoexisting.active, 'v98') assert.strictEqual(app.ctx.db.data.core.testnoexisting.active, 'v98')
@ -241,7 +261,9 @@ t.describe('#runVersion("version")', function() {
app.ctx.log.info.reset() app.ctx.log.info.reset()
app.ctx.log.event.info.reset() app.ctx.log.event.info.reset()
assert.strictEqual(app.fresh, true)
await app.runVersion('v97') await app.runVersion('v97')
assert.strictEqual(app.fresh, false)
assert.ok(app.ctx.log.info.called) assert.ok(app.ctx.log.info.called)
assert.ok(app.ctx.log.event.info.called) assert.ok(app.ctx.log.event.info.called)

View file

@ -0,0 +1,50 @@
import { Eltro as t, assert, stub } from 'eltro'
import fs from 'fs/promises'
import Application from '../core/application.mjs'
import GitProvider from '../core/providers/git.mjs'
import Util from '../core/util.mjs'
import { createFakeContext } from './helpers.mjs'
const util = new Util(import.meta.url)
let ctx
let app
let provider
t.before(function() {
return createFakeContext({ }, util, util.getPathFromRoot('./db_test.json'))
.then(function(res) {
ctx = res
provider = new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/sc-helloworld/releases' })
app = new Application(ctx, provider, 'testapp')
return provider.getLatestVersion()
}).then(function(version) {
return fs.rm(`./test/testapp/${version.version}`, { force: true, recursive: true })
})
})
t.after(function() {
return fs.rm(util.getPathFromRoot('./db_test.json'))
.then(function() {
if (ctx.db.data.core.testapp.versions.length) {
return fs.rm(`./test/testapp/${ctx.db.data.core.testapp.versions[0].id}`, { force: true, recursive: true })
}
})
})
t.timeout(10000).test('should run update and install correctly', async function(){
try {
await app.update()
} catch (err) {
console.log(err)
if (ctx.db.data.core.testapp.versions.length) {
console.log(ctx.db.data.core.testapp.versions[0].log)
}
throw err
}
assert.ok(ctx.db.data.core.testapp.latestInstalled)
await fs.stat(util.getPathFromRoot(`./testapp/${ctx.db.data.core.testapp.latestInstalled}/index.mjs`))
await fs.stat(util.getPathFromRoot(`./testapp/${ctx.db.data.core.testapp.latestInstalled}/package.json`))
await fs.stat(util.getPathFromRoot(`./testapp/${ctx.db.data.core.testapp.latestInstalled}/node_modules`))
})

View file

@ -0,0 +1,70 @@
import { Eltro as t, assert} from 'eltro'
import fs from 'fs/promises'
import http from 'http'
import Util from '../core/util.mjs'
import { request } from '../core/client.mjs'
const util = new Util(import.meta.url)
const port = 61412
const defaultHandler = function(req, res) {
res.statusCode = 200
res.end('{"a":1}');
}
let server = null
let prefix = `http://localhost:${port}/`
let handler = defaultHandler
let files = []
let logs = []
t.describe('', function() {
t.before(function(cb) {
server = http.createServer(function(req, res) {
req.on('error', function(err) {
console.log('error', err)
})
res.on('error', function(err) {
console.log('error', err)
})
handler(req, res)
})
server.listen(port, cb)
})
t.after(function() {
return Promise.resolve()
return Promise.all(files.map(function(file) {
return fs.rm(file, { force: true, recursive: true })
}))
})
const stable_version_1 = `
export function start(http, port, ctx) {
const server = http.createServer(function (req, res) {
res.writeHead(200);
res.end(JSON.stringify({ version: 'stable_version_1' }))
})
return server.listenAsync(port, '0.0.0.0')
.then(() => {
ctx.log.info(\`Server is listening on \${port} serving stable_version_1\`)
})
}
`
function file(relative) {
let file = util.getPathFromRoot(relative)
files.push(file)
return file
}
function log(message) {
logs.push(message)
console.log(message)
}
t.test('should be fully operational', async function() {
let index = file('./index.mjs')
await fs.writeFile(index, stable_version_1)
await util.runCommand(util.get7zipExecutable(), ['a', file('./v1.7z'), index], util.getPathFromRoot('./testapp/'), log)
})
})

View file

@ -356,7 +356,7 @@ t.describe('#init()', function() {
assert.strictEqual(application.name, assertAppName) assert.strictEqual(application.name, assertAppName)
assert.strictEqual(application.ctx.db, core.db) assert.strictEqual(application.ctx.db, core.db)
assert.strictEqual(application.ctx.util, core.util) assert.strictEqual(application.ctx.util, core.util)
assert.strictEqual(application.ctx.log, core.log) assert.notStrictEqual(application.ctx.log, core.log)
assert.strictEqual(application.ctx.core, core) assert.strictEqual(application.ctx.core, core)
assert.strictEqual(application.config.teststring, assertTestString) assert.strictEqual(application.config.teststring, assertTestString)
assert.ok(application.fresh) assert.ok(application.fresh)
@ -375,6 +375,7 @@ t.describe('#init()', function() {
Core.providers.set(assertProviderFailName, FakeFailProvider) Core.providers.set(assertProviderFailName, FakeFailProvider)
const assertError = new Error('Justice of light') const assertError = new Error('Justice of light')
const assertPathLog = 'log_test_1.log'
const assertFirstAppName = 'Kaeshite' const assertFirstAppName = 'Kaeshite'
const assertSecondAppName = 'Motteke' const assertSecondAppName = 'Motteke'
const assertTestFirstString = 'Magia' const assertTestFirstString = 'Magia'
@ -383,6 +384,10 @@ t.describe('#init()', function() {
[assertFirstAppName]: { [assertFirstAppName]: {
provider: assertProviderName, provider: assertProviderName,
teststring: assertTestFirstString, teststring: assertTestFirstString,
log: [
{ path: assertPathLog, level: 'info' },
{ stream: 'process.stdout', level: 'warn' },
]
}, },
[assertSecondAppName]: { [assertSecondAppName]: {
provider: assertProviderFailName, provider: assertProviderFailName,
@ -403,7 +408,7 @@ t.describe('#init()', function() {
assert.strictEqual(application.name, assertFirstAppName) assert.strictEqual(application.name, assertFirstAppName)
assert.strictEqual(application.ctx.db, core.db) assert.strictEqual(application.ctx.db, core.db)
assert.strictEqual(application.ctx.util, core.util) assert.strictEqual(application.ctx.util, core.util)
assert.strictEqual(application.ctx.log, core.log) assert.notStrictEqual(application.ctx.log, core.log) //
assert.strictEqual(application.ctx.core, core) assert.strictEqual(application.ctx.core, core)
assert.strictEqual(application.config.teststring, assertTestFirstString) assert.strictEqual(application.config.teststring, assertTestFirstString)
assert.ok(application.fresh) assert.ok(application.fresh)
@ -413,54 +418,168 @@ t.describe('#init()', function() {
assert.match(log.error.firstCall[1], new RegExp(assertProviderFailName)) 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(assertSecondAppName))
assert.match(log.error.firstCall[1], new RegExp(assertError.message)) 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.only().describe('#runApplication()', function() { 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 core
let testApp let testApp
let testAppName let testAppName
let stubWrite
t.beforeEach(function() { t.beforeEach(function() {
testAppName = 'Precure'
db.data.core = { db.data.core = {
[testAppName]: { [testAppName]: {
versions: [] versions: []
} }
} }
core = new Core(db, util, log, function() {}) core = new Core(db, util, log, function() {})
db.write = stubWrite = stub().resolves()
log.info.reset() log.info.reset()
log.warn.reset() log.warn.reset()
log.error.reset() log.error.reset()
testAppName = 'Precure'
testApp = { testApp = {
fresh: true, name: testAppName,
fresh: false,
closeServer: stub(), closeServer: stub(),
runVersion: stub(), runVersion: stub(),
} }
core.applicationMap.set(testAppName, testApp) core.applicationMap.set(testAppName, testApp)
}) })
t.test('should throw if application not found', async function() {
const assertAppName = 'Sweet Sweet Sweets'
let err = await assert.isRejected(core.runApplication(assertAppName))
assert.match(err.message, new RegExp(assertAppName))
assert.match(err.message, /exist/i)
})
t.test('Should always attempt to close application first', async function() { t.test('Should always attempt to close application first', async function() {
const assertAppName = 'Pyramid Collapse' const assertAppName = 'Pyramid Collapse'
const assertError = new Error('Pyramid Collapse') const assertError = new Error('Pyramid Collapse')
const stubClose = stub().rejects(assertError) const stubClose = stub().rejects(assertError)
db.data.core[assertAppName] = { db.data.core[assertAppName] = {
versions: [{}] versions: [{installed: true, stable: 0}]
} }
core.applicationMap.set(assertAppName, { core.applicationMap.set(assertAppName, {
name: assertAppName,
closeServer: stubClose, closeServer: stubClose,
}) })
let err = await assert.isRejected(core.runApplication(assertAppName)) let err = await assert.isRejected(core.runApplication(core.applicationMap.get(assertAppName)))
assert.strictEqual(err, assertError) assert.strictEqual(err, assertError)
}) })
@ -476,7 +595,7 @@ t.only().describe('#runApplication()', function() {
stable: 0, stable: 0,
}) })
let err = await assert.isRejected(core.runApplication(testAppName)) let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError) assert.notStrictEqual(err, assertError)
assert.ok(log.error.called) assert.ok(log.error.called)
@ -484,5 +603,222 @@ t.only().describe('#runApplication()', function() {
assert.match(log.error.firstCall[1], new RegExp(testAppName)) 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(assertVersion))
assert.match(log.error.firstCall[1], new RegExp(assertError.message)) 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)
}) })
}) })

View file

@ -3,13 +3,13 @@ import Util from '../../core/util.mjs'
import fs from 'fs/promises' import fs from 'fs/promises'
import GitProvider from '../../core/providers/git.mjs' import GitProvider from '../../core/providers/git.mjs'
t.timeout(5000).describe('Git integration', function() { t.describe('test', function() {
t.after(function() { t.after(function() {
return fs.rm('./test/providers/file.7z') return fs.rm('./test/providers/file.7z')
.catch(function() { }) .catch(function() { })
}) })
t.describe('#getLatestVersion()', function() { t.timeout(5000).describe('#getLatestVersion()', function() {
t.test('should return latest version in a valid repository', async function() { t.test('should return latest version in a valid repository', async function() {
let provider = new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/sc-helloworld/releases' }) let provider = new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/sc-helloworld/releases' })
let version = await provider.getLatestVersion() let version = await provider.getLatestVersion()
@ -21,7 +21,7 @@ t.timeout(5000).describe('Git integration', function() {
}) })
}) })
t.describe('#checkConfig()', function() { t.timeout(5000).describe('#checkConfig()', function() {
t.test('should fail if link does not return json repository object', async function() { t.test('should fail if link does not return json repository object', async function() {
let err = await assert.isRejected(new GitProvider({ url: 'http://git.nfp.is/api/v1/repos/thething/ProgramQueuer' }).checkConfig()) let err = await assert.isRejected(new GitProvider({ url: 'http://git.nfp.is/api/v1/repos/thething/ProgramQueuer' }).checkConfig())
assert.match(err.message, /valid/i) assert.match(err.message, /valid/i)
@ -31,13 +31,13 @@ t.timeout(5000).describe('Git integration', function() {
assert.match(err.message, /release/i) assert.match(err.message, /release/i)
}) })
t.test('should fail if link returns no active release repository with assets', async function() { t.test('should fail if no active release repository with assets', async function() {
let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/eltro/releases' }).checkConfig()) let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/eltro/releases' }).checkConfig())
assert.match(err.message, /service-core/i) assert.match(err.message, /service-core/i)
assert.match(err.message, /release/i) assert.match(err.message, /release/i)
}) })
t.test('should fail on private repositories if token missing', async function() { t.test('should fail on private repositories', async function() {
let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases' }).checkConfig()) let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases' }).checkConfig())
assert.match(err.message, /fail/i) assert.match(err.message, /fail/i)
assert.match(err.message, /404/i) assert.match(err.message, /404/i)
@ -53,7 +53,7 @@ t.timeout(5000).describe('Git integration', function() {
if (!process.env.gittesttoken) { if (!process.env.gittesttoken) {
test = test.skip() test = test.skip()
} }
test.test('should succeed on private repo if token is provided', function() { test.test('should succeed on private repo with token', function() {
return new GitProvider({ return new GitProvider({
token: process.env.gittesttoken.trim(), token: process.env.gittesttoken.trim(),
url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases', url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases',
@ -61,7 +61,7 @@ t.timeout(5000).describe('Git integration', function() {
}) })
}) })
t.describe('#downloadVersion()', function() { t.timeout(5000).describe('#downloadVersion()', function() {
const util = new Util(import.meta.url) const util = new Util(import.meta.url)
let test = t let test = t

47
test/runner.mjs Normal file
View file

@ -0,0 +1,47 @@
import fs from 'fs'
import Core from '../core/core.mjs'
import GetDB from '../core/db.mjs'
import getLog from '../core/log.mjs'
import Util from '../core/util.mjs'
const name = 'service-core-runner'
const util = new Util(import.meta.url)
const log = getLog(name)
const config = {
name: name,
testapp: {
port: 31313,
provider: 'static',
}
}
try {
fs.rmSync(util.getPathFromRoot('./db.json'))
} catch {}
GetDB(config, log, util.getPathFromRoot('./db.json')).then(async function(db) {
const core = new Core(db, util, log, function() {})
console.log(db.data)
await core.init()
console.log(db.data)
db.data.core.testapp.versions.push({
id: 'ok',
version: 'ok',
stable: 0,
installed: true,
})
await core.run()
})
.then(
function() {
log.info('core is running')
},
function(err) {
log.error(err, 'Error starting runner')
process.exit(1)
})

17
test/testapp/ok/index.mjs Normal file
View file

@ -0,0 +1,17 @@
export function start(http, port, ctx) {
const server = http.createServer(function (req, res) {
res.writeHead(200);
res.end(JSON.stringify({ version: 'exampleindex' }))
})
return new Promise(function(res, rej) {
server.listen(port || 4000, '0.0.0.0', function(err) {
if (err) {
return rej(err)
}
ctx.log.event.info(`Server is listening on ${port} serving exampleindex`)
ctx.log.info(`Server is listening on ${port} serving exampleindex`)
res()
})
})
}