82f1dfe149
Detect reachable daemon metadata before spawning a detached daemon so the CLI does not print a dead token when the GUI already owns the daemon. Clarify daemon-required command errors when the daemon is reachable but no token is exported, and await engine disposal for local CLI shutdown.
135 lines
3.2 KiB
TypeScript
135 lines
3.2 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import test from "node:test"
|
|
|
|
import type { LegionLogAppendInput } from "../../src/main/legion-log-store"
|
|
import { RootSupervisor } from "../../src/engine/root-supervisor"
|
|
|
|
test("root supervisor starts children, degrades on non-critical failure, and stops in reverse", async () => {
|
|
const events: string[] = []
|
|
const logs: LegionLogAppendInput[] = []
|
|
const supervisor = new RootSupervisor({
|
|
recordSystemLog: async (entry) => {
|
|
logs.push(entry)
|
|
},
|
|
children: [
|
|
{
|
|
id: "first",
|
|
start: () => {
|
|
events.push("start:first")
|
|
},
|
|
stop: () => {
|
|
events.push("stop:first")
|
|
}
|
|
},
|
|
{
|
|
id: "second",
|
|
start: () => {
|
|
events.push("start:second")
|
|
throw new Error("second failed")
|
|
},
|
|
stop: () => {
|
|
events.push("stop:second")
|
|
}
|
|
},
|
|
{
|
|
id: "third",
|
|
start: () => {
|
|
events.push("start:third")
|
|
},
|
|
stop: () => {
|
|
events.push("stop:third")
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
await supervisor.start()
|
|
assert.equal(supervisor.getSnapshot().status, "degraded")
|
|
assert.deepEqual(
|
|
supervisor.getSnapshot().children.map((child) => [child.id, child.status]),
|
|
[
|
|
["first", "running"],
|
|
["second", "failed"],
|
|
["third", "running"]
|
|
]
|
|
)
|
|
assert.equal(logs[0]?.type, "legion.root.child_start.error")
|
|
|
|
await supervisor.stop()
|
|
assert.deepEqual(events, [
|
|
"start:first",
|
|
"start:second",
|
|
"start:third",
|
|
"stop:third",
|
|
"stop:second",
|
|
"stop:first"
|
|
])
|
|
assert.equal(supervisor.getSnapshot().status, "stopped")
|
|
})
|
|
|
|
test("root supervisor stops startup after critical child failure", async () => {
|
|
const events: string[] = []
|
|
const supervisor = new RootSupervisor({
|
|
children: [
|
|
{
|
|
id: "critical",
|
|
critical: true,
|
|
start: () => {
|
|
events.push("start:critical")
|
|
throw new Error("critical failed")
|
|
},
|
|
stop: () => {
|
|
events.push("stop:critical")
|
|
}
|
|
},
|
|
{
|
|
id: "next",
|
|
start: () => {
|
|
events.push("start:next")
|
|
},
|
|
stop: () => {
|
|
events.push("stop:next")
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
await assert.rejects(() => supervisor.start(), /critical failed/)
|
|
assert.deepEqual(events, ["start:critical"])
|
|
assert.equal(supervisor.getSnapshot().status, "failed")
|
|
})
|
|
|
|
test("root supervisor dispose waits for async child shutdown", async () => {
|
|
const events: string[] = []
|
|
let releaseStop!: () => void
|
|
const stopReleased = new Promise<void>((resolve) => {
|
|
releaseStop = resolve
|
|
})
|
|
const supervisor = new RootSupervisor({
|
|
children: [
|
|
{
|
|
id: "async",
|
|
start: () => {
|
|
events.push("start")
|
|
},
|
|
stop: async () => {
|
|
events.push("stop:start")
|
|
await stopReleased
|
|
events.push("stop:end")
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
await supervisor.start()
|
|
const disposed = supervisor.dispose()
|
|
await Promise.resolve()
|
|
|
|
assert.deepEqual(events, ["start", "stop:start"])
|
|
releaseStop()
|
|
await disposed
|
|
|
|
assert.deepEqual(events, ["start", "stop:start", "stop:end"])
|
|
assert.equal(supervisor.getSnapshot().status, "stopped")
|
|
})
|