import { describe, expect, it } from "vitest"; import { commandModel, splitCommand } from "../../runner/authorModel"; /** The model adapter with real processes: a command that reads the prompt on stdin and answers on stdout. */ describe("splitCommand", () => { it("splits on spaces, keeps quoted parts together, and refuses an unclosed quote or nothing", () => { expect(splitCommand(`claude -p --tools ""`)).toEqual(["claude", "-p", "--tools", ""]); expect(splitCommand(`node -e 'console.log("a b")'`)).toEqual(["node", "-e", 'console.log("a b")']); expect(splitCommand(`a "b c" d\\ e`)).toEqual(["a", "b c", "d e"]); expect(() => splitCommand(`a "b`)).toThrow(/quote/); expect(() => splitCommand(" ")).toThrow(/empty/); }); }); describe("commandModel", () => { const node = process.execPath; it("sends the prompt on stdin and returns what the command prints", async () => { const model = commandModel({ command: [node, "-e", "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>process.stdout.write('got:'+s.toUpperCase()))"] }); expect(await model.propose("hello prompt")).toBe("got:HELLO PROMPT"); }); it("does not hand the command the runner's secrets", async () => { process.env.AITE_GITLAB_TOKEN = "glpat-abcdefghij0123456789"; // aite:allow-secret try { const model = commandModel({ command: [node, "-e", "process.stdout.write(Object.keys(process.env).filter(k=>k.startsWith('AITE_')).join(',') + '|' + (process.env.PATH ? 'path' : 'nopath'))"] }); expect(await model.propose("x")).toBe("|path"); } finally { delete process.env.AITE_GITLAB_TOKEN; } }); it("a command that exits with an error is an error carrying its first line of complaint", async () => { const model = commandModel({ command: [node, "-e", "console.error('model is down\\nsecond line');process.exit(3)"] }); await expect(model.propose("x")).rejects.toThrow(/exit 3.*model is down/s); }); it("a command that is not there, and one that answers nothing, are errors", async () => { await expect(commandModel({ command: ["/no/such/command-aite"] }).propose("x")).rejects.toThrow(/cannot run/); await expect(commandModel({ command: [node, "-e", "0"] }).propose("x")).rejects.toThrow(/nothing/); }); it("a command that takes too long is stopped", async () => { const model = commandModel({ command: [node, "-e", "setInterval(()=>{},1000)"], timeoutMs: 700 }); await expect(model.propose("x")).rejects.toThrow(/did not answer within/); }); });