import { setTimeout, setImmediate } from 'timers/promises' import { Eltro as t, assert, stub } from 'eltro' import fs from 'fs/promises' import Application from '../core/application.mjs' import Util from '../core/util.mjs' import StaticProvider from '../core/providers/static.mjs' import { createFakeContext } from './helpers.mjs' import bunyan from 'bunyan-lite' import HttpServer from '../core/http.mjs' import { request } from '../core/client.mjs' import getLog from '../core/log.mjs' const util = new Util(import.meta.url) const logger = { info: stub(), warn: stub(), error: stub(), } function createProvider() { return { getLatestVersion: stub(), downloadVersion: stub(), } } t.describe('constructor()', function() { let ctx t.beforeEach(function() { return createFakeContext() .then(function(res) { ctx = res }) }) t.test('should auto-create application', function() { assert.notOk(ctx.db.data.core.test) new Application(ctx, {}, 'test') assert.ok(ctx.db.data.core.test) assert.ok(ctx.db.data.core.test.versions) assert.strictEqual(ctx.db.data.core.test.active, '') assert.strictEqual(ctx.db.data.core.test.latestInstalled, '') assert.strictEqual(ctx.db.data.core.test.latestVersion, '') }) t.test('should keep config and other of itself', function() { const assertTest = { a: 1 } const assertName = 'test' ctx.db.config = { test: assertTest, app: { b: 2}, manage: { c: 3 }, } let app = new Application(ctx, {}, assertName) assert.notStrictEqual(app.config, assertTest) assert.strictEqual(app.config.a, assertTest.a) assert.strictEqual(app.config.updateEvery, 180) assert.strictEqual(app.config.startWaitUntilFail, 60 * 1000) assert.strictEqual(app.config.heartbeatTimeout, 3 * 1000) assert.strictEqual(app.config.heartbeatAttempts, 5) assert.strictEqual(app.config.heartbeatAttemptsWait, 2 * 1000) assert.strictEqual(app.config.heartbeatPath, '/') assert.strictEqual(app.config.clusterWaitOnCrash, 1 * 1000) assert.strictEqual(app.ctx.db, ctx.db) assert.strictEqual(app.ctx.app, app) assert.strictEqual(app.ctx.util, ctx.util) assert.strictEqual(app.ctx.sc.Util, Util) assert.strictEqual(app.ctx.sc.bunyan, bunyan) assert.strictEqual(app.ctx.sc.HttpServer, HttpServer) assert.strictEqual(app.ctx.sc.request, request) assert.strictEqual(app.ctx.sc.getLog, getLog) assert.strictEqual(app.name, assertName) assert.strictEqual(app.fresh, true) assert.strictEqual(app.running, false) assert.strictEqual(app.monitoringCluster, false) assert.deepStrictEqual(app.workers, {}) assert.strictEqual(app.isSlave, false) assert.ok(app.http) assert.ok(app.http.sockets) assert.strictEqual(typeof(app.http.createServer), 'function') assert.strictEqual(typeof(app.http.closeServer), 'function') }) t.test('should support overriding defaults', function() { const assertTest = { a: 1, updateEvery: 10, startWaitUntilFail: 10, heartbeatTimeout: 10, heartbeatAttempts: 10, heartbeatAttemptsWait: 10, heartbeatPath: '/asdf', clusterWaitOnCrash: 10, } const assertName = 'test' ctx.db.config = { test: assertTest, app: { b: 2}, manage: { c: 3 }, } let app = new Application(ctx, {}, assertName) assert.notStrictEqual(app.config, assertTest) assert.strictEqual(app.config.a, assertTest.a) assert.strictEqual(app.config.updateEvery, 10) assert.strictEqual(app.config.startWaitUntilFail, 10) assert.strictEqual(app.config.heartbeatTimeout, 10) assert.strictEqual(app.config.heartbeatAttempts, 10) assert.strictEqual(app.config.heartbeatAttemptsWait, 10) assert.strictEqual(app.config.heartbeatPath, '/asdf') assert.strictEqual(app.config.clusterWaitOnCrash, 10) }) t.test('should fill out workers indexes if is master', function() { const assertTest = { a: 1, cluster: 2 } const assertName = 'test' ctx.db.config = { test: assertTest, app: { b: 2}, manage: { c: 3 }, } let app = new Application(ctx, {}, assertName) assert.notStrictEqual(app.config, assertTest) assert.strictEqual(app.config.a, assertTest.a) assert.deepStrictEqual(app.workers, { 1: null, 2: null, }) }) t.test('should leave workers empty if not master', function() { const assertTest = { a: 1, cluster: 2 } const assertName = 'test' ctx.db.config = { test: assertTest, app: { b: 2}, manage: { c: 3 }, } let app = new Application(ctx, {}, assertName, { cluster: { isWorker: true } }) assert.notStrictEqual(app.config, assertTest) assert.strictEqual(app.config.a, assertTest.a) assert.deepStrictEqual(app.workers, {}) }) t.test('should not default updateEvery if its zero', function() { const assertTest = { a: 1, updateEvery: 0 } const assertName = 'test' ctx.db.config = { test: assertTest, app: { b: 2}, manage: { c: 3 }, } let app = new Application(ctx, {}, assertName) assert.notStrictEqual(app.config, assertTest) assert.strictEqual(app.config.a, assertTest.a) assert.strictEqual(app.config.updateEvery, assertTest.updateEvery) assert.strictEqual(app.config.updateEvery, 0) }) t.test('should create http instance correctly', function() { ctx.db.config = { testapp: { a: 1, https: true }, app: { b: 2}, manage: { c: 3 }, } let app = new Application(ctx, {}, 'testapp') assert.ok(app.http) assert.ok(app.http.ishttps) }) t.test('should keep provider', function() { const assertProvider = { a: 1 } let app = new Application(ctx, assertProvider, 'test') assert.strictEqual(app.provider, assertProvider) }) }) t.describe('#startAutoupdater()', function() { let ctx t.beforeEach(function() { return createFakeContext() .then(function(res) { ctx = res }) }) t.test('should do nothing if provider is static', async function() { const stubInterval = stub() stubInterval.throws(new Error('should not be seen')) let provider = new StaticProvider() let app = new Application(ctx, provider, 'teststatic', { setInterval: stubInterval }) app.startAutoupdater() }) t.test('should do nothing if isSlave but should warn', async function() { const stubInterval = stub() stubInterval.throws(new Error('should not be seen')) let app = new Application(ctx, { }, 'teststatic', { setInterval: stubInterval }) app.ctx.log.warn.reset() assert.notOk(app.ctx.log.warn.called) app.isSlave = true app.startAutoupdater() assert.ok(app.ctx.log.warn.called) assert.match(app.ctx.log.warn.firstCall[0], /slave/i) }) t.test('should do nothing if updateEvery is zero', async function() { const stubInterval = stub() stubInterval.throws(new Error('should not be seen')) ctx.db.config.test = { updateEvery: 0 } let app = new Application(ctx, { }, 'test', { setInterval: stubInterval }) app.startAutoupdater() }) t.test('should call setInterval correctly', function() { const assertTimeMinutes = 1440 const stubInterval = stub() const stubUnref = stub() stubInterval.returns({ unref: stubUnref }) ctx.db.config.test = { updateEvery: assertTimeMinutes, } let app = new Application(ctx, {}, 'test', { setInterval: stubInterval }) assert.strictEqual(stubInterval.called, false) assert.strictEqual(stubUnref.called, false) app.startAutoupdater() assert.strictEqual(stubInterval.called, true) assert.strictEqual(stubUnref.called, true) assert.strictEqual(typeof(stubInterval.firstCall[0]), 'function') assert.strictEqual(stubInterval.firstCall[1], assertTimeMinutes * 60 * 1000) }) t.test('should support default value if updateEvery is not defined', function() { const stubInterval = stub() stubInterval.returns({ unref: function() {} }) let app = new Application(ctx, {}, 'test', { setInterval: stubInterval }) assert.strictEqual(stubInterval.called, false) app.startAutoupdater() assert.strictEqual(stubInterval.called, true) assert.strictEqual(typeof(stubInterval.firstCall[0]), 'function') assert.strictEqual(stubInterval.firstCall[1], 3 * 60 * 60 * 1000) }) t.test('should call update as promise correctly', async function() { const stubUpdate = stub() const stubInterval = stub() stubInterval.returns({ unref: function() {} }) stubUpdate.returnWith(function() { return Promise.resolve() }) let app = new Application(ctx, {}, 'test', { setInterval: stubInterval }) app.update = stubUpdate app.startAutoupdater() assert.strictEqual(typeof(stubInterval.firstCall[0]), 'function') assert.notStrictEqual(stubInterval.firstCall, stubUpdate) stubInterval.firstCall[0]() while (ctx.db.data.core.test.updater === '') { await setTimeout(10) } assert.match(ctx.db.data.core.test.updater, /auto/i) assert.match(ctx.db.data.core.test.updater, /update/i) }) t.test('should add any errors to last in db update check on errors when updating', async function() { const stubInterval = stub() const assertErrorMessage = 'Ai Do' stubInterval.returns({ unref: function() {} }) let app = new Application(ctx, {}, 'test', { setInterval: stubInterval }) app.update = function() { return Promise.reject(new Error(assertErrorMessage)) } app.startAutoupdater() assert.strictEqual(ctx.db.data.core.test.updater, '') stubInterval.firstCall[0]() while (ctx.db.data.core.test.updater === '') { await setTimeout(10) } assert.match(ctx.db.data.core.test.updater, /auto/i) assert.match(ctx.db.data.core.test.updater, /update/i) assert.match(ctx.db.data.core.test.updater, new RegExp(assertErrorMessage)) }) }) t.describe('#closeServer()', function() { let app let cluster let stubCloseServer t.beforeEach(function() { cluster = { off: stub(), } return createFakeContext() .then(function(res) { let provider = createProvider() app = new Application(res, provider, 'testapp', { cluster: cluster }) app.http.closeServer = stubCloseServer = stub().resolves() }) }) t.test('should call closeServer correctly', async function() { const assertError = new Error('Moonlight Fiesta') stubCloseServer.rejects(assertError) let err = await assert.isRejected(app.closeServer()) assert.strictEqual(err, assertError) }) t.test('should kill entire cluster if cluster master', async function() { let workers = {} let handle = app.__clusterWorkerDied = function() {} app.config.cluster = Math.floor(Math.random() * (10 - 4 + 1) + 4) for (let i = 1; i <= app.config.cluster; i++) { if (i % 2 === 0) { workers[i] = app.workers[i] = { process: { kill: stub().returnWith(function() { assert.strictEqual(app.__clusterWorkerDied, null) assert.strictEqual(cluster.off.callCount, 1) assert.strictEqual(cluster.off.firstCall[0], 'exit') assert.strictEqual(cluster.off.firstCall[1], handle) }) } } } else { workers[i] = app.workers[i] = null } } await app.closeServer() for (let i = 1; i <= app.config.cluster; i++) { if (workers[i]) { assert.ok(workers[i].process.kill.called) assert.strictEqual(app.__clusterWorkerDied, null) } } }) t.test('otherwise should work fine', async function() { await app.closeServer() }) }) t.describe('#workerDied()', function() { let app let stubTimeout let stubFork t.beforeEach(function() { stubTimeout = stub() return createFakeContext() .then(function(res) { let provider = createProvider() app = new Application(res, provider, 'testapp', { setTimeout: stubTimeout }) app.startForkProcess = stubFork = stub() }) }) t.test('should do nothing if worker is not found at index', function() { const assertWorkerId = 2 const assertWorker = { a: 1, w_id: assertWorkerId } stubTimeout.throws(new Error('should not be seen')) stubFork.throws(new Error('should not be seen')) app.workers[1] = assertWorker app.workerDied(assertWorker) }) t.test('should mark worker as null and call setTimeout', function() { const assertWorkerId = 2 const assertTimeoutDuration = 12421 const assertVersion = 'v11.22.33' const assertWorker = { a: 1, w_id: assertWorkerId } app.ctx.db.data.core[app.name].active = assertVersion app.workers[assertWorkerId] = assertWorker app.clusterWaitOnCrash = assertTimeoutDuration app.workerDied(assertWorker) assert.strictEqual(app.workers[assertWorkerId], null) assert.ok(stubTimeout.called) assert.strictEqual(typeof(stubTimeout.firstCall[0]), 'function') assert.strictEqual(stubTimeout.firstCall[1], assertTimeoutDuration) assert.notOk(stubFork.called) stubTimeout.firstCall[0]() assert.ok(stubFork.called) assert.ok(stubFork.firstCall[0], assertWorkerId) assert.ok(stubFork.firstCall[1], assertVersion) }) t.test('setTimeout should not call startForkProcess if one was already started', function() { const assertWorkerId = 2 const assertWorker = { a: 1, w_id: assertWorkerId } app.workers[assertWorkerId] = assertWorker app.workerDied(assertWorker) assert.strictEqual(app.workers[assertWorkerId], null) app.workers[assertWorkerId] = { a : 2 } stubTimeout.firstCall[0]() assert.notOk(stubFork.called) }) }) t.describe('#update()', function() { let ctx let app let provider let stubExtract let stubRunCommand let stubWrite let stubFsMkdir let stubFsRm let stubFsStat t.beforeEach(function() { return createFakeContext() .then(function(res) { ctx = res ctx.util.extractFile = stubExtract = stub().resolves() ctx.util.runCommand = stubRunCommand = stub().resolves() ctx.db.write = stubWrite = stub().resolves() provider = createProvider() app = new Application(ctx, provider, 'testapp', { fs: { mkdir: stubFsMkdir = stub(), rm: stubFsRm = stub(), stat: stubFsStat = stub(), }, }) stubFsMkdir.resolves() stubFsRm.resolves() stubFsStat.resolves({}) provider.downloadVersion.resolves() provider.getLatestVersion.resolves({ version: '123456789', link: 'httplinkhere', filename: 'test.7z' }) }) }) t.afterEach(function() { return fs.rm('./test/testapp/123456789', { force: true, recursive: true }) }) t.test('should do nothing if provider is static', async function() { provider = new StaticProvider() app = new Application(ctx, provider, 'teststatic') stubWrite.reset().resolves() let result = await app.update() assert.strictEqual(result, null) assert.match(ctx.db.data.core.teststatic.updater, /static/i) assert.match(ctx.db.data.core.teststatic.updater, /nothing/i) let old = ctx.db.data.core.teststatic.updater assert.ok(stubWrite.called) assert.strictEqual(stubWrite.callCount, 1) await app.update() assert.strictEqual(result, null) assert.strictEqual(ctx.db.data.core.teststatic.updater, old) assert.strictEqual(stubWrite.callCount, 1) ctx.db.data.core.teststatic.updater = 'asdf' await app.update() assert.strictEqual(result, null) assert.strictEqual(ctx.db.data.core.teststatic.updater, old) assert.strictEqual(stubWrite.callCount, 2) await app.update() assert.strictEqual(result, null) assert.strictEqual(ctx.db.data.core.teststatic.updater, old) assert.strictEqual(stubWrite.callCount, 2) }) t.test('should do nothing if slave but warn', async function() { stubWrite.reset().resolves() app.isSlave = true app.ctx.log.warn.reset() assert.notOk(app.ctx.log.warn.called) let result = await app.update() assert.strictEqual(result, null) assert.notOk(ctx.db.data.core.teststatic) assert.notOk(stubWrite.called) assert.ok(app.ctx.log.warn.called) assert.match(app.ctx.log.warn.firstCall[0], /slave/i) }) t.test('multiple calls should be safe', async function() { ctx.db.data.core.testapp.updater = '' provider.getLatestVersion.returnWith(function() { return new Promise(function() {}) }) assert.strictEqual(app.updating, false) app.update() await setImmediate() assert.strictEqual(app.updating, true) assert.strictEqual(provider.getLatestVersion.callCount, 1) let result = await app.update() await setImmediate() assert.strictEqual(provider.getLatestVersion.callCount, 1) assert.strictEqual(result, null) }) t.test('should check for latest version', async function() { const assertError = new Error('Ore wa Subete wo Shihaisuru') provider.getLatestVersion.rejects(assertError) ctx.db.data.core.testapp.updater = '' let err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.ok(stubWrite.called) assert.ok(stubWrite.callCount >= 1) assert.match(ctx.db.data.core.testapp.updater, /check/i) assert.match(ctx.db.data.core.testapp.updater, /version/i) assert.match(ctx.db.data.core.testapp.updater, new RegExp(new Date().toISOString().split('T')[0])) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertError.message)) }) t.test('should call provider download latest correctly if new 7zip version', async function() { const assertError = new Error('Without a fight') const assertLink = 'All of you' const assertVersion = { version: '123456789', link: assertLink, filename: 'test.7z' } const assertTarget = util.getPathFromRoot('./testapp/123456789/file.7z') assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) ctx.db.data.core.testapp.updater = '' let err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.match(ctx.db.data.core.testapp.updater, /found/i) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertVersion.version)) assert.match(ctx.db.data.core.testapp.updater, /downloading/i) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertLink)) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertTarget.replace(/\\/g, '\\\\'))) assert.strictEqual(provider.downloadVersion.firstCall[0], assertVersion) assert.strictEqual(provider.downloadVersion.firstCall[1], assertTarget) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertError.message)) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.ok(assertVersion.log) assert.match(assertVersion.log, /\. \n/) assert.match(assertVersion.log, new RegExp(assertError.message)) assert.ok(assertVersion.log.endsWith('\n')) assert.strictEqual(assertVersion.failtodownload, 1) assert.ok(stubWrite.called) assert.ok(stubWrite.callCount >= 2) assert.ok(stubFsMkdir.called) assert.match(stubFsMkdir.firstCall[0], /123456789/) assert.ok(stubFsMkdir.firstCall[1]?.recursive) // Test if subsequent calls increment counter err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.strictEqual(assertVersion.failtodownload, 2) }) t.test('should call provider download latest correctly if new 7zip version', async function() { const assertError = new Error('Without a fight') const assertLink = 'All of you' const assertVersion = { version: '123456789', link: assertLink, filename: 'test.7z' } const assertTarget = util.getPathFromRoot('./testapp/123456789/file.7z') assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) ctx.db.data.core.testapp.updater = '' let err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.match(ctx.db.data.core.testapp.updater, /found/i) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertVersion.version)) assert.match(ctx.db.data.core.testapp.updater, /downloading/i) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertLink)) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertTarget.replace(/\\/g, '\\\\'))) assert.strictEqual(provider.downloadVersion.firstCall[0], assertVersion) assert.strictEqual(provider.downloadVersion.firstCall[1], assertTarget) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertError.message)) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.ok(assertVersion.log) assert.match(assertVersion.log, /\. \n/) assert.match(assertVersion.log, new RegExp(assertError.message)) assert.ok(assertVersion.log.endsWith('\n')) assert.strictEqual(assertVersion.failtodownload, 1) assert.ok(stubFsRm.called) assert.match(stubFsRm.firstCall[0], /123456789/) assert.ok(stubFsRm.firstCall[1]?.recursive) assert.ok(stubFsRm.firstCall[1]?.force) // Test if subsequent calls increment counter err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.strictEqual(assertVersion.failtodownload, 2) }) t.test('should call extract on util correctly', async function() { const assertExtractText = 'Reverdations of Success' const assertError = new Error('Dai Gekisen') const assertTarget = util.getPathFromRoot('./testapp/123456789/file.7z') const assertFolder = util.getPathFromRoot('./testapp/123456789') stubExtract.returnWith(function(target, stream) { stream(assertExtractText) return Promise.reject(assertError) }) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) let err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.strictEqual(app.updating, false) assert.strictEqual(stubExtract.firstCall[0], assertTarget) assert.match(ctx.db.data.core.testapp.updater, new RegExp('extracting[^.]+file\.7z', 'i')) assert.match(ctx.db.data.core.testapp.updater, new RegExp(assertError.message)) assert.ok(stubFsRm.called) assert.strictEqual(stubFsRm.firstCall[0], assertFolder) assert.ok(stubFsRm.firstCall[1]?.recursive) assert.ok(stubFsRm.firstCall[1]?.force) assert.ok(stubWrite.called) assert.ok(stubWrite.callCount >= 3) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) let version = ctx.db.data.core.testapp.versions[0] assert.ok(version.log) assert.match(version.log, /\. \n/) assert.match(version.log, new RegExp('extracting[^.]+file\.7z', 'i')) assert.match(version.log, new RegExp(assertError.message)) assert.match(version.log, new RegExp(assertExtractText)) assert.ok(version.log.endsWith('\n')) assert.strictEqual(version.failtodownload, 1) // Test if subsequent calls increment counter err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], version) assert.strictEqual(version.failtodownload, 2) }) t.test('should call fs remove the archieve file', async function() { const assertError = new Error('Tiny Kizuna') const assertTarget = util.getPathFromRoot('./testapp/123456789/file.7z') assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) stubFsRm.rejects(assertError) await app.update() assert.strictEqual(app.updating, false) assert.ok(stubFsRm.called) assert.strictEqual(stubFsRm.callCount, 1) assert.strictEqual(stubFsRm.firstCall[0], assertTarget) assert.ok(stubFsRm.firstCall[1]?.force) }) t.test('should not call npm install if stat fails to find index.mjs', async function() { const assertError = new Error('File not found') const assertTarget = util.getPathFromRoot('./testapp/123456789/index.mjs') const stubUpdated = stub() stubRunCommand.rejects(new Error('should not be seen')) stubFsStat.rejects(assertError) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) app.once('updated', stubUpdated) let err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.ok(stubExtract.called) assert.strictEqual(err, assertError) assert.strictEqual(stubFsStat.firstCall[0], assertTarget) assert.match(ctx.db.data.core.testapp.updater, /index\.mjs/i) assert.match(ctx.db.data.core.testapp.updater, /missing/i) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) let version = ctx.db.data.core.testapp.versions[0] assert.ok(version.log) assert.match(version.log, /index\.mjs/i) assert.match(version.log, /missing/i) assert.ok(version.log.endsWith('\n')) assert.strictEqual(version.failtodownload, 1) // Test if subsequent calls increment counter err = await assert.isRejected(app.update()) assert.notOk(stubUpdated.called) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], version) assert.strictEqual(version.failtodownload, 2) }) t.test('should not call npm install if package.json is missing but shuld pass', async function() { const assertError = new Error('File not found') const assertTarget = util.getPathFromRoot('./testapp/123456789/package.json') stubRunCommand.rejects(new Error('should not be seen')) stubFsStat.returnWith(function(path) { if (path.endsWith('package.json')) { return Promise.reject(assertError) } return Promise.resolve({}) }) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) let result = await new Promise(function(res, rej) { app.once('updated', res) app.update().catch(rej) }) assert.strictEqual(app.updating, false) assert.strictEqual(stubFsStat.callCount, 2) assert.strictEqual(stubFsStat.secondCall[0], assertTarget) assert.ok(stubExtract.called) assert.notOk(stubRunCommand.called) assert.match(ctx.db.data.core.testapp.updater, /package\.json/i) assert.match(ctx.db.data.core.testapp.updater, /contain/i) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) let version = ctx.db.data.core.testapp.versions[0] assert.ok(version.log) assert.strictEqual(result, version) assert.match(version.log, /package\.json/i) assert.match(version.log, /contain/i) assert.ok(version.log.endsWith('\n')) assert.ok(ctx.db.data.core.testapp.latestInstalled) assert.match(version.log, /finished/i) assert.match(version.log, /updating/i) // Test if subsequent calls do nothing provider.downloadVersion.reset() result = await app.update() assert.strictEqual(result, null) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.notOk(provider.downloadVersion.called) assert.match(ctx.db.data.core.testapp.updater, /already/i) assert.match(ctx.db.data.core.testapp.updater, /nothing/i) }) t.test('should otherwise call npm install correctly', async function() { const assertExtractText = 'Egao no Hikair ni Tsutsumarete' const assertNpmText = 'Dadadadash' const assertVersion = { version: '123456789', link: 'somelinkhere', filename: 'test.7z' } const assertError = new Error('Nagisa') const assertTarget = util.getPathFromRoot('./testapp/123456789') const assertTargetCheck = util.getPathFromRoot('./testapp/123456789/') const stubUpdated = stub() provider.getLatestVersion.resolves(assertVersion) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) stubExtract.returnWith(function(target, stream) { stream(assertExtractText) return Promise.resolve() }) stubRunCommand.returnWith(function(command, options, folder, stream) { stream(assertNpmText) return Promise.reject(assertError) }) app.once('updated', stubUpdated) let err = await assert.isRejected(app.update()) assert.notOk(stubUpdated.called) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.strictEqual(stubRunCommand.firstCall[0], util.getNpmExecutable()) assert.ok(stubRunCommand.firstCall[1]) assert.strictEqual(stubRunCommand.firstCall[1][0], 'install') assert.ok(stubRunCommand.firstCall[1].includes('--production'), 'should have --production') assert.ok(stubRunCommand.firstCall[1].includes('--no-optional'), 'should have --no-optional') assert.ok(stubRunCommand.firstCall[1].includes('--no-package-lock'), 'should have --no-package-lock') assert.ok(stubRunCommand.firstCall[1].includes('--no-audit'), 'should have --no-audit') assert.strictEqual(stubRunCommand.firstCall[2], assertTarget) for (let i = 0; i < stubFsRm.callCount; i++) { assert.notStrictEqual(stubFsRm.getCall(i)[0], assertTarget) assert.notStrictEqual(stubFsRm.getCall(i)[0], assertTargetCheck) } assert.ok(stubWrite.called) assert.ok(stubWrite.callCount >= 4) assert.match(ctx.db.data.core.testapp.updater, /npm/i) assert.match(ctx.db.data.core.testapp.updater, /install/i) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.ok(assertVersion.log) assert.match(assertVersion.log, /\. \n/) assert.match(assertVersion.log, new RegExp(assertExtractText)) assert.match(assertVersion.log, new RegExp(assertNpmText)) assert.match(assertVersion.log, new RegExp(assertError.message)) assert.match(assertVersion.log, /npm/i) assert.match(assertVersion.log, /install/i) assert.ok(assertVersion.log.endsWith('\n')) assert.strictEqual(assertVersion.failtoinstall, 1) // Test if subsequent calls increment counter err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.strictEqual(assertVersion.failtoinstall, 2) }) t.test('should update latest installed correctly', async function() { const assertVersion = { version: '123456789', link: 'httplinkhere', filename: 'test.7z' } provider.getLatestVersion.resolves(assertVersion) assert.notStrictEqual(ctx.db.data.core.testapp.latestInstalled, assertVersion.version) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) let result = await new Promise(function(res, rej) { app.once('updated', res) app.update().catch(rej) }) assert.strictEqual(app.updating, false) assert.strictEqual(ctx.db.data.core.testapp.latestInstalled, assertVersion.version) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.strictEqual(result, assertVersion) assert.ok(assertVersion.log) assert.ok(stubWrite.callCount >= 4) assert.strictEqual(assertVersion.installed, true) assert.strictEqual(assertVersion.stable, 0) assert.match(assertVersion.log, /found/i) assert.match(assertVersion.log, new RegExp(assertVersion.version)) assert.match(assertVersion.log, /downloading/i) assert.match(assertVersion.log, new RegExp('extracting[^.]+file\.7z', 'i')) assert.match(assertVersion.log, /finished/i) assert.match(assertVersion.log, /updating/i) }) t.test('should update existing version if found', async function() { const assertNewLink = 'THE last pain' const assertNewFilename = 'The place of hope.7z' const assertStable = 100 const stubUpdated = stub() const oldLog = 'The Smell of Sea\n' const assertVersion = { version: '123456789', link: 'httplinkhere', filename: 'test.7z', log: oldLog, stable: assertStable } assertVersion.id = assertVersion.version ctx.db.upsert(ctx.db.data.core.testapp.versions, assertVersion) provider.getLatestVersion.resolves({ version: assertVersion.version, link: assertNewLink, filename: assertNewFilename }) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) app.once('updated', stubUpdated) let result = await app.update() assert.ok(stubUpdated.called) assert.strictEqual(result, stubUpdated.firstCall[0]) assert.ok(assertVersion.log) assert.ok(stubWrite.callCount >= 4) assert.strictEqual(app.updating, false) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.strictEqual(result, assertVersion) assert.strictEqual(assertVersion.stable, assertStable) assert.ok(assertVersion.log) assert.ok(assertVersion.log.startsWith(oldLog)) assert.strictEqual(assertVersion.installed, true) assert.match(assertVersion.log, /found/i) assert.match(assertVersion.log, new RegExp(assertVersion.version)) assert.match(assertVersion.log, /downloading/i) assert.match(assertVersion.log, new RegExp('extracting[^.]+file\.7z', 'i')) assert.match(assertVersion.log, /finished/i) assert.match(assertVersion.log, /updating/i) }) t.test('should do nothing if latestInstalled matches version', async function() { const assertError = new Error('should not be seen') const assertVersion = { version: '999.888.777.666', filename: 'test.7z' } const stubUpdated = stub() provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) ctx.db.data.core.testapp.updater = '' ctx.db.data.core.testapp.latestInstalled = assertVersion.version app.once('updated', stubUpdated) let result = await app.update() assert.strictEqual(result, null) assert.notOk(stubUpdated.called) result = await app.update() assert.notOk(stubUpdated.called) assert.strictEqual(result, null) assert.ok(stubWrite.callCount >= 1) assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(ctx.db.data.core.testapp.updater, /already/i) assert.match(ctx.db.data.core.testapp.updater, /nothing/i) }) t.test('should do nothing if installed version is found', async function() { const assertError = new Error('should not be seen') const assertVersion = { version: '999.888.777.666', filename: 'test.7z' } const stubUpdated = stub() provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) ctx.db.data.core.testapp.updater = '' ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '111.111.111.111', installed: true }) ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '222.222.222.222', installed: true }) ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', installed: true }) ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '333.333.333.333', installed: true }) app.once('updated', stubUpdated) await app.update() assert.notOk(stubUpdated.called) assert.ok(stubWrite.callCount >= 1) assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(ctx.db.data.core.testapp.updater, /already/i) assert.match(ctx.db.data.core.testapp.updater, /nothing/i) }) t.test('should do nothing it exists and failtodownload is higher than 3', async function() { const assertError = new Error('should not be seen') const assertVersion = { version: '999.888.777.666', filename: 'test.7z' } const stubUpdated = stub() provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) ctx.db.data.core.testapp.updater = '' ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtodownload: 4 }) app.once('updated', stubUpdated) let result = await app.update() assert.notOk(stubUpdated.called) assert.strictEqual(result, null) assert.ok(stubWrite.callCount >= 1) assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(ctx.db.data.core.testapp.updater, /many/i) assert.match(ctx.db.data.core.testapp.updater, /fail/i) assert.match(ctx.db.data.core.testapp.updater, /skip/i) }) t.test('should do nothing it exists and failtoinstall is higher than 3', async function() { const assertError = new Error('should not be seen') const assertVersion = { version: '999.888.777.666', filename: 'test.7z' } const stubUpdated = stub() provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) ctx.db.data.core.testapp.updater = '' ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtoinstall: 4 }) app.once('updated', stubUpdated) let result = await app.update() assert.notOk(stubUpdated.called) assert.strictEqual(result, null) assert.ok(stubWrite.callCount >= 1) assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(ctx.db.data.core.testapp.updater, /many/i) assert.match(ctx.db.data.core.testapp.updater, /fail/i) assert.match(ctx.db.data.core.testapp.updater, /skip/i) }) }) t.describe('#registerModule()', function() { const assertAppName = 'testappregister' let ctx let app t.beforeEach(function() { return createFakeContext() .then(function(res) { ctx = res let provider = new StaticProvider() app = new Application(ctx, provider, assertAppName) }) }) t.test('should fail if not an object with start', function() { let tests = [ [1, 'number'], [0, 'empty number'], ['text', 'string'], ['', 'empty string'], [{}, 'empty object'], [[], 'array'], [{ a: 1 }, 'non-empty object'], ] tests.forEach(function(check) { assert.throws(function() { app.registerModule(check[0]) }, function(err) { assert.match(err.message, /registerModule/i) assert.match(err.message, /function/i) assert.match(err.message, /start/i) assert.match(err.message, new RegExp(assertAppName, 'i')) return true }, `should fail if called with ${check[1]}`) }) }) t.test('should otherwise succeed if object has function start', function() { const assertFunction = function() {} const assertModule = { start: assertFunction } app.registerModule(assertModule) assert.strictEqual(app.module, assertModule) app.registerModule(assertFunction) assert.notStrictEqual(app.module, assertModule) assert.deepStrictEqual(app.module, assertModule) }) })