test: cover client API and message parsing

This commit is contained in:
Federico Jaramillo Martinez
2026-05-08 08:46:13 +02:00
parent 9ce13df62d
commit bb52a46fa6
2 changed files with 123 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
import { describe, expect, it } from "vitest";
import { parseCommandResult, parseFileContentResponse, parseFileSuggestion, parseGitStatusResponse, parseMessagePage, parseSessionStatus, parseSlashCommand } from "./parsers";
describe("API parsers", () => {
it("accepts legacy array message pages and paged message responses", () => {
expect(parseMessagePage(["a", "b"])).toEqual({ messages: ["a", "b"], start: 0, total: 2 });
expect(parseMessagePage({ messages: ["c"], start: 3, total: 9 })).toEqual({ messages: ["c"], start: 3, total: 9 });
});
it("validates session status including optional model and nullable context usage", () => {
expect(parseSessionStatus({
sessionId: "s1",
isStreaming: false,
isCompacting: true,
isBashRunning: false,
pendingMessageCount: 2,
tokens: { input: 1, output: 2, cacheRead: 3, cacheWrite: 4, total: 10 },
cost: 0.12,
model: { provider: "p", id: "m", contextWindow: 100, reasoning: { effort: "low" } },
contextUsage: { tokens: null, contextWindow: 100, percent: 0.5 },
thinkingLevel: "medium",
})).toEqual({
sessionId: "s1",
isStreaming: false,
isCompacting: true,
isBashRunning: false,
pendingMessageCount: 2,
tokens: { input: 1, output: 2, cacheRead: 3, cacheWrite: 4, total: 10 },
cost: 0.12,
model: { provider: "p", id: "m", contextWindow: 100, reasoning: { effort: "low" } },
contextUsage: { tokens: null, contextWindow: 100, percent: 0.5 },
thinkingLevel: "medium",
});
});
it("rejects invalid enum-like fields", () => {
expect(() => parseSlashCommand({ name: "bad", source: "remote" })).toThrow("Invalid command source");
expect(() => parseFileSuggestion({ path: "a", kind: "deleted" })).toThrow("Invalid file kind");
expect(() => parseGitStatusResponse({ isGitRepo: true, hash: "h", files: [{ path: "a", index: "weird", workingTree: "modified" }] })).toThrow("Invalid git file state");
});
it("validates file content responses", () => {
expect(parseFileContentResponse({
path: "README.md",
language: "markdown",
encoding: "utf8",
size: 4,
modifiedAt: "now",
content: "text",
truncated: false,
binary: false,
})).toMatchObject({ path: "README.md", language: "markdown", content: "text" });
expect(() => parseFileContentResponse({ encoding: "base64" })).toThrow("Invalid file encoding");
});
it("parses command result variants", () => {
expect(parseCommandResult({ type: "unsupported", message: "nope" })).toEqual({ type: "unsupported", message: "nope" });
expect(parseCommandResult({ type: "select", requestId: "r1", title: "Pick", options: [{ value: "v", label: "Label", description: "desc" }] })).toEqual({ type: "select", requestId: "r1", title: "Pick", options: [{ value: "v", label: "Label", description: "desc" }] });
expect(parseCommandResult({ type: "done", message: "ok" })).toEqual({ type: "done", message: "ok" });
expect(() => parseCommandResult({ type: "later" })).toThrow("Invalid command result type");
});
});
+60
View File
@@ -0,0 +1,60 @@
import { describe, expect, it } from "vitest";
import { appendText, normalizeMessage, normalizeMessages, textMessage } from "./chatMessages";
describe("chat message normalization", () => {
it("normalizes simple text messages and drops empty content", () => {
expect(normalizeMessages([
{ role: "user", content: "hello" },
{ role: "assistant", content: "" },
{ role: "unknown", content: "system text" },
])).toEqual([
textMessage("user", "hello"),
textMessage("system", "system text"),
]);
});
it("normalizes tool calls and tool results", () => {
expect(normalizeMessage({ role: "assistant", content: [{ type: "toolCall", name: "bash", arguments: { command: "npm test" } }] })).toEqual([
{ role: "assistant", parts: [{ type: "toolCall", toolName: "bash", summary: "npm test" }] },
]);
expect(normalizeMessage({ role: "toolResult", toolName: "bash", isError: true, content: [{ type: "text", text: "failed" }] })).toEqual([
{ role: "tool", parts: [{ type: "toolResult", toolName: "bash", text: "failed", isError: true }] },
]);
});
it("extracts skill invocation blocks into dedicated skill and user messages", () => {
expect(normalizeMessage({ role: "user", content: "<skill name=\"playwright\" location=\"/skills/playwright\">\nUse browser\n</skill>\n\nNow test the UI" })).toEqual([
{ role: "user", parts: [{ type: "skillInvocation", name: "playwright", location: "/skills/playwright", content: "Use browser" }] },
textMessage("user", "Now test the UI"),
]);
});
it("formats bash execution records as bash chat lines", () => {
expect(normalizeMessage({
role: "bashExecution",
command: "npm test",
excludeFromContext: true,
output: "ok",
exitCode: 0,
truncated: true,
fullOutputPath: "/tmp/out.log",
})).toEqual([
textMessage("bash", "excluded from context\n\n$ npm test\n\nok\n\nexit 0\n\noutput truncated\n\nfull output: /tmp/out.log"),
]);
});
});
describe("appendText", () => {
it("appends to the previous same-role text message", () => {
expect(appendText([textMessage("assistant", "hello")], "assistant", " world")).toEqual([
textMessage("assistant", "hello world"),
]);
});
it("starts a new message when role or last part does not match", () => {
expect(appendText([textMessage("user", "hello")], "assistant", "hi")).toEqual([
textMessage("user", "hello"),
textMessage("assistant", "hi"),
]);
});
});