Files
HealthLog/src/lib/insights/__tests__/status-shared.test.ts
T
Marc-André BombeckandClaude Opus 4.8 e6ef9f2a3e fix(insights): tolerate fenced JSON from providers without a JSON mode
Anthropic and the local provider do not send a JSON mode, so a compliant model
still wraps its { "summary": … } reply in a code fence or prefixes a sentence,
which broke JSON.parse and surfaced the raw fenced string as the assessment.
Add a shared stripJsonFences helper (drop the fence, narrow to the first { …
last }) and apply it as a parse fallback in parseSummaryFromContent. No-op on
already-clean JSON.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:58:28 +02:00

63 lines
2.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
parseSummaryFromContent,
stripJsonFences,
} from "@/lib/insights/status-shared";
/**
* The Anthropic + local providers have no native JSON mode, so a compliant
* model still routinely wraps its `{ "summary": … }` reply in a ```json
* fence or prefixes it with a sentence. These guards pin the fence-strip
* fallback so such replies parse instead of surfacing the raw fenced string
* as the user-facing assessment.
*/
describe("stripJsonFences", () => {
it("strips a ```json fence", () => {
const out = stripJsonFences('```json\n{"summary":"ok"}\n```');
expect(out).toBe('{"summary":"ok"}');
});
it("strips a bare ``` fence", () => {
const out = stripJsonFences('```\n{"summary":"ok"}\n```');
expect(out).toBe('{"summary":"ok"}');
});
it("narrows to the first { … last } when prose surrounds the object", () => {
const out = stripJsonFences('Here is the result: {"summary":"ok"} done.');
expect(out).toBe('{"summary":"ok"}');
});
it("is a no-op on already-clean JSON", () => {
expect(stripJsonFences('{"summary":"ok"}')).toBe('{"summary":"ok"}');
});
it("returns the trimmed input when there is no object body", () => {
expect(stripJsonFences(" plain prose ")).toBe("plain prose");
});
});
describe("parseSummaryFromContent", () => {
it("parses a clean JSON envelope", () => {
expect(parseSummaryFromContent('{"summary":"hello"}')).toBe("hello");
});
it("parses a ```json-fenced envelope (no native JSON mode)", () => {
expect(
parseSummaryFromContent('```json\n{"summary":"hello"}\n```'),
).toBe("hello");
});
it("parses a sentence-prefixed envelope", () => {
expect(
parseSummaryFromContent('Sure:\n{"summary":"hello"} — done'),
).toBe("hello");
});
it("falls back to the raw content for bare prose", () => {
expect(parseSummaryFromContent("just prose, no json")).toBe(
"just prose, no json",
);
});
});