import { afterAll, beforeAll, describe, expect, it } from "vitest"; import http from "node:http"; import type { AddressInfo } from "node:net"; import seedWorkPackages from "@/agentic_pm_demo_codex_plans/data/work-packages.seed.json"; import { parseCommand } from "@/lib/command-parser"; import { resolveChatRequestContext } from "@/lib/chat-request-context"; import type { WorkPackage } from "@/lib/work-package-types"; let server: http.Server; let baseUrl = ""; const workPackages = seedWorkPackages as WorkPackage[]; function buildFakeLlmResponse(joinedMessages: string) { if (joinedMessages.includes("Connection test.")) { return "Connected to the configured model."; } if (joinedMessages.includes('"mode": "ask"')) { return "Verification method explains how the requirement will be checked, such as by test, inspection, or analysis."; } if (joinedMessages.includes('"mode": "plan"')) { return "Please break this package into review tasks."; } if (joinedMessages.includes('"mode": "change"')) { return "Please update the objective to include cybersecurity acceptance criteria."; } return JSON.stringify({ choices: [ { message: { content: JSON.stringify({ assistantMessage: "Fallback JSON response.", boardAction: { type: "none", workPackageId: null }, }), }, }, ], }); } beforeAll(async () => { server = http.createServer((req, res) => { let body = ""; req.on("data", (chunk) => { body += chunk; }); req.on("end", () => { let payload: { messages?: Array<{ content?: string }> } = {}; try { payload = JSON.parse(body || "{}"); } catch { payload = {}; } const joinedMessages = (payload.messages ?? []) .map((message) => String(message.content ?? "")) .join("\n\n"); res.writeHead(200, { "content-type": "text/plain" }); res.end(buildFakeLlmResponse(joinedMessages)); }); }); await new Promise((resolve) => { server.listen(0, "127.0.0.1", () => { const address = server.address() as AddressInfo; baseUrl = `http://127.0.0.1:${address.port}/v1`; resolve(); }); }); }); afterAll(async () => { await new Promise((resolve, reject) => { server.close((error) => { if (error) { reject(error); return; } resolve(); }); }); }); describe("live model integration routes", () => { it("verifies model settings through the connection route", async () => { const { POST } = await import("./test-connection/route"); const response = await POST( new Request("http://localhost/api/test-connection", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ llmConfig: { apiKey: "live-key", baseUrl, model: "fake-model", }, }), }), ); const payload = (await response.json()) as { ok?: boolean; status?: string; message?: string; }; expect(payload.ok).toBe(true); expect(payload.status).toBe("connected"); expect(payload.message).toBe("Connected to the configured model."); }); it("returns a human ask response without board changes when the live model replies in plain text", async () => { const { POST } = await import("./chat/route"); const response = await POST( new Request("http://localhost/api/chat", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ messages: [ { role: "user", content: "@SRS ask What does verification method mean?" }, ], workPackages: seedWorkPackages, selectedWorkPackageId: "wp-srs", parsedCommand: { referencedPackageName: "System Requirements Specification", mode: "ask", instruction: "What does verification method mean?", }, llmConfig: { apiKey: "live-key", baseUrl, model: "fake-model", }, }), }), ); const payload = (await response.json()) as { assistantMessage: string; boardAction?: { type?: string; workPackageId?: string | null }; }; expect(payload.assistantMessage).toContain("Verification method explains"); expect(payload.assistantMessage).not.toContain("Network or parsing error"); expect(payload.assistantMessage.trim().startsWith("{")).toBe(false); expect(payload.boardAction?.type).toBe("none"); expect(payload.boardAction?.workPackageId).toBeNull(); }); it("falls back to a package-scoped plan update when the live model replies in plain text", async () => { const { POST } = await import("./chat/route"); const response = await POST( new Request("http://localhost/api/chat", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ messages: [ { role: "user", content: "@SRS plan Break this into review tasks." }, ], workPackages: seedWorkPackages, selectedWorkPackageId: "wp-srs", parsedCommand: { referencedPackageName: "System Requirements Specification", mode: "plan", instruction: "Break this into review tasks.", }, llmConfig: { apiKey: "live-key", baseUrl, model: "fake-model", }, }), }), ); const payload = (await response.json()) as { assistantMessage: string; boardAction?: { type?: string; workPackageId?: string | null; fields?: { tasks?: unknown[]; status?: string }; }; }; expect(payload.assistantMessage).toContain("Planned next steps for SRS"); expect(payload.boardAction?.type).toBe("update"); expect(payload.boardAction?.workPackageId).toBe("wp-srs"); expect(payload.boardAction?.fields?.tasks?.length).toBeGreaterThan(0); expect(payload.boardAction?.fields?.status).toBe("in_progress"); }); it("falls back to a package-scoped change update when the live model replies in plain text", async () => { const { POST } = await import("./chat/route"); const response = await POST( new Request("http://localhost/api/chat", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ messages: [ { role: "user", content: "@SRS change Add cybersecurity acceptance criteria.", }, ], workPackages: seedWorkPackages, selectedWorkPackageId: "wp-srs", parsedCommand: { referencedPackageName: "System Requirements Specification", mode: "change", instruction: "Add cybersecurity acceptance criteria.", }, llmConfig: { apiKey: "live-key", baseUrl, model: "fake-model", }, }), }), ); const payload = (await response.json()) as { assistantMessage: string; boardAction?: { type?: string; workPackageId?: string | null; fields?: { objective?: string; status?: string }; }; }; expect(payload.assistantMessage).toContain("Updated SRS"); expect(payload.boardAction?.type).toBe("update"); expect(payload.boardAction?.workPackageId).toBe("wp-srs"); expect(payload.boardAction?.fields?.objective).toContain( "Add cybersecurity acceptance criteria", ); expect(payload.boardAction?.fields?.status).toBe("in_progress"); }); it("handles slash plan commands through the same selected-package routing chain used by the client", async () => { const parsed = parseCommand("/plan Break this into review tasks.", workPackages); const selectedWorkPackage = workPackages.find((workPackage) => workPackage.id === "wp-srs"); const resolved = resolveChatRequestContext({ parsed: parsed.parsed, detailOpen: false, selectedWorkPackage, }); expect(resolved.parsedCommand.mode).toBe("plan"); expect(resolved.parsedCommand.referencedPackageName).toBe( selectedWorkPackage?.title, ); const { POST } = await import("./chat/route"); const response = await POST( new Request("http://localhost/api/chat", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ messages: [ { role: "user", content: "/plan Break this into review tasks." }, ], workPackages: seedWorkPackages, selectedWorkPackageId: "wp-srs", parsedCommand: resolved.parsedCommand, llmConfig: { apiKey: "live-key", baseUrl, model: "fake-model", }, }), }), ); const payload = (await response.json()) as { assistantMessage: string; boardAction?: { type?: string; workPackageId?: string | null; fields?: { tasks?: unknown[]; status?: string }; }; }; expect(payload.assistantMessage).toContain("Planned next steps for SRS"); expect(payload.boardAction?.type).toBe("update"); expect(payload.boardAction?.workPackageId).toBe("wp-srs"); expect(payload.boardAction?.fields?.tasks?.length).toBeGreaterThan(0); expect(payload.boardAction?.fields?.status).toBe("in_progress"); }); });