mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
Largest feature delta in the v1.4.x line. Insights expands into seven dedicated metric sub-pages; GLP-1 medication tracking lands end-to-end (drug-level chart with Research Mode acknowledgment, EMA titration ladder, cadence + compliance, side-effect taxonomy, pen/vial inventory); cross-source priority becomes a two-axis resolver; per-user timezone threads through ten analytics surfaces; Withings activity + sleep v2 sync; onboarding rebuilt as a nested-route wizard; Personal Records ship end-to-end; Health Score provenance accordion; Coach runs native first-party prompts across six locales with a 1800-assertion refusal-probe matrix; OpenAPI drift gate hard-fails; multi-arch GHCR image. Migrations 0043 through 0060 are additive and forward-only. PROMPT_VERSION 4.24.0 → 4.25.0 with GROUND RULE 9 (refuses dose recommendations) and GROUND RULE 15 (refuses drug-level estimates with MDR + MDCG 2021-24 cites). See CHANGELOG.md for the full per-section delta.
113 lines
3.7 KiB
TypeScript
113 lines
3.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { WideEventBuilder } from "../event-builder";
|
|
|
|
describe("WideEventBuilder.setHttp", () => {
|
|
it("stores plain http metadata as-is", () => {
|
|
const evt = new WideEventBuilder("http");
|
|
evt.setHttp({
|
|
method: "GET",
|
|
path: "/api/health",
|
|
route: "/api/health",
|
|
status: 200,
|
|
});
|
|
const json = evt.toJSON();
|
|
expect(json.http).toMatchObject({
|
|
method: "GET",
|
|
path: "/api/health",
|
|
route: "/api/health",
|
|
status: 200,
|
|
});
|
|
});
|
|
|
|
// Fix-J (v1.4.25 W21, sec-C1): WITHINGS_WEBHOOK_SECRET lands in
|
|
// http.path on every request without this scrub. Verify the redaction
|
|
// is applied at setHttp time so the secret never reaches stdout, the
|
|
// in-memory ring buffer, or Loki.
|
|
it("redacts Withings webhook secret from http.path and http.route", () => {
|
|
const evt = new WideEventBuilder("http");
|
|
evt.setHttp({
|
|
method: "POST",
|
|
path: "/api/withings/webhook/test-secret",
|
|
route: "/api/withings/webhook/test-secret",
|
|
status: 200,
|
|
});
|
|
const http = evt.toJSON().http;
|
|
expect(http?.path).toBe("/api/withings/webhook/[REDACTED]");
|
|
expect(http?.route).toBe("/api/withings/webhook/[REDACTED]");
|
|
});
|
|
|
|
it("preserves trailing path segments after the redacted secret", () => {
|
|
const evt = new WideEventBuilder("http");
|
|
evt.setHttp({
|
|
method: "GET",
|
|
path: "/api/withings/webhook/sup3r-secr3t/verify",
|
|
route: "/api/withings/webhook/[token]/verify",
|
|
status: 200,
|
|
});
|
|
const http = evt.toJSON().http;
|
|
expect(http?.path).toBe("/api/withings/webhook/[REDACTED]/verify");
|
|
// The Next.js route template `[token]` does not match the
|
|
// path-segment rule's `<segment>` shape boundary (the brackets are
|
|
// treated as a regular segment), so it is still redacted; either
|
|
// shape is acceptable since the goal is to never leak the secret.
|
|
expect(http?.route).toBe("/api/withings/webhook/[REDACTED]/verify");
|
|
});
|
|
|
|
it("does not over-redact unrelated paths", () => {
|
|
const evt = new WideEventBuilder("http");
|
|
evt.setHttp({
|
|
method: "GET",
|
|
path: "/api/measurements/123",
|
|
route: "/api/measurements/[id]",
|
|
status: 200,
|
|
});
|
|
const http = evt.toJSON().http;
|
|
expect(http?.path).toBe("/api/measurements/123");
|
|
expect(http?.route).toBe("/api/measurements/[id]");
|
|
});
|
|
|
|
it("still scrubs query-string secrets via the existing rule", () => {
|
|
const evt = new WideEventBuilder("http");
|
|
evt.setHttp({
|
|
method: "POST",
|
|
path: "/api/withings/webhook?secret=abc123",
|
|
route: "/api/withings/webhook",
|
|
status: 200,
|
|
});
|
|
const http = evt.toJSON().http;
|
|
expect(http?.path).toBe("/api/withings/webhook?secret=[REDACTED]");
|
|
expect(http?.route).toBe("/api/withings/webhook");
|
|
});
|
|
|
|
it("preserves other http fields verbatim", () => {
|
|
const evt = new WideEventBuilder("http");
|
|
evt.setHttp({
|
|
method: "POST",
|
|
path: "/api/withings/webhook/leak",
|
|
route: "/api/withings/webhook/leak",
|
|
status: 200,
|
|
user_agent: "WithingsClient/1.0",
|
|
ip: "10.0.0.1",
|
|
content_length: 42,
|
|
response_size: 17,
|
|
});
|
|
const http = evt.toJSON().http;
|
|
expect(http).toMatchObject({
|
|
method: "POST",
|
|
path: "/api/withings/webhook/[REDACTED]",
|
|
route: "/api/withings/webhook/[REDACTED]",
|
|
status: 200,
|
|
user_agent: "WithingsClient/1.0",
|
|
ip: "10.0.0.1",
|
|
content_length: 42,
|
|
response_size: 17,
|
|
});
|
|
});
|
|
|
|
it("handles an undefined http payload without throwing", () => {
|
|
const evt = new WideEventBuilder("http");
|
|
evt.setHttp(undefined);
|
|
expect(evt.toJSON().http).toBeUndefined();
|
|
});
|
|
});
|