Spaces:
Running
Running
| 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<void>((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<void>((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"); | |
| }); | |
| }); | |