import { setTimeout, setImmediate } from 'timers/promises' import { Eltro as t, assert, stub } from 'eltro' import fs from 'fs/promises' import lowdb from '../core/db.mjs' import Application from '../core/application.mjs' import Util from '../core/util.mjs' import StaticProvider from '../core/providers/static.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 db t.beforeEach(function() { return lowdb({ test: { } }, logger, null).then(function(res) { db = res }) }) t.test('should auto-create application', function() { assert.notOk(db.data.core.test) new Application(util, db, {}, 'test') assert.ok(db.data.core.test) assert.ok(db.data.core.test.versions) assert.strictEqual(db.data.core.test.active, null) assert.strictEqual(db.data.core.test.latestInstalled, null) assert.strictEqual(db.data.core.test.latestVersion, null) }) t.test('should keep config and other of itself', function() { const assertTest = { a: 1 } const assertName = 'test' db.config = { test: assertTest, app: { b: 2}, manage: { c: 3 }, } let app = new Application(util, db, {}, assertName) assert.strictEqual(app.config, assertTest) assert.strictEqual(app.db, db) assert.strictEqual(app.util, util) assert.strictEqual(app.name, assertName) }) t.test('should keep provider', function() { const assertProvider = { a: 1 } let app = new Application(util, db, assertProvider, 'test') assert.strictEqual(app.provider, assertProvider) }) }) t.timeout(250).describe('#startAutoupdater()', function() { let db t.beforeEach(function() { return lowdb({ test: { }, testapp: { } }, logger, null).then(function(res) { db = 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(util, db, provider, 'teststatic', { setInterval: stubInterval }) app.startAutoupdater() }) t.test('should call setInterval correctly', function() { const assertTimeMinutes = 1440 const stubInterval = stub() const stubUnref = stub() stubInterval.returns({ unref: stubUnref }) db.config.test.updateEvery = assertTimeMinutes let app = new Application(util, db, {}, '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(util, db, {}, '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(util, db, {}, 'test', { setInterval: stubInterval }) app.update = stubUpdate app.startAutoupdater() assert.strictEqual(typeof(stubInterval.firstCall[0]), 'function') assert.notStrictEqual(stubInterval.firstCall, stubUpdate) stubInterval.firstCall[0]() while (db.data.core.test.updater === '') { await setTimeout(10) } assert.match(db.data.core.test.updater, /auto/i) assert.match(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(util, db, {}, 'test', { setInterval: stubInterval }) app.update = function() { return Promise.reject(new Error(assertErrorMessage)) } app.startAutoupdater() assert.strictEqual(db.data.core.test.updater, '') stubInterval.firstCall[0]() while (db.data.core.test.updater === '') { await setTimeout(10) } assert.match(db.data.core.test.updater, /auto/i) assert.match(db.data.core.test.updater, /update/i) assert.match(db.data.core.test.updater, new RegExp(assertErrorMessage)) }) }) t.timeout(250).describe('#update()', function() { let db let app let provider let stubExtract let stubRunCommand let stubWrite let stubFsMkdir let stubFsRm let stubFsStat t.beforeEach(function() { util.extractFile = stubExtract = stub() util.runCommand = stubRunCommand = stub() stubExtract.resolves() stubRunCommand.resolves() return lowdb({ test: { } }, logger, null).then(function(res) { db = res db.write = stubWrite = stub() provider = createProvider() app = new Application(util, db, 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(util, db, provider, 'teststatic') await app.update() assert.match(db.data.core.teststatic.updater, /static/i) assert.match(db.data.core.teststatic.updater, /nothing/i) }) t.test('multiple calls should be safe', async function() { 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) app.update() await setImmediate() assert.strictEqual(provider.getLatestVersion.callCount, 1) }) t.test('should check for latest version', async function() { const assertError = new Error('Ore wa Subete wo Shihaisuru') provider.getLatestVersion.rejects(assertError) db.data.core.testapp.updater = '' let err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.match(db.data.core.testapp.updater, /check/i) assert.match(db.data.core.testapp.updater, /version/i) assert.match(db.data.core.testapp.updater, new RegExp(new Date().toISOString().split('T')[0])) assert.match(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(db.data.core.testapp.versions.length, 0) provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) db.data.core.testapp.updater = '' let err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.match(db.data.core.testapp.updater, /found/i) assert.match(db.data.core.testapp.updater, new RegExp(assertVersion.version)) assert.match(db.data.core.testapp.updater, /downloading/i) assert.match(db.data.core.testapp.updater, new RegExp(assertLink)) assert.match(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(db.data.core.testapp.updater, new RegExp(assertError.message)) assert.strictEqual(db.data.core.testapp.versions.length, 1) assert.strictEqual(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(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(db.data.core.testapp.versions.length, 1) assert.strictEqual(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(db.data.core.testapp.versions.length, 0) provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) db.data.core.testapp.updater = '' let err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.match(db.data.core.testapp.updater, /found/i) assert.match(db.data.core.testapp.updater, new RegExp(assertVersion.version)) assert.match(db.data.core.testapp.updater, /downloading/i) assert.match(db.data.core.testapp.updater, new RegExp(assertLink)) assert.match(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(db.data.core.testapp.updater, new RegExp(assertError.message)) assert.strictEqual(db.data.core.testapp.versions.length, 1) assert.strictEqual(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(db.data.core.testapp.versions.length, 1) assert.strictEqual(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(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.notOk(stubWrite.called) assert.match(db.data.core.testapp.updater, new RegExp('extracting[^.]+file\.7z', 'i')) assert.match(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.strictEqual(db.data.core.testapp.versions.length, 1) let version = 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(db.data.core.testapp.versions.length, 1) assert.strictEqual(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(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') stubRunCommand.rejects(new Error('should not be seen')) stubFsStat.rejects(assertError) assert.strictEqual(db.data.core.testapp.versions.length, 0) 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(db.data.core.testapp.updater, /index\.mjs/i) assert.match(db.data.core.testapp.updater, /missing/i) assert.strictEqual(db.data.core.testapp.versions.length, 1) let version = 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.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.strictEqual(db.data.core.testapp.versions.length, 1) assert.strictEqual(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(db.data.core.testapp.versions.length, 0) await app.update() 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(db.data.core.testapp.updater, /package\.json/i) assert.match(db.data.core.testapp.updater, /contain/i) assert.strictEqual(db.data.core.testapp.versions.length, 1) let version = db.data.core.testapp.versions[0] assert.ok(version.log) assert.match(version.log, /package\.json/i) assert.match(version.log, /contain/i) assert.ok(version.log.endsWith('\n')) assert.ok(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() await app.update() assert.strictEqual(db.data.core.testapp.versions.length, 1) assert.notOk(provider.downloadVersion.called) assert.match(db.data.core.testapp.updater, /already/i) assert.match(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/') provider.getLatestVersion.resolves(assertVersion) assert.strictEqual(db.data.core.testapp.versions.length, 0) assert.notOk(stubWrite.called) stubExtract.returnWith(function(target, stream) { stream(assertExtractText) return Promise.resolve() }) stubRunCommand.returnWith(function(command, options, folder, stream) { stream(assertNpmText) return Promise.reject(assertError) }) let err = await assert.isRejected(app.update()) assert.strictEqual(app.updating, false) assert.strictEqual(err, assertError) assert.strictEqual(stubRunCommand.firstCall[0], 'npm.cmd') 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.match(db.data.core.testapp.updater, /npm/i) assert.match(db.data.core.testapp.updater, /install/i) assert.strictEqual(db.data.core.testapp.versions.length, 1) assert.strictEqual(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(db.data.core.testapp.versions.length, 1) assert.strictEqual(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(db.data.core.testapp.latestInstalled, assertVersion.version) assert.strictEqual(db.data.core.testapp.versions.length, 0) await app.update() assert.strictEqual(app.updating, false) assert.strictEqual(db.data.core.testapp.latestInstalled, assertVersion.version) assert.strictEqual(db.data.core.testapp.versions.length, 1) assert.strictEqual(db.data.core.testapp.versions[0], assertVersion) assert.ok(assertVersion.log) 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 oldLog = 'The Smell of Sea\n' const assertVersion = { version: '123456789', link: 'httplinkhere', filename: 'test.7z', log: oldLog } assertVersion.id = assertVersion.version db.upsert(db.data.core.testapp.versions, assertVersion) provider.getLatestVersion.resolves({ version: assertVersion.version, link: assertNewLink, filename: assertNewFilename }) assert.strictEqual(db.data.core.testapp.versions.length, 1) await app.update() assert.strictEqual(app.updating, false) assert.strictEqual(db.data.core.testapp.versions.length, 1) assert.strictEqual(db.data.core.testapp.versions[0], assertVersion) assert.ok(assertVersion.log) assert.ok(assertVersion.log.startsWith(oldLog)) 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' } provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) db.data.core.testapp.updater = '' db.data.core.testapp.latestInstalled = assertVersion.version await app.update() assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(db.data.core.testapp.updater, /already/i) assert.match(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' } provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) db.data.core.testapp.updater = '' db.upsert(db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtodownload: 4 }) await app.update() assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(db.data.core.testapp.updater, /many/i) assert.match(db.data.core.testapp.updater, /fail/i) assert.match(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' } provider.getLatestVersion.resolves(assertVersion) provider.downloadVersion.rejects(assertError) db.data.core.testapp.updater = '' db.upsert(db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtoinstall: 4 }) await app.update() assert.strictEqual(app.updating, false) assert.notOk(provider.downloadVersion.called) assert.match(db.data.core.testapp.updater, /many/i) assert.match(db.data.core.testapp.updater, /fail/i) assert.match(db.data.core.testapp.updater, /skip/i) }) })