import { describe, expect, it } from "vitest"; import { isCompleteJson, salvageTruncatedPayload, } from "../src/llm/truncation.js"; describe("isCompleteJson", () => { it("accepts complete objects and empty", () => { expect(isCompleteJson('{"content":"x"}')).toBe(true); expect(isCompleteJson("")).toBe(true); }); it("rejects truncated objects", () => { expect(isCompleteJson('{"content":"第一章')).toBe(false); }); }); describe("salvageTruncatedPayload", () => { it("returns null when the field never started", () => { expect(salvageTruncatedPayload('{"content', ["content"])).toBeNull(); expect(salvageTruncatedPayload('{"other":"x"}', ["content"])).toBeNull(); }); it("keeps the generated text before the cut", () => { const raw = '{"content":"第一章\\n她推开门'; expect(salvageTruncatedPayload(raw, ["content"])).toEqual({ field: "content", text: "第一章\n她推开门", }); }); it("stops at the closing quote when the value is complete", () => { const raw = '{"content":"正文","outputTags":["a"'; expect(salvageTruncatedPayload(raw, ["content"])?.text).toBe("正文"); }); it("tries fields in order", () => { const raw = '{"display_stream":"走了","content":"没到"'; expect(salvageTruncatedPayload(raw, ["content", "display_stream"])?.field).toBe( "content", ); expect( salvageTruncatedPayload('{"display_stream":"走了', [ "content", "display_stream", ])?.field, ).toBe("display_stream"); }); it("decodes unicode escapes and skips dangling escapes", () => { expect( salvageTruncatedPayload('{"content":"\\u7b2c\\u4e00\\u7ae0', [ "content", ])?.text, ).toBe("第一章"); expect( salvageTruncatedPayload('{"content":"收尾\\', ["content"])?.text, ).toBe("收尾"); }); });