CoDEVX / app /api /chat /route.test.ts
CodexMacTiger
feat: live package-scoped chat and thinking logs
837e3ac
import { afterEach, describe, expect, it, vi } from "vitest";
const generateLlmText = vi.fn();
vi.mock("@/lib/llm-client", () => ({
generateLlmText,
}));
afterEach(() => {
generateLlmText.mockReset();
});
describe("/api/chat route", () => {
it("returns resolved context and thinking summary for mock package asks", async () => {
const { POST } = await import("./route");
const seed = (await import("@/agentic_pm_demo_codex_plans/data/work-packages.seed.json"))
.default as Array<Record<string, unknown>>;
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: seed,
selectedWorkPackageId: "wp-srs",
parsedCommand: {
referencedPackageName: "System Requirements Specification",
mode: "ask",
instruction: "What does verification method mean?",
},
}),
}),
);
const payload = (await response.json()) as {
boardAction?: { type?: string };
resolvedContext?: {
scope?: string;
workPackageId?: string | null;
mode?: string;
provider?: string;
boardMutationPolicy?: string;
};
thinkingSummary?: string[];
};
expect(payload.boardAction?.type).toBe("none");
expect(payload.resolvedContext).toMatchObject({
scope: "package",
workPackageId: "wp-srs",
mode: "ask",
provider: "mock",
boardMutationPolicy: "none",
});
expect(payload.thinkingSummary?.join("\n")).toContain("Resolved scope to System Requirements Specification.");
expect(payload.thinkingSummary?.join("\n")).toContain("Using mock mode.");
});
it("falls back to package-scoped mock ask guidance when the live model call throws", async () => {
generateLlmText.mockRejectedValueOnce(new Error("Invalid JSON"));
const { POST } = await import("./route");
const seed = (await import("@/agentic_pm_demo_codex_plans/data/work-packages.seed.json"))
.default as Array<Record<string, unknown>>;
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: seed,
selectedWorkPackageId: "wp-srs",
parsedCommand: {
referencedPackageName: "System Requirements Specification",
mode: "ask",
instruction: "What does verification method mean?",
},
llmConfig: {
apiKey: "live-key",
baseUrl: "https://api.example.com/v1",
model: "gpt-test",
},
}),
}),
);
const payload = (await response.json()) as {
assistantMessage: string;
boardAction?: { type?: string };
resolvedContext?: {
scope?: string;
workPackageId?: string | null;
mode?: string;
provider?: string;
boardMutationPolicy?: string;
};
thinkingSummary?: string[];
};
expect(payload.assistantMessage).toContain("For SRS, the objective here is:");
expect(payload.assistantMessage).toContain("verification");
expect(payload.assistantMessage).not.toContain("Network or parsing error calling LLM");
expect(payload.boardAction?.type).toBe("none");
expect(payload.resolvedContext).toMatchObject({
scope: "package",
workPackageId: "wp-srs",
mode: "ask",
provider: "live",
boardMutationPolicy: "none",
});
expect(payload.thinkingSummary?.join("\n")).toContain(
"Provider call failed; preserving the package-scoped fallback.",
);
});
it("falls back to a scoped mock plan update when the live model call throws", async () => {
generateLlmText.mockRejectedValueOnce(new Error("Invalid JSON"));
const { POST } = await import("./route");
const seed = (await import("@/agentic_pm_demo_codex_plans/data/work-packages.seed.json"))
.default as Array<Record<string, unknown>>;
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 steps." },
],
workPackages: seed,
selectedWorkPackageId: "wp-srs",
parsedCommand: {
referencedPackageName: "System Requirements Specification",
mode: "plan",
instruction: "Break this into review steps.",
},
llmConfig: {
apiKey: "live-key",
baseUrl: "https://api.example.com/v1",
model: "gpt-test",
},
}),
}),
);
const payload = (await response.json()) as {
assistantMessage: string;
boardAction?: {
type?: string;
workPackageId?: string | null;
fields?: { tasks?: unknown[]; status?: string };
};
resolvedContext?: {
scope?: string;
workPackageId?: string | null;
mode?: string;
provider?: string;
boardMutationPolicy?: string;
};
thinkingSummary?: 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");
expect(payload.resolvedContext).toMatchObject({
scope: "package",
workPackageId: "wp-srs",
mode: "plan",
provider: "live",
boardMutationPolicy: "selected_package_only",
});
expect(payload.thinkingSummary?.join("\n")).toContain(
"Provider call failed; preserving the package-scoped fallback.",
);
});
it("neutralizes live package updates that target a different work package", async () => {
generateLlmText.mockResolvedValueOnce({
text: JSON.stringify({
assistantMessage: "Changed the wrong package.",
boardAction: {
type: "update",
workPackageId: "wp-final-concept",
fields: {
objective: "This should not land on Final Concept.",
},
},
}),
endpoint: "https://api.example.com/v1/chat/completions",
requestPreview: "request",
responsePreview: "response",
});
const { POST } = await import("./route");
const seed = (await import("@/agentic_pm_demo_codex_plans/data/work-packages.seed.json"))
.default as Array<Record<string, unknown>>;
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: seed,
selectedWorkPackageId: "wp-srs",
parsedCommand: {
referencedPackageName: "System Requirements Specification",
mode: "change",
instruction: "Add cybersecurity acceptance criteria.",
},
llmConfig: {
apiKey: "live-key",
baseUrl: "https://api.example.com/v1",
model: "gpt-test",
},
}),
}),
);
const payload = (await response.json()) as {
boardAction?: { type?: string; workPackageId?: string | null };
thinkingSummary?: string[];
};
expect(payload.boardAction).toMatchObject({
type: "none",
workPackageId: null,
});
expect(payload.thinkingSummary?.join("\n")).toContain(
"Blocked a board update that targeted a different work package.",
);
});
});