Files
HealthLog/src/lib/logging/__tests__/in-memory-buffer.test.ts
T
Marc-André BombeckandClaude Opus 4.7 a66c128dd9 feat(insights): citation footnote on recommendations with medical reference
When a recommendation carries a `referenceId` pointing into the
curated medical-reference bundle, render a small footnote underneath
linking to the guideline. Format: "Source: ESH 2023 — ESH 2023
Guidelines for the Management of Arterial Hypertension" (locale-
routed title), with a tiny external-link icon, opens in a new tab via
target="_blank" rel="noreferrer". Per the v1.4.16 research doc §7
the URL never opens in an in-app webview.

`InsightResult.recommendations` widens to accept either the legacy
plain-string shape OR a structured `{ text, referenceId? }` object so
existing rendering paths keep working while the new citation surface
plugs in through the same component. `getMedicalReferenceById()`
returns undefined for unknown ids — the UI silently drops the
footnote in that case rather than rendering a broken link (defence
in depth on top of the schema validation).

i18n keys `insights.recommendation.source` and
`insights.recommendation.viewSource` land in both EN and DE.

6 component tests pin the no-footnote / EN-locale / DE-locale /
unknown-id-defence / multi-rec paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 00:04:24 +02:00

120 lines
4.0 KiB
TypeScript

import { describe, expect, it, beforeEach } from "vitest";
import {
appendLogEvent,
readLogBuffer,
clearLogBuffer,
LOG_BUFFER_MAX,
} from "../in-memory-buffer";
import type { WideEvent } from "../types";
function makeEvent(overrides: Partial<WideEvent> = {}): WideEvent {
return {
timestamp: new Date().toISOString(),
duration_ms: 12,
request_id: "req-" + Math.random().toString(16).slice(2),
trace_id: "trace-" + Math.random().toString(16).slice(2),
level: "info",
kind: "http",
service: "healthlog",
environment: "test",
...overrides,
};
}
describe("in-memory log buffer", () => {
beforeEach(() => {
clearLogBuffer();
});
it("returns nothing when empty", () => {
expect(readLogBuffer({})).toEqual([]);
});
it("stores appended events and returns them newest-first", () => {
const a = makeEvent({ timestamp: "2026-05-09T20:00:00.000Z" });
const b = makeEvent({ timestamp: "2026-05-09T20:01:00.000Z" });
const c = makeEvent({ timestamp: "2026-05-09T20:02:00.000Z" });
appendLogEvent(a);
appendLogEvent(b);
appendLogEvent(c);
const out = readLogBuffer({});
expect(out).toHaveLength(3);
// Newest-first
expect(out[0].timestamp).toBe(c.timestamp);
expect(out[1].timestamp).toBe(b.timestamp);
expect(out[2].timestamp).toBe(a.timestamp);
});
it("FIFO-evicts when capacity is exceeded (cap = LOG_BUFFER_MAX)", () => {
expect(LOG_BUFFER_MAX).toBe(500);
for (let i = 0; i < 510; i++) {
appendLogEvent(makeEvent({ request_id: `req-${i}` }));
}
const out = readLogBuffer({});
expect(out).toHaveLength(500);
// oldest 10 events were dropped — req-0..req-9 must NOT be present
const ids = new Set(out.map((e) => e.request_id));
expect(ids.has("req-0")).toBe(false);
expect(ids.has("req-9")).toBe(false);
expect(ids.has("req-10")).toBe(true);
expect(ids.has("req-509")).toBe(true);
});
it("filters by traceId", () => {
appendLogEvent(makeEvent({ trace_id: "alpha" }));
appendLogEvent(makeEvent({ trace_id: "beta" }));
appendLogEvent(makeEvent({ trace_id: "alpha" }));
const out = readLogBuffer({ traceId: "alpha" });
expect(out).toHaveLength(2);
expect(out.every((e) => e.trace_id === "alpha")).toBe(true);
});
it("filters by level", () => {
appendLogEvent(makeEvent({ level: "info" }));
appendLogEvent(makeEvent({ level: "warn" }));
appendLogEvent(makeEvent({ level: "error" }));
expect(readLogBuffer({ level: "warn" })).toHaveLength(1);
expect(readLogBuffer({ level: "error" })).toHaveLength(1);
});
it("filters by action substring (case-insensitive)", () => {
appendLogEvent(makeEvent({ action: { name: "measurement.create" } }));
appendLogEvent(makeEvent({ action: { name: "auth.login" } }));
appendLogEvent(makeEvent({ action: { name: "measurement.delete" } }));
const out = readLogBuffer({ action: "measurement" });
expect(out).toHaveLength(2);
expect(out.every((e) => e.action?.name?.startsWith("measurement"))).toBe(
true,
);
// Case-insensitive
expect(readLogBuffer({ action: "AUTH" })).toHaveLength(1);
});
it("filters by since/until window", () => {
appendLogEvent(makeEvent({ timestamp: "2026-05-09T10:00:00.000Z" }));
appendLogEvent(makeEvent({ timestamp: "2026-05-09T12:00:00.000Z" }));
appendLogEvent(makeEvent({ timestamp: "2026-05-09T14:00:00.000Z" }));
const out = readLogBuffer({
since: new Date("2026-05-09T11:00:00.000Z"),
until: new Date("2026-05-09T13:00:00.000Z"),
});
expect(out).toHaveLength(1);
expect(out[0].timestamp).toBe("2026-05-09T12:00:00.000Z");
});
it("respects the limit option", () => {
for (let i = 0; i < 50; i++) {
appendLogEvent(makeEvent({ request_id: `r-${i}` }));
}
expect(readLogBuffer({ limit: 5 })).toHaveLength(5);
});
it("clears the buffer", () => {
appendLogEvent(makeEvent());
appendLogEvent(makeEvent());
expect(readLogBuffer({})).toHaveLength(2);
clearLogBuffer();
expect(readLogBuffer({})).toHaveLength(0);
});
});