mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-23 16:36:23 +00:00
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).
192 lines
5.1 KiB
TypeScript
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'])
|
|
})
|