fix(labs): strict numeric parsing, atomic confirm guard, test integrity

- normalize.ts num(): parseFloat truncated at the first comma, so "1,200"
  silently became 1 (1000x error) and "5,5" became 5. Now strictly matches
  either US thousands-grouping or a plain number spanning the whole string;
  anything else (incl. ambiguous "5,5") returns null instead of a wrong value.
- labs.ts confirm handler: the pending-status check ran before the request
  body was read, so two concurrent confirms could both pass it and
  double-insert. Added a guarded UPDATE ... WHERE status = 'pending' as the
  first statement inside the existing synchronous transaction; zero rows
  affected throws and the route returns 409, with the fast-path check kept
  for the common case.
- Added missing `await` on two rejects.toThrow assertions (llm.test.ts,
  extract.test.ts) that were previously resolving before the assertion
  settled.
- Bumped the 11th-failed-login rate-limit test to a 30s timeout — 10
  sequential argon2id verifies can exceed bun:test's 5s default under load.
This commit is contained in:
marcuspaico
2026-08-17 16:17:03 -07:00
parent 876aa0f181
commit e81d07975a
7 changed files with 80 additions and 18 deletions

View File

@@ -54,7 +54,7 @@ describe("auth", () => {
// The global window applies to everyone, including a request with the correct password.
const blocked = await app.request("/api/login", json({ password: "hunter2hunter2" }));
expect(blocked.status).toBe(429);
});
}, 30000); // 10 sequential argon2id verifies can exceed the 5s default timeout
test("unknown /api/* returns 404 when authed, 401 when unauthed", async () => {
const app = makeApp();

View File

@@ -41,6 +41,6 @@ describe("extractFromText", () => {
test("malformed LLM output → llm_error, not a crash", async () => {
const mock = (async () => new Response(JSON.stringify({ choices: [{ message: { content: '{"markers": "not an array"}' } }] }), { status: 200 })) as unknown as typeof fetch;
const dir = mkdtempSync(join(tmpdir(), "helios-"));
expect(extractFromText({ db: openDb(dir), key: loadOrCreateKey(dir), fetchImpl: mock }, "x")).rejects.toThrow(/llm_error/);
await expect(extractFromText({ db: openDb(dir), key: loadOrCreateKey(dir), fetchImpl: mock }, "x")).rejects.toThrow(/llm_error/);
});
});

View File

@@ -64,6 +64,13 @@ describe("draft review", () => {
body: JSON.stringify({ collectedDate: "2026-01-15", labName: null, markers: [{ panel: null, name: "X", value: "1", unit: null, referenceRange: null, flagged: false }] }),
});
expect(again.status).toBe(409);
// Second (rejected) confirm must not have inserted a second draw or any
// extra biomarker rows — exactly one draw, one set of biomarkers.
const drawsAfter = await db.select().from(labDraws);
expect(drawsAfter).toHaveLength(1);
const rowsAfter = await db.select().from(biomarkers);
expect(rowsAfter).toHaveLength(2);
});
test("discard marks draft discarded", async () => {

View File

@@ -46,6 +46,6 @@ describe("chatJSON", () => {
test("non-2xx throws llm_error", async () => {
const mock = (async () => new Response("nope", { status: 401 })) as unknown as typeof fetch;
expect(chatJSON(deps(mock), { system: "s", user: "u" })).rejects.toThrow(/llm_error/);
await expect(chatJSON(deps(mock), { system: "s", user: "u" })).rejects.toThrow(/llm_error/);
});
});

View File

@@ -61,4 +61,21 @@ describe("normalizeMarker", () => {
const n = normalizeMarker({ name: "DHEA-S", value: "250", unit: "ug/dL" });
expect(n.valueCanonical).toBeCloseTo(6.78, 1); // 250 * 0.02713
});
test("thousands-grouped value parses as 1200, not 1 (parseFloat truncation bug)", () => {
const n = normalizeMarker({ name: "Vitamin B12", value: "1,200", unit: "pg/mL" });
expect(n.valueNum).toBe(1200);
expect(n.analyteKey).toBe("vitamin_b12");
expect(n.valueCanonical).toBeCloseTo(885.35, 1);
expect(n.canonicalUnit).toBe("pmol/L");
});
test("ambiguous European-decimal-looking value is left unmapped, not silently wrong", () => {
const n = normalizeMarker({ name: "Glucose", value: "5,5", unit: "mg/dL" });
expect(n.valueNum).toBeNull();
expect(n.valueCanonical).toBeNull();
expect(n.value).toBe("5,5"); // raw value preserved
});
test("comparator value still parses after strict-parsing rewrite", () => {
const n = normalizeMarker({ name: "hs-CRP", value: "<0.3", unit: "mg/L" });
expect(n.valueNum).toBeCloseTo(0.3);
});
});