112 lines
4.1 KiB
JavaScript
112 lines
4.1 KiB
JavaScript
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)
|
|
})
|