hermes-agent/apps/desktop/electron/ssh-bootstrap-coordinator.test.ts
yoniebans faa3bce6dc style(desktop): satisfy merged eslint/prettier config
The SSH modules predate the stricter lint config that landed on main (curly, no-empty, perfectionist sorting, prettier). Mechanical lint:fix + fmt pass, empty catch blocks filled with the codebase's void-0 convention, and inline no-control-regex disables on the three deliberate control-char patterns (same pattern as lib/ansi.ts).
2026-07-20 23:01:49 +02:00

192 lines
5.1 KiB
TypeScript

import assert from 'node:assert/strict'
import { test } from 'vitest'
import { createBootstrapCoordinator, sshConfigFingerprint } from './ssh-bootstrap-coordinator'
function deferred() {
let resolve
let reject
const promise = new Promise((ok, fail) => {
resolve = ok
reject = fail
})
return { promise, reject, resolve }
}
const config = { host: 'box', user: 'alice', port: 22, keyPath: '/key', remoteHermesPath: '/hermes' }
test('sshConfigFingerprint covers scope and every connection field', () => {
const base = sshConfigFingerprint('', config)
assert.equal(base, sshConfigFingerprint('', { ...config }))
for (const [field, value] of Object.entries({
host: 'other',
user: 'bob',
port: 2222,
keyPath: '/other',
remoteHermesPath: '/other-hermes',
effectiveConfigFingerprint: 'changed-config'
})) {
assert.notEqual(base, sshConfigFingerprint('', { ...config, [field]: value }))
}
assert.notEqual(base, sshConfigFingerprint('profile', config))
})
test('same scope and fingerprint share one bootstrap', async () => {
const coordinator = createBootstrapCoordinator()
const gate = deferred()
let runs = 0
const first = coordinator.start('', 'same', async () => {
runs++
return gate.promise
})
const second = coordinator.start('', 'same', async () => {
runs++
return 'wrong'
})
assert.equal(first, second)
gate.resolve('done')
assert.equal(await second, 'done')
assert.equal(runs, 1)
})
test('changed fingerprint waits for old rollback before starting', async () => {
const coordinator = createBootstrapCoordinator()
const gate = deferred()
const events: string[] = []
let oldLease
const oldPromise = coordinator.start('', 'old', async lease => {
oldLease = lease
events.push('old-start')
await gate.promise
events.push('old-rollback')
lease.assertCurrent()
})
await Promise.resolve()
const newPromise = coordinator.start('', 'new', async lease => {
events.push('new-start')
lease.assertCurrent()
return 'new'
})
assert.equal(oldLease.signal.aborted, true)
await Promise.resolve()
assert.deepEqual(events, ['old-start'])
gate.resolve()
await assert.rejects(oldPromise, (error: any) => error.kind === 'superseded')
assert.equal(await newPromise, 'new')
assert.deepEqual(events, ['old-start', 'old-rollback', 'new-start'])
})
test('forceCleanupAll runs registered pending resource cleanup', async () => {
const coordinator = createBootstrapCoordinator()
const gate = deferred()
let cleaned = 0
const promise = coordinator.start('', 'x', async lease => {
lease.onForceCleanup(async () => {
cleaned++
})
await gate.promise
})
await Promise.resolve()
await coordinator.forceCleanupAll()
assert.equal(cleaned, 1)
gate.resolve()
await promise
})
test('cancelAll invalidates every pending scope and exposes promises for quit', async () => {
const coordinator = createBootstrapCoordinator()
const gates = [deferred(), deferred()]
const promises = gates.map((gate, index) =>
coordinator.start(String(index), 'x', async lease => {
await gate.promise
lease.assertCurrent()
})
)
assert.equal(coordinator.promises().length, 2)
coordinator.cancelAll()
gates.forEach(gate => gate.resolve())
const results = await Promise.allSettled(promises)
assert.ok(results.every(result => result.status === 'rejected' && (result.reason as any).kind === 'superseded'))
})
test('cancelAndWait drains only the requested scope', async () => {
const coordinator = createBootstrapCoordinator()
const firstGate = deferred()
const secondGate = deferred()
const first = coordinator.start('first', 'x', async lease => {
await firstGate.promise
lease.assertCurrent()
})
const second = coordinator.start('second', 'x', async lease => {
await secondGate.promise
lease.assertCurrent()
return 'second'
})
await Promise.resolve()
let drained = false
const drain = coordinator.cancelAndWait('first').then(() => {
drained = true
})
await Promise.resolve()
assert.equal(drained, false)
firstGate.resolve()
await drain
await assert.rejects(first, (error: any) => error.kind === 'superseded')
assert.equal(coordinator.pending.has('second'), true)
secondGate.resolve()
assert.equal(await second, 'second')
})
test('a generation started during cancelAndWait cannot run before the drain completes', async () => {
const coordinator = createBootstrapCoordinator()
const oldGate = deferred()
const events: string[] = []
const old = coordinator.start('scope', 'old', async lease => {
events.push('old-start')
await oldGate.promise
lease.assertCurrent()
})
await Promise.resolve()
const drain = coordinator.cancelAndWait('scope')
const next = coordinator.start('scope', 'new', async () => {
events.push('new-start')
return 'new'
})
await Promise.resolve()
assert.deepEqual(events, ['old-start'])
oldGate.resolve()
await drain
await assert.rejects(old, (error: any) => error.kind === 'superseded')
assert.equal(await next, 'new')
assert.deepEqual(events, ['old-start', 'new-start'])
})