kilo-loop/test/controller.test.mjs

112 lines
4.1 KiB
JavaScript
Raw Normal View History

2026-09-13 16:29:03 +00:00
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { EventEmitter } from 'node:events'
import { RunController } from '../src/controller.mjs'
function fakeOrchestrator(config, { status = 'running' } = {}) {
const runState = new EventEmitter()
runState.state = { status, runId: config.runId }
const orchestrator = {
config,
state: runState,
run: async () => ({ status: 'done', runId: config.runId }),
requestStop: () => true,
submitAnswers: () => true,
listCheckpointMeta: () => [{ id: 'iter-01' }],
revertIteration: () => ({ ok: true }),
}
return orchestrator
}
function makeController() {
const created = []
const baseConfig = { project: '/repo', goal: '', goalText: '', iterations: 5, agent: 'code-design', hitl: 'on-question' }
const controller = new RunController(baseConfig, {
createOrchestrator: (config) => {
const orch = fakeOrchestrator(config, { status: 'running' })
created.push(orch)
return orch
},
})
return { controller, created, baseConfig }
}
test('snapshot reports idle before any run', () => {
const { controller } = makeController()
const snap = controller.snapshot()
assert.equal(snap.idle, true)
assert.equal(snap.status, 'idle')
assert.equal(controller.isActive(), false)
})
test('start merges per-run overrides into the config', async () => {
const { controller, created } = makeController()
await controller.start({ goal: 'goal.json', verify: 'pnpm test', iterations: 3, agent: 'code', dryRun: true })
assert.equal(created.length, 1)
const config = created[0].config
assert.equal(config.goal, 'goal.json')
assert.equal(config.verify, 'pnpm test')
assert.equal(config.iterations, 3)
assert.equal(config.agent, 'code')
assert.equal(config.dryRun, true)
assert.ok(config.runId)
})
test('start applies a per-run custom instruction (promptExtra) override', async () => {
const { controller, created } = makeController()
await controller.start({ goal: 'goal.json', promptExtra: 'focus on the parser only' })
assert.equal(created[0].config.promptExtra, 'focus on the parser only')
})
test('start leaves the base custom instruction when the override is omitted', async () => {
const controller = new RunController(
{ project: '/repo', goal: '', iterations: 5, agent: 'code-design', promptExtra: 'base instructions' },
{ createOrchestrator: (config) => fakeOrchestrator(config, { status: 'running' }) },
)
await controller.start({ goal: 'goal.json' })
assert.equal(controller.config.promptExtra, 'base instructions')
})
test('start refuses while a run is active', () => {
const { controller } = makeController()
controller.start({ goal: 'goal.json' })
assert.equal(controller.isActive(), true)
assert.throws(() => controller.start({ goal: 'other.json' }), /already active/)
})
test('start requires a goal', () => {
const { controller } = makeController()
assert.throws(() => controller.start({}), /goal is required/)
})
test('control methods delegate to the attached orchestrator', () => {
const { controller, created } = makeController()
controller.start({ goal: 'goal.json' })
assert.equal(controller.requestStop(), true)
assert.equal(controller.submitAnswers([]), true)
assert.deepEqual(controller.listCheckpointMeta(), [{ id: 'iter-01' }])
assert.deepEqual(controller.revertIteration(1), { ok: true })
assert.equal(created.length, 1)
})
test('control methods are safe with no orchestrator', () => {
const { controller } = makeController()
assert.equal(controller.requestStop(), false)
assert.equal(controller.submitAnswers([]), false)
assert.deepEqual(controller.listCheckpointMeta(), [])
assert.equal(controller.revertIteration(1).ok, false)
})
test('attach adopts an existing orchestrator and forwards its events', () => {
const { controller } = makeController()
const orch = fakeOrchestrator({ runId: 'adopted' }, { status: 'running' })
let states = 0
controller.on('state', () => (states += 1))
controller.attach(orch)
assert.equal(controller.orchestrator, orch)
assert.equal(states >= 1, true)
orch.state.emit('update')
assert.equal(states >= 2, true)
})