From 4f4bc8cf6aacceb56d1a3fdb9f3f4f18b68f900a Mon Sep 17 00:00:00 2001 From: Jonatan Nilsson Date: Fri, 11 Feb 2022 13:59:10 +0000 Subject: [PATCH] More core development, full integration started --- core/application.mjs | 4 + core/core.mjs | 67 +++- package.json | 2 + test/application.integration.test.mjs | 54 --- test/application.run.test.mjs | 22 ++ test/application.test.integration.mjs | 50 +++ test/core.test.integration.mjs | 70 ++++ test/core.test.mjs | 370 +++++++++++++++++- ...tion.test.mjs => git.test.integration.mjs} | 20 +- test/runner.mjs | 47 +++ test/testapp/ok/index.mjs | 17 + 11 files changed, 631 insertions(+), 92 deletions(-) delete mode 100644 test/application.integration.test.mjs create mode 100644 test/application.test.integration.mjs create mode 100644 test/core.test.integration.mjs rename test/providers/{git.integration.test.mjs => git.test.integration.mjs} (87%) create mode 100644 test/runner.mjs create mode 100644 test/testapp/ok/index.mjs diff --git a/core/application.mjs b/core/application.mjs index 8c4648d..df72c84 100644 --- a/core/application.mjs +++ b/core/application.mjs @@ -291,8 +291,12 @@ export default class Application extends EventEmitter { await this.fs.stat(indexPath).catch((err) => { 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`)) this.registerModule(module, version) + } else { + this.fresh = false } let errTimeout = new Error(`Version timed out (took over ${this.config.startWaitUntilFail}ms) while running start()`) diff --git a/core/core.mjs b/core/core.mjs index 0c5fc24..61ade5b 100644 --- a/core/core.mjs +++ b/core/core.mjs @@ -1,6 +1,9 @@ import Application from './application.mjs' import Util from './util.mjs' +import getLog from './log.mjs' import { Low } from 'lowdb' +import StaticProvider from './providers/static.mjs' +import GitProvider from './providers/git.mjs' export default class Core { static providers = new Map() @@ -46,10 +49,13 @@ export default class Core { } 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) + this.log.info(`Found applications: ${names.join(', ')}.`) + let lastError = null for (let name of names) { @@ -61,7 +67,7 @@ export default class Core { let application = new Application({ db: this.db, util: this.util, - log: this.log, + log: getLog(name, this.db.config[name].log || null), core: this, }, provider, name) this.applications.push(application) @@ -77,26 +83,65 @@ export default class Core { } } - async runApplication(name) { - let application = this.applicationMap.get(name) - - if (!application) { - return Promise.reject(new Error(`Core.runApplication was called on a non-existing application name ${name}`)) - } + async run() { + this.log.info(`Running updater on ${this.applications.length} apps`) + await Promise.all(this.applications.map((app) => { + return app.update().catch(err => { + this.log.error(err, `Error updating ${app.name}: ${err.message}`) + }) + })) 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++) { 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() 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) { - 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) diff --git a/package.json b/package.json index 912e3d5..28e00c6 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "scripts": { "dev": "nodemon --watch dev/api --watch core --watch runner.mjs --watch db.mjs --watch log.mjs runner.mjs | bunyan", "test": "eltro \"test/**/*.test.mjs\" -r dot", + "test: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:watch": "npm-watch test" }, diff --git a/test/application.integration.test.mjs b/test/application.integration.test.mjs deleted file mode 100644 index f9b7c44..0000000 --- a/test/application.integration.test.mjs +++ /dev/null @@ -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`)) - }) -}) \ No newline at end of file diff --git a/test/application.run.test.mjs b/test/application.run.test.mjs index 358eb63..e4b059f 100644 --- a/test/application.run.test.mjs +++ b/test/application.run.test.mjs @@ -48,7 +48,9 @@ t.describe('#runVersion("static")', function() { assert.strictEqual(checkCtx.app, app) }) + assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('static')) + assert.strictEqual(app.fresh, false) assert.match(err.message, /http/i) assert.match(err.message, /createServer/i) @@ -60,7 +62,9 @@ t.describe('#runVersion("static")', function() { app.config.startWaitUntilFail = 50 app.registerModule(function() { return new Promise(function() {}) }) + assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('static')) + assert.strictEqual(app.fresh, false) assert.match(err.message, /time/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') + assert.strictEqual(app.fresh, false) assert.strictEqual(ctx.db.data.core.testapp.active, 'static') }) @@ -94,7 +100,10 @@ t.describe('#runVersion("static")', function() { app.config.heartbeatAttempts = 3 app.registerModule(defaultHandler(handler)) + assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('static')) + assert.strictEqual(app.fresh, false) + assert.match(err.message, /failed/i) assert.match(err.message, /400/i) assert.strictEqual(called, 3) @@ -110,9 +119,12 @@ t.describe('#runVersion("static")', function() { app.config.heartbeatAttemptsWait = 30 app.registerModule(defaultHandler(handler)) + assert.strictEqual(app.fresh, true) let start = performance.now() let err = await assert.isRejected(app.runVersion('static')) let end = performance.now() + assert.strictEqual(app.fresh, false) + assert.match(err.message, /failed/i) assert.match(err.message, /time/i) assert.match(err.message, /out/i) @@ -193,7 +205,10 @@ t.describe('#runVersion("version")', function() { app.config.port = assertPort stubFsStat.rejects(assertNotError) + assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('v100')) + assert.strictEqual(app.fresh, true) + assert.notStrictEqual(err, assertNotError) assert.match(err.message, new RegExp(assertNotError.message)) 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.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')) + assert.strictEqual(app.fresh, false) + assert.notStrictEqual(err, assertError) 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.writeFile(util.getPathFromRoot('./testnoexisting/v98/index.mjs'), ``) + assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('v98')) + assert.strictEqual(app.fresh, false) assert.match(err.message, /start/i) 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.event.info.reset() + assert.strictEqual(app.fresh, true) await app.runVersion('v97') + assert.strictEqual(app.fresh, false) assert.ok(app.ctx.log.info.called) assert.ok(app.ctx.log.event.info.called) diff --git a/test/application.test.integration.mjs b/test/application.test.integration.mjs new file mode 100644 index 0000000..fdcdf9a --- /dev/null +++ b/test/application.test.integration.mjs @@ -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`)) +}) diff --git a/test/core.test.integration.mjs b/test/core.test.integration.mjs new file mode 100644 index 0000000..2c9b19a --- /dev/null +++ b/test/core.test.integration.mjs @@ -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) + }) +}) diff --git a/test/core.test.mjs b/test/core.test.mjs index efa12f7..ecf4605 100644 --- a/test/core.test.mjs +++ b/test/core.test.mjs @@ -356,7 +356,7 @@ t.describe('#init()', function() { assert.strictEqual(application.name, assertAppName) assert.strictEqual(application.ctx.db, core.db) assert.strictEqual(application.ctx.util, core.util) - assert.strictEqual(application.ctx.log, core.log) + assert.notStrictEqual(application.ctx.log, core.log) assert.strictEqual(application.ctx.core, core) assert.strictEqual(application.config.teststring, assertTestString) assert.ok(application.fresh) @@ -375,6 +375,7 @@ t.describe('#init()', function() { 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' @@ -383,6 +384,10 @@ t.describe('#init()', function() { [assertFirstAppName]: { provider: assertProviderName, teststring: assertTestFirstString, + log: [ + { path: assertPathLog, level: 'info' }, + { stream: 'process.stdout', level: 'warn' }, + ] }, [assertSecondAppName]: { provider: assertProviderFailName, @@ -403,7 +408,7 @@ t.describe('#init()', function() { assert.strictEqual(application.name, assertFirstAppName) assert.strictEqual(application.ctx.db, core.db) 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.config.teststring, assertTestFirstString) 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(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.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 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() - testAppName = 'Precure' testApp = { - fresh: true, + name: testAppName, + fresh: false, closeServer: stub(), runVersion: stub(), } 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() { const assertAppName = 'Pyramid Collapse' const assertError = new Error('Pyramid Collapse') const stubClose = stub().rejects(assertError) db.data.core[assertAppName] = { - versions: [{}] + versions: [{installed: true, stable: 0}] } core.applicationMap.set(assertAppName, { + name: assertAppName, 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) }) @@ -476,7 +595,7 @@ t.only().describe('#runApplication()', function() { stable: 0, }) - let err = await assert.isRejected(core.runApplication(testAppName)) + let err = await assert.isRejected(core.runApplication(testApp)) assert.notStrictEqual(err, assertError) 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(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) }) }) diff --git a/test/providers/git.integration.test.mjs b/test/providers/git.test.integration.mjs similarity index 87% rename from test/providers/git.integration.test.mjs rename to test/providers/git.test.integration.mjs index e6d036a..e5234f5 100644 --- a/test/providers/git.integration.test.mjs +++ b/test/providers/git.test.integration.mjs @@ -3,13 +3,13 @@ import Util from '../../core/util.mjs' import fs from 'fs/promises' import GitProvider from '../../core/providers/git.mjs' -t.timeout(5000).describe('Git integration', function() { +t.describe('test', function() { t.after(function() { return fs.rm('./test/providers/file.7z') .catch(function() { }) }) - t.describe('#getLatestVersion()', function() { + t.timeout(5000).describe('#getLatestVersion()', 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 version = await provider.getLatestVersion() @@ -20,8 +20,8 @@ t.timeout(5000).describe('Git integration', function() { assert.match(version.link, /\/attachments\//) }) }) - - t.describe('#checkConfig()', function() { + + t.timeout(5000).describe('#checkConfig()', 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()) assert.match(err.message, /valid/i) @@ -30,14 +30,14 @@ t.timeout(5000).describe('Git integration', function() { assert.match(err.message, /service-core/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()) assert.match(err.message, /service-core/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()) assert.match(err.message, /fail/i) assert.match(err.message, /404/i) @@ -53,7 +53,7 @@ t.timeout(5000).describe('Git integration', function() { if (!process.env.gittesttoken) { 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({ token: process.env.gittesttoken.trim(), 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) let test = t diff --git a/test/runner.mjs b/test/runner.mjs new file mode 100644 index 0000000..255ac2f --- /dev/null +++ b/test/runner.mjs @@ -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) + }) \ No newline at end of file diff --git a/test/testapp/ok/index.mjs b/test/testapp/ok/index.mjs new file mode 100644 index 0000000..ae9413d --- /dev/null +++ b/test/testapp/ok/index.mjs @@ -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() + }) + }) +}