/** * KHAELOR * File: tests/cli/print.test.ts * Description: --print helpers — final-answer extraction from the durable stream, log redaction. * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai */ import { describe, expect, it } from "vitest"; import { redactForLog } from "../../src/cli/logger.js"; import { finalAnswerText } from "../../src/cli/print.js"; import { renderResumedTranscript } from "../../src/cli/sessions.js"; import type { DurableEvent } from "../../src/session/index.js"; import { envelope } from "../session/fixtures.js"; function turnEvents(): DurableEvent[] { return [ envelope(1, { type: "user.message-created", payload: { text: "what is in the file?", mentions: [] } }), envelope(2, { type: "model.request-started", payload: { requestId: "req-1", model: "m", purpose: "main", contextStats: { estimatedInputTokens: 1, sections: [] } }, }), envelope(3, { type: "model.text-block-completed", payload: { requestId: "req-1", blockIndex: 0, text: "Let me read it." }, }), envelope(4, { type: "tool.requested", payload: { requestId: "req-1", blockIndex: 1, toolUseId: "t1", toolName: "read", input: { file_path: "x" } }, }), envelope(5, { type: "model.response-completed", payload: { requestId: "req-1", stopReason: "tool_use", usage: { inputTokens: 1, outputTokens: 1, cacheReadTokens: 0, cacheWriteTokens: 0 }, durationMs: 5, }, }), envelope(6, { type: "tool.completed", payload: { toolUseId: "t1", modelText: "codeword: AZURE-FALCON", durationMs: 3, ui: { kind: "read", summary: "Read x" } }, }), envelope(7, { type: "model.request-started", payload: { requestId: "req-2", model: "m", purpose: "main", contextStats: { estimatedInputTokens: 1, sections: [] } }, }), envelope(8, { type: "model.text-block-completed", payload: { requestId: "req-2", blockIndex: 0, text: "The codeword is AZURE-FALCON." }, }), envelope(9, { type: "model.response-completed", payload: { requestId: "req-2", stopReason: "end_turn", usage: { inputTokens: 1, outputTokens: 1, cacheReadTokens: 0, cacheWriteTokens: 0 }, durationMs: 5, }, }), ]; } describe("finalAnswerText", () => { it("returns the text of the last non-tool_use response, not intermediate turns", () => { expect(finalAnswerText(turnEvents())).toBe("The codeword is AZURE-FALCON."); }); it("joins multiple settled blocks in block order", () => { const events = [ ...turnEvents(), envelope(10, { type: "model.request-started", payload: { requestId: "req-3", model: "m", purpose: "main", contextStats: { estimatedInputTokens: 1, sections: [] } }, }), envelope(11, { type: "model.text-block-completed", payload: { requestId: "req-3", blockIndex: 1, text: "second" }, }), envelope(12, { type: "model.text-block-completed", payload: { requestId: "req-3", blockIndex: 0, text: "first" }, }), envelope(13, { type: "model.response-completed", payload: { requestId: "req-3", stopReason: "end_turn", usage: { inputTokens: 1, outputTokens: 1, cacheReadTokens: 0, cacheWriteTokens: 0 }, durationMs: 5, }, }), ]; expect(finalAnswerText(events)).toBe("first\nsecond"); }); it("is empty when no response completed the turn", () => { expect(finalAnswerText(turnEvents().slice(0, 5))).toBe(""); }); }); describe("resumed transcript", () => { it("shows user, assistant, and tool entries compactly", () => { const lines = renderResumedTranscript(turnEvents()).join("\n"); expect(lines).toContain("❯ what is in the file?"); expect(lines).toContain("▸ Read x"); expect(lines).toContain("The codeword is AZURE-FALCON."); }); }); describe("log redaction", () => { it("removes API keys and authorization material", () => { const dirty = "request failed authorization: Bearer abc123def456ghi with key sk-ant-api03-SECRETSECRET and x-api-key: sk-ant-other"; const clean = redactForLog(dirty); expect(clean).not.toContain("sk-ant-api03-SECRETSECRET"); expect(clean).not.toContain("abc123def456ghi"); expect(clean).toContain("[redacted]"); }); it("leaves ordinary text untouched", () => { expect(redactForLog("tool read completed in 12ms")).toBe("tool read completed in 12ms"); }); });