const assert = require('node:assert/strict') const test = require('node:test') const fs = require('node:fs') const os = require('node:os') const path = require('node:path') const { runBootstrap, resolveInstallScript, installedAgentInstallScript, cachedScriptPath } = require('./bootstrap-runner.cjs') const SCRIPT_NAME = process.platform === 'win32' ? 'install.ps1' : 'install.sh' function mkTmpHome() { return fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-bootstrap-test-')) } test('runBootstrap bails immediately when the signal is already aborted', async () => { const controller = new AbortController() controller.abort() const events = [] const result = await runBootstrap({ installStamp: null, activeRoot: '/tmp/hermes-runner-test', sourceRepoRoot: null, hermesHome: '/tmp/hermes-runner-test', logRoot: '/tmp/hermes-runner-test', onEvent: ev => events.push(ev), abortSignal: controller.signal }) // Cancelled before any install script is spawned. assert.deepEqual(result, { ok: false, cancelled: true }) assert.ok( events.some(ev => ev.type === 'failed' && /cancelled/i.test(ev.error)), 'should emit a cancelled failure event' ) }) test('installedAgentInstallScript resolves the installer in the agent checkout', () => { const home = mkTmpHome() try { assert.equal(installedAgentInstallScript(home), null, 'absent before the checkout exists') const scriptsDir = path.join(home, 'hermes-agent', 'scripts') fs.mkdirSync(scriptsDir, { recursive: true }) const scriptPath = path.join(scriptsDir, SCRIPT_NAME) fs.writeFileSync(scriptPath, '#!/bin/sh\necho hi\n') assert.equal(installedAgentInstallScript(home), scriptPath) assert.equal(installedAgentInstallScript(null), null, 'null home -> null') } finally { fs.rmSync(home, { recursive: true, force: true }) } }) test('resolveInstallScript prefers a cached script without touching the network', async () => { const home = mkTmpHome() try { const commit = 'a'.repeat(40) const cached = cachedScriptPath(home, commit) fs.mkdirSync(path.dirname(cached), { recursive: true }) fs.writeFileSync(cached, '#!/bin/sh\necho cached\n') const logs = [] const result = await resolveInstallScript({ installStamp: { commit }, sourceRepoRoot: null, hermesHome: home, emit: ev => logs.push(ev) }) assert.equal(result.source, 'cache') assert.equal(result.path, cached) } finally { fs.rmSync(home, { recursive: true, force: true }) } }) test('resolveInstallScript falls back to the installed agent checkout on a 404', async () => { const home = mkTmpHome() try { const commit = 'a'.repeat(40) // Seed the installed agent checkout so the fallback has something to resolve. const scriptsDir = path.join(home, 'hermes-agent', 'scripts') fs.mkdirSync(scriptsDir, { recursive: true }) const installed = path.join(scriptsDir, SCRIPT_NAME) fs.writeFileSync(installed, '#!/bin/sh\necho fallback\n') const logs = [] const result = await resolveInstallScript({ installStamp: { commit }, sourceRepoRoot: null, hermesHome: home, emit: ev => logs.push(ev), // Simulate GitHub returning a 404 for the pinned commit. _download: async () => { throw new Error('Failed to download install.sh: HTTP 404') } }) assert.equal(result.source, 'installed-agent') // It should have copied the installer into the bootstrap cache. assert.equal(result.path, cachedScriptPath(home, commit)) assert.ok(fs.existsSync(result.path), 'fallback script copied into cache') assert.ok( logs.some(ev => /falling back to installed agent/.test(ev.line || '')), 'emits a fallback log line' ) } finally { fs.rmSync(home, { recursive: true, force: true }) } }) test('resolveInstallScript rethrows when the 404 fallback is unavailable', async () => { const home = mkTmpHome() try { const commit = 'a'.repeat(40) // No installed agent checkout seeded -> nothing to fall back to. await assert.rejects( resolveInstallScript({ installStamp: { commit }, sourceRepoRoot: null, hermesHome: home, emit: () => {}, _download: async () => { throw new Error('Failed to download install.sh: HTTP 404') } }), /HTTP 404|Failed to download/ ) } finally { fs.rmSync(home, { recursive: true, force: true }) } })