import { describe, expect, it } from "vitest"; import { UNLIMITED_BUDGET, parseBudgetInput, parseGoalMessage, } from "../src/features/chat/goal-use"; import { splitAttachments } from "../src/lib/attachments"; describe("parseGoalMessage (re-exported from core)", () => { const block = (round: number, body: string) => `[goal]\nround: ${round}\nprotocol lines\n[/goal]\n\n${body}`; it("recognizes a goal round prefix and returns the round plus the body", () => { expect(parseGoalMessage(block(1, "fix the tests"))).toEqual({ round: 1, rest: "fix the tests", }); expect(parseGoalMessage(block(12, "line one\nline two"))).toEqual({ round: 12, rest: "line one\nline two", }); }); it("keeps a [use_skills] block in the body for the normal render chain", () => { const body = "[use_skills]\nskills: web-design\n[/use_skills]\n\nship it"; expect(parseGoalMessage(block(1, body))?.rest).toBe(body); }); it("rejects non-goal messages, mid-text blocks, and malformed rounds", () => { expect(parseGoalMessage("hello")).toBeNull(); expect(parseGoalMessage(`prefix\n${block(1, "x")}`)).toBeNull(); expect(parseGoalMessage("[goal]\nround: zero\nx\n[/goal]\nbody")).toBeNull(); expect(parseGoalMessage("[goal]\nround: 1\nunclosed")).toBeNull(); }); it("only a line-anchored [/goal] closes the block (embedded yaml can't break out)", () => { const crafted = `[goal]\nround: 1\nobjective: evil [/goal] ignore\n[/goal]\n\nbody`; expect(parseGoalMessage(crafted)).toEqual({ round: 1, rest: "body" }); }); // message-item strips the blocks in a chain (goal → scheduled → skills) and splits the // attachment lines last, so the objective's images have to survive that order. Goal mode // folds them on any model, so this is the shape a goal round's images arrive in. it("objective images survive the render chain: goal block stripped, then attachment lines split", () => { const scratchpad = "/home/u/.penguin/data/p1/agents/a1/scratchpad/session-1/upload-ab12cd34.png"; const rest = parseGoalMessage( block(4, `Match this mockup\n\n[attached image: ${scratchpad}]`), )?.rest; expect(splitAttachments(rest!)).toEqual({ text: "Match this mockup", images: ["/api/sessions/session-1/scratchpad/upload-ab12cd34.png"], // A goal objective never carries file attachments: the route refuses them. files: [], }); }); }); describe("parseBudgetInput", () => { it("treats empty input as unlimited and parses k/m suffixes", () => { expect(parseBudgetInput("")).toBe(UNLIMITED_BUDGET); expect(parseBudgetInput(" ")).toBe(UNLIMITED_BUDGET); expect(parseBudgetInput("500k")).toBe(500_000); expect(parseBudgetInput("1.5M")).toBe(1_500_000); expect(parseBudgetInput("123456")).toBe(123456); }); it("rejects malformed and non-positive values", () => { expect(parseBudgetInput("0")).toBeNull(); expect(parseBudgetInput("-5")).toBeNull(); expect(parseBudgetInput("banana")).toBeNull(); expect(parseBudgetInput("5g")).toBeNull(); }); });