Merge branch 'fix/v147-nightly' into release/v1.47.0

This commit is contained in:
Marc-André Bombeck committed 2026-11-03 22:30:50 +01:00
commit 7cb67c2e75
61 files changed
+2983 -2280

No files matched your search

+11
View File
@@ -284,3 +284,14 @@ earns its place; do not extend the date.
Order dependence is looked for on purpose: the nightly workflow runs both
suites with `--sequence.shuffle` and prints the seed. Replay a failure with
`--sequence.shuffle --sequence.seed=<seed>`.
A case resets or states what it reads: `vi.resetAllMocks()` (or
`mockReset()`) where a case swaps in its own implementation, since
`clearAllMocks` keeps it and a `mockResolvedValueOnce` the code never consumed
answers the next case; a module cache dropped through its own invalidator; a
row the case needs written by the case or its own `beforeAll`. A file that is
one walk by design (a parity sequence, a migration's before and after, a
dry run followed by the import and the same file again) says so in a comment
and passes `{ shuffle: false }` to its `describe`, which keeps that block in
order while the rest of the suite still shuffles. Wrap the whole file in one
such block when its top-level blocks build on each other.
@@ -60,6 +60,14 @@ import { GET } from "../route";
import { prisma } from "@/lib/db";
import { requireAdmin } from "@/lib/api-handler";
// Reset, not clear, before every case: cases replace the paused rows, the
// settings row and the account groups, and a cleared mock would hand the
// next case whatever the last one left there. A reset mock goes back to the
// empty answer it was created with.
beforeEach(() => {
vi.resetAllMocks();
});
const groupBy = vi.mocked(prisma.providerHealth.groupBy);
const usageRows = vi.mocked(prisma.coachUsage.findMany);
@@ -101,7 +109,6 @@ function group(
describe("GET /api/admin/provider-health", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(requireAdmin).mockResolvedValue({
user: { id: "admin1" },
} as never);
@@ -302,7 +309,6 @@ describe("GET /api/admin/provider-health — pause and model check (v1.42)", ()
const settings = vi.mocked(prisma.appSettings.findUnique);
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(requireAdmin).mockResolvedValue({
user: { id: "admin" },
} as never);
@@ -131,7 +131,10 @@ function putReq(
}
beforeEach(() => {
vi.clearAllMocks();
// Reset, not clear: cases replace the profile reads and the question store
// with their own implementations, and a cleared mock keeps them. A reset
// mock returns to the implementation it was created with.
vi.resetAllMocks();
requireModuleEnabled.mockResolvedValue({ enabled: true });
});
@@ -174,6 +174,10 @@ beforeEach(() => {
vi.mocked(isModuleEnabled).mockResolvedValue(true);
vi.mocked(requireModuleEnabled).mockResolvedValue({ enabled: true } as never);
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
// A session that signed in just now, the factory's own answer. Reset, not
// only cleared: a case below swaps in a stale session, and `clearAllMocks`
// keeps the implementation it left behind for whichever case runs next.
vi.mocked(prisma.session.findUnique).mockReset();
vi.mocked(checkRateLimit).mockResolvedValue({
allowed: true,
limit: 60,
@@ -84,6 +84,10 @@ beforeEach(() => {
vi.clearAllMocks();
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
vi.mocked(validateAttachmentCandidate).mockResolvedValue({ ok: true });
// Open again for every case (the factory's own answer): the detach case
// closes every capability, and `clearAllMocks` would carry that refusal
// into whichever case runs next.
vi.mocked(requireAiCapability).mockReset();
});
describe("POST attach", () => {
@@ -276,7 +276,10 @@ function chatReq(body: Record<string, unknown>): Request {
describe("coach chat — tool-mode routing (F1)", () => {
beforeEach(() => {
vi.clearAllMocks();
// Reset, not clear: cases swap in their own tool loop and parsers, and a
// cleared mock would hand those to the next case. Every mock here goes
// back to the implementation it was created with.
vi.resetAllMocks();
reserveBudget.mockResolvedValue({
allowed: true,
reserved: 3000,
@@ -52,6 +52,12 @@ import { primeLocales } from "@/__tests__/helpers/prime-locales";
primeLocales("de");
// Every case starts on an account that never set a module, whichever case
// ran before it and left its own modules behind.
beforeEach(() => {
modulesRef.value = undefined;
});
function renderSSR(node: React.ReactElement, locale: "en" | "de" = "de") {
return renderToStaticMarkup(
<I18nProvider initialLocale={locale}>{node}</I18nProvider>,
@@ -162,10 +168,6 @@ describe("<DashboardHeader> — quick-add menu", () => {
});
describe("<DashboardHeader> — Log workout", () => {
beforeEach(() => {
modulesRef.value = undefined;
});
it("offers it beside the other capture entries", () => {
modulesRef.value = { workouts: true };
const html = renderSSR(
@@ -187,10 +189,6 @@ describe("<DashboardHeader> — Log workout", () => {
});
describe("<DashboardHeader> — every entry follows its module, as the capture picker does", () => {
beforeEach(() => {
modulesRef.value = undefined;
});
it("withholds Log mood when the mood module is off", () => {
modulesRef.value = { mood: false };
const html = renderSSR(
@@ -5,7 +5,7 @@
* off the illness page, its settings and every picker once a record held more
* than that, and they could then be found only through search.
*/
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
const calls = vi.hoisted(() => [] as string[]);
@@ -41,6 +41,12 @@ vi.mock("@/lib/api/api-fetch", () => ({
import { useIllnessEpisodes } from "../use-illness";
describe("useIllnessEpisodes", () => {
// Each case states its own journal and counts only its own requests.
beforeEach(() => {
calls.length = 0;
journal.total = 230;
});
it("reads page after page until the whole journal is in", async () => {
const options = useIllnessEpisodes(true) as unknown as {
queryFn: () => Promise<{ id: string }[]>;
@@ -55,7 +61,6 @@ describe("useIllnessEpisodes", () => {
});
it("stops on a full last page the route says is the end", async () => {
calls.length = 0;
journal.total = 200;
const options = useIllnessEpisodes(true) as unknown as {
queryFn: () => Promise<{ id: string }[]>;
@@ -63,6 +68,5 @@ describe("useIllnessEpisodes", () => {
expect(await options.queryFn()).toHaveLength(200);
// Two full pages of 100, no third request for an empty one.
expect(calls).toHaveLength(2);
journal.total = 230;
});
});
@@ -289,7 +289,10 @@ describe("<MeasurementForm> type", () => {
await first.screen.getByLabelText("Value (kg)").fill("80");
await first.save.click();
await expect.poll(() => first.onSuccess.mock.calls.length).toBe(1);
first.screen.unmount();
// Awaited: the unmount runs inside `act`, and one left running overlaps
// the next render's `act`, after which React commits nothing for the
// rest of the file (every later case finds an empty container).
await first.screen.unmount();
const { screen } = await mount();
await expect
@@ -242,6 +242,11 @@ describe("useUrlFilterSync with FilterBar", () => {
window.history.back();
await expectSearch("?type=WEIGHT");
await expect.element(pill("Type: Weight")).toBeInTheDocument();
// Back to the newest entry. Left one step behind, the page keeps a
// forward entry, and the next case's push replaces it instead of adding
// one: `history.length` stays put and its count of entries is off by one.
window.history.forward();
await expectSearch("?type=PULSE");
});
it("keeps the state in memory and the address untouched with syncToUrl off", async () => {
@@ -94,6 +94,9 @@ describe("buildCoachSnapshot — Apple Health additive metrics (v1.4.23)", () =>
// for 60 s; reset between tests so each fixture is read fresh.
__resetCoachSnapshotCacheForTests();
resolveModuleMapMock.mockResolvedValue(allModulesEnabled());
// No readings unless a case seeds its own; the case that only inspects
// the query used to inherit whatever rows the case before it left.
prismaMock.measurement.findMany.mockResolvedValue([]);
prismaMock.moodEntry.findMany.mockResolvedValue([]);
prismaMock.medicationIntakeEvent.findMany.mockResolvedValue([]);
prismaMock.medication.findMany.mockResolvedValue([]);
@@ -129,9 +129,27 @@ function day(): Event[] {
return events.sort((a, b) => a.at.localeCompare(b.at));
}
const ALL_SCOPES_AT_WARM = new Set<string>();
/**
* Every scope the day dirties: run it once on `full` with no warm, which
* enqueues each scope at least once. Each case derives it itself rather than
* reading what an earlier case left behind.
*/
async function discoverWarmScopes(): Promise<Set<string>> {
sim.notes.clear();
sim.level = "FULL";
for (const event of day()) {
vi.setSystemTime(new Date(`2026-10-15T${event.at}:00Z`));
await invalidateStatusInsightsForTypes("u1", event.types, {
source: event.source,
});
}
return new Set(sim.notes.keys());
}
async function simulate(level: string | null): Promise<{
async function simulate(
level: string | null,
warmScopes: ReadonlySet<string>,
): Promise<{
warm: number;
ingest: number;
bySource: Record<"sync" | "manual", number>;
@@ -141,8 +159,8 @@ async function simulate(level: string | null): Promise<{
sim.level = level;
// The 04:30 warm regenerates every scope the day will touch.
const warmAt = Date.parse("2026-10-15T04:30:00Z");
for (const scope of ALL_SCOPES_AT_WARM) sim.notes.set(scope, warmAt);
const warm = ALL_SCOPES_AT_WARM.size;
for (const scope of warmScopes) sim.notes.set(scope, warmAt);
const warm = warmScopes.size;
const bySource = { sync: 0, manual: 0 };
for (const event of day()) {
vi.setSystemTime(new Date(`2026-10-15T${event.at}:00Z`));
@@ -161,25 +179,14 @@ beforeEach(() => {
describe("generations per day on a watch-synced account", () => {
it("counts the scopes once to seed the warm", async () => {
// Discover every scope the day dirties: run it once on `full` with no
// warm, which enqueues each scope at least once.
ALL_SCOPES_AT_WARM.clear();
sim.notes.clear();
sim.level = "FULL";
for (const event of day()) {
vi.setSystemTime(new Date(`2026-10-15T${event.at}:00Z`));
await invalidateStatusInsightsForTypes("u1", event.types, {
source: event.source,
});
}
for (const scope of sim.notes.keys()) ALL_SCOPES_AT_WARM.add(scope);
expect(ALL_SCOPES_AT_WARM.size).toBe(19);
expect((await discoverWarmScopes()).size).toBe(19);
});
it("before v1.43 (every ingest at one hour) versus each level", async () => {
const full = await simulate("FULL"); // the behaviour before v1.43
const standard = await simulate("STANDARD");
const frugal = await simulate("FRUGAL");
const warmScopes = await discoverWarmScopes();
const full = await simulate("FULL", warmScopes); // the behaviour before v1.43
const standard = await simulate("STANDARD", warmScopes);
const frugal = await simulate("FRUGAL", warmScopes);
const result = { full, standard, frugal };
process.stdout.write(`SIMULATION ${JSON.stringify(result)}\n`);
@@ -34,6 +34,7 @@ import {
resolveReadOnlyStatusMiss,
} from "@/lib/insights/status-cache";
import { runStatusCompletion } from "@/lib/insights/status-provider";
import { invalidateUserTimezone } from "@/lib/tz/resolver";
import {
resolveDerivedAssessment,
generateDerivedScoreAssessment,
@@ -71,6 +72,9 @@ function okDerived(): Derived<unknown> {
beforeEach(() => {
vi.resetAllMocks();
// The zone resolver keeps a per-user cache for a minute; without this the
// first case to run fills it and every later one skips the read.
invalidateUserTimezone("u-ny");
findUnique.mockResolvedValue({ timezone: "America/New_York" });
});
@@ -248,6 +248,13 @@ describe("handleDataBackup on a large instance", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.store.mockResolvedValue(undefined);
// Stated here rather than inherited: without a payload the pass stores
// nothing, and these cases used to pass only when another block's setup
// had run first and left its payload behind.
mocks.buildFullBackupPayload.mockResolvedValue({
payload: { schemaVersion: "1", userId: "user-dr" },
counts: {},
});
});
it("backs up the accounts with the oldest copies first and stops on the budget", async () => {
+20 -1
View File
@@ -244,11 +244,30 @@ describe("least-squares line", () => {
return;
}
expect(fit.r2).toBeGreaterThanOrEqual(0);
expect(fit.r2).toBeLessThanOrEqual(1 + 1e-9);
expect(fit.r2).toBeLessThanOrEqual(1);
}),
);
});
// Shrunk counterexamples of the property above from two nightly seeds
// (802398975 and 1070990592): two points on a line far from zero, where
// the centred sums rounded r² above 1.
it.each([
[
[0, 1],
[-999.87, -999.97],
],
[
[0, -1],
[9996.28, 9993.55],
],
])("keeps r² at most 1 for x %j, y %j", (xs, ys) => {
const fit = linearFit(xs, ys)!;
expect(fit.r2).not.toBeNull();
expect(fit.r2!).toBeLessThanOrEqual(1);
expect(fit.r2!).toBeGreaterThan(0.999);
});
it("refuses a vertical line instead of inventing a slope", () => {
assertProperty(
fc.property(
+7 -2
View File
@@ -48,7 +48,9 @@ export function fitFromSums(sums: FitSums): LinearFit | null {
if (n < 2) return null;
const sxx = sumXx - (sumX * sumX) / n;
const sxy = sumXy - (sumX * sumY) / n;
const syy = sumYy - (sumY * sumY) / n;
// A sum of squares is never negative; the subtraction can still land a
// hair below zero when every y sits close to one large value.
const syy = Math.max(0, sumYy - (sumY * sumY) / n);
if (sxx === 0) return null;
const slope = sxy / sxx;
const meanX = sumX / n;
@@ -56,7 +58,10 @@ export function fitFromSums(sums: FitSums): LinearFit | null {
return {
slope,
intercept: meanY - slope * meanX,
r2: syy === 0 ? null : (sxy * sxy) / (sxx * syy),
// r² is at most 1 by Cauchy–Schwarz. With y far from zero and little
// spread (two weighings of about 1000, a tenth apart) the centred sums
// lose their last digits and the ratio came out at 1.00000004.
r2: syy === 0 ? null : Math.min(1, (sxy * sxy) / (sxx * syy)),
n,
meanX,
meanY,
@@ -0,0 +1,410 @@
/**
* The record-shaped backup slices, one rule at a time: the health score
* day, the intraday curve, a medication with its composition and custom
* category, an intake row, and the cycle's own symptoms.
*
* Like `backup-slices.test.ts`, each rule meets the value just inside and
* just outside it. The defaults are pinned as well: a file written before a
* field existed restores through them, so a default that drifted would
* change what an older file means without any parse failing.
*/
import { describe, expect, it } from "vitest";
import type { z } from "zod/v4";
import { customCycleSymptomSchema } from "@/lib/cycle/backup-schema";
import { healthScoreRecordBackupSchema } from "@/lib/export/health-score-backup-schema";
import { intradayProfileBackupSchema } from "@/lib/export/intraday-profile-backup-schema";
import {
customMedicationCategorySchema,
intakeEventSchema,
medicationIngredientSchema,
medicationSchema,
medicationSideEffectSchema,
} from "@/lib/export/medications-backup-schema";
/** The paths a value is refused on, or `[]` when it parses. */
function refused(schema: z.ZodType, value: unknown): string[] {
const result = schema.safeParse(value);
return result.success
? []
: result.error.issues.map((issue) => issue.path.join("."));
}
function parsed<T>(schema: z.ZodType<T>, value: unknown): T {
return schema.parse(value);
}
const AT = "2026-07-01T08:00:00.000Z";
const BAD_DAY_KEYS = [
"x2026-07-01",
"2026-07-01x",
"226-07-01",
"2026-7-01",
"2026-07-1",
"abcd-07-01",
"2026-ab-01",
"2026-07-ab",
];
describe("the health score day", () => {
const day = {
id: "hs-1",
dayKey: "2026-07-01",
timezone: "Europe/Berlin",
composite: 72,
band: "green",
scoreVersion: 3,
composition: ["sleep", "activity"],
pillarScores: { sleep: 80 },
inputFingerprint: "a".repeat(64),
computedAt: AT,
};
it("takes a well-formed day and defaults the config stamp to null", () => {
expect(refused(healthScoreRecordBackupSchema, day)).toEqual([]);
const out = parsed(healthScoreRecordBackupSchema, day) as {
configVersion: unknown;
configChangedAt: unknown;
};
expect(out.configVersion).toBeNull();
expect(out.configChangedAt).toBeNull();
});
it("wants an anchored YYYY-MM-DD day key", () => {
for (const dayKey of BAD_DAY_KEYS) {
expect(
refused(healthScoreRecordBackupSchema, { ...day, dayKey }),
dayKey,
).toEqual(["dayKey"]);
}
});
it("refuses an empty id and an empty zone", () => {
expect(refused(healthScoreRecordBackupSchema, { ...day, id: "" })).toEqual([
"id",
]);
expect(
refused(healthScoreRecordBackupSchema, { ...day, timezone: "" }),
).toEqual(["timezone"]);
});
it("holds the composite to 0 through 100", () => {
for (const composite of [0, 100]) {
expect(
refused(healthScoreRecordBackupSchema, { ...day, composite }),
).toEqual([]);
}
for (const composite of [-1, 101]) {
expect(
refused(healthScoreRecordBackupSchema, { ...day, composite }),
).toEqual(["composite"]);
}
});
it("takes every band the column allows and nothing else", () => {
for (const band of ["green", "yellow", "red"]) {
expect(refused(healthScoreRecordBackupSchema, { ...day, band })).toEqual(
[],
);
}
expect(
refused(healthScoreRecordBackupSchema, { ...day, band: "" }),
).toEqual(["band"]);
});
it("wants at least one named pillar in the composition", () => {
expect(
refused(healthScoreRecordBackupSchema, { ...day, composition: ["a"] }),
).toEqual([]);
expect(
refused(healthScoreRecordBackupSchema, { ...day, composition: [] }),
).toEqual(["composition"]);
expect(
refused(healthScoreRecordBackupSchema, {
...day,
composition: ["sleep", ""],
}),
).toEqual(["composition.1"]);
});
it("wants a fingerprint of exactly 64 lowercase hex digits", () => {
for (const inputFingerprint of [
"a".repeat(63),
"a".repeat(65),
"g".repeat(64),
`x${"a".repeat(64)}`,
`${"a".repeat(64)}x`,
"a",
]) {
expect(
refused(healthScoreRecordBackupSchema, { ...day, inputFingerprint }),
inputFingerprint,
).toEqual(["inputFingerprint"]);
}
});
});
describe("the intraday curve", () => {
const curve = {
id: "ip-1",
type: "ACTIVITY_STEPS",
dateKey: "2026-07-01",
hourlyCumulative: [0, 120],
dayTotal: 120,
sampleCount: 2,
timezone: "UTC",
};
it("reads a file from before the hours travelled as a fold of samples", () => {
const out = parsed(intradayProfileBackupSchema, curve) as {
hourlyIncrements: unknown;
basis: unknown;
bucketCount: unknown;
};
expect(out.hourlyIncrements).toEqual([]);
expect(out.basis).toBe("PER_SAMPLE");
expect(out.bucketCount).toBeNull();
});
it("takes both bases and refuses another", () => {
for (const basis of ["PER_SAMPLE", "HOUR_BUCKETS"]) {
expect(refused(intradayProfileBackupSchema, { ...curve, basis })).toEqual(
[],
);
}
expect(
refused(intradayProfileBackupSchema, { ...curve, basis: "" }),
).toEqual(["basis"]);
});
it("wants an anchored day key, a zone and a non-empty id", () => {
for (const dateKey of BAD_DAY_KEYS) {
expect(
refused(intradayProfileBackupSchema, { ...curve, dateKey }),
dateKey,
).toEqual(["dateKey"]);
}
expect(
refused(intradayProfileBackupSchema, { ...curve, timezone: "" }),
).toEqual(["timezone"]);
expect(refused(intradayProfileBackupSchema, { ...curve, id: "" })).toEqual([
"id",
]);
});
});
describe("a custom medication category", () => {
const KEY = "custom:0f8fad5b-d9cb-469f-a165-70867728950e";
it("takes a readable label and defaults its order and state", () => {
const out = parsed(customMedicationCategorySchema, {
key: KEY,
label: " Supplements ",
}) as { label: unknown; sortOrder: unknown; isActive: unknown };
expect(out.label).toBe("Supplements");
expect(out.sortOrder).toBe(0);
expect(out.isActive).toBe(true);
});
it("wants the custom: prefix and a 36-character id, anchored", () => {
for (const key of [
KEY.slice(0, -1),
`${KEY}0`,
`x${KEY}`,
"custom:",
"custom:zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz",
]) {
expect(
refused(customMedicationCategorySchema, { key, label: "A" }),
key,
).toContain("key");
}
});
it("holds the label to 1 through 40 characters after trimming", () => {
expect(
refused(customMedicationCategorySchema, {
key: KEY,
label: "x".repeat(40),
}),
).toEqual([]);
expect(
refused(customMedicationCategorySchema, {
key: KEY,
label: "x".repeat(41),
}),
).toEqual(["label"]);
// Blank after trimming: the length rule and the label-or-ciphertext rule
// both refuse it.
expect(
refused(customMedicationCategorySchema, { key: KEY, label: " " }),
).toEqual(["label", "label"]);
});
it("refuses a negative order", () => {
expect(
refused(customMedicationCategorySchema, {
key: KEY,
label: "A",
sortOrder: -1,
}),
).toEqual(["sortOrder"]);
});
it("needs its label or its ciphertext, and names the label when both are missing", () => {
expect(
refused(customMedicationCategorySchema, {
key: KEY,
labelEncrypted: "QUJDRA==",
}),
).toEqual([]);
expect(
refused(customMedicationCategorySchema, {
key: KEY,
label: null,
labelEncrypted: "QUJDRA==",
}),
).toEqual([]);
const result = customMedicationCategorySchema.safeParse({ key: KEY });
expect(result.success).toBe(false);
expect(result.error?.issues).toEqual([
expect.objectContaining({
path: ["label"],
message: "a custom category needs its label or its ciphertext",
}),
]);
expect(
refused(customMedicationCategorySchema, { key: KEY, label: null }),
).toEqual(["label"]);
});
});
describe("a medication", () => {
it("restores a file from before its parts travelled with every part empty", () => {
const out = parsed(medicationSchema, { name: "Ramipril", dose: "5 mg" });
expect(out).toMatchObject({
schedules: [],
sideEffects: [],
pauseEras: [],
ingredients: [],
doseChanges: [],
inventoryItems: [],
inventoryEvents: [],
efficacyTargets: [],
scheduleRevisions: [],
});
expect((out as { courses?: unknown }).courses).toBeUndefined();
});
it("refuses an empty name, id or ref and a category past 64 characters", () => {
const base = { name: "Ramipril", dose: "5 mg" };
expect(refused(medicationSchema, { ...base, name: "" })).toEqual(["name"]);
expect(refused(medicationSchema, { ...base, id: "" })).toEqual(["id"]);
expect(refused(medicationSchema, { ...base, ref: "" })).toEqual(["ref"]);
expect(
refused(medicationSchema, { ...base, category: "x".repeat(64) }),
).toEqual([]);
expect(
refused(medicationSchema, { ...base, category: "x".repeat(65) }),
).toEqual(["category"]);
});
it("names the side effect severity outside 1 through 5", () => {
const effect = {
occurredAt: AT,
category: medicationSideEffectSchema.shape.category.options[0],
entry: medicationSideEffectSchema.shape.entry.options[0],
};
for (const severity of [1, 5]) {
expect(
refused(medicationSideEffectSchema, { ...effect, severity }),
).toEqual([]);
}
for (const severity of [0, 6]) {
expect(
refused(medicationSideEffectSchema, { ...effect, severity }),
).toEqual(["severity"]);
}
expect(
refused(medicationSideEffectSchema, { ...effect, severity: 3, id: "" }),
).toEqual(["id"]);
});
});
describe("a composition ingredient", () => {
const ingredient = {
nutrient: "vitamin_d",
amountPerUnit: 20,
unit: "µg",
};
it("defaults its source to a hand entry and takes the label reader", () => {
expect(
(parsed(medicationIngredientSchema, ingredient) as { source: unknown })
.source,
).toBe("MANUAL");
expect(
refused(medicationIngredientSchema, {
...ingredient,
source: "LABEL_AI",
}),
).toEqual([]);
expect(
refused(medicationIngredientSchema, { ...ingredient, source: "" }),
).toEqual(["source"]);
});
it("holds the nutrient to 1 through 40 and the units to 8 characters", () => {
const at = (patch: Record<string, unknown>) =>
refused(medicationIngredientSchema, { ...ingredient, ...patch });
expect(at({ nutrient: "x".repeat(40) })).toEqual([]);
expect(at({ nutrient: "x".repeat(41) })).toEqual(["nutrient"]);
expect(at({ nutrient: "" })).toEqual(["nutrient"]);
expect(at({ unit: "x".repeat(8) })).toEqual([]);
expect(at({ unit: "x".repeat(9) })).toEqual(["unit"]);
expect(at({ unit: "" })).toEqual(["unit"]);
expect(at({ enteredUnit: "x".repeat(8) })).toEqual([]);
expect(at({ enteredUnit: "x".repeat(9) })).toEqual(["enteredUnit"]);
expect(at({ id: "" })).toEqual(["id"]);
});
});
describe("an intake row", () => {
it("names its medication, and refuses an empty one or empty ids", () => {
const row = { medication: "Ramipril", scheduledFor: AT };
expect(refused(intakeEventSchema, row)).toEqual([]);
expect(refused(intakeEventSchema, { ...row, medication: "" })).toEqual([
"medication",
]);
expect(refused(intakeEventSchema, { ...row, id: "" })).toEqual(["id"]);
expect(refused(intakeEventSchema, { ...row, medicationId: "" })).toEqual([
"medicationId",
]);
});
});
describe("a custom cycle symptom", () => {
const symptom = {
key: "custom-cramps",
labelKey: "cycle.symptom.custom",
categoryId: "cat-1",
};
it("defaults to active at the top of its list", () => {
const out = parsed(customCycleSymptomSchema, symptom) as {
isActive: unknown;
sortOrder: unknown;
};
expect(out.isActive).toBe(true);
expect(out.sortOrder).toBe(0);
});
it("refuses an empty key, label key, category or id", () => {
for (const field of ["key", "labelKey", "categoryId", "id"]) {
expect(
refused(customCycleSymptomSchema, { ...symptom, [field]: "" }),
field,
).toEqual([field]);
}
});
});
@@ -211,8 +211,10 @@ describe("syncUserMeasurements — plausibility gate", () => {
it("does not overwrite a stored reading with an out-of-band one", async () => {
// The slot already holds a real pulse; the provider re-sends the same
// instant carrying a decode slip. Ungated, the update branch rewrote the
// good value with the bad one.
vi.mocked(prisma.measurement.findMany).mockResolvedValueOnce([
// good value with the bad one. Not `mockResolvedValueOnce`: the gate
// drops the reading before the stored rows are read, so a once-value
// would sit unconsumed and answer the next test's read instead.
vi.mocked(prisma.measurement.findMany).mockResolvedValue([
{
id: "existing-row",
type: "PULSE",
+1 -1
View File
@@ -61,7 +61,7 @@
"src/lib/export/backup-schema-kit.ts",
"src/lib/cycle/backup-schema.ts"
],
"floor": 41
"floor": 53
}
}
}
@@ -253,7 +253,20 @@ async function readAll(
return out;
}
let baseline: Record<string, { status: number; body: unknown }> | null = null;
type Reads = Record<string, { status: number; body: unknown }>;
let baseline: Reads | null = null;
/**
* The all-on reads, taken once in a world of their own and then cleared, so
* a state case compares against them whichever case runs first.
*/
async function readBaseline(): Promise<Reads> {
if (baseline === null) {
baseline = await readAll(await enterState("S0", seed), "S0");
await resetWorld();
}
return baseline;
}
beforeEach(async () => {
await resetWorld();
@@ -261,8 +274,7 @@ beforeEach(async () => {
describe("data routes under every AI state", () => {
it("S0 (all on) is the baseline, and every read answers", async () => {
const world = await enterState("S0", seed);
baseline = await readAll(world, "S0");
const baseline = await readBaseline();
for (const [name, { status }] of Object.entries(baseline)) {
expect(status, name).toBe(200);
}
@@ -284,12 +296,12 @@ describe("data routes under every AI state", () => {
it.each(["S1", "S2", "S3", "S4", "S5", "S6", "S8"] as const)(
"%s answers every read with the baseline's data",
async (state) => {
expect(baseline, "the S0 baseline runs first").not.toBeNull();
const base = await readBaseline();
const world = await enterState(state, seed);
const reads = await readAll(world, state);
for (const [name, { status, body }] of Object.entries(reads)) {
expect(status, `${state} ${name}`).toBe(200);
expect(body, `${state} ${name}`).toEqual(baseline![name].body);
expect(body, `${state} ${name}`).toEqual(base[name].body);
}
},
);
@@ -18,7 +18,7 @@
* Means are compared to nine significant digits: the tier sums the same day
* values in a different order. Extremes are compared exactly.
*/
import { beforeAll, describe, expect, it } from "vitest";
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
import { getPrismaClient, truncateAllTables } from "./setup";
import { Prisma, type MeasurementType } from "@/generated/prisma/client";
@@ -246,6 +246,12 @@ beforeAll(async () => {
}, 300_000);
describe("readAllTimeExtremes over the DAY tier", () => {
// A clean tier over whatever rows are left for every case: some cases
// break a tier row on purpose, and the next case must not read it.
beforeEach(async () => {
await recomputeUserRollups(USER, { granularities: ["DAY"] });
}, 120_000);
it("reads the tier for every type", async () => {
const covered = await prisma.measurementRollup.groupBy({
by: ["type"],
+4 -1
View File
@@ -1109,7 +1109,10 @@ async function recreateOwner(prisma: PrismaClient) {
// ── The cases ────────────────────────────────────────────────────────────────
describe("backup parity", () => {
// One account through the numbered steps above, in order: each restore and
// export reads what the step before it left. The order holds under a
// shuffled run.
describe("backup parity", { shuffle: false }, () => {
const prisma = getPrismaClient();
let adminId = "";
let drJson = "";
+4 -1
View File
@@ -147,7 +147,10 @@ afterAll(() => {
rmSync(work, { recursive: true, force: true });
});
describe("the nightly database dump", () => {
// One backup directory through the cases in order: the first dump is the one
// the retention, failure and restore cases count and restore. The order holds
// under a shuffled run.
describe("the nightly database dump", { shuffle: false }, () => {
let dumpFile = "";
it("writes a checked dump and manifest, records the run and copies both off-host", async () => {
@@ -71,7 +71,10 @@ beforeAll(async () => {
await prisma.$executeRawUnsafe("ANALYZE audit_logs");
}, 120_000);
describe("runGeoBackfill", () => {
// Three passes in order: the second must find what the first marked, the
// third runs past the wait the first one set. The order holds under a
// shuffled run.
describe("runGeoBackfill", { shuffle: false }, () => {
it("reads its candidates through the partial index", async () => {
const before = await indexScans();
const summary = await runGeoBackfill(prisma, NOW);
@@ -58,7 +58,7 @@ import v8 from "node:v8";
import vm from "node:vm";
import type { Job } from "pg-boss";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { afterAll, describe, expect, it, vi } from "vitest";
import type { WideEvent } from "@/lib/logging/types";
import { getPrismaClient, truncateAllTables } from "./setup";
@@ -320,10 +320,6 @@ async function seed(points: number): Promise<void> {
fake.upperBoundRequests = 0;
}
beforeAll(async () => {
await seed(POINTS);
}, 120_000);
afterAll(async () => {
await truncateAllTables(getPrismaClient());
});
@@ -353,6 +349,9 @@ function nextSlice(): AdmissionPayload | null {
describe(`Google Health backfill over ${DAYS} days of heart rate (#1220)`, () => {
it("completes slice by slice, imports every reading once, and keeps the hourly sync running", async () => {
// Its own history, as the case below seeds its own: neither reads what
// the other left behind, whichever runs first.
await seed(POINTS);
const { drainIntegrationBackfillAdmission } =
await import("@/lib/jobs/integration-backfill-drain");
const { handleGoogleHealthSync } =
@@ -106,84 +106,93 @@ afterAll(async () => {
if (column.udt_name === "measurement_type") await runMigration();
});
describe("migration 0401 over enum-typed ledger rows", () => {
it("starts from the enum column", async () => {
expect((await typeColumn()).udt_name).toBe("measurement_type");
});
// Before the migration, the migration, after it: the cases are its steps,
// in order, under a shuffled run too.
describe(
"migration 0401 over enum-typed ledger rows",
{ shuffle: false },
() => {
it("starts from the enum column", async () => {
expect((await typeColumn()).udt_name).toBe("measurement_type");
});
it("keeps every row, its key and its dates, and seeds the background date", async () => {
await runMigration();
it("keeps every row, its key and its dates, and seeds the background date", async () => {
await runMigration();
expect(await typeColumn()).toEqual({ data_type: "text", udt_name: "text" });
const rows = await readRows();
expect(rows.map((r) => r.type)).toEqual([
"PULSE",
"RESPIRATORY_RATE",
"WEIGHT",
]);
const byType = Object.fromEntries(rows.map((r) => [r.type, r]));
for (const [type, at] of Object.entries(RECEIVED)) {
expect(byType[type].last_received_at.toISOString()).toBe(
`${at.replace(" ", "T")}.000Z`,
expect(await typeColumn()).toEqual({
data_type: "text",
udt_name: "text",
});
const rows = await readRows();
expect(rows.map((r) => r.type)).toEqual([
"PULSE",
"RESPIRATORY_RATE",
"WEIGHT",
]);
const byType = Object.fromEntries(rows.map((r) => [r.type, r]));
for (const [type, at] of Object.entries(RECEIVED)) {
expect(byType[type].last_received_at.toISOString()).toBe(
`${at.replace(" ", "T")}.000Z`,
);
}
expect(byType.WEIGHT.last_trigger).toBe("background");
expect(byType.WEIGHT.last_new_sample_at?.toISOString()).toBe(
"2026-09-01T06:00:00.000Z",
);
}
expect(byType.WEIGHT.last_trigger).toBe("background");
expect(byType.WEIGHT.last_new_sample_at?.toISOString()).toBe(
"2026-09-01T06:00:00.000Z",
);
expect(byType.PULSE.last_new_sample_at).toBeNull();
// Only a row whose latest arrival was a background one knows a date.
expect(byType.WEIGHT.last_background_at?.toISOString()).toBe(
"2026-09-01T06:00:00.000Z",
);
expect(byType.PULSE.last_background_at).toBeNull();
expect(byType.RESPIRATORY_RATE.last_background_at).toBeNull();
});
expect(byType.PULSE.last_new_sample_at).toBeNull();
// Only a row whose latest arrival was a background one knows a date.
expect(byType.WEIGHT.last_background_at?.toISOString()).toBe(
"2026-09-01T06:00:00.000Z",
);
expect(byType.PULSE.last_background_at).toBeNull();
expect(byType.RESPIRATORY_RATE.last_background_at).toBeNull();
});
it("keeps the primary key on (user, type)", async () => {
const pkey = await prisma.$queryRaw<Array<{ indexdef: string }>>`
it("keeps the primary key on (user, type)", async () => {
const pkey = await prisma.$queryRaw<Array<{ indexdef: string }>>`
SELECT indexdef FROM pg_indexes
WHERE tablename = 'healthkit_type_syncs'
AND indexname = 'healthkit_type_syncs_pkey'
`;
expect(pkey[0]?.indexdef).toContain("(user_id, type)");
await expect(
prisma.$executeRawUnsafe(
`INSERT INTO "healthkit_type_syncs" ("user_id", "type", "last_received_at") VALUES ('${USER}', 'WEIGHT', now())`,
),
).rejects.toThrow();
});
expect(pkey[0]?.indexdef).toContain("(user_id, type)");
await expect(
prisma.$executeRawUnsafe(
`INSERT INTO "healthkit_type_syncs" ("user_id", "type", "last_received_at") VALUES ('${USER}', 'WEIGHT', now())`,
),
).rejects.toThrow();
});
it("lets the writer upsert a measurement type and record a dose", async () => {
const at = new Date("2026-10-01T09:00:00.000Z");
await recordHealthKitTypeArrivals(prisma, {
userId: USER,
counts: new Map([
["WEIGHT", { accepted: 1, newSamples: 1 }],
["MEDICATION_DOSE", { accepted: 2, newSamples: 2 }],
]),
trigger: "foreground",
at,
it("lets the writer upsert a measurement type and record a dose", async () => {
const at = new Date("2026-10-01T09:00:00.000Z");
await recordHealthKitTypeArrivals(prisma, {
userId: USER,
counts: new Map([
["WEIGHT", { accepted: 1, newSamples: 1 }],
["MEDICATION_DOSE", { accepted: 2, newSamples: 2 }],
]),
trigger: "foreground",
at,
});
const rows = await prisma.healthKitTypeSync.findMany({
where: { userId: USER },
orderBy: { type: "asc" },
});
expect(rows.map((r) => r.type)).toEqual([
"MEDICATION_DOSE",
"PULSE",
"RESPIRATORY_RATE",
"WEIGHT",
]);
const weight = rows.find((r) => r.type === "WEIGHT")!;
expect(weight.lastReceivedAt.toISOString()).toBe(at.toISOString());
expect(weight.lastTrigger).toBe("foreground");
// The foreground arrival leaves the seeded background date standing.
expect(weight.lastBackgroundAt?.toISOString()).toBe(
"2026-09-01T06:00:00.000Z",
);
const dose = rows.find((r) => r.type === "MEDICATION_DOSE")!;
expect(dose.lastNewSampleAt?.toISOString()).toBe(at.toISOString());
expect(dose.lastBackgroundAt).toBeNull();
});
const rows = await prisma.healthKitTypeSync.findMany({
where: { userId: USER },
orderBy: { type: "asc" },
});
expect(rows.map((r) => r.type)).toEqual([
"MEDICATION_DOSE",
"PULSE",
"RESPIRATORY_RATE",
"WEIGHT",
]);
const weight = rows.find((r) => r.type === "WEIGHT")!;
expect(weight.lastReceivedAt.toISOString()).toBe(at.toISOString());
expect(weight.lastTrigger).toBe("foreground");
// The foreground arrival leaves the seeded background date standing.
expect(weight.lastBackgroundAt?.toISOString()).toBe(
"2026-09-01T06:00:00.000Z",
);
const dose = rows.find((r) => r.type === "MEDICATION_DOSE")!;
expect(dose.lastNewSampleAt?.toISOString()).toBe(at.toISOString());
expect(dose.lastBackgroundAt).toBeNull();
});
});
},
);
@@ -177,7 +177,9 @@ beforeAll(async () => {
await runMigration();
}, 120_000);
describe("migration 0420", () => {
// The migration's effects, then the refold and the rerun that change them:
// the cases read in order, under a shuffled run too.
describe("migration 0420", { shuffle: false }, () => {
it("moves every Fitbit and Google Health SDNN row to HRV_RMSSD, tombstones included", async () => {
const moved = await prisma.measurement.findMany({
where: { userId: USER, source: { in: ["FITBIT", "GOOGLE_HEALTH"] } },
+13 -3
View File
@@ -13,7 +13,7 @@
* are failed through its own API.
*/
import { PgBoss } from "pg-boss";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
import {
readActiveJobsStartedBefore,
@@ -47,7 +47,7 @@ async function failOneJob(
return jobId;
}
beforeAll(async () => {
async function startBoss(): Promise<void> {
boss = new PgBoss({
connectionString: process.env.DATABASE_URL,
// No maintenance or monitoring timers — the test drives every transition.
@@ -57,7 +57,15 @@ beforeAll(async () => {
await boss.start();
await boss.createQueue(QUEUE);
await boss.createQueue(OTHER_QUEUE);
}, 60_000);
}
beforeAll(startBoss, 60_000);
// Every case reads the queue it filled itself: no job a case before it sent,
// failed or left active is still there to be counted or fetched.
beforeEach(async () => {
await getPrismaClient().$executeRawUnsafe(`DELETE FROM pgboss.job`);
});
afterAll(async () => {
await boss?.stop({ graceful: false, close: true });
@@ -244,6 +252,8 @@ describe("no queue schema at all", () => {
await prisma.$executeRawUnsafe(
`ALTER SCHEMA pgboss_gone RENAME TO pgboss`,
);
// Running again for whichever case comes next.
await startBoss();
}
});
});
@@ -138,11 +138,14 @@ function byInstant(at: string) {
String(a.type ?? "").localeCompare(String(b.type ?? ""));
}
beforeAll(async () => {
/** This server: the seeded catalogue and the owner whose files are read. */
async function seedServer(): Promise<void> {
await truncateAllTables(prisma);
await seedJsonImportCatalogue(prisma);
({ ownerId } = await seedJsonImportOwner(prisma));
});
}
beforeAll(seedServer);
afterAll(async () => {
await truncateAllTables(prisma);
@@ -350,6 +353,10 @@ describe("the JSON import takes the app's own files back", () => {
});
describe("a full backup moved to another server", () => {
// The case empties the database to stand in for another server. Put this
// server back for the cases that run after it.
afterAll(seedServer);
it("comes back equal, the account's own tags, groups and rated factor included", async () => {
const original = await fullBackupOf(ownerId);
expect(original.customMoodTags.length).toBeGreaterThanOrEqual(2);
@@ -77,11 +77,16 @@ describe("lab CSV import of a value without a unit", () => {
});
it("refuses a real unit under the marker without one", async () => {
// A marker of its own, kept in "1", rather than the one the case above
// mints: the cases run in either order.
await prisma.biomarker.create({
data: { userId: USER, name: "LDL/HDL-Quotient", unit: "1" },
});
const result = await run(
[
"Analyt;Wert;Einheit;Datum",
"Cholesterin/HDL-Quotient;3,4;mg/dL;02.03.2025",
"Cholesterin/HDL-Quotient;3,2;1;03.03.2025",
"LDL/HDL-Quotient;3,4;mg/dL;02.03.2025",
"LDL/HDL-Quotient;3,2;1;03.03.2025",
].join("\n"),
);
expect(result.errors).toEqual([
+228 -221
View File
@@ -126,244 +126,251 @@ type Dto = {
rangeStatus: string;
};
describe("POST /api/labs", () => {
it("converts a reading in another unit and keeps what the report printed", async () => {
const { status, json } = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 5.6,
unit: "mmol/L",
sourceReferenceLow: 3.9,
sourceReferenceHigh: 6.0,
takenAt: "2026-05-01T08:00:00.000Z",
});
expect(status).toBe(201);
const dto = json.data!;
expect(dto.converted).toBe(true);
expect(dto.unit).toBe("mg/dL");
expect(dto.value).toBeCloseTo(5.6 * MGDL_PER_MMOL, 9);
expect(dto.sourceValue).toBe(5.6);
expect(dto.sourceUnit).toBe("mmol/L");
// The report's own window moved with the value.
expect(dto.sourceReferenceLow).toBeCloseTo(3.9 * MGDL_PER_MMOL, 9);
expect(dto.sourceReferenceHigh).toBeCloseTo(6.0 * MGDL_PER_MMOL, 9);
// And the verdict is judged against it: 5.6 of 3.9-6.0 is in range.
expect(dto.rangeStatus).toBe("in-range");
// One record through the cases in order: the OCR commit is committed again
// as a duplicate, and the edits and the list read the readings the earlier
// cases wrote. The order holds under a shuffled run.
describe("lab unit conversion, one record", { shuffle: false }, () => {
describe("POST /api/labs", () => {
it("converts a reading in another unit and keeps what the report printed", async () => {
const { status, json } = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 5.6,
unit: "mmol/L",
sourceReferenceLow: 3.9,
sourceReferenceHigh: 6.0,
takenAt: "2026-05-01T08:00:00.000Z",
});
expect(status).toBe(201);
const dto = json.data!;
expect(dto.converted).toBe(true);
expect(dto.unit).toBe("mg/dL");
expect(dto.value).toBeCloseTo(5.6 * MGDL_PER_MMOL, 9);
expect(dto.sourceValue).toBe(5.6);
expect(dto.sourceUnit).toBe("mmol/L");
// The report's own window moved with the value.
expect(dto.sourceReferenceLow).toBeCloseTo(3.9 * MGDL_PER_MMOL, 9);
expect(dto.sourceReferenceHigh).toBeCloseTo(6.0 * MGDL_PER_MMOL, 9);
// And the verdict is judged against it: 5.6 of 3.9-6.0 is in range.
expect(dto.rangeStatus).toBe("in-range");
const row = await prisma.labResult.findUniqueOrThrow({
where: { id: dto.id },
const row = await prisma.labResult.findUniqueOrThrow({
where: { id: dto.id },
});
expect(row.value).toBeCloseTo(5.6 * MGDL_PER_MMOL, 9);
expect(row.sourceValue).toBe(5.6);
expect(row.sourceUnit).toBe("mmol/L");
});
it("flags a converted reading outside the printed window as such", async () => {
const { json } = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 7.2,
unit: "mmol/L",
sourceReferenceLow: 3.9,
sourceReferenceHigh: 6.0,
takenAt: "2026-05-02T08:00:00.000Z",
});
expect(json.data!.rangeStatus).toBe("above");
});
it("stores a reading in the marker's unit as stated, with no conversion record", async () => {
const { json } = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 95,
unit: "mg/dL",
takenAt: "2026-05-03T08:00:00.000Z",
});
const dto = json.data!;
expect(dto.converted).toBe(false);
expect(dto.value).toBe(95);
expect(dto.sourceValue).toBeNull();
expect(dto.sourceUnit).toBeNull();
});
it("links a marker that had no key yet, the first time it is needed", async () => {
const before = await prisma.biomarker.findUniqueOrThrow({
where: { id: "bm-glucose" },
});
// The marker was created without a key in this test; the first conversion
// above has already filled it.
expect(before.analyteKey).toBe("fasting-glucose");
});
it("still refuses a pair that cannot be converted, now with the reason", async () => {
const { status, json } = await call("labs", "POST", {
biomarkerId: "bm-free",
value: 5.6,
unit: "mmol/L",
takenAt: "2026-05-04T08:00:00.000Z",
});
expect(status).toBe(422);
expect(json.meta).toMatchObject({
errorCode: "labs.unit.mismatch",
markerUnit: "mg/dL",
readingUnit: "mmol/L",
reason: "unknown_analyte",
});
expect(
await prisma.labResult.count({ where: { biomarkerId: "bm-free" } }),
).toBe(0);
});
expect(row.value).toBeCloseTo(5.6 * MGDL_PER_MMOL, 9);
expect(row.sourceValue).toBe(5.6);
expect(row.sourceUnit).toBe("mmol/L");
});
it("flags a converted reading outside the printed window as such", async () => {
const { json } = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 7.2,
unit: "mmol/L",
sourceReferenceLow: 3.9,
sourceReferenceHigh: 6.0,
takenAt: "2026-05-02T08:00:00.000Z",
});
expect(json.data!.rangeStatus).toBe("above");
});
it("stores a reading in the marker's unit as stated, with no conversion record", async () => {
const { json } = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 95,
unit: "mg/dL",
takenAt: "2026-05-03T08:00:00.000Z",
});
const dto = json.data!;
expect(dto.converted).toBe(false);
expect(dto.value).toBe(95);
expect(dto.sourceValue).toBeNull();
expect(dto.sourceUnit).toBeNull();
});
it("links a marker that had no key yet, the first time it is needed", async () => {
const before = await prisma.biomarker.findUniqueOrThrow({
where: { id: "bm-glucose" },
});
// The marker was created without a key in this test; the first conversion
// above has already filled it.
expect(before.analyteKey).toBe("fasting-glucose");
});
it("still refuses a pair that cannot be converted, now with the reason", async () => {
const { status, json } = await call("labs", "POST", {
biomarkerId: "bm-free",
value: 5.6,
unit: "mmol/L",
takenAt: "2026-05-04T08:00:00.000Z",
});
expect(status).toBe(422);
expect(json.meta).toMatchObject({
errorCode: "labs.unit.mismatch",
markerUnit: "mg/dL",
readingUnit: "mmol/L",
reason: "unknown_analyte",
});
expect(
await prisma.labResult.count({ where: { biomarkerId: "bm-free" } }),
).toBe(0);
});
});
describe("the OCR commit", () => {
const row = {
analyte: "Glucose",
value: 5.0,
unit: "mmol/L",
referenceText: "3.9 - 5.6",
takenAt: "2026-06-01T08:00:00.000Z",
};
it("converts the row and reports it", async () => {
const { status, json } = await call<{
inserted: Dto[];
skipped: unknown[];
}>("ocr", "POST", { rows: [row] });
expect(status).toBe(200);
expect(json.data!.skipped).toEqual([]);
const [inserted] = json.data!.inserted;
expect(inserted.converted).toBe(true);
expect(inserted.unit).toBe("mg/dL");
expect(inserted.value).toBeCloseTo(5.0 * MGDL_PER_MMOL, 9);
expect(inserted.sourceValue).toBe(5.0);
expect(inserted.sourceUnit).toBe("mmol/L");
expect(inserted.sourceReferenceLow).toBeCloseTo(3.9 * MGDL_PER_MMOL, 9);
expect(inserted.sourceReferenceHigh).toBeCloseTo(5.6 * MGDL_PER_MMOL, 9);
});
it("treats the same scan committed again as a duplicate", async () => {
const { json } = await call<{
inserted: unknown[];
skipped: Array<{ reason: string }>;
}>("ocr", "POST", { rows: [row] });
expect(json.data!.inserted).toEqual([]);
expect(json.data!.skipped.map((s) => s.reason)).toEqual(["duplicate"]);
});
it("tells the scan review which analyte the marker is, and flags a re-scan as a duplicate", async () => {
const { annotateRow } = await import("@/lib/labs/ocr-extract");
const scanned = {
describe("the OCR commit", () => {
const row = {
analyte: "Glucose",
value: 5.0,
valueText: null,
unit: "mmol/L",
referenceLow: null,
referenceHigh: null,
referenceText: null,
takenAt: "2026-06-01",
confidence: { analyte: 1, value: 1, unit: 1, range: 1 },
referenceText: "3.9 - 5.6",
takenAt: "2026-06-01T08:00:00.000Z",
};
const annotated = await annotateRow(USER, scanned, "2026-06-01", "UTC");
expect(annotated).toMatchObject({
biomarkerMatch: "existing",
markerUnit: "mg/dL",
markerAnalyteKey: "fasting-glucose",
it("converts the row and reports it", async () => {
const { status, json } = await call<{
inserted: Dto[];
skipped: unknown[];
}>("ocr", "POST", { rows: [row] });
expect(status).toBe(200);
expect(json.data!.skipped).toEqual([]);
const [inserted] = json.data!.inserted;
expect(inserted.converted).toBe(true);
expect(inserted.unit).toBe("mg/dL");
expect(inserted.value).toBeCloseTo(5.0 * MGDL_PER_MMOL, 9);
expect(inserted.sourceValue).toBe(5.0);
expect(inserted.sourceUnit).toBe("mmol/L");
expect(inserted.sourceReferenceLow).toBeCloseTo(3.9 * MGDL_PER_MMOL, 9);
expect(inserted.sourceReferenceHigh).toBeCloseTo(5.6 * MGDL_PER_MMOL, 9);
});
// The converted reading from the commit above holds 90.09 mg/dL; the scan
// says 5.0 mmol/L, which is what the report printed for it.
expect(annotated.duplicateOf).not.toBeNull();
// A different value is not a duplicate.
const other = await annotateRow(
USER,
{ ...scanned, value: 6.5 },
"2026-06-01",
"UTC",
);
expect(other.duplicateOf).toBeNull();
it("treats the same scan committed again as a duplicate", async () => {
const { json } = await call<{
inserted: unknown[];
skipped: Array<{ reason: string }>;
}>("ocr", "POST", { rows: [row] });
expect(json.data!.inserted).toEqual([]);
expect(json.data!.skipped.map((s) => s.reason)).toEqual(["duplicate"]);
});
// A marker the registry knows nothing about reports no analyte.
const free = await annotateRow(
USER,
{ ...scanned, analyte: "My own marker", unit: "mg/dL" },
"2026-06-01",
"UTC",
);
expect(free.markerAnalyteKey).toBeNull();
it("tells the scan review which analyte the marker is, and flags a re-scan as a duplicate", async () => {
const { annotateRow } = await import("@/lib/labs/ocr-extract");
const scanned = {
analyte: "Glucose",
value: 5.0,
valueText: null,
unit: "mmol/L",
referenceLow: null,
referenceHigh: null,
referenceText: null,
takenAt: "2026-06-01",
confidence: { analyte: 1, value: 1, unit: 1, range: 1 },
};
const annotated = await annotateRow(USER, scanned, "2026-06-01", "UTC");
expect(annotated).toMatchObject({
biomarkerMatch: "existing",
markerUnit: "mg/dL",
markerAnalyteKey: "fasting-glucose",
});
// The converted reading from the commit above holds 90.09 mg/dL; the scan
// says 5.0 mmol/L, which is what the report printed for it.
expect(annotated.duplicateOf).not.toBeNull();
// A different value is not a duplicate.
const other = await annotateRow(
USER,
{ ...scanned, value: 6.5 },
"2026-06-01",
"UTC",
);
expect(other.duplicateOf).toBeNull();
// A marker the registry knows nothing about reports no analyte.
const free = await annotateRow(
USER,
{ ...scanned, analyte: "My own marker", unit: "mg/dL" },
"2026-06-01",
"UTC",
);
expect(free.markerAnalyteKey).toBeNull();
});
it("skips a row whose unit cannot be converted, as before", async () => {
const { json } = await call<{
inserted: unknown[];
skipped: Array<{ reason: string }>;
}>("ocr", "POST", {
rows: [
{
analyte: "My own marker",
value: 3,
unit: "mmol/L",
takenAt: "2026-06-02T08:00:00.000Z",
},
],
});
expect(json.data!.inserted).toEqual([]);
expect(json.data!.skipped.map((s) => s.reason)).toEqual([
"unit_mismatch",
]);
});
});
it("skips a row whose unit cannot be converted, as before", async () => {
const { json } = await call<{
inserted: unknown[];
skipped: Array<{ reason: string }>;
}>("ocr", "POST", {
rows: [
describe("editing and reading back", () => {
it("clears the conversion record when the value is edited", async () => {
const created = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 6.0,
unit: "mmol/L",
takenAt: "2026-07-01T08:00:00.000Z",
});
const id = created.json.data!.id;
expect(created.json.data!.converted).toBe(true);
const edited = await call<Dto>("one", "PUT", { value: 110 }, id);
expect(edited.status).toBe(200);
expect(edited.json.data!.value).toBe(110);
expect(edited.json.data!.converted).toBe(false);
expect(edited.json.data!.sourceValue).toBeNull();
expect(edited.json.data!.sourceUnit).toBeNull();
});
it("keeps the conversion record when only the note is edited", async () => {
const created = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 6.1,
unit: "mmol/L",
takenAt: "2026-07-02T08:00:00.000Z",
});
const id = created.json.data!.id;
const storedValue = created.json.data!.value;
// The web edit dialog sends the form back whole, the unchanged value
// included; only a changed value clears the record.
const edited = await call<Dto>(
"one",
"PUT",
{
analyte: "My own marker",
value: 3,
unit: "mmol/L",
takenAt: "2026-06-02T08:00:00.000Z",
value: storedValue,
takenAt: "2026-07-02T09:00:00.000Z",
note: "fasting",
},
],
id,
);
expect(edited.status).toBe(200);
expect(edited.json.data!.value).toBe(storedValue);
expect(edited.json.data!.converted).toBe(true);
expect(edited.json.data!.sourceValue).toBe(6.1);
expect(edited.json.data!.sourceUnit).toBe("mmol/L");
});
expect(json.data!.inserted).toEqual([]);
expect(json.data!.skipped.map((s) => s.reason)).toEqual(["unit_mismatch"]);
});
});
describe("editing and reading back", () => {
it("clears the conversion record when the value is edited", async () => {
const created = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 6.0,
unit: "mmol/L",
takenAt: "2026-07-01T08:00:00.000Z",
it("returns the fields on the list and on the single reading", async () => {
const list = await call<{ results: Dto[] }>("labs", "GET");
const converted = list.json.data!.results.filter((r) => r.converted);
expect(converted.length).toBeGreaterThan(0);
for (const r of converted) {
expect(r.sourceValue).not.toBeNull();
expect(r.sourceUnit).not.toBeNull();
}
const one = await call<Dto>("one", "GET", undefined, converted[0].id);
expect(one.json.data!.converted).toBe(true);
});
const id = created.json.data!.id;
expect(created.json.data!.converted).toBe(true);
const edited = await call<Dto>("one", "PUT", { value: 110 }, id);
expect(edited.status).toBe(200);
expect(edited.json.data!.value).toBe(110);
expect(edited.json.data!.converted).toBe(false);
expect(edited.json.data!.sourceValue).toBeNull();
expect(edited.json.data!.sourceUnit).toBeNull();
});
it("keeps the conversion record when only the note is edited", async () => {
const created = await call<Dto>("labs", "POST", {
biomarkerId: "bm-glucose",
value: 6.1,
unit: "mmol/L",
takenAt: "2026-07-02T08:00:00.000Z",
});
const id = created.json.data!.id;
const storedValue = created.json.data!.value;
// The web edit dialog sends the form back whole, the unchanged value
// included; only a changed value clears the record.
const edited = await call<Dto>(
"one",
"PUT",
{
value: storedValue,
takenAt: "2026-07-02T09:00:00.000Z",
note: "fasting",
},
id,
);
expect(edited.status).toBe(200);
expect(edited.json.data!.value).toBe(storedValue);
expect(edited.json.data!.converted).toBe(true);
expect(edited.json.data!.sourceValue).toBe(6.1);
expect(edited.json.data!.sourceUnit).toBe("mmol/L");
});
it("returns the fields on the list and on the single reading", async () => {
const list = await call<{ results: Dto[] }>("labs", "GET");
const converted = list.json.data!.results.filter((r) => r.converted);
expect(converted.length).toBeGreaterThan(0);
for (const r of converted) {
expect(r.sourceValue).not.toBeNull();
expect(r.sourceUnit).not.toBeNull();
}
const one = await call<Dto>("one", "GET", undefined, converted[0].id);
expect(one.json.data!.converted).toBe(true);
});
});
@@ -55,18 +55,27 @@ describe("an update that changes nothing", () => {
const row = await seedReading();
await new Promise((r) => setTimeout(r, 20));
// Read as a difference within the transaction. Since Postgres 15 the
// `pg_stat_xact_*` views also carry counts an earlier transaction on the
// same connection has not flushed yet (stats flush at most once a
// second), so an update from the case before this one could show up.
const hot = await prisma.$transaction(async (tx) => {
const read = async () => {
const [stats] = await tx.$queryRaw<Array<{ hot: bigint; upd: bigint }>>`
SELECT n_tup_hot_upd AS hot, n_tup_upd AS upd
FROM pg_stat_xact_user_tables WHERE relname = 'measurements'`;
return { hot: Number(stats?.hot ?? 0), upd: Number(stats?.upd ?? 0) };
};
const before = await read();
await tx.measurement.update({
where: { id: row.id },
data: { value: 72.3, deletedAt: null, syncVersion: { increment: 1 } },
});
const [stats] = await tx.$queryRaw<Array<{ hot: bigint; upd: bigint }>>`
SELECT n_tup_hot_upd AS hot, n_tup_upd AS upd
FROM pg_stat_xact_user_tables WHERE relname = 'measurements'`;
return stats;
const after = await read();
return { hot: after.hot - before.hot, upd: after.upd - before.upd };
});
expect(Number(hot.upd)).toBe(1);
expect(Number(hot.hot)).toBe(1);
expect(hot.upd).toBe(1);
expect(hot.hot).toBe(1);
const after = await prisma.measurement.findUniqueOrThrow({
where: { id: row.id },
+201 -193
View File
@@ -211,27 +211,32 @@ async function readersOfD1(method: PulseDayMeanMethod) {
};
}
describe("every reader follows the active method", () => {
it("weighs every hour alike under hour_mean_v1", async () => {
const seen = await readersOfD1("hour_mean_v1");
for (const [reader, value] of Object.entries(seen)) {
expect(value, reader).toBeCloseTo(V1, 6);
}
// One instance through the cases in order: the readers, the dry run, the
// rebuild after apply (which reads the dry run's record) and the large
// account last, whose rows every earlier count leaves out. The order holds
// under a shuffled run.
describe("the pulse day-mean method, one instance", { shuffle: false }, () => {
describe("every reader follows the active method", () => {
it("weighs every hour alike under hour_mean_v1", async () => {
const seen = await readersOfD1("hour_mean_v1");
for (const [reader, value] of Object.entries(seen)) {
expect(value, reader).toBeCloseTo(V1, 6);
}
});
it("weighs the lone reading's hour a third under weighted_hour_mean_v2", async () => {
const seen = await readersOfD1("weighted_hour_mean_v2");
for (const [reader, value] of Object.entries(seen)) {
expect(value, reader).toBeCloseTo(V2, 6);
}
expect(Number(V2.toFixed(1))).toBe(60.9);
setActivePulseDayMeanMethod("hour_mean_v1");
});
});
it("weighs the lone reading's hour a third under weighted_hour_mean_v2", async () => {
const seen = await readersOfD1("weighted_hour_mean_v2");
for (const [reader, value] of Object.entries(seen)) {
expect(value, reader).toBeCloseTo(V2, 6);
}
expect(Number(V2.toFixed(1))).toBe(60.9);
setActivePulseDayMeanMethod("hour_mean_v1");
});
});
/** Every table except the settings row, as one digest per table. */
async function snapshotTables(): Promise<Record<string, string>> {
const tables = await prisma.$queryRaw<Array<{ name: string }>>`
/** Every table except the settings row, as one digest per table. */
async function snapshotTables(): Promise<Record<string, string>> {
const tables = await prisma.$queryRaw<Array<{ name: string }>>`
SELECT table_name AS name
FROM information_schema.tables
WHERE table_schema = 'public'
@@ -239,147 +244,147 @@ async function snapshotTables(): Promise<Record<string, string>> {
AND table_name NOT IN ('app_settings', '_prisma_migrations')
ORDER BY table_name
`;
const out: Record<string, string> = {};
for (const { name } of tables) {
if (!/^[a-z0-9_]+$/.test(name)) throw new Error(`table name ${name}`);
const [r] = await prisma.$queryRawUnsafe<Array<{ n: number; d: string }>>(
`SELECT count(*)::int AS n,
const out: Record<string, string> = {};
for (const { name } of tables) {
if (!/^[a-z0-9_]+$/.test(name)) throw new Error(`table name ${name}`);
const [r] = await prisma.$queryRawUnsafe<Array<{ n: number; d: string }>>(
`SELECT count(*)::int AS n,
coalesce(md5(string_agg(t::text, '|' ORDER BY t::text)), '') AS d
FROM "${name}" t`,
);
out[name] = `${r.n}:${r.d}`;
);
out[name] = `${r.n}:${r.d}`;
}
return out;
}
return out;
}
/** The settings row without the dry-run column. */
async function settingsWithoutDryRun() {
const [r] = await prisma.$queryRaw<Array<{ j: Record<string, unknown> }>>`
/** The settings row without the dry-run column. */
async function settingsWithoutDryRun() {
const [r] = await prisma.$queryRaw<Array<{ j: Record<string, unknown> }>>`
SELECT to_jsonb(s) - 'pulse_day_mean_dry_run_json' - 'updated_at' AS j
FROM app_settings s WHERE s.id = 'singleton'
`;
return r.j;
}
return r.j;
}
describe("the dry run", () => {
it("writes its result and nothing else", async () => {
const tablesBefore = await snapshotTables();
const settingsBefore = await settingsWithoutDryRun();
expect(tablesBefore.measurement_rollups).not.toMatch(/^0:/);
describe("the dry run", () => {
it("writes its result and nothing else", async () => {
const tablesBefore = await snapshotTables();
const settingsBefore = await settingsWithoutDryRun();
expect(tablesBefore.measurement_rollups).not.toMatch(/^0:/);
const outcome = await handlePulseDayMean(job("dry_run"));
expect(outcome.ok).toBe(true);
const outcome = await handlePulseDayMean(job("dry_run"));
expect(outcome.ok).toBe(true);
expect(await snapshotTables()).toEqual(tablesBefore);
expect(await settingsWithoutDryRun()).toEqual(settingsBefore);
expect(await snapshotTables()).toEqual(tablesBefore);
expect(await settingsWithoutDryRun()).toEqual(settingsBefore);
const stored = await prisma.appSettings.findUniqueOrThrow({
where: { id: "singleton" },
});
const run = pulseDayMeanStoredRunsSchema.parse(
stored.pulseDayMeanDryRunJson,
);
expect(run.status).toBe("done");
expect(run.finishedAt).not.toBeNull();
expect(run.result).toEqual({
accounts: 2,
daysCompared: 3,
daysChangedOver1Bpm: 2,
maxChangeBpm: 10,
distribution: [
{ bucket: "0-1", days: 1 },
{ bucket: "1-2", days: 1 },
{ bucket: "2-5", days: 0 },
{ bucket: "5+", days: 1 },
],
perAccount: [
{ account: "#1", days: 2, changedOver1Bpm: 1 },
{ account: "#2", days: 1, changedOver1Bpm: 1 },
],
});
// No account id leaves the job.
expect(JSON.stringify(stored.pulseDayMeanDryRunJson)).not.toContain(A);
expect(stored.pulseDayMeanMethod).toBe("hour_mean_v1");
});
});
describe("the rebuild after apply", () => {
const dayMean = async (userId: string, day: number) =>
(
await readCanonicalRollupBuckets({
userId,
type: "PULSE",
granularity: "DAY",
from: new Date(day),
to: new Date(day),
toInclusive: true,
})
)[0]?.mean;
it("rewrites the PULSE rollups under the stored method", async () => {
expect(await dayMean(A, D1)).toBeCloseTo(V1, 6);
expect(await dayMean(B, D1)).toBeCloseTo(80, 6);
await prisma.appSettings.update({
where: { id: "singleton" },
data: { pulseDayMeanMethod: "weighted_hour_mean_v2" },
});
const outcome = await handlePulseDayMean(job("rebuild"));
expect(outcome.ok).toBe(true);
// The rebuild is on the record beside the dry run, with what it did.
const { readPulseDayMeanStatus } =
await import("@/lib/jobs/pulse-day-mean");
const status = await readPulseDayMeanStatus();
expect(status.lastRebuild).toMatchObject({ status: "done" });
expect(status.lastRebuild?.accounts).toBeGreaterThan(0);
expect(status.lastDryRun?.status).toBe("done");
expect(await dayMean(A, D1)).toBeCloseTo(V2, 6);
expect(await dayMean(A, D2)).toBeCloseTo(70, 6);
expect(await dayMean(B, D1)).toBeCloseTo(70, 6);
await prisma.appSettings.update({
where: { id: "singleton" },
data: { pulseDayMeanMethod: "hour_mean_v1" },
const stored = await prisma.appSettings.findUniqueOrThrow({
where: { id: "singleton" },
});
const run = pulseDayMeanStoredRunsSchema.parse(
stored.pulseDayMeanDryRunJson,
);
expect(run.status).toBe("done");
expect(run.finishedAt).not.toBeNull();
expect(run.result).toEqual({
accounts: 2,
daysCompared: 3,
daysChangedOver1Bpm: 2,
maxChangeBpm: 10,
distribution: [
{ bucket: "0-1", days: 1 },
{ bucket: "1-2", days: 1 },
{ bucket: "2-5", days: 0 },
{ bucket: "5+", days: 1 },
],
perAccount: [
{ account: "#1", days: 2, changedOver1Bpm: 1 },
{ account: "#2", days: 1, changedOver1Bpm: 1 },
],
});
// No account id leaves the job.
expect(JSON.stringify(stored.pulseDayMeanDryRunJson)).not.toContain(A);
expect(stored.pulseDayMeanMethod).toBe("hour_mean_v1");
});
});
});
describe("a large account under the production heap", () => {
const BIG = "pulse-method-big";
const YEARS = 2;
/** The production container's heap ceiling. */
const PROD_HEAP_MB = 524;
/**
* The seed's transactions run past Prisma's 5 s default under a loaded
* host: a month took at most 0.55 s on a quiet machine and 9.9 s at a load
* average near 95, which expired the transaction. The bound is the
* fixture's, not the code's under test.
*/
const SEED_TRANSACTION = { maxWait: 30_000, timeout: 120_000 };
describe("the rebuild after apply", () => {
const dayMean = async (userId: string, day: number) =>
(
await readCanonicalRollupBuckets({
userId,
type: "PULSE",
granularity: "DAY",
from: new Date(day),
to: new Date(day),
toInclusive: true,
})
)[0]?.mean;
beforeAll(async () => {
await prisma.user.create({
data: { id: BIG, username: BIG, timezone: "UTC" },
it("rewrites the PULSE rollups under the stored method", async () => {
expect(await dayMean(A, D1)).toBeCloseTo(V1, 6);
expect(await dayMean(B, D1)).toBeCloseTo(80, 6);
await prisma.appSettings.update({
where: { id: "singleton" },
data: { pulseDayMeanMethod: "weighted_hour_mean_v2" },
});
const outcome = await handlePulseDayMean(job("rebuild"));
expect(outcome.ok).toBe(true);
// The rebuild is on the record beside the dry run, with what it did.
const { readPulseDayMeanStatus } =
await import("@/lib/jobs/pulse-day-mean");
const status = await readPulseDayMeanStatus();
expect(status.lastRebuild).toMatchObject({ status: "done" });
expect(status.lastRebuild?.accounts).toBeGreaterThan(0);
expect(status.lastDryRun?.status).toBe("done");
expect(await dayMean(A, D1)).toBeCloseTo(V2, 6);
expect(await dayMean(A, D2)).toBeCloseTo(70, 6);
expect(await dayMean(B, D1)).toBeCloseTo(70, 6);
await prisma.appSettings.update({
where: { id: "singleton" },
data: { pulseDayMeanMethod: "hour_mean_v1" },
});
});
// One reading a minute for two years, about a million rows, and every
// seventh hour holding a single reading so the two methods disagree.
//
// Written a month at a time, each month in its own transaction with the
// statement timeout lifted for that transaction only and a transaction
// timeout sized for a loaded host (`SEED_TRANSACTION`): as one statement
// the million-row insert outran the timeout on CI's shared Postgres, and
// both timeouts guard the code under test, not this fixture.
const end = new Date();
end.setUTCHours(0, 0, 0, 0);
const start = new Date(end.getTime() - YEARS * 365 * DAY);
const CHUNK = 30 * DAY;
for (let from = start.getTime(); from < end.getTime(); from += CHUNK) {
const chunkFrom = new Date(from);
const chunkTo = new Date(Math.min(from + CHUNK, end.getTime()));
await prisma.$transaction(async (tx) => {
await tx.$executeRaw`SET LOCAL statement_timeout = 0`;
await tx.$executeRaw`
});
describe("a large account under the production heap", () => {
const BIG = "pulse-method-big";
const YEARS = 2;
/** The production container's heap ceiling. */
const PROD_HEAP_MB = 524;
/**
* The seed's transactions run past Prisma's 5 s default under a loaded
* host: a month took at most 0.55 s on a quiet machine and 9.9 s at a load
* average near 95, which expired the transaction. The bound is the
* fixture's, not the code's under test.
*/
const SEED_TRANSACTION = { maxWait: 30_000, timeout: 120_000 };
beforeAll(async () => {
await prisma.user.create({
data: { id: BIG, username: BIG, timezone: "UTC" },
});
// One reading a minute for two years, about a million rows, and every
// seventh hour holding a single reading so the two methods disagree.
//
// Written a month at a time, each month in its own transaction with the
// statement timeout lifted for that transaction only and a transaction
// timeout sized for a loaded host (`SEED_TRANSACTION`): as one statement
// the million-row insert outran the timeout on CI's shared Postgres, and
// both timeouts guard the code under test, not this fixture.
const end = new Date();
end.setUTCHours(0, 0, 0, 0);
const start = new Date(end.getTime() - YEARS * 365 * DAY);
const CHUNK = 30 * DAY;
for (let from = start.getTime(); from < end.getTime(); from += CHUNK) {
const chunkFrom = new Date(from);
const chunkTo = new Date(Math.min(from + CHUNK, end.getTime()));
await prisma.$transaction(async (tx) => {
await tx.$executeRaw`SET LOCAL statement_timeout = 0`;
await tx.$executeRaw`
INSERT INTO measurements (id, user_id, type, unit, source, value, measured_at, created_at, updated_at)
SELECT 'big-' || (extract(epoch FROM ts)::bigint / 60)::text, ${BIG}, 'PULSE', 'bpm', 'APPLE_HEALTH',
60 + ((extract(epoch FROM ts)::bigint / 60) % 40),
@@ -391,60 +396,63 @@ describe("a large account under the production heap", () => {
) AS s(ts)
WHERE extract(hour FROM ts)::int % 7 <> 3 OR extract(minute FROM ts)::int = 0
`;
}, SEED_TRANSACTION);
}
await prisma.$transaction(async (tx) => {
await tx.$executeRaw`SET LOCAL statement_timeout = 0`;
await tx.$executeRaw`ANALYZE measurements`;
}, SEED_TRANSACTION);
}
await prisma.$transaction(async (tx) => {
await tx.$executeRaw`SET LOCAL statement_timeout = 0`;
await tx.$executeRaw`ANALYZE measurements`;
}, SEED_TRANSACTION);
}, 600_000);
}, 600_000);
async function peakHeapMb(work: () => Promise<unknown>) {
const base = process.memoryUsage().heapUsed;
let peak = base;
const timer = setInterval(() => {
async function peakHeapMb(work: () => Promise<unknown>) {
const base = process.memoryUsage().heapUsed;
let peak = base;
const timer = setInterval(() => {
peak = Math.max(peak, process.memoryUsage().heapUsed);
}, 25);
try {
await work();
} finally {
clearInterval(timer);
}
peak = Math.max(peak, process.memoryUsage().heapUsed);
}, 25);
try {
await work();
} finally {
clearInterval(timer);
return {
grewMb: (peak - base) / 1_048_576,
peakMb: peak / 1_048_576,
};
}
peak = Math.max(peak, process.memoryUsage().heapUsed);
return {
grewMb: (peak - base) / 1_048_576,
peakMb: peak / 1_048_576,
};
}
it("stays flat through the dry run and the rebuild", async () => {
const rows = await prisma.measurement.count({ where: { userId: BIG } });
expect(rows).toBeGreaterThan(900_000);
it("stays flat through the dry run and the rebuild", async () => {
const rows = await prisma.measurement.count({ where: { userId: BIG } });
expect(rows).toBeGreaterThan(900_000);
const dry = await peakHeapMb(() => computePulseDayMeanDryRun());
await prisma.appSettings.update({
where: { id: "singleton" },
data: { pulseDayMeanMethod: "weighted_hour_mean_v2" },
});
const rebuild = await peakHeapMb(() => handlePulseDayMean(job("rebuild")));
process.stderr.write(
`[pulse-day-mean heap] rows=${rows} dryRun grew=${dry.grewMb.toFixed(1)} MB peak=${dry.peakMb.toFixed(1)} MB; rebuild grew=${rebuild.grewMb.toFixed(1)} MB peak=${rebuild.peakMb.toFixed(1)} MB\n`,
);
// A window's aggregate rows, never the readings: a few MB at most. The
// bound leaves room for the test runner's own churn and is still a small
// slice of the production ceiling.
expect(dry.grewMb).toBeLessThan(64);
expect(rebuild.grewMb).toBeLessThan(64);
expect(dry.grewMb).toBeLessThan(PROD_HEAP_MB / 8);
const dry = await peakHeapMb(() => computePulseDayMeanDryRun());
await prisma.appSettings.update({
where: { id: "singleton" },
data: { pulseDayMeanMethod: "weighted_hour_mean_v2" },
});
const rebuild = await peakHeapMb(() =>
handlePulseDayMean(job("rebuild")),
);
process.stderr.write(
`[pulse-day-mean heap] rows=${rows} dryRun grew=${dry.grewMb.toFixed(1)} MB peak=${dry.peakMb.toFixed(1)} MB; rebuild grew=${rebuild.grewMb.toFixed(1)} MB peak=${rebuild.peakMb.toFixed(1)} MB\n`,
);
// A window's aggregate rows, never the readings: a few MB at most. The
// bound leaves room for the test runner's own churn and is still a small
// slice of the production ceiling.
expect(dry.grewMb).toBeLessThan(64);
expect(rebuild.grewMb).toBeLessThan(64);
expect(dry.grewMb).toBeLessThan(PROD_HEAP_MB / 8);
const days = await readCanonicalRollupBuckets({
userId: BIG,
type: "PULSE",
granularity: "DAY",
from: new Date(Date.now() - 400 * DAY),
to: new Date(),
toInclusive: true,
});
expect(days.length).toBeGreaterThan(300);
}, 900_000);
const days = await readCanonicalRollupBuckets({
userId: BIG,
type: "PULSE",
granularity: "DAY",
from: new Date(Date.now() - 400 * DAY),
to: new Date(),
toInclusive: true,
});
expect(days.length).toBeGreaterThan(300);
}, 900_000);
});
});
@@ -424,15 +424,35 @@ describe("statement budgets on the hot routes (real Postgres)", () => {
externalId: `budget-hr-${i}`,
};
});
const m = await countStatements(async () => {
const res = await (POST as unknown as Handler)(
const post = (body: unknown) =>
(POST as unknown as Handler)(
new NextRequest("http://localhost/api/measurements/batch", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ entries }),
body: JSON.stringify(body),
}),
{ params: Promise.resolve({}) },
);
// One reading a month earlier first, as the reads warm up: it fills the
// process-level caches a running server holds, which this case would
// otherwise pay for whenever it runs before the reads do.
const warmAt = daysAgo(30).toISOString();
const warm = await post({
entries: [
{
hkIdentifier: "HKQuantityTypeIdentifierHeartRate",
value: 60,
unit: "count/min",
startDate: warmAt,
endDate: warmAt,
externalId: "budget-hr-warm",
},
],
});
expect(warm.status, "batch warm-up").toBe(200);
await evictUserCaches();
const m = await countStatements(async () => {
const res = await post({ entries });
return {
status: res.status,
body: (await res.json()) as { data: { inserted: number } },
+25 -6
View File
@@ -90,7 +90,9 @@ beforeAll(async () => {
});
});
describe("lab CSV import", () => {
// One walk: a dry run, the import, the same file again. Each step reads what
// the one before it wrote, so the order holds under a shuffled run.
describe("lab CSV import", { shuffle: false }, () => {
it("a dry run writes nothing and reports the plan", async () => {
const result = await importLabsCsv({
userId: USER,
@@ -189,6 +191,14 @@ describe("lab CSV import", () => {
});
describe("lab CSV import in a zone of the file's own", () => {
// An account of its own, so its two draws are not in the other cases' counts.
const ZONE_USER = "csv-import-zone-user";
beforeAll(async () => {
await prisma.user.create({
data: { id: ZONE_USER, username: ZONE_USER, timezone: TZ },
});
});
it("cuts the duplicate day in the zone the file is read in", async () => {
// Read in New York, these are draws on two days (1 and 2 March). In the
// account's Berlin they both fall on 2 March, which used to make the
@@ -199,7 +209,7 @@ describe("lab CSV import in a zone of the file's own", () => {
"Zone marker;7;mg/dL;2025-03-02T02:00",
].join("\n");
const result = await importLabsCsv({
userId: USER,
userId: ZONE_USER,
tz: TZ,
stream: textToStream(csv),
maxBytes: 1_000_000,
@@ -381,6 +391,15 @@ describe("medication CSV import", () => {
});
describe("medication CSV import, a crash between medication and category", () => {
// An account of its own: the medication import case counts every
// medication its account holds.
const CRASH_USER = "csv-import-crash-user";
beforeAll(async () => {
await prisma.user.create({
data: { id: CRASH_USER, username: CRASH_USER, timezone: TZ },
});
});
const CSV = ["name,dose,times", "Atorvastatin,20 mg,20:00"].join("\n");
const query = medicationsCsvQuerySchema.parse({
name: "0",
@@ -393,7 +412,7 @@ describe("medication CSV import, a crash between medication and category", () =>
categoryFault.on = true;
await expect(
importMedicationsCsv({
userId: USER,
userId: CRASH_USER,
tz: TZ,
stream: textToStream(CSV),
maxBytes: 100_000,
@@ -403,12 +422,12 @@ describe("medication CSV import, a crash between medication and category", () =>
categoryFault.on = false;
expect(
await prisma.medication.count({
where: { userId: USER, name: "Atorvastatin" },
where: { userId: CRASH_USER, name: "Atorvastatin" },
}),
).toBe(0);
const retry = await importMedicationsCsv({
userId: USER,
userId: CRASH_USER,
tz: TZ,
stream: textToStream(CSV),
maxBytes: 100_000,
@@ -416,7 +435,7 @@ describe("medication CSV import, a crash between medication and category", () =>
});
expect(retry).toMatchObject({ inserted: 1, duplicates: 0 });
const med = await prisma.medication.findFirstOrThrow({
where: { userId: USER, name: "Atorvastatin" },
where: { userId: CRASH_USER, name: "Atorvastatin" },
select: { id: true },
});
expect(
@@ -157,6 +157,9 @@ export function defineRequestClockCases(expectedZone: string): void {
});
it("cuts every window of the live aggregate on the request's clock", async () => {
// The live path is the one without a tier; drop whatever the rollup
// case built if it ran first.
await prisma.measurementRollup.deleteMany({ where: { userId } });
await expectWindows("live");
});
@@ -210,7 +210,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("GET /api/analytics parity", () => {
describe("GET /api/analytics parity", { shuffle: false }, () => {
it("01 a slice outside the closed set", async () => {
await step("01-invalid-slice", { query: { slice: "summary" } });
});
@@ -159,7 +159,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("GET /api/dashboard/summary parity", () => {
describe("GET /api/dashboard/summary parity", { shuffle: false }, () => {
it("01 cold, then warm", async () => {
await step("01-cold");
await step("02-warm");
@@ -169,7 +169,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("/api/dashboard/widgets parity", () => {
describe("/api/dashboard/widgets parity", { shuffle: false }, () => {
let lastToken = "";
it("01 the first read, filled into the cache", async () => {
@@ -215,129 +215,133 @@ afterAll(() => {
vi.useRealTimers();
});
describe("POST /api/documents/inbound parity", () => {
it("a stored upload and the same bytes again", async () => {
const body = () =>
form(PDF("discharge"), "discharge.pdf", {
title: "Discharge letter",
kind: "DISCHARGE_LETTER",
documentDate: "2026-10-02",
episodeIds: [episodeId],
// One walk: each request sees what the earlier ones stored, so the cases
// keep their order even when a run shuffles (`shuffle: false`).
describe("documents-inbound parity walk", { shuffle: false }, () => {
describe("POST /api/documents/inbound parity", () => {
it("a stored upload and the same bytes again", async () => {
const body = () =>
form(PDF("discharge"), "discharge.pdf", {
title: "Discharge letter",
kind: "DISCHARGE_LETTER",
documentDate: "2026-10-02",
episodeIds: [episodeId],
});
await upload("post-01-stored", body());
await upload("post-02-duplicate", body());
await upload(
"post-03-second",
form(PDF("labs"), "labs.pdf", {
title: "Blood test",
kind: "LAB_RESULT",
documentDate: "2025-03-04",
}),
);
});
it("the refusals", async () => {
await upload("post-04-no-file", form(null, "x", { title: "Nothing" }));
await upload(
"post-05-invalid-metadata",
form(PDF("bad-kind"), "bad.pdf", { kind: "SHOPPING_LIST" }),
);
await upload("post-06-empty", form(Buffer.alloc(0), "empty.pdf"));
await upload(
"post-07-unsupported",
form(
Buffer.from("<!doctype html><html><body>hi</body></html>\n"),
"page.html",
),
);
await upload(
"post-08-unknown-episode",
form(PDF("orphan"), "orphan.pdf", {
episodeIds: ["cmissingepisode000000000"],
}),
);
await upload("post-09-half-key", form(PDF("half"), "half.pdf"), {
query: { sourceSystem: "PAPERLESS" },
});
await upload("post-01-stored", body());
await upload("post-02-duplicate", body());
await upload(
"post-03-second",
form(PDF("labs"), "labs.pdf", {
title: "Blood test",
kind: "LAB_RESULT",
documentDate: "2025-03-04",
}),
);
await upload(
"post-10-keys-differ",
form(PDF("differ"), "differ.pdf", {
sourceSystem: "PAPERLESS",
sourceId: "77",
}),
{ query: { sourceSystem: "PAPERLESS", sourceId: "78" } },
);
});
it("a narrow token storing under a source key", async () => {
const stored = await upload(
"post-11-token-stored",
form(PDF("imported"), "imported.pdf", {
sourceSystem: "PAPERLESS",
sourceId: "41",
aiRead: "defer",
}),
{ auth: writer },
);
await upload("post-12-token-query-key", form(PDF("imported"), "x.pdf"), {
auth: writer,
query: { sourceSystem: "PAPERLESS", sourceId: "41" },
});
await prisma.inboundDocument.update({
where: { id: stored.id! },
data: { deletedAt: new Date("2026-10-14T09:00:00.000Z") },
});
await upload(
"post-13-token-deleted",
form(PDF("imported"), "imported.pdf", {
sourceSystem: "PAPERLESS",
sourceId: "41",
}),
{ auth: writer },
);
});
it("the module switched off", async () => {
await prisma.user.update({
where: { id: userId },
data: { modulePreferencesJson: { inboundDocuments: false } },
});
await upload("post-14-module-off", form(PDF("dark"), "dark.pdf"));
await prisma.user.update({
where: { id: userId },
data: { modulePreferencesJson: { inboundDocuments: true } },
});
});
});
it("the refusals", async () => {
await upload("post-04-no-file", form(null, "x", { title: "Nothing" }));
await upload(
"post-05-invalid-metadata",
form(PDF("bad-kind"), "bad.pdf", { kind: "SHOPPING_LIST" }),
);
await upload("post-06-empty", form(Buffer.alloc(0), "empty.pdf"));
await upload(
"post-07-unsupported",
form(
Buffer.from("<!doctype html><html><body>hi</body></html>\n"),
"page.html",
),
);
await upload(
"post-08-unknown-episode",
form(PDF("orphan"), "orphan.pdf", {
episodeIds: ["cmissingepisode000000000"],
}),
);
await upload("post-09-half-key", form(PDF("half"), "half.pdf"), {
query: { sourceSystem: "PAPERLESS" },
describe("GET /api/documents/inbound parity", () => {
it("the default page, a search and the filters", async () => {
await list("get-01-default", {});
await list("get-02-search", { q: "blood" });
await list("get-03-kind", { kind: "DISCHARGE_LETTER,LAB_RESULT" });
await list("get-04-episode", { episodeId });
await list("get-05-year", { year: 2025 });
await list("get-06-range", { from: "2026-10-01", to: "2026-10-02" });
});
await upload(
"post-10-keys-differ",
form(PDF("differ"), "differ.pdf", {
sourceSystem: "PAPERLESS",
sourceId: "77",
}),
{ query: { sourceSystem: "PAPERLESS", sourceId: "78" } },
);
});
it("a narrow token storing under a source key", async () => {
const stored = await upload(
"post-11-token-stored",
form(PDF("imported"), "imported.pdf", {
sourceSystem: "PAPERLESS",
sourceId: "41",
aiRead: "defer",
}),
{ auth: writer },
);
await upload("post-12-token-query-key", form(PDF("imported"), "x.pdf"), {
auth: writer,
query: { sourceSystem: "PAPERLESS", sourceId: "41" },
it("a sorted page and the page after it", async () => {
const first = await list("get-07-sorted", {
sort: "title",
order: "asc",
limit: 1,
});
await list("get-08-next-page", {
sort: "title",
order: "asc",
limit: 1,
cursor: first.nextCursor!,
});
});
await prisma.inboundDocument.update({
where: { id: stored.id! },
data: { deletedAt: new Date("2026-10-14T09:00:00.000Z") },
});
await upload(
"post-13-token-deleted",
form(PDF("imported"), "imported.pdf", {
sourceSystem: "PAPERLESS",
sourceId: "41",
}),
{ auth: writer },
);
});
it("the module switched off", async () => {
await prisma.user.update({
where: { id: userId },
data: { modulePreferencesJson: { inboundDocuments: false } },
});
await upload("post-14-module-off", form(PDF("dark"), "dark.pdf"));
await prisma.user.update({
where: { id: userId },
data: { modulePreferencesJson: { inboundDocuments: true } },
it("a refused query, and a delegate reading the record", async () => {
await list("get-09-invalid", { sort: "size" });
await switchSessionTo(delegate.sessionId, userId);
await list("get-10-delegated", {}, delegate);
await switchSessionTo(delegate.sessionId, null);
});
});
});
describe("GET /api/documents/inbound parity", () => {
it("the default page, a search and the filters", async () => {
await list("get-01-default", {});
await list("get-02-search", { q: "blood" });
await list("get-03-kind", { kind: "DISCHARGE_LETTER,LAB_RESULT" });
await list("get-04-episode", { episodeId });
await list("get-05-year", { year: 2025 });
await list("get-06-range", { from: "2026-10-01", to: "2026-10-02" });
});
it("a sorted page and the page after it", async () => {
const first = await list("get-07-sorted", {
sort: "title",
order: "asc",
limit: 1,
});
await list("get-08-next-page", {
sort: "title",
order: "asc",
limit: 1,
cursor: first.nextCursor!,
});
});
it("a refused query, and a delegate reading the record", async () => {
await list("get-09-invalid", { sort: "size" });
await switchSessionTo(delegate.sessionId, userId);
await list("get-10-delegated", {}, delegate);
await switchSessionTo(delegate.sessionId, null);
});
});
@@ -199,7 +199,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("POST /api/import/csv parity", () => {
describe("POST /api/import/csv parity", { shuffle: false }, () => {
it("01 a dry run", async () => {
await step("01-dry-run", FILE, { dryRun: "1" });
});
@@ -263,7 +263,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("POST /api/import parity", () => {
describe("POST /api/import parity", { shuffle: false }, () => {
it("01 a first file", async () => {
await step("01-first-file", FILE);
});
@@ -408,7 +408,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("GET /api/insights/comprehensive parity", () => {
describe("GET /api/insights/comprehensive parity", { shuffle: false }, () => {
it("01 an anonymous call", async () => {
await step("01-anonymous", { auth: anonymous });
});
@@ -635,380 +635,394 @@ afterAll(() => {
vi.useRealTimers();
});
describe("GET /api/insights/generate parity", () => {
it("G01 an anonymous call", async () => {
await get("G01-anonymous", { auth: anonymous });
});
it("G02 the AI analysis opt-out", async () => {
await get("G02-opted-out", { auth: optedOut, userId: OPTED_OUT_ID });
});
it("G03 no cached briefing", async () => {
await resetBriefing(OWNER_ID);
await get("G03-empty");
});
it("G04 a briefing written today", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "en",
// One walk: each request sees what the earlier ones stored, so the cases
// keep their order even when a run shuffles (`shuffle: false`).
describe("insights-generate parity walk", { shuffle: false }, () => {
describe("GET /api/insights/generate parity", () => {
it("G01 an anonymous call", async () => {
await get("G01-anonymous", { auth: anonymous });
});
await get("G04-fresh");
});
it("G05 a briefing written yesterday", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - 20 * HOUR)),
cachedAt: new Date(NOW.getTime() - 2 * HOUR),
cachedLocale: "en",
it("G02 the AI analysis opt-out", async () => {
await get("G02-opted-out", { auth: optedOut, userId: OPTED_OUT_ID });
});
await get("G05-stale");
});
it("G06 a briefing in another language", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "de",
});
await get("G06-other-language");
});
it("G07 an unparseable cache row", async () => {
await resetBriefing(OWNER_ID, {
text: "{not json",
cachedAt: new Date(NOW.getTime() - HOUR),
});
await get("G07-unparseable");
});
it("G08 a legacy payload, untagged", async () => {
await resetBriefing(OWNER_ID, {
text: { summary: "Legacy", recommendations: ["Drink water."] },
cachedAt: new Date(NOW.getTime() - HOUR),
});
await get("G08-legacy");
});
it("G09 a failure newer than the last good briefing", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - 3 * HOUR)),
cachedAt: new Date(NOW.getTime() - 3 * HOUR),
cachedLocale: "en",
markers: [
{
reason: "all-providers-failed",
at: new Date(NOW.getTime() - HOUR),
httpStatus: 429,
},
],
});
await get("G09-held-after-failure");
});
it("G10 a failure with no good briefing, and an older superseded one", async () => {
await resetBriefing(OWNER_ID, {
markers: [
{ reason: "provider-error", at: new Date(NOW.getTime() - 5 * HOUR) },
{ reason: "invalid-json", at: new Date(NOW.getTime() - HOUR) },
],
});
await get("G10-failed-empty");
});
it("G11 a briefing the grounding gate withheld", async () => {
await resetBriefing(OWNER_ID, {
text: cached(null, new Date(NOW.getTime() - 2 * HOUR)),
cachedAt: new Date(NOW.getTime() - 2 * HOUR),
cachedLocale: "en",
markers: [
{ reason: "briefing-ungrounded", at: new Date(NOW.getTime() - HOUR) },
],
});
await get("G11-withheld");
});
});
describe("POST /api/insights/generate parity", () => {
it("P01 an anonymous call", async () => {
await post("P01-anonymous", { force: true }, { auth: anonymous });
});
it("P02 the AI analysis opt-out", async () => {
await post(
"P02-opted-out",
{ force: true },
{ auth: optedOut, userId: OPTED_OUT_ID },
);
});
it("P03 a body over the cap", async () => {
await resetBriefing(OWNER_ID);
await post("P03-body-too-large", { pad: "x".repeat(17 * 1024) });
});
it("P04 a cached briefing written today", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "en",
});
await post("P04-cache-hit", {});
});
it("P05 a cached briefing from yesterday, regenerated", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - 20 * HOUR)),
cachedAt: new Date(NOW.getTime() - 2 * HOUR),
cachedLocale: "en",
});
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P05-yesterday-regenerated", {});
});
it("P06 a cache in another language, regenerated", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "de",
});
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P06-other-language-regenerated", {});
});
it("P07 a briefingless cache when the hour's quota is spent", async () => {
await resetBriefing(OWNER_ID, {
text: cached(null, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "en",
quotaUsed: 10,
});
await post("P07-quota-cached-fallback", {});
});
it("P08 a forced generation when the hour's quota is spent", async () => {
await resetBriefing(OWNER_ID, { quotaUsed: 10 });
await post("P08-quota-refused");
});
it("P09 a key no resolver can open", async () => {
await resetBriefing(UNREADABLE_KEY_ID);
await post(
"P09-unreadable-key",
{ force: true },
{ auth: unreadableKey, userId: UNREADABLE_KEY_ID },
);
});
it("P10 a fresh generation", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({ content: reply(GROUNDED_BRIEFING), tokensUsed: 2100 });
await post("P10-generated");
});
it("P11 a reply the strict schema accepts", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: reply(GROUNDED_BRIEFING, { disclaimer: "Consult your doctor." }),
});
await post("P11-strict-reply");
});
it("P12 a generation in German with the comparison, about-me and short form", async () => {
await prisma.user.update({
where: { id: OWNER_ID },
data: {
locale: "de",
aiUsageLevel: "FRUGAL",
dashboardWidgetsJson: { comparisonBaseline: "lastMonth" },
},
});
await prisma.allergy.create({
data: { userId: OWNER_ID, substance: "Penicillin" },
});
await resetBriefing(OWNER_ID);
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P12-generated-de-short");
await prisma.allergy.deleteMany({ where: { userId: OWNER_ID } });
await prisma.user.update({
where: { id: OWNER_ID },
data: { locale: "en", aiUsageLevel: null, dashboardWidgetsJson: {} },
});
});
it("P13 an oversized raw payload, downgraded once", async () => {
await prisma.user.update({
where: { id: OWNER_ID },
data: { insightsPrivacyMode: "raw" },
});
await resetBriefing(OWNER_ID);
seams.oversize.push(6_100_000);
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P13-downgraded");
});
it("P14 an oversized aggregate too, downgraded twice", async () => {
await resetBriefing(OWNER_ID);
seams.oversize.push(6_100_000, 5_400_000);
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P14-hard-downgraded");
});
it("P15 oversized on every rung", async () => {
await resetBriefing(OWNER_ID);
seams.oversize.push(6_100_000, 5_400_000, 5_200_000);
await post("P15-too-large");
await prisma.user.update({
where: { id: OWNER_ID },
data: { insightsPrivacyMode: "aggregated" },
});
});
it("P16 the day's token ceiling", async () => {
await resetBriefing(OWNER_ID, { spentToday: 5_000_000 });
await post("P16-budget-exceeded");
});
it("P17 every provider refused the credential", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
error: () => new AllProvidersFailedError([hop(401), hop(403, 2)]),
});
await post("P17-chain-auth");
});
it("P18 every provider rate-limited", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({ error: () => new AllProvidersFailedError([hop(429)]) });
await post("P18-chain-rate-limited");
});
it("P19 a provider in a 5xx", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
error: () => new AllProvidersFailedError([hop(401), hop(502, 2)]),
});
await post("P19-chain-5xx");
});
it("P20 every provider unreachable", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
error: () => new AllProvidersFailedError([hop(null), hop(null, 2)]),
});
await post("P20-chain-transport");
});
it("P21 a mixed chain failure", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
error: () => new AllProvidersFailedError([hop(404), hop(429, 2)]),
});
await post("P21-chain-mixed");
});
it("P22 one provider error per status class", async () => {
for (const status of [401, 429, 500, 400]) {
it("G03 no cached briefing", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({ error: providerError(status) });
await post(`P22-provider-${status}`);
}
});
it("P23 a reply that is not JSON", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({ content: "Sure! Here is your briefing." });
await post("P23-invalid-json");
});
it("P24 a reply cut off at the token ceiling", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({ content: '{"summary": "Your', finishReason: "length" });
await post("P24-truncated");
});
it("P25 an ungrounded briefing the retry fixes", async () => {
await resetBriefing(OWNER_ID);
seams.script.push(
{ content: reply(UNGROUNDED_BRIEFING) },
{ content: reply(GROUNDED_BRIEFING), tokensUsed: 900 },
);
await post("P25-grounding-retry-kept");
});
it("P26 still ungrounded, the previous briefing stands in", async () => {
await resetBriefing(OWNER_ID, {
text: cached(PREVIOUS_BRIEFING, new Date(NOW.getTime() - 26 * HOUR)),
cachedAt: new Date(NOW.getTime() - 26 * HOUR),
cachedLocale: "en",
await get("G03-empty");
});
seams.script.push(
{ content: reply(UNGROUNDED_BRIEFING) },
{ content: reply(UNGROUNDED_BRIEFING) },
);
await post("P26-grounding-previous");
});
it("P27 still ungrounded, nothing to stand in", async () => {
await resetBriefing(OWNER_ID);
seams.script.push(
{ content: reply(UNGROUNDED_BRIEFING) },
{ content: reply(UNGROUNDED_BRIEFING) },
);
await post("P27-grounding-stripped");
});
it("G04 a briefing written today", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "en",
});
await get("G04-fresh");
});
it("P28 an ungrounded briefing whose retry is not JSON", async () => {
await resetBriefing(OWNER_ID);
seams.script.push(
{ content: reply(UNGROUNDED_BRIEFING) },
{ content: "not json" },
);
await post("P28-grounding-retry-unparseable");
});
it("G05 a briefing written yesterday", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - 20 * HOUR)),
cachedAt: new Date(NOW.getTime() - 2 * HOUR),
cachedLocale: "en",
});
await get("G05-stale");
});
it("P29 a reply the outbound screen blocks", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: reply(GROUNDED_BRIEFING, {
summary:
"Your 10-year cardiovascular risk is elevated — consider stepping your dose up.",
}),
it("G06 a briefing in another language", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "de",
});
await get("G06-other-language");
});
it("G07 an unparseable cache row", async () => {
await resetBriefing(OWNER_ID, {
text: "{not json",
cachedAt: new Date(NOW.getTime() - HOUR),
});
await get("G07-unparseable");
});
it("G08 a legacy payload, untagged", async () => {
await resetBriefing(OWNER_ID, {
text: { summary: "Legacy", recommendations: ["Drink water."] },
cachedAt: new Date(NOW.getTime() - HOUR),
});
await get("G08-legacy");
});
it("G09 a failure newer than the last good briefing", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - 3 * HOUR)),
cachedAt: new Date(NOW.getTime() - 3 * HOUR),
cachedLocale: "en",
markers: [
{
reason: "all-providers-failed",
at: new Date(NOW.getTime() - HOUR),
httpStatus: 429,
},
],
});
await get("G09-held-after-failure");
});
it("G10 a failure with no good briefing, and an older superseded one", async () => {
await resetBriefing(OWNER_ID, {
markers: [
{ reason: "provider-error", at: new Date(NOW.getTime() - 5 * HOUR) },
{ reason: "invalid-json", at: new Date(NOW.getTime() - HOUR) },
],
});
await get("G10-failed-empty");
});
it("G11 a briefing the grounding gate withheld", async () => {
await resetBriefing(OWNER_ID, {
text: cached(null, new Date(NOW.getTime() - 2 * HOUR)),
cachedAt: new Date(NOW.getTime() - 2 * HOUR),
cachedLocale: "en",
markers: [
{ reason: "briefing-ungrounded", at: new Date(NOW.getTime() - HOUR) },
],
});
await get("G11-withheld");
});
await post("P29-outbound-screened", { force: true });
});
it("P30 the about-me text changing during the generation", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: reply(GROUNDED_BRIEFING),
during: async () => {
await prisma.allergy.create({
data: { userId: OWNER_ID, substance: "Latex" },
});
},
describe("POST /api/insights/generate parity", () => {
it("P01 an anonymous call", async () => {
await post("P01-anonymous", { force: true }, { auth: anonymous });
});
await post("P30-profile-scope-changed");
await prisma.allergy.deleteMany({ where: { userId: OWNER_ID } });
});
it("P31 the profile changing during the generation", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: reply(GROUNDED_BRIEFING),
during: async () => {
await prisma.user.update({
where: { id: OWNER_ID },
data: { displayName: "Renamed Person" },
});
},
it("P02 the AI analysis opt-out", async () => {
await post(
"P02-opted-out",
{ force: true },
{ auth: optedOut, userId: OPTED_OUT_ID },
);
});
await post("P31-scope-changed");
await prisma.user.update({
where: { id: OWNER_ID },
data: { displayName: "Parity Person" },
it("P03 a body over the cap", async () => {
await resetBriefing(OWNER_ID);
await post("P03-body-too-large", { pad: "x".repeat(17 * 1024) });
});
it("P04 a cached briefing written today", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "en",
});
await post("P04-cache-hit", {});
});
it("P05 a cached briefing from yesterday, regenerated", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - 20 * HOUR)),
cachedAt: new Date(NOW.getTime() - 2 * HOUR),
cachedLocale: "en",
});
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P05-yesterday-regenerated", {});
});
it("P06 a cache in another language, regenerated", async () => {
await resetBriefing(OWNER_ID, {
text: cached(GROUNDED_BRIEFING, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "de",
});
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P06-other-language-regenerated", {});
});
it("P07 a briefingless cache when the hour's quota is spent", async () => {
await resetBriefing(OWNER_ID, {
text: cached(null, new Date(NOW.getTime() - HOUR)),
cachedAt: new Date(NOW.getTime() - HOUR),
cachedLocale: "en",
quotaUsed: 10,
});
await post("P07-quota-cached-fallback", {});
});
it("P08 a forced generation when the hour's quota is spent", async () => {
await resetBriefing(OWNER_ID, { quotaUsed: 10 });
await post("P08-quota-refused");
});
it("P09 a key no resolver can open", async () => {
await resetBriefing(UNREADABLE_KEY_ID);
await post(
"P09-unreadable-key",
{ force: true },
{ auth: unreadableKey, userId: UNREADABLE_KEY_ID },
);
});
it("P10 a fresh generation", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: reply(GROUNDED_BRIEFING),
tokensUsed: 2100,
});
await post("P10-generated");
});
it("P11 a reply the strict schema accepts", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: reply(GROUNDED_BRIEFING, {
disclaimer: "Consult your doctor.",
}),
});
await post("P11-strict-reply");
});
it("P12 a generation in German with the comparison, about-me and short form", async () => {
await prisma.user.update({
where: { id: OWNER_ID },
data: {
locale: "de",
aiUsageLevel: "FRUGAL",
dashboardWidgetsJson: { comparisonBaseline: "lastMonth" },
},
});
await prisma.allergy.create({
data: { userId: OWNER_ID, substance: "Penicillin" },
});
await resetBriefing(OWNER_ID);
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P12-generated-de-short");
await prisma.allergy.deleteMany({ where: { userId: OWNER_ID } });
await prisma.user.update({
where: { id: OWNER_ID },
data: { locale: "en", aiUsageLevel: null, dashboardWidgetsJson: {} },
});
});
it("P13 an oversized raw payload, downgraded once", async () => {
await prisma.user.update({
where: { id: OWNER_ID },
data: { insightsPrivacyMode: "raw" },
});
await resetBriefing(OWNER_ID);
seams.oversize.push(6_100_000);
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P13-downgraded");
});
it("P14 an oversized aggregate too, downgraded twice", async () => {
await resetBriefing(OWNER_ID);
seams.oversize.push(6_100_000, 5_400_000);
seams.script.push({ content: reply(GROUNDED_BRIEFING) });
await post("P14-hard-downgraded");
});
it("P15 oversized on every rung", async () => {
await resetBriefing(OWNER_ID);
seams.oversize.push(6_100_000, 5_400_000, 5_200_000);
await post("P15-too-large");
await prisma.user.update({
where: { id: OWNER_ID },
data: { insightsPrivacyMode: "aggregated" },
});
});
it("P16 the day's token ceiling", async () => {
await resetBriefing(OWNER_ID, { spentToday: 5_000_000 });
await post("P16-budget-exceeded");
});
it("P17 every provider refused the credential", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
error: () => new AllProvidersFailedError([hop(401), hop(403, 2)]),
});
await post("P17-chain-auth");
});
it("P18 every provider rate-limited", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
error: () => new AllProvidersFailedError([hop(429)]),
});
await post("P18-chain-rate-limited");
});
it("P19 a provider in a 5xx", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
error: () => new AllProvidersFailedError([hop(401), hop(502, 2)]),
});
await post("P19-chain-5xx");
});
it("P20 every provider unreachable", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
error: () => new AllProvidersFailedError([hop(null), hop(null, 2)]),
});
await post("P20-chain-transport");
});
it("P21 a mixed chain failure", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
error: () => new AllProvidersFailedError([hop(404), hop(429, 2)]),
});
await post("P21-chain-mixed");
});
it("P22 one provider error per status class", async () => {
for (const status of [401, 429, 500, 400]) {
await resetBriefing(OWNER_ID);
seams.script.push({ error: providerError(status) });
await post(`P22-provider-${status}`);
}
});
it("P23 a reply that is not JSON", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({ content: "Sure! Here is your briefing." });
await post("P23-invalid-json");
});
it("P24 a reply cut off at the token ceiling", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: '{"summary": "Your',
finishReason: "length",
});
await post("P24-truncated");
});
it("P25 an ungrounded briefing the retry fixes", async () => {
await resetBriefing(OWNER_ID);
seams.script.push(
{ content: reply(UNGROUNDED_BRIEFING) },
{ content: reply(GROUNDED_BRIEFING), tokensUsed: 900 },
);
await post("P25-grounding-retry-kept");
});
it("P26 still ungrounded, the previous briefing stands in", async () => {
await resetBriefing(OWNER_ID, {
text: cached(PREVIOUS_BRIEFING, new Date(NOW.getTime() - 26 * HOUR)),
cachedAt: new Date(NOW.getTime() - 26 * HOUR),
cachedLocale: "en",
});
seams.script.push(
{ content: reply(UNGROUNDED_BRIEFING) },
{ content: reply(UNGROUNDED_BRIEFING) },
);
await post("P26-grounding-previous");
});
it("P27 still ungrounded, nothing to stand in", async () => {
await resetBriefing(OWNER_ID);
seams.script.push(
{ content: reply(UNGROUNDED_BRIEFING) },
{ content: reply(UNGROUNDED_BRIEFING) },
);
await post("P27-grounding-stripped");
});
it("P28 an ungrounded briefing whose retry is not JSON", async () => {
await resetBriefing(OWNER_ID);
seams.script.push(
{ content: reply(UNGROUNDED_BRIEFING) },
{ content: "not json" },
);
await post("P28-grounding-retry-unparseable");
});
it("P29 a reply the outbound screen blocks", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: reply(GROUNDED_BRIEFING, {
summary:
"Your 10-year cardiovascular risk is elevated — consider stepping your dose up.",
}),
});
await post("P29-outbound-screened", { force: true });
});
it("P30 the about-me text changing during the generation", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: reply(GROUNDED_BRIEFING),
during: async () => {
await prisma.allergy.create({
data: { userId: OWNER_ID, substance: "Latex" },
});
},
});
await post("P30-profile-scope-changed");
await prisma.allergy.deleteMany({ where: { userId: OWNER_ID } });
});
it("P31 the profile changing during the generation", async () => {
await resetBriefing(OWNER_ID);
seams.script.push({
content: reply(GROUNDED_BRIEFING),
during: async () => {
await prisma.user.update({
where: { id: OWNER_ID },
data: { displayName: "Renamed Person" },
});
},
});
await post("P31-scope-changed");
await prisma.user.update({
where: { id: OWNER_ID },
data: { displayName: "Parity Person" },
});
});
});
});
@@ -239,7 +239,7 @@ beforeAll(async () => {
delegate = { auth: session };
});
describe("GET /api/measurements/series parity", () => {
describe("GET /api/measurements/series parity", { shuffle: false }, () => {
it("the plain readers", async () => {
await step("01-weight", { kind: "weight" });
await step("02-steps", { kind: "steps", days: 14 });
@@ -327,7 +327,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("POST /api/measurements/batch parity", () => {
describe("POST /api/measurements/batch parity", { shuffle: false }, () => {
it("01 a first sync of mixed entries", async () => {
await step("01-first-sync", {
entries: FIRST_SYNC,
@@ -220,185 +220,192 @@ afterAll(() => {
vi.useRealTimers();
});
describe("POST /api/measurements parity", () => {
it("a single reading, its duplicate, a refused one", async () => {
const weight = {
type: "WEIGHT",
value: 80.4,
measuredAt: "2026-10-14T06:30:00.000Z",
notes: "after the run",
deviceType: "scale",
};
await post("post-01-single", weight);
await post("post-02-single-duplicate", weight);
await post("post-03-single-invalid", {
type: "WEIGHT",
value: "heavy",
measuredAt: "2026-10-14T06:30:00.000Z",
});
await post("post-04-single-glucose", {
type: "BLOOD_GLUCOSE",
value: 104,
measuredAt: "2026-10-14T07:00:00.000Z",
glucoseContext: "FASTING",
symptomsPresent: false,
});
});
it("a combined two-value reading, its duplicate, a refused one", async () => {
const reading = [
{
type: "BLOOD_PRESSURE_SYS",
value: 128,
measuredAt: "2026-10-14T08:00:00.000Z",
},
{
type: "BLOOD_PRESSURE_DIA",
value: 82,
measuredAt: "2026-10-14T08:00:00.000Z",
},
{
type: "PULSE",
value: 66,
measuredAt: "2026-10-14T08:00:00.000Z",
notes: "seated",
symptomsPresent: true,
},
];
await post("post-05-batch", reading);
await post("post-06-batch-duplicate", reading);
await post("post-07-batch-invalid", [{ type: "PULSE", value: -1 }]);
});
it("a named sleep-stage segment, its re-post and its mirror", async () => {
const segment = {
type: "SLEEP_DURATION",
sleepStage: "DEEP",
startDate: "2026-10-14T01:00:00.000Z",
endDate: "2026-10-14T02:30:00.000Z",
externalId: "bridge-sleep-1",
};
await post("post-08-sleep-stage", segment);
await post("post-09-sleep-stage-again", segment);
await post("post-10-sleep-stage-mirror", {
...segment,
externalId: "hk-sleep-1",
source: "APPLE_HEALTH",
});
await post("post-11-sleep-stage-invalid", {
type: "SLEEP_DURATION",
sleepStage: "DEEP",
});
});
it("a narrow bridge token with and without a source", async () => {
const bridge = await makeToken(
{ id: ownerId },
{ permissions: [MEASUREMENTS_WRITE_SCOPE] },
);
await post(
"post-12-bridge-with-source",
{
// One walk: each request sees what the earlier ones stored, so the cases
// keep their order even when a run shuffles (`shuffle: false`).
describe("measurements parity walk", { shuffle: false }, () => {
describe("POST /api/measurements parity", () => {
it("a single reading, its duplicate, a refused one", async () => {
const weight = {
type: "WEIGHT",
value: 80.1,
measuredAt: "2026-10-15T06:30:00.000Z",
source: "MANUAL",
},
bridge,
);
await post(
"post-13-bridge",
{
value: 80.4,
measuredAt: "2026-10-14T06:30:00.000Z",
notes: "after the run",
deviceType: "scale",
};
await post("post-01-single", weight);
await post("post-02-single-duplicate", weight);
await post("post-03-single-invalid", {
type: "WEIGHT",
value: 80.1,
measuredAt: "2026-10-15T06:30:00.000Z",
},
bridge,
);
});
it("a delegate writing into a shared record", async () => {
const delegateUser = await makeUser({ timezone: "UTC" });
await makeGrant({ id: ownerId }, delegateUser, { access: "WRITE" });
const delegate = await makeSession(delegateUser);
await switchSessionTo(delegate.sessionId, ownerId);
await post(
"post-14-delegated",
{
type: "PULSE",
value: 71,
measuredAt: "2026-10-15T07:00:00.000Z",
},
delegate,
);
await switchSessionTo(delegate.sessionId, null);
});
});
describe("GET /api/measurements parity", () => {
beforeAll(async () => {
// Day totals for the cumulative list modes, from the phone.
for (const [day, value] of [
["2026-10-12", 7_100],
["2026-10-13", 8_400],
["2026-10-14", 5_100],
] as const) {
await prisma.measurement.create({
data: {
userId: ownerId,
type: "ACTIVITY_STEPS",
value,
unit: "steps",
source: "APPLE_HEALTH",
measuredAt: new Date(`${day}T12:00:00.000Z`),
externalId: `stats:${STEPS}:${day}`,
aggregationProvenance: "HEALTHKIT_STATISTICS",
},
value: "heavy",
measuredAt: "2026-10-14T06:30:00.000Z",
});
await post("post-04-single-glucose", {
type: "BLOOD_GLUCOSE",
value: 104,
measuredAt: "2026-10-14T07:00:00.000Z",
glucoseContext: "FASTING",
symptomsPresent: false,
});
}
});
it("the plain page and its filters", async () => {
await get("get-01-page", { limit: 50 });
await get("get-02-type-sorted", {
type: "PULSE",
sortBy: "value",
sortDir: "asc",
});
await get("get-03-filtered", {
sourceEq: "MANUAL",
valueMin: 70,
valueMax: 130,
from: "2026-10-14T00:00:00.000Z",
to: "2026-10-15T23:59:59.000Z",
limit: 2,
offset: 1,
it("a combined two-value reading, its duplicate, a refused one", async () => {
const reading = [
{
type: "BLOOD_PRESSURE_SYS",
value: 128,
measuredAt: "2026-10-14T08:00:00.000Z",
},
{
type: "BLOOD_PRESSURE_DIA",
value: 82,
measuredAt: "2026-10-14T08:00:00.000Z",
},
{
type: "PULSE",
value: 66,
measuredAt: "2026-10-14T08:00:00.000Z",
notes: "seated",
symptomsPresent: true,
},
];
await post("post-05-batch", reading);
await post("post-06-batch-duplicate", reading);
await post("post-07-batch-invalid", [{ type: "PULSE", value: -1 }]);
});
it("a named sleep-stage segment, its re-post and its mirror", async () => {
const segment = {
type: "SLEEP_DURATION",
sleepStage: "DEEP",
startDate: "2026-10-14T01:00:00.000Z",
endDate: "2026-10-14T02:30:00.000Z",
externalId: "bridge-sleep-1",
};
await post("post-08-sleep-stage", segment);
await post("post-09-sleep-stage-again", segment);
await post("post-10-sleep-stage-mirror", {
...segment,
externalId: "hk-sleep-1",
source: "APPLE_HEALTH",
});
await post("post-11-sleep-stage-invalid", {
type: "SLEEP_DURATION",
sleepStage: "DEEP",
});
});
it("a narrow bridge token with and without a source", async () => {
const bridge = await makeToken(
{ id: ownerId },
{ permissions: [MEASUREMENTS_WRITE_SCOPE] },
);
await post(
"post-12-bridge-with-source",
{
type: "WEIGHT",
value: 80.1,
measuredAt: "2026-10-15T06:30:00.000Z",
source: "MANUAL",
},
bridge,
);
await post(
"post-13-bridge",
{
type: "WEIGHT",
value: 80.1,
measuredAt: "2026-10-15T06:30:00.000Z",
},
bridge,
);
});
it("a delegate writing into a shared record", async () => {
const delegateUser = await makeUser({ timezone: "UTC" });
await makeGrant({ id: ownerId }, delegateUser, { access: "WRITE" });
const delegate = await makeSession(delegateUser);
await switchSessionTo(delegate.sessionId, ownerId);
await post(
"post-14-delegated",
{
type: "PULSE",
value: 71,
measuredAt: "2026-10-15T07:00:00.000Z",
},
delegate,
);
await switchSessionTo(delegate.sessionId, null);
});
});
it("the list modes", async () => {
await get("get-04-sleep", { type: "SLEEP_DURATION" });
await get("get-05-drill-down", {
type: "ACTIVITY_STEPS",
dayKey: "2026-10-13",
describe("GET /api/measurements parity", () => {
beforeAll(async () => {
// Day totals for the cumulative list modes, from the phone.
for (const [day, value] of [
["2026-10-12", 7_100],
["2026-10-13", 8_400],
["2026-10-14", 5_100],
] as const) {
await prisma.measurement.create({
data: {
userId: ownerId,
type: "ACTIVITY_STEPS",
value,
unit: "steps",
source: "APPLE_HEALTH",
measuredAt: new Date(`${day}T12:00:00.000Z`),
externalId: `stats:${STEPS}:${day}`,
aggregationProvenance: "HEALTHKIT_STATISTICS",
},
});
}
});
await get("get-06-day-grouped", { type: "ACTIVITY_STEPS", groupBy: "day" });
await get("get-07-rollup-series", {
type: "ACTIVITY_STEPS",
source: "rollup",
aggregate: "daily",
from: "2026-10-10T00:00:00.000Z",
to: "2026-10-15T23:59:59.000Z",
});
await get("get-08-live-aggregate", {
type: "PULSE",
aggregate: "weekly",
from: "2026-09-01T00:00:00.000Z",
to: "2026-10-15T23:59:59.000Z",
});
});
it("a refused query", async () => {
await get("get-09-invalid", { limit: 0, sortDir: "sideways" });
it("the plain page and its filters", async () => {
await get("get-01-page", { limit: 50 });
await get("get-02-type-sorted", {
type: "PULSE",
sortBy: "value",
sortDir: "asc",
});
await get("get-03-filtered", {
sourceEq: "MANUAL",
valueMin: 70,
valueMax: 130,
from: "2026-10-14T00:00:00.000Z",
to: "2026-10-15T23:59:59.000Z",
limit: 2,
offset: 1,
});
});
it("the list modes", async () => {
await get("get-04-sleep", { type: "SLEEP_DURATION" });
await get("get-05-drill-down", {
type: "ACTIVITY_STEPS",
dayKey: "2026-10-13",
});
await get("get-06-day-grouped", {
type: "ACTIVITY_STEPS",
groupBy: "day",
});
await get("get-07-rollup-series", {
type: "ACTIVITY_STEPS",
source: "rollup",
aggregate: "daily",
from: "2026-10-10T00:00:00.000Z",
to: "2026-10-15T23:59:59.000Z",
});
await get("get-08-live-aggregate", {
type: "PULSE",
aggregate: "weekly",
from: "2026-09-01T00:00:00.000Z",
to: "2026-10-15T23:59:59.000Z",
});
});
it("a refused query", async () => {
await get("get-09-invalid", { limit: 0, sortDir: "sideways" });
});
});
});
@@ -358,212 +358,216 @@ afterAll(() => {
vi.useRealTimers();
});
describe("GET /api/medications/{id} parity", () => {
it("01 a rolling injection with stock, a composition and a custom category", async () => {
await get("01-rolling", med.rolling);
// One walk: each request sees what the earlier ones stored, so the cases
// keep their order even when a run shuffles (`shuffle: false`).
describe("medication-detail parity walk", { shuffle: false }, () => {
describe("GET /api/medications/{id} parity", () => {
it("01 a rolling injection with stock, a composition and a custom category", async () => {
await get("01-rolling", med.rolling);
});
it("02 a daily tablet without stock", async () => {
await get("02-daily", med.tablet);
});
it("03 a record-only medication", async () => {
await get("03-record-only", med.recordOnly);
});
it("04 another account's medication", async () => {
await get("04-not-found", med.foreign);
});
it("05 a reading delegate, and nobody", async () => {
await get("05a-reader", med.rolling, reader);
await get("05b-anonymous", med.rolling, { auth: anonymous });
});
});
it("02 a daily tablet without stock", async () => {
await get("02-daily", med.tablet);
describe("PUT /api/medications/{id} parity", () => {
it("11 plain fields", async () => {
await put("11-plain-fields", med.tablet, {
name: "Daily tablet renamed",
dose: "20mg",
treatmentClass: "GENERIC",
dosesPerUnit: 30,
reorderLeadDays: 5,
deliveryForm: "ORAL",
notificationsEnabled: false,
liveActivityEnabled: true,
criticalAlarmEnabled: true,
atcCode: "C09AA05",
rxNormCode: null,
});
});
it("12 a schedule replace whose dose time leaves the stale window", async () => {
await put("12-schedule-replace", med.tablet, {
schedules: [
{
windowStart: "08:00",
windowEnd: "10:00",
timesOfDay: ["11:30"],
label: "Late morning",
dose: "10mg",
unitsPerDose: 2,
reminderGraceMinutes: 30,
},
{
windowStart: "20:00",
windowEnd: "22:00",
daysOfWeek: [1, 3, 5],
intervalWeeks: 2,
doseWindows: [{ timeOfDay: "20:00", start: "19:30", end: "21:00" }],
},
],
});
});
it("13 the same schedule echoed back", async () => {
await put("13-schedule-echo", med.tablet, {
schedules: [
{
windowStart: "11:30",
windowEnd: "11:30",
timesOfDay: ["11:30"],
label: "Late morning",
dose: "10mg",
unitsPerDose: 2,
reminderGraceMinutes: 30,
rrule: "FREQ=DAILY",
},
{
windowStart: "20:00",
windowEnd: "22:00",
daysOfWeek: [1, 3, 5],
intervalWeeks: 2,
doseWindows: [{ timeOfDay: "20:00", start: "19:30", end: "21:00" }],
},
],
});
});
it("14 the top-level reminder window", async () => {
await put("14-grace-bridge", med.tablet, { reminderGraceMinutes: 45 });
});
it("15 a pause and a resume", async () => {
await put("15a-pause", med.tablet, { active: false });
await put("15b-resume", med.tablet, { active: true });
});
it("16 automatic recording switched on", async () => {
await put("16-auto-mark-taken", med.tablet, { autoMarkTaken: true });
});
it("17 the category writes", async () => {
await put("17a-custom-category", med.tablet, { category: CUSTOM_KEY });
await put(
"17b-native-other-kept",
med.tablet,
{ category: "OTHER" },
{ auth: ownerBearer },
);
await put("17c-web-other", med.tablet, { category: "OTHER" });
await put("17d-unknown-category", med.tablet, {
category: "custom:0a6c2d4e-1111-4222-8333-944455556666",
});
});
it("18 a record-only medication and both tracking transitions", async () => {
await put("18a-record-schedule-kept", med.recordOnly, {
name: "Record only renamed",
schedules: [daily("06:00", "07:00")],
oneShot: false,
});
await put("18b-tracking-on", med.recordOnly, { trackIntake: true });
await put("18c-tracking-off", med.recordOnly, { trackIntake: false });
});
it("19 the as-needed flips and their refusals", async () => {
await put("19a-scheduled-without-schedules", med.asNeeded, {
asNeeded: false,
});
await put("19b-scheduled", med.asNeeded, {
asNeeded: false,
schedules: [daily("08:00", "09:00")],
});
await put("19c-as-needed-keeping-schedules", med.asNeeded, {
asNeeded: true,
});
await put("19d-as-needed", med.asNeeded, {
asNeeded: true,
schedules: [],
});
});
it("20 the one-shot refusals and a conversion", async () => {
await put("20a-one-shot-two-schedules", med.booster, {
oneShot: true,
startsOn: "2026-10-20",
schedules: [daily("09:00", "10:00"), daily("18:00", "19:00")],
});
await put("20b-one-shot-recurrence", med.booster, {
oneShot: true,
startsOn: "2026-10-20",
schedules: [
{ windowStart: "09:00", windowEnd: "10:00", rrule: "FREQ=DAILY" },
],
});
await put("20c-one-shot", med.booster, {
oneShot: true,
startsOn: "2026-10-20",
schedules: [{ windowStart: "09:00", windowEnd: "10:00" }],
});
});
it("21 the course refusals and a course edit", async () => {
await put("21a-course-overlap", med.course, { startsOn: "2026-08-20" });
await put("21b-course-window-required", med.course, {
startsOn: null,
endsOn: null,
});
await put("21c-course-end", med.course, { endsOn: "2026-12-31" });
});
it("22 a units change on a medication with a composition", async () => {
await put("22-composition-units", med.rolling, { unitsPerDose: 2 });
});
it("23 a body the schema refuses, and another account's medication", async () => {
await put("23a-invalid", med.tablet, {
name: "",
unitsPerDose: -1,
schedules: "daily",
});
await put("23b-not-found", med.foreign, { name: "Taken over" });
});
it("24 a delegate who may write, one who may manage, and nobody", async () => {
await put("24a-writer", med.tablet, { name: "By a writer" }, writer);
await put("24b-manager", med.tablet, { name: "By a manager" }, manager);
await put(
"24c-anonymous",
med.tablet,
{ name: "By nobody" },
{ auth: anonymous },
);
});
});
it("03 a record-only medication", async () => {
await get("03-record-only", med.recordOnly);
});
describe("DELETE /api/medications/{id} parity", () => {
it("31 the owner deletes a medication with a composition and an ingest token", async () => {
await del("31-delete", med.doomed);
});
it("04 another account's medication", async () => {
await get("04-not-found", med.foreign);
});
it("32 the same medication again", async () => {
await del("32-not-found", med.doomed);
});
it("05 a reading delegate, and nobody", async () => {
await get("05a-reader", med.rolling, reader);
await get("05b-anonymous", med.rolling, { auth: anonymous });
});
});
describe("PUT /api/medications/{id} parity", () => {
it("11 plain fields", async () => {
await put("11-plain-fields", med.tablet, {
name: "Daily tablet renamed",
dose: "20mg",
treatmentClass: "GENERIC",
dosesPerUnit: 30,
reorderLeadDays: 5,
deliveryForm: "ORAL",
notificationsEnabled: false,
liveActivityEnabled: true,
criticalAlarmEnabled: true,
atcCode: "C09AA05",
rxNormCode: null,
it("33 a managing delegate, and nobody", async () => {
await del("33a-manager", med.booster, manager);
await del("33b-anonymous", med.booster, { auth: anonymous });
});
});
it("12 a schedule replace whose dose time leaves the stale window", async () => {
await put("12-schedule-replace", med.tablet, {
schedules: [
{
windowStart: "08:00",
windowEnd: "10:00",
timesOfDay: ["11:30"],
label: "Late morning",
dose: "10mg",
unitsPerDose: 2,
reminderGraceMinutes: 30,
},
{
windowStart: "20:00",
windowEnd: "22:00",
daysOfWeek: [1, 3, 5],
intervalWeeks: 2,
doseWindows: [{ timeOfDay: "20:00", start: "19:30", end: "21:00" }],
},
],
});
});
it("13 the same schedule echoed back", async () => {
await put("13-schedule-echo", med.tablet, {
schedules: [
{
windowStart: "11:30",
windowEnd: "11:30",
timesOfDay: ["11:30"],
label: "Late morning",
dose: "10mg",
unitsPerDose: 2,
reminderGraceMinutes: 30,
rrule: "FREQ=DAILY",
},
{
windowStart: "20:00",
windowEnd: "22:00",
daysOfWeek: [1, 3, 5],
intervalWeeks: 2,
doseWindows: [{ timeOfDay: "20:00", start: "19:30", end: "21:00" }],
},
],
});
});
it("14 the top-level reminder window", async () => {
await put("14-grace-bridge", med.tablet, { reminderGraceMinutes: 45 });
});
it("15 a pause and a resume", async () => {
await put("15a-pause", med.tablet, { active: false });
await put("15b-resume", med.tablet, { active: true });
});
it("16 automatic recording switched on", async () => {
await put("16-auto-mark-taken", med.tablet, { autoMarkTaken: true });
});
it("17 the category writes", async () => {
await put("17a-custom-category", med.tablet, { category: CUSTOM_KEY });
await put(
"17b-native-other-kept",
med.tablet,
{ category: "OTHER" },
{ auth: ownerBearer },
);
await put("17c-web-other", med.tablet, { category: "OTHER" });
await put("17d-unknown-category", med.tablet, {
category: "custom:0a6c2d4e-1111-4222-8333-944455556666",
});
});
it("18 a record-only medication and both tracking transitions", async () => {
await put("18a-record-schedule-kept", med.recordOnly, {
name: "Record only renamed",
schedules: [daily("06:00", "07:00")],
oneShot: false,
});
await put("18b-tracking-on", med.recordOnly, { trackIntake: true });
await put("18c-tracking-off", med.recordOnly, { trackIntake: false });
});
it("19 the as-needed flips and their refusals", async () => {
await put("19a-scheduled-without-schedules", med.asNeeded, {
asNeeded: false,
});
await put("19b-scheduled", med.asNeeded, {
asNeeded: false,
schedules: [daily("08:00", "09:00")],
});
await put("19c-as-needed-keeping-schedules", med.asNeeded, {
asNeeded: true,
});
await put("19d-as-needed", med.asNeeded, {
asNeeded: true,
schedules: [],
});
});
it("20 the one-shot refusals and a conversion", async () => {
await put("20a-one-shot-two-schedules", med.booster, {
oneShot: true,
startsOn: "2026-10-20",
schedules: [daily("09:00", "10:00"), daily("18:00", "19:00")],
});
await put("20b-one-shot-recurrence", med.booster, {
oneShot: true,
startsOn: "2026-10-20",
schedules: [
{ windowStart: "09:00", windowEnd: "10:00", rrule: "FREQ=DAILY" },
],
});
await put("20c-one-shot", med.booster, {
oneShot: true,
startsOn: "2026-10-20",
schedules: [{ windowStart: "09:00", windowEnd: "10:00" }],
});
});
it("21 the course refusals and a course edit", async () => {
await put("21a-course-overlap", med.course, { startsOn: "2026-08-20" });
await put("21b-course-window-required", med.course, {
startsOn: null,
endsOn: null,
});
await put("21c-course-end", med.course, { endsOn: "2026-12-31" });
});
it("22 a units change on a medication with a composition", async () => {
await put("22-composition-units", med.rolling, { unitsPerDose: 2 });
});
it("23 a body the schema refuses, and another account's medication", async () => {
await put("23a-invalid", med.tablet, {
name: "",
unitsPerDose: -1,
schedules: "daily",
});
await put("23b-not-found", med.foreign, { name: "Taken over" });
});
it("24 a delegate who may write, one who may manage, and nobody", async () => {
await put("24a-writer", med.tablet, { name: "By a writer" }, writer);
await put("24b-manager", med.tablet, { name: "By a manager" }, manager);
await put(
"24c-anonymous",
med.tablet,
{ name: "By nobody" },
{ auth: anonymous },
);
});
});
describe("DELETE /api/medications/{id} parity", () => {
it("31 the owner deletes a medication with a composition and an ingest token", async () => {
await del("31-delete", med.doomed);
});
it("32 the same medication again", async () => {
await del("32-not-found", med.doomed);
});
it("33 a managing delegate, and nobody", async () => {
await del("33a-manager", med.booster, manager);
await del("33b-anonymous", med.booster, { auth: anonymous });
});
});
@@ -375,7 +375,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("POST /api/medications/intake/bulk parity", () => {
describe("POST /api/medications/intake/bulk parity", { shuffle: false }, () => {
it("01 a mixed first batch", async () => {
await post("01-mixed", mixedBatch());
});
@@ -229,131 +229,135 @@ afterAll(() => {
vi.useRealTimers();
});
describe("PUT /api/medications/{id}/intake/{eventId} parity", () => {
it("01 a pending dose taken inside its own window", async () => {
await put("01-take-in-place", events.pending, {
takenAt: "2026-10-13T08:40:00.000Z",
// One walk: each request sees what the earlier ones stored, so the cases
// keep their order even when a run shuffles (`shuffle: false`).
describe("medication-intake-event parity walk", { shuffle: false }, () => {
describe("PUT /api/medications/{id}/intake/{eventId} parity", () => {
it("01 a pending dose taken inside its own window", async () => {
await put("01-take-in-place", events.pending, {
takenAt: "2026-10-13T08:40:00.000Z",
});
});
it("02 the same dose flipped to a skip", async () => {
await put("02-skip-flip", events.pending, { skipped: true });
});
it("03 a take corrected into the other window, onto a skipped slot", async () => {
const res = await put("03-move-by-band", events.morningTaken, {
takenAt: "2026-10-14T20:30:00.000Z",
});
events.moved = (res.data as { id: string }).id;
});
it("04 a legacy take pinned onto a free slot", async () => {
const res = await put("04-pin-free-slot", events.legacyTaken, {
forceSlotInstant: "2026-10-12T20:00:00.000Z",
});
events.pinned = (res.data as { id: string }).id;
});
it("05 a pin onto an instant that is no slot", async () => {
await put("05-pin-not-a-slot", events.moved, {
forceSlotInstant: "2026-10-12T15:00:00.000Z",
});
});
it("06 a pin onto a slot another dose holds", async () => {
await put("06-pin-occupied", events.moved, {
forceSlotInstant: "2026-10-12T20:00:00.000Z",
});
});
it("07 an unpin", async () => {
const res = await put("07-unpin", events.pinned, {
forceSlotInstant: null,
});
events.pinned = (res.data as { id: string }).id;
});
it("08 an explicit slot in the body", async () => {
const res = await put("08-explicit-slot", events.moved, {
scheduledFor: "2026-10-14T08:00:00.000Z",
takenAt: "2026-10-14T08:45:00.000Z",
});
events.moved = (res.data as { id: string }).id;
});
it("09 a take before the start day", async () => {
await put("09-before-start", events.pinned, {
takenAt: "2026-09-20T08:00:00.000Z",
});
});
it("10 a body the schema refuses", async () => {
await put("10-invalid", events.pinned, {
takenAt: "yesterday",
skipped: "no",
});
});
it("11 a tombstoned dose, and a dose under another medication", async () => {
await put("11a-tombstoned", events.tombstoned, { skipped: true });
await put(
"11b-other-medication",
events.pinned,
{ skipped: true },
{ medicationId: oneShotId },
);
});
it("12 a delegate who may write, and one who may manage", async () => {
await put(
"12a-writer",
events.pinned,
{ takenAt: "2026-10-13T20:50:00.000Z" },
writer,
);
await put(
"12b-manager",
events.pinned,
{ takenAt: "2026-10-13T20:50:00.000Z" },
manager,
);
});
it("13 no credentials", async () => {
await put(
"13-anonymous",
events.pinned,
{ skipped: true },
{
auth: anonymous,
},
);
});
});
it("02 the same dose flipped to a skip", async () => {
await put("02-skip-flip", events.pending, { skipped: true });
});
it("03 a take corrected into the other window, onto a skipped slot", async () => {
const res = await put("03-move-by-band", events.morningTaken, {
takenAt: "2026-10-14T20:30:00.000Z",
describe("DELETE /api/medications/{id}/intake/{eventId} parity", () => {
it("21 the owner undoes a dose", async () => {
await del("21-delete", events.forDelete);
});
events.moved = (res.data as { id: string }).id;
});
it("04 a legacy take pinned onto a free slot", async () => {
const res = await put("04-pin-free-slot", events.legacyTaken, {
forceSlotInstant: "2026-10-12T20:00:00.000Z",
it("22 undoing the only dose of a one-shot medication", async () => {
await del("22-delete-one-shot", events.oneShotDose, {
medicationId: oneShotId,
});
});
events.pinned = (res.data as { id: string }).id;
});
it("05 a pin onto an instant that is no slot", async () => {
await put("05-pin-not-a-slot", events.moved, {
forceSlotInstant: "2026-10-12T15:00:00.000Z",
it("23 a dose under another medication", async () => {
await del("23-other-medication", events.forManagerDelete, {
medicationId: oneShotId,
});
});
});
it("06 a pin onto a slot another dose holds", async () => {
await put("06-pin-occupied", events.moved, {
forceSlotInstant: "2026-10-12T20:00:00.000Z",
it("24 a delegate who may write, and one who may manage", async () => {
await del("24a-writer", events.forManagerDelete, writer);
await del("24b-manager", events.forManagerDelete, manager);
});
});
it("07 an unpin", async () => {
const res = await put("07-unpin", events.pinned, {
forceSlotInstant: null,
it("25 no credentials", async () => {
await del("25-anonymous", events.forDelete, { auth: anonymous });
});
events.pinned = (res.data as { id: string }).id;
});
it("08 an explicit slot in the body", async () => {
const res = await put("08-explicit-slot", events.moved, {
scheduledFor: "2026-10-14T08:00:00.000Z",
takenAt: "2026-10-14T08:45:00.000Z",
});
events.moved = (res.data as { id: string }).id;
});
it("09 a take before the start day", async () => {
await put("09-before-start", events.pinned, {
takenAt: "2026-09-20T08:00:00.000Z",
});
});
it("10 a body the schema refuses", async () => {
await put("10-invalid", events.pinned, {
takenAt: "yesterday",
skipped: "no",
});
});
it("11 a tombstoned dose, and a dose under another medication", async () => {
await put("11a-tombstoned", events.tombstoned, { skipped: true });
await put(
"11b-other-medication",
events.pinned,
{ skipped: true },
{ medicationId: oneShotId },
);
});
it("12 a delegate who may write, and one who may manage", async () => {
await put(
"12a-writer",
events.pinned,
{ takenAt: "2026-10-13T20:50:00.000Z" },
writer,
);
await put(
"12b-manager",
events.pinned,
{ takenAt: "2026-10-13T20:50:00.000Z" },
manager,
);
});
it("13 no credentials", async () => {
await put(
"13-anonymous",
events.pinned,
{ skipped: true },
{
auth: anonymous,
},
);
});
});
describe("DELETE /api/medications/{id}/intake/{eventId} parity", () => {
it("21 the owner undoes a dose", async () => {
await del("21-delete", events.forDelete);
});
it("22 undoing the only dose of a one-shot medication", async () => {
await del("22-delete-one-shot", events.oneShotDose, {
medicationId: oneShotId,
});
});
it("23 a dose under another medication", async () => {
await del("23-other-medication", events.forManagerDelete, {
medicationId: oneShotId,
});
});
it("24 a delegate who may write, and one who may manage", async () => {
await del("24a-writer", events.forManagerDelete, writer);
await del("24b-manager", events.forManagerDelete, manager);
});
it("25 no credentials", async () => {
await del("25-anonymous", events.forDelete, { auth: anonymous });
});
});
@@ -317,138 +317,145 @@ afterAll(() => {
vi.useRealTimers();
});
describe("GET /api/medications/intake parity", () => {
it("01 today's list", async () => {
await get("01-today", { scope: "today" });
// One walk: each request sees what the earlier ones stored, so the cases
// keep their order even when a run shuffles (`shuffle: false`).
describe("medication-intake-status parity walk", { shuffle: false }, () => {
describe("GET /api/medications/intake parity", () => {
it("01 today's list", async () => {
await get("01-today", { scope: "today" });
});
it("02 the compliance buckets, cold and from the cache", async () => {
await get("02a-compliance-cold", { scope: "compliance", days: "7" });
await get("02b-compliance-warm", { scope: "compliance", days: "7" });
});
it("03 a query the schema refuses", async () => {
await get("03-invalid", { scope: "yesterday", days: "0" });
});
it("04 a reading delegate, and nobody", async () => {
await get("04a-reader", { scope: "today" }, reader);
await get("04b-anonymous", { scope: "today" }, { auth: anonymous });
});
});
it("02 the compliance buckets, cold and from the cache", async () => {
await get("02a-compliance-cold", { scope: "compliance", days: "7" });
await get("02b-compliance-warm", { scope: "compliance", days: "7" });
});
describe("POST /api/medications/intake parity", () => {
it("11 a take in place", async () => {
await post("11-take-in-place", {
intakeId: ev.todayMorning,
status: "taken",
takenAt: "2026-10-15T08:30:00.000Z",
});
});
it("03 a query the schema refuses", async () => {
await get("03-invalid", { scope: "yesterday", days: "0" });
});
it("12 a take that re-binds to another slot", async () => {
await post("12-take-slot-move", {
intakeId: ev.moveMe,
status: "taken",
takenAt: "2026-10-14T20:30:00.000Z",
});
});
it("04 a reading delegate, and nobody", async () => {
await get("04a-reader", { scope: "today" }, reader);
await get("04b-anonymous", { scope: "today" }, { auth: anonymous });
});
});
describe("POST /api/medications/intake parity", () => {
it("11 a take in place", async () => {
await post("11-take-in-place", {
intakeId: ev.todayMorning,
status: "taken",
takenAt: "2026-10-15T08:30:00.000Z",
it("13 a pinned take, and a pin that is no slot", async () => {
await post("13a-pin", {
intakeId: ev.pinMe,
status: "taken",
takenAt: "2026-10-13T11:00:00.000Z",
forceSlotInstant: "2026-10-13T08:00:00.000Z",
});
await post("13b-pin-not-a-slot", {
intakeId: ev.snoozeMe,
status: "taken",
forceSlotInstant: "2026-10-11T15:00:00.000Z",
});
});
});
it("12 a take that re-binds to another slot", async () => {
await post("12-take-slot-move", {
intakeId: ev.moveMe,
status: "taken",
takenAt: "2026-10-14T20:30:00.000Z",
it("14 a skip over a recorded dose", async () => {
await post("14-skip", { intakeId: ev.skipMe, status: "skipped" });
});
});
it("13 a pinned take, and a pin that is no slot", async () => {
await post("13a-pin", {
intakeId: ev.pinMe,
status: "taken",
takenAt: "2026-10-13T11:00:00.000Z",
forceSlotInstant: "2026-10-13T08:00:00.000Z",
});
await post("13b-pin-not-a-slot", {
intakeId: ev.snoozeMe,
status: "taken",
forceSlotInstant: "2026-10-11T15:00:00.000Z",
it("15 a snooze with its end, and one without", async () => {
await post("15a-snooze-until", {
intakeId: ev.snoozeMe,
status: "snoozed",
snoozedUntil: "2026-10-15T13:00:00.000Z",
});
await post("15b-snooze-default", {
intakeId: ev.snoozeMe,
status: "snoozed",
});
});
});
it("14 a skip over a recorded dose", async () => {
await post("14-skip", { intakeId: ev.skipMe, status: "skipped" });
});
it("15 a snooze with its end, and one without", async () => {
await post("15a-snooze-until", {
intakeId: ev.snoozeMe,
status: "snoozed",
snoozedUntil: "2026-10-15T13:00:00.000Z",
});
await post("15b-snooze-default", {
intakeId: ev.snoozeMe,
status: "snoozed",
it("16 the not-found answers", async () => {
await post("16a-unknown", {
intakeId: "cmv0000000000000000000000",
status: "taken",
});
await post("16b-foreign", { intakeId: ev.foreign, status: "taken" });
await post("16c-tombstoned", {
intakeId: ev.tombstoned,
status: "taken",
});
});
});
it("16 the not-found answers", async () => {
await post("16a-unknown", {
intakeId: "cmv0000000000000000000000",
status: "taken",
it("17 the record-only rule both ways", async () => {
await post("17a-record-only-open", {
intakeId: ev.recordOpen,
status: "taken",
});
await post("17b-record-only-correction", {
intakeId: ev.recordTaken,
status: "taken",
takenAt: "2026-10-13T07:20:00.000Z",
});
});
await post("16b-foreign", { intakeId: ev.foreign, status: "taken" });
await post("16c-tombstoned", { intakeId: ev.tombstoned, status: "taken" });
});
it("17 the record-only rule both ways", async () => {
await post("17a-record-only-open", {
intakeId: ev.recordOpen,
status: "taken",
it("18 the injection-site refusal, and an allowed site", async () => {
await post("18a-site-not-allowed", {
intakeId: ev.penToday,
status: "taken",
takenAt: "2026-10-15T09:15:00.000Z",
injectionSite: "ABDOMEN_RIGHT",
});
await post("18b-site", {
intakeId: ev.penToday,
status: "taken",
takenAt: "2026-10-15T09:15:00.000Z",
injectionSite: "ABDOMEN_LEFT",
});
});
await post("17b-record-only-correction", {
intakeId: ev.recordTaken,
status: "taken",
takenAt: "2026-10-13T07:20:00.000Z",
});
});
it("18 the injection-site refusal, and an allowed site", async () => {
await post("18a-site-not-allowed", {
intakeId: ev.penToday,
status: "taken",
takenAt: "2026-10-15T09:15:00.000Z",
injectionSite: "ABDOMEN_RIGHT",
});
await post("18b-site", {
intakeId: ev.penToday,
status: "taken",
takenAt: "2026-10-15T09:15:00.000Z",
injectionSite: "ABDOMEN_LEFT",
it("19 a body the schema refuses", async () => {
await post("19-invalid", { intakeId: "", status: "forgotten" });
});
});
it("19 a body the schema refuses", async () => {
await post("19-invalid", { intakeId: "", status: "forgotten" });
});
it("20 a delegate who marks an open dose, a snooze, a correction, a reader and nobody", async () => {
await post(
"20a-writer-take",
{ intakeId: ev.todayEvening, status: "taken" },
{ ...writer },
);
await post(
"20b-writer-snooze-refused",
{ intakeId: ev.snoozeMe, status: "snoozed" },
writer,
);
await post(
"20c-writer-correction-refused",
{ intakeId: ev.resolved, status: "skipped" },
writer,
);
await post(
"20d-reader",
{ intakeId: ev.snoozeMe, status: "taken" },
reader,
);
await post(
"20e-anonymous",
{ intakeId: ev.snoozeMe, status: "taken" },
{ auth: anonymous },
);
it("20 a delegate who marks an open dose, a snooze, a correction, a reader and nobody", async () => {
await post(
"20a-writer-take",
{ intakeId: ev.todayEvening, status: "taken" },
{ ...writer },
);
await post(
"20b-writer-snooze-refused",
{ intakeId: ev.snoozeMe, status: "snoozed" },
writer,
);
await post(
"20c-writer-correction-refused",
{ intakeId: ev.resolved, status: "skipped" },
writer,
);
await post(
"20d-reader",
{ intakeId: ev.snoozeMe, status: "taken" },
reader,
);
await post(
"20e-anonymous",
{ intakeId: ev.snoozeMe, status: "taken" },
{ auth: anonymous },
);
});
});
});
@@ -343,189 +343,193 @@ afterAll(() => {
vi.useRealTimers();
});
describe("POST /api/medications/{id}/intake parity", () => {
it("01 a take inside its band, onto the pending reminder row", async () => {
await post("01-take-in-band", { takenAt: "2026-10-15T08:30:00.000Z" });
});
it("02 an as-needed take recorded standalone, and its repeat inside the dedup window", async () => {
await post(
"02a-take-standalone",
{ takenAt: "2026-10-15T11:00:00.000Z" },
{ medicationId: med.asNeeded },
);
await post(
"02b-take-standalone-repeat",
{ takenAt: "2026-10-15T11:00:00.000Z" },
{ medicationId: med.asNeeded },
);
});
it("03 a skip on a named slot, a slot-less skip, and a skip onto a standalone dose", async () => {
await post("03a-skip-named-slot", {
skipped: true,
scheduledFor: "2026-10-15T20:00:00.000Z",
// One walk: each request sees what the earlier ones stored, so the cases
// keep their order even when a run shuffles (`shuffle: false`).
describe("medication-intake parity walk", { shuffle: false }, () => {
describe("POST /api/medications/{id}/intake parity", () => {
it("01 a take inside its band, onto the pending reminder row", async () => {
await post("01-take-in-band", { takenAt: "2026-10-15T08:30:00.000Z" });
});
await post("03b-skip-slotless", { skipped: true });
await post(
"03c-delegate-skip-standalone-refused",
{ skipped: true, scheduledFor: "2026-10-15T11:00:00.000Z" },
{ ...writer, medicationId: med.asNeeded, settle: () => refusalRows(1) },
);
await post(
"03d-owner-skip-standalone",
{ skipped: true, scheduledFor: "2026-10-15T11:00:00.000Z" },
{ medicationId: med.asNeeded },
);
});
it("04 a pinned take and the two pin refusals", async () => {
await post("04a-pin", {
takenAt: "2026-10-14T11:00:00.000Z",
forceSlotInstant: "2026-10-14T08:00:00.000Z",
it("02 an as-needed take recorded standalone, and its repeat inside the dedup window", async () => {
await post(
"02a-take-standalone",
{ takenAt: "2026-10-15T11:00:00.000Z" },
{ medicationId: med.asNeeded },
);
await post(
"02b-take-standalone-repeat",
{ takenAt: "2026-10-15T11:00:00.000Z" },
{ medicationId: med.asNeeded },
);
});
await post("04b-pin-not-a-slot", {
takenAt: "2026-10-14T16:00:00.000Z",
forceSlotInstant: "2026-10-14T15:00:00.000Z",
it("03 a skip on a named slot, a slot-less skip, and a skip onto a standalone dose", async () => {
await post("03a-skip-named-slot", {
skipped: true,
scheduledFor: "2026-10-15T20:00:00.000Z",
});
await post("03b-skip-slotless", { skipped: true });
await post(
"03c-delegate-skip-standalone-refused",
{ skipped: true, scheduledFor: "2026-10-15T11:00:00.000Z" },
{ ...writer, medicationId: med.asNeeded, settle: () => refusalRows(1) },
);
await post(
"03d-owner-skip-standalone",
{ skipped: true, scheduledFor: "2026-10-15T11:00:00.000Z" },
{ medicationId: med.asNeeded },
);
});
await post("04c-pin-occupied", {
takenAt: "2026-10-14T23:00:00.000Z",
forceSlotInstant: "2026-10-14T20:00:00.000Z",
it("04 a pinned take and the two pin refusals", async () => {
await post("04a-pin", {
takenAt: "2026-10-14T11:00:00.000Z",
forceSlotInstant: "2026-10-14T08:00:00.000Z",
});
await post("04b-pin-not-a-slot", {
takenAt: "2026-10-14T16:00:00.000Z",
forceSlotInstant: "2026-10-14T15:00:00.000Z",
});
await post("04c-pin-occupied", {
takenAt: "2026-10-14T23:00:00.000Z",
forceSlotInstant: "2026-10-14T20:00:00.000Z",
});
});
it("05 a backfill onto a named earlier slot", async () => {
await post("05-named-backfill", {
takenAt: "2026-10-15T11:30:00.000Z",
scheduledFor: "2026-10-13T08:00:00.000Z",
});
});
it("06 a body idempotency key and its replay", async () => {
const body = {
takenAt: "2026-10-13T20:30:00.000Z",
idempotencyKey: "dose-key-0001",
};
await post("06a-body-key", body);
await post("06b-body-key-replay", body);
});
it("07 an Idempotency-Key header and its replay", async () => {
const headers = {
"idempotency-key": "4b7e1c9a-2d3f-4a5b-8c6d-7e8f9a0b1c2d",
};
const body = { takenAt: "2026-10-12T08:15:00.000Z" };
await post("07a-header-key", body, { headers });
await post("07b-header-key-replay", body, { headers });
});
it("08 a native take", async () => {
await post(
"08-native",
{ takenAt: "2026-10-11T20:10:00.000Z" },
{ auth: ownerBearer },
);
});
it("09 a skip over a recorded dose, by the owner and by a delegate", async () => {
await post(
"09a-delegate-skip-refused",
{ skipped: true, scheduledFor: "2026-10-14T20:00:00.000Z" },
{ ...writer, settle: () => refusalRows(2) },
);
await post("09b-owner-skip-flip", {
skipped: true,
scheduledFor: "2026-10-14T20:00:00.000Z",
});
});
it("10 a delegate's take on an open slot, a reader, and nobody", async () => {
await post(
"10a-writer-take",
{ takenAt: "2026-10-11T08:20:00.000Z" },
writer,
);
await post("10b-reader", { takenAt: "2026-10-10T08:20:00.000Z" }, reader);
await post(
"10c-anonymous",
{ takenAt: "2026-10-10T08:20:00.000Z" },
{ auth: anonymous },
);
});
it("11 the injection-site refusal, and a site with a dose override", async () => {
await post(
"11a-site-not-allowed",
{ takenAt: "2026-10-15T09:10:00.000Z", injectionSite: "ABDOMEN_RIGHT" },
{ medicationId: med.pen },
);
await post(
"11b-site-and-dose",
{
takenAt: "2026-10-15T09:10:00.000Z",
injectionSite: "ABDOMEN_LEFT",
doseTaken: "0.5 pen",
},
{ medicationId: med.pen },
);
});
it("12 a one-shot dose", async () => {
await post(
"12-one-shot",
{ takenAt: "2026-10-15T10:00:00.000Z" },
{ medicationId: med.oneShot },
);
});
it("13 a body the schema refuses", async () => {
await post("13-invalid", {
takenAt: "yesterday",
idempotencyKey: "<HKDoseEvent: 0x600003a1c0c0>",
});
});
it("14 another account's medication, and a record-only one", async () => {
await post(
"14a-not-found",
{ takenAt: "2026-10-15T08:30:00.000Z" },
{ medicationId: med.foreign },
);
await post(
"14b-record-only",
{ takenAt: "2026-10-15T07:10:00.000Z" },
{ medicationId: med.recordOnly },
);
});
});
it("05 a backfill onto a named earlier slot", async () => {
await post("05-named-backfill", {
takenAt: "2026-10-15T11:30:00.000Z",
scheduledFor: "2026-10-13T08:00:00.000Z",
describe("GET /api/medications/{id}/intake parity", () => {
it("21 the default list", async () => {
await list("21-default");
});
});
it("06 a body idempotency key and its replay", async () => {
const body = {
takenAt: "2026-10-13T20:30:00.000Z",
idempotencyKey: "dose-key-0001",
};
await post("06a-body-key", body);
await post("06b-body-key-replay", body);
});
it("07 an Idempotency-Key header and its replay", async () => {
const headers = {
"idempotency-key": "4b7e1c9a-2d3f-4a5b-8c6d-7e8f9a0b1c2d",
};
const body = { takenAt: "2026-10-12T08:15:00.000Z" };
await post("07a-header-key", body, { headers });
await post("07b-header-key-replay", body, { headers });
});
it("08 a native take", async () => {
await post(
"08-native",
{ takenAt: "2026-10-11T20:10:00.000Z" },
{ auth: ownerBearer },
);
});
it("09 a skip over a recorded dose, by the owner and by a delegate", async () => {
await post(
"09a-delegate-skip-refused",
{ skipped: true, scheduledFor: "2026-10-14T20:00:00.000Z" },
{ ...writer, settle: () => refusalRows(2) },
);
await post("09b-owner-skip-flip", {
skipped: true,
scheduledFor: "2026-10-14T20:00:00.000Z",
it("22 a completed page sorted by take time", async () => {
await list("22-completed-page", {
status: "completed",
sortBy: "takenAt",
sortDir: "asc",
limit: "3",
offset: "1",
});
});
});
it("10 a delegate's take on an open slot, a reader, and nobody", async () => {
await post(
"10a-writer-take",
{ takenAt: "2026-10-11T08:20:00.000Z" },
writer,
);
await post("10b-reader", { takenAt: "2026-10-10T08:20:00.000Z" }, reader);
await post(
"10c-anonymous",
{ takenAt: "2026-10-10T08:20:00.000Z" },
{ auth: anonymous },
);
});
it("11 the injection-site refusal, and a site with a dose override", async () => {
await post(
"11a-site-not-allowed",
{ takenAt: "2026-10-15T09:10:00.000Z", injectionSite: "ABDOMEN_RIGHT" },
{ medicationId: med.pen },
);
await post(
"11b-site-and-dose",
{
takenAt: "2026-10-15T09:10:00.000Z",
injectionSite: "ABDOMEN_LEFT",
doseTaken: "0.5 pen",
},
{ medicationId: med.pen },
);
});
it("12 a one-shot dose", async () => {
await post(
"12-one-shot",
{ takenAt: "2026-10-15T10:00:00.000Z" },
{ medicationId: med.oneShot },
);
});
it("13 a body the schema refuses", async () => {
await post("13-invalid", {
takenAt: "yesterday",
idempotencyKey: "<HKDoseEvent: 0x600003a1c0c0>",
it("23 the skips, and the takes", async () => {
await list("23a-skipped", { status: "skipped" });
await list("23b-taken", { status: "taken", sortDir: "asc" });
});
});
it("14 another account's medication, and a record-only one", async () => {
await post(
"14a-not-found",
{ takenAt: "2026-10-15T08:30:00.000Z" },
{ medicationId: med.foreign },
);
await post(
"14b-record-only",
{ takenAt: "2026-10-15T07:10:00.000Z" },
{ medicationId: med.recordOnly },
);
});
});
describe("GET /api/medications/{id}/intake parity", () => {
it("21 the default list", async () => {
await list("21-default");
});
it("22 a completed page sorted by take time", async () => {
await list("22-completed-page", {
status: "completed",
sortBy: "takenAt",
sortDir: "asc",
limit: "3",
offset: "1",
it("24 a query the schema refuses", async () => {
await list("24-invalid", { limit: "lots", status: "maybe" });
});
});
it("23 the skips, and the takes", async () => {
await list("23a-skipped", { status: "skipped" });
await list("23b-taken", { status: "taken", sortDir: "asc" });
});
it("24 a query the schema refuses", async () => {
await list("24-invalid", { limit: "lots", status: "maybe" });
});
it("25 another account's medication, a reading delegate, and nobody", async () => {
await list("25a-not-found", {}, { medicationId: med.foreign });
await list("25b-reader", {}, reader);
await list("25c-anonymous", {}, { auth: anonymous });
it("25 another account's medication, a reading delegate, and nobody", async () => {
await list("25a-not-found", {}, { medicationId: med.foreign });
await list("25b-reader", {}, reader);
await list("25c-anonymous", {}, { auth: anonymous });
});
});
});
@@ -138,197 +138,204 @@ afterAll(() => {
vi.useRealTimers();
});
describe("GET /api/medications parity", () => {
it("01 the owner's list", async () => {
await list("01-list");
});
// One walk: each request sees what the earlier ones stored, so the cases
// keep their order even when a run shuffles (`shuffle: false`).
describe("medications parity walk", { shuffle: false }, () => {
describe("GET /api/medications parity", () => {
it("01 the owner's list", async () => {
await list("01-list");
});
it("02 a reading delegate's view of it, and nobody's", async () => {
await list("02a-list-reader", reader);
await list("02b-list-anonymous", { auth: anonymous });
});
});
describe("POST /api/medications parity", () => {
it("11 a plain create", async () => {
await create("11-plain", {
name: "Plain tablet",
dose: "5mg",
schedules: [schedule("07:00", "09:00")],
it("02 a reading delegate's view of it, and nobody's", async () => {
await list("02a-list-reader", reader);
await list("02b-list-anonymous", { auth: anonymous });
});
});
it("12 a full create with a custom category, a course and auto-take", async () => {
await create("12-full", {
name: "Full injection",
dose: "1 pen",
category: CUSTOM_KEY,
treatmentClass: "GLP1",
dosesPerUnit: 4,
unitsPerDose: 0.5,
reorderLeadDays: 7,
deliveryForm: "INJECTION",
trackInjectionSites: true,
allowedInjectionSites: ["ABDOMEN_LEFT", "THIGH_RIGHT"],
notificationsEnabled: false,
liveActivityEnabled: true,
criticalAlarmEnabled: true,
atcCode: "A10BJ06",
rxNormCode: "1991302",
startsOn: "2026-10-10",
endsOn: "2026-12-31",
autoMarkTaken: true,
schedules: [
schedule("18:00", "20:00", {
label: "Evening",
dose: "0.5 pen",
unitsPerDose: 1,
timesOfDay: ["18:30"],
reminderGraceMinutes: 45,
rrule: "FREQ=WEEKLY;BYDAY=MO",
doseWindows: [{ timeOfDay: "18:30", start: "18:00", end: "20:00" }],
}),
],
describe("POST /api/medications parity", () => {
it("11 a plain create", async () => {
await create("11-plain", {
name: "Plain tablet",
dose: "5mg",
schedules: [schedule("07:00", "09:00")],
});
});
});
it("13 an as-needed create, a one-shot one and a PRN schedule", async () => {
await create("13a-as-needed", {
name: "As needed",
dose: "400mg",
category: "PAIN_RELIEF",
asNeeded: true,
it("12 a full create with a custom category, a course and auto-take", async () => {
await create("12-full", {
name: "Full injection",
dose: "1 pen",
category: CUSTOM_KEY,
treatmentClass: "GLP1",
dosesPerUnit: 4,
unitsPerDose: 0.5,
reorderLeadDays: 7,
deliveryForm: "INJECTION",
trackInjectionSites: true,
allowedInjectionSites: ["ABDOMEN_LEFT", "THIGH_RIGHT"],
notificationsEnabled: false,
liveActivityEnabled: true,
criticalAlarmEnabled: true,
atcCode: "A10BJ06",
rxNormCode: "1991302",
startsOn: "2026-10-10",
endsOn: "2026-12-31",
autoMarkTaken: true,
schedules: [
schedule("18:00", "20:00", {
label: "Evening",
dose: "0.5 pen",
unitsPerDose: 1,
timesOfDay: ["18:30"],
reminderGraceMinutes: 45,
rrule: "FREQ=WEEKLY;BYDAY=MO",
doseWindows: [{ timeOfDay: "18:30", start: "18:00", end: "20:00" }],
}),
],
});
});
await create("13b-one-shot", {
name: "One shot",
dose: "1 dose",
oneShot: true,
startsOn: "2026-10-20",
endsOn: "2026-11-20",
schedules: [schedule("09:00", "11:00")],
});
await create("13c-prn-and-legacy-days", {
name: "Mixed schedules",
dose: "2mg",
trackIntake: false,
schedules: [
schedule("06:00", "07:00", { scheduleType: "PRN" }),
schedule("21:00", "22:00", { daysOfWeek: [1, 3, 5], intervalWeeks: 2 }),
schedule("12:00", "13:00", { rollingIntervalDays: 3 }),
],
});
await create("13d-ended-window", {
name: "Ended window",
dose: "3mg",
endsOn: "2026-10-05",
schedules: [schedule("08:00", "09:00")],
});
});
it("14 the refusals", async () => {
await create("14a-unknown-category", {
name: "Unknown category",
dose: "1mg",
category: "custom:0a6c2d4e-1111-4222-8333-944455556666",
schedules: [schedule("08:00", "09:00")],
it("13 an as-needed create, a one-shot one and a PRN schedule", async () => {
await create("13a-as-needed", {
name: "As needed",
dose: "400mg",
category: "PAIN_RELIEF",
asNeeded: true,
});
await create("13b-one-shot", {
name: "One shot",
dose: "1 dose",
oneShot: true,
startsOn: "2026-10-20",
endsOn: "2026-11-20",
schedules: [schedule("09:00", "11:00")],
});
await create("13c-prn-and-legacy-days", {
name: "Mixed schedules",
dose: "2mg",
trackIntake: false,
schedules: [
schedule("06:00", "07:00", { scheduleType: "PRN" }),
schedule("21:00", "22:00", {
daysOfWeek: [1, 3, 5],
intervalWeeks: 2,
}),
schedule("12:00", "13:00", { rollingIntervalDays: 3 }),
],
});
await create("13d-ended-window", {
name: "Ended window",
dose: "3mg",
endsOn: "2026-10-05",
schedules: [schedule("08:00", "09:00")],
});
});
await create("14b-course-ends-before-start", {
name: "Backwards course",
dose: "1mg",
startsOn: "2026-10-20",
endsOn: "2026-10-10",
schedules: [schedule("08:00", "09:00")],
});
await create("14c-one-shot-two-schedules", {
name: "One shot twice",
dose: "1mg",
oneShot: true,
startsOn: "2026-10-20",
schedules: [schedule("08:00", "09:00"), schedule("20:00", "21:00")],
});
await create("14d-one-shot-recurrence", {
name: "One shot recurring",
dose: "1mg",
oneShot: true,
startsOn: "2026-10-20",
schedules: [schedule("08:00", "09:00", { rrule: "FREQ=DAILY" })],
});
await create("14e-unstable-concept-id", {
name: "Phantom",
dose: "1mg",
externalSource: "APPLE_HEALTH",
externalId: "<HKMedicationConcept: 0x600003a1c0c0>",
schedules: [schedule("08:00", "09:00")],
});
await create("14f-invalid", { name: "", dose: 5, schedules: "daily" });
});
it("15 a mirrored create and its replay", async () => {
const mirrored = {
name: "Mirrored",
dose: "50mg",
externalSource: "APPLE_HEALTH",
externalId: "hk-concept-0001",
schedules: [schedule("08:00", "09:00")],
};
await create("15a-mirror", mirrored);
await create("15b-mirror-replay", { ...mirrored, name: "Renamed" });
});
it("16 the mirror cap", async () => {
const capped = await makeUser({ timezone: "UTC" });
await prisma.medication.createMany({
data: Array.from({ length: 30 }, (_, i) => ({
userId: capped.id,
name: `Mirror ${String(i).padStart(2, "0")}`,
it("14 the refusals", async () => {
await create("14a-unknown-category", {
name: "Unknown category",
dose: "1mg",
externalSource: "APPLE_HEALTH" as const,
externalId: `hk-cap-${String(i).padStart(2, "0")}`,
})),
});
await create(
"16-mirror-cap",
{
name: "One too many",
category: "custom:0a6c2d4e-1111-4222-8333-944455556666",
schedules: [schedule("08:00", "09:00")],
});
await create("14b-course-ends-before-start", {
name: "Backwards course",
dose: "1mg",
startsOn: "2026-10-20",
endsOn: "2026-10-10",
schedules: [schedule("08:00", "09:00")],
});
await create("14c-one-shot-two-schedules", {
name: "One shot twice",
dose: "1mg",
oneShot: true,
startsOn: "2026-10-20",
schedules: [schedule("08:00", "09:00"), schedule("20:00", "21:00")],
});
await create("14d-one-shot-recurrence", {
name: "One shot recurring",
dose: "1mg",
oneShot: true,
startsOn: "2026-10-20",
schedules: [schedule("08:00", "09:00", { rrule: "FREQ=DAILY" })],
});
await create("14e-unstable-concept-id", {
name: "Phantom",
dose: "1mg",
externalSource: "APPLE_HEALTH",
externalId: "hk-cap-30",
externalId: "<HKMedicationConcept: 0x600003a1c0c0>",
schedules: [schedule("08:00", "09:00")],
},
{ auth: await makeSession(capped), userIds: [capped.id] },
);
});
});
await create("14f-invalid", { name: "", dose: 5, schedules: "daily" });
});
it("17 a retried create under one idempotency key", async () => {
const body = {
name: "Retried",
dose: "10mg",
schedules: [schedule("08:00", "09:00")],
};
const headers = {
"idempotency-key": "6f9d2c4a-8b1e-4f3a-9d7c-5e2b1a0c9d8e",
};
await create("17a-idempotent-first", body, { headers });
await create("17b-idempotent-replay", body, { headers });
});
it("15 a mirrored create and its replay", async () => {
const mirrored = {
name: "Mirrored",
dose: "50mg",
externalSource: "APPLE_HEALTH",
externalId: "hk-concept-0001",
schedules: [schedule("08:00", "09:00")],
};
await create("15a-mirror", mirrored);
await create("15b-mirror-replay", { ...mirrored, name: "Renamed" });
});
it("18 a delegate who may write, and one who may only read", async () => {
const body = {
name: "Brought by a delegate",
dose: "20mg",
schedules: [schedule("08:00", "09:00")],
};
await create("18a-writer", body, writer);
await create("18b-reader", body, reader);
});
it("16 the mirror cap", async () => {
const capped = await makeUser({ timezone: "UTC" });
await prisma.medication.createMany({
data: Array.from({ length: 30 }, (_, i) => ({
userId: capped.id,
name: `Mirror ${String(i).padStart(2, "0")}`,
dose: "1mg",
externalSource: "APPLE_HEALTH" as const,
externalId: `hk-cap-${String(i).padStart(2, "0")}`,
})),
});
await create(
"16-mirror-cap",
{
name: "One too many",
dose: "1mg",
externalSource: "APPLE_HEALTH",
externalId: "hk-cap-30",
schedules: [schedule("08:00", "09:00")],
},
{ auth: await makeSession(capped), userIds: [capped.id] },
);
});
it("19 no credentials", async () => {
await create(
"19-anonymous",
{ name: "Nobody", dose: "1mg" },
{
auth: anonymous,
},
);
it("17 a retried create under one idempotency key", async () => {
const body = {
name: "Retried",
dose: "10mg",
schedules: [schedule("08:00", "09:00")],
};
const headers = {
"idempotency-key": "6f9d2c4a-8b1e-4f3a-9d7c-5e2b1a0c9d8e",
};
await create("17a-idempotent-first", body, { headers });
await create("17b-idempotent-replay", body, { headers });
});
it("18 a delegate who may write, and one who may only read", async () => {
const body = {
name: "Brought by a delegate",
dose: "20mg",
schedules: [schedule("08:00", "09:00")],
};
await create("18a-writer", body, writer);
await create("18b-reader", body, reader);
});
it("19 no credentials", async () => {
await create(
"19-anonymous",
{ name: "Nobody", dose: "1mg" },
{
auth: anonymous,
},
);
});
});
});
@@ -214,7 +214,7 @@ afterAll(async () => {
await prisma.moodTag.delete({ where: { id: SHORT_FACTOR_ID } });
});
describe("POST /api/mood-entries/bulk parity", () => {
describe("POST /api/mood-entries/bulk parity", { shuffle: false }, () => {
it("01 a first drain", async () => {
await step("01-first-drain", { entries: firstDrain });
});
@@ -270,7 +270,7 @@ afterAll(() => {
}
});
describe("GET /api/auth/oidc/callback parity", () => {
describe("GET /api/auth/oidc/callback parity", { shuffle: false }, () => {
it("01 SSO switched off", async () => {
await step("01-disabled", { state: {} });
process.env.OIDC_ISSUER_URL = ISSUER;
@@ -298,7 +298,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("GET /api/sync/changes parity", () => {
describe("GET /api/sync/changes parity", { shuffle: false }, () => {
let firstCursor: string;
it("drains the feed in small pages to the end", async () => {
@@ -176,63 +176,67 @@ beforeAll(async () => {
});
});
describe("the Apple Health write-back of a measurement token", () => {
it("walks the cases in order", async () => {
await step("01-summary", { kind: "summary" });
await step("02-on", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: true },
describe(
"the Apple Health write-back of a measurement token",
{ shuffle: false },
() => {
it("walks the cases in order", async () => {
await step("01-summary", { kind: "summary" });
await step("02-on", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: true },
});
await step("03-on-again", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: true },
});
await step("04-list", { kind: "list" });
const revoked = await callRoute(revoke, {
method: "DELETE",
route: "/api/tokens/{id}",
params: { id: scale.tokenId },
auth: owner,
});
expect(revoked.status).toBe(200);
await step("05-off-revoked", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: false },
});
await step("06-wildcard", {
kind: "patch",
id: wildcard.tokenId,
body: { mirrorToAppleHealth: true },
});
await step("07-connector", {
kind: "patch",
id: connector.tokenId,
body: { mirrorToAppleHealth: true },
});
await step("08-another-account", {
kind: "patch",
id: theirs.tokenId,
body: { mirrorToAppleHealth: true },
});
await step("09-unknown", {
kind: "patch",
id: "missing-token",
body: { mirrorToAppleHealth: true },
});
await step("10-invalid-body", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: "yes", name: "Renamed" },
});
await step("11-bearer", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: true },
auth: wildcard,
});
await step("12-summary-bearer", { kind: "summary", auth: wildcard });
});
await step("03-on-again", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: true },
});
await step("04-list", { kind: "list" });
const revoked = await callRoute(revoke, {
method: "DELETE",
route: "/api/tokens/{id}",
params: { id: scale.tokenId },
auth: owner,
});
expect(revoked.status).toBe(200);
await step("05-off-revoked", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: false },
});
await step("06-wildcard", {
kind: "patch",
id: wildcard.tokenId,
body: { mirrorToAppleHealth: true },
});
await step("07-connector", {
kind: "patch",
id: connector.tokenId,
body: { mirrorToAppleHealth: true },
});
await step("08-another-account", {
kind: "patch",
id: theirs.tokenId,
body: { mirrorToAppleHealth: true },
});
await step("09-unknown", {
kind: "patch",
id: "missing-token",
body: { mirrorToAppleHealth: true },
});
await step("10-invalid-body", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: "yes", name: "Renamed" },
});
await step("11-bearer", {
kind: "patch",
id: scale.tokenId,
body: { mirrorToAppleHealth: true },
auth: wildcard,
});
await step("12-summary-bearer", { kind: "summary", auth: wildcard });
});
});
},
);
@@ -248,64 +248,68 @@ afterAll(() => {
vi.useRealTimers();
});
describe("GET /api/workouts/{id} parity", () => {
it("the detail reads", async () => {
await step("get-01-canonical", { method: "GET", id: ids.run });
await step("get-02-compact", {
method: "GET",
id: ids.run,
query: { compact: "1" },
// One walk: each request sees what the earlier ones stored, so the cases
// keep their order even when a run shuffles (`shuffle: false`).
describe("workout-detail parity walk", { shuffle: false }, () => {
describe("GET /api/workouts/{id} parity", () => {
it("the detail reads", async () => {
await step("get-01-canonical", { method: "GET", id: ids.run });
await step("get-02-compact", {
method: "GET",
id: ids.run,
query: { compact: "1" },
});
await step("get-03-hidden-twin", { method: "GET", id: ids.twin });
await step("get-04-ride", { method: "GET", id: ids.ride });
await step("get-05-other-account", {
method: "GET",
id: ids.run,
auth: stranger,
});
await step("get-06-missing", {
method: "GET",
id: "cmissingworkout000000000",
});
});
await step("get-03-hidden-twin", { method: "GET", id: ids.twin });
await step("get-04-ride", { method: "GET", id: ids.ride });
await step("get-05-other-account", {
method: "GET",
id: ids.run,
auth: stranger,
});
await step("get-06-missing", {
method: "GET",
id: "cmissingworkout000000000",
it("the module switched off", async () => {
await prisma.user.update({
where: { id: userId },
data: { modulePreferencesJson: { workouts: false } },
});
await step("get-07-module-off", { method: "GET", id: ids.run });
await prisma.user.update({
where: { id: userId },
data: { modulePreferencesJson: {} },
});
});
});
it("the module switched off", async () => {
await prisma.user.update({
where: { id: userId },
data: { modulePreferencesJson: { workouts: false } },
describe("DELETE /api/workouts/{id} parity", () => {
it("a synced workout, a missing one, another account's", async () => {
await step("delete-01-synced", {
method: "DELETE",
id: ids.ride,
readBack: true,
});
await step("delete-02-missing", {
method: "DELETE",
id: "cmissingworkout000000000",
});
await step("delete-03-other-account", {
method: "DELETE",
id: ids.walk,
auth: stranger,
readBack: true,
});
});
await step("get-07-module-off", { method: "GET", id: ids.run });
await prisma.user.update({
where: { id: userId },
data: { modulePreferencesJson: {} },
});
});
});
describe("DELETE /api/workouts/{id} parity", () => {
it("a synced workout, a missing one, another account's", async () => {
await step("delete-01-synced", {
method: "DELETE",
id: ids.ride,
readBack: true,
});
await step("delete-02-missing", {
method: "DELETE",
id: "cmissingworkout000000000",
});
await step("delete-03-other-account", {
method: "DELETE",
id: ids.walk,
auth: stranger,
readBack: true,
});
});
it("a hand-entered workout and the record it set", async () => {
await step("delete-04-manual", {
method: "DELETE",
id: ids.walk,
readBack: true,
it("a hand-entered workout and the record it set", async () => {
await step("delete-04-manual", {
method: "DELETE",
id: ids.walk,
readBack: true,
});
});
});
});
@@ -238,7 +238,7 @@ afterAll(() => {
vi.useRealTimers();
});
describe("POST /api/workouts/batch parity", () => {
describe("POST /api/workouts/batch parity", { shuffle: false }, () => {
it("01 a first batch", async () => {
await step("01-first-batch", { workouts: FIRST_BATCH });
});
+179 -167
View File
@@ -996,182 +996,194 @@ describe("every setting a person can choose", () => {
expect(new Set(listed).size).toBe(listed.length);
});
describe("set, read back, backed up, restored, read back again", () => {
let owner: User;
let session: SessionFixture;
const before = new Map<number, unknown>();
const meBefore = new Map<number, unknown>();
let stored: Map<string, unknown>;
let defaults: Map<string, unknown>;
// One trip, in the order of its name: each leg reads what the leg before it
// wrote. The order holds under a shuffled run.
describe(
"set, read back, backed up, restored, read back again",
{ shuffle: false },
() => {
let owner: User;
let session: SessionFixture;
const before = new Map<number, unknown>();
const meBefore = new Map<number, unknown>();
let stored: Map<string, unknown>;
let defaults: Map<string, unknown>;
beforeAll(async () => {
await truncateAllTables(prisma);
owner = await makeUser({ onboardingCompletedAt: null });
session = await makeSession(owner);
defaults = columnsOf(await makeUser({ onboardingCompletedAt: null }));
});
beforeAll(async () => {
await truncateAllTables(prisma);
owner = await makeUser({ onboardingCompletedAt: null });
session = await makeSession(owner);
defaults = columnsOf(await makeUser({ onboardingCompletedAt: null }));
});
it("stores each setting through its write route and answers it on its read route", async () => {
const failures: string[] = [];
for (const [index, entry] of entries(owner).entries()) {
const label = entry.columns.join(", ");
let written: RouteResult;
let read: RouteResult;
try {
written = await call(entry.write, session);
if (written.status >= 300) {
failures.push(
`${label}: write answered ${written.status} ${JSON.stringify(written.body).slice(0, 300)}`,
);
it("stores each setting through its write route and answers it on its read route", async () => {
const failures: string[] = [];
for (const [index, entry] of entries(owner).entries()) {
const label = entry.columns.join(", ");
let written: RouteResult;
let read: RouteResult;
try {
written = await call(entry.write, session);
if (written.status >= 300) {
failures.push(
`${label}: write answered ${written.status} ${JSON.stringify(written.body).slice(0, 300)}`,
);
continue;
}
read = await call(entry.read.call, session);
} catch (error) {
failures.push(`${label}: ${String(error).slice(0, 400)}`);
continue;
}
read = await call(entry.read.call, session);
} catch (error) {
failures.push(`${label}: ${String(error).slice(0, 400)}`);
continue;
if (read.status !== 200) {
failures.push(`${label}: read answered ${read.status}`);
continue;
}
const value = entry.read.pick(read);
before.set(index, value);
if (
"expected" in entry &&
!isDeepStrictEqual(value, entry.expected)
) {
failures.push(
`${label}: read back ${JSON.stringify(value)}, wrote ${JSON.stringify(entry.expected)}`,
);
}
}
if (read.status !== 200) {
failures.push(`${label}: read answered ${read.status}`);
continue;
expect(failures).toEqual([]);
});
it("publishes the settings the account payload carries", async () => {
const me = await callRoute(getMe, {
method: "GET",
route: "/api/auth/me",
auth: session,
});
expect(me.status).toBe(200);
const failures: string[] = [];
for (const [index, entry] of entries(owner).entries()) {
if (!entry.me) continue;
const value = entry.me.pick(me.data);
meBefore.set(index, value);
if (
"expected" in entry.me &&
!isDeepStrictEqual(value, entry.me.expected)
) {
failures.push(
`${entry.columns.join(", ")}: /api/auth/me has ${JSON.stringify(value)}`,
);
}
}
const value = entry.read.pick(read);
before.set(index, value);
if ("expected" in entry && !isDeepStrictEqual(value, entry.expected)) {
failures.push(
`${label}: read back ${JSON.stringify(value)}, wrote ${JSON.stringify(entry.expected)}`,
);
}
}
expect(failures).toEqual([]);
});
it("publishes the settings the account payload carries", async () => {
const me = await callRoute(getMe, {
method: "GET",
route: "/api/auth/me",
auth: session,
expect(failures).toEqual([]);
});
expect(me.status).toBe(200);
const failures: string[] = [];
for (const [index, entry] of entries(owner).entries()) {
if (!entry.me) continue;
const value = entry.me.pick(me.data);
meBefore.set(index, value);
if (
"expected" in entry.me &&
!isDeepStrictEqual(value, entry.me.expected)
) {
failures.push(
`${entry.columns.join(", ")}: /api/auth/me has ${JSON.stringify(value)}`,
);
}
}
expect(failures).toEqual([]);
});
it("moves every setting column off its default", async () => {
stored = columnsOf(
await prisma.user.findUniqueOrThrow({ where: { id: owner.id } }),
);
const untouched = SETTING_COLUMNS.filter(
(column) =>
comparable(stored.get(column)) === comparable(defaults.get(column)),
);
expect(
untouched,
"these columns still hold what a fresh account holds",
).toEqual([]);
});
it("comes back from a backup restored into a recreated account", async () => {
const exported = await callRoute(exportFullBackup, {
method: "GET",
route: "/api/export/full-backup",
auth: session,
});
expect(exported.status).toBe(200);
// The account is recreated under the same id, the way an operator
// brings an account back on a new host, and nothing else of it remains.
await prisma.user.delete({ where: { id: owner.id } });
const fresh = await makeUser({
id: owner.id,
username: owner.username,
email: owner.email,
onboardingCompletedAt: null,
});
const admin = await makeAdmin();
const operator = await makeSession(admin);
const uploaded = await callRoute<{ id: string }>(uploadBackup, {
method: "POST",
route: "/api/admin/backups/upload",
body: exported.body,
auth: operator,
});
expect(
uploaded.status,
JSON.stringify(uploaded.body).slice(0, 400),
).toBeLessThan(300);
const backupId = uploaded.data.id;
const restored = await callRoute(restoreBackup, {
method: "POST",
route: "/api/admin/backups/{id}/restore",
params: { id: backupId },
body: { confirm: "RESTORE" },
auth: operator,
contract: {
skip: "the restore driver answers 200 with the job's report in place of the route's queued 202",
},
});
expect(restored.status, JSON.stringify(restored.body).slice(0, 400)).toBe(
200,
);
const after = columnsOf(
await prisma.user.findUniqueOrThrow({ where: { id: fresh.id } }),
);
const lostInStorage = SETTING_COLUMNS.filter(
(column) =>
!SEALED.has(column) &&
comparable(after.get(column)) !== comparable(stored.get(column)),
);
expect(
lostInStorage.map(
it("moves every setting column off its default", async () => {
stored = columnsOf(
await prisma.user.findUniqueOrThrow({ where: { id: owner.id } }),
);
const untouched = SETTING_COLUMNS.filter(
(column) =>
`${column}: ${comparable(stored.get(column))} -> ${comparable(after.get(column))}`,
),
"these columns came back with a different stored value",
).toEqual([]);
const restoredSession = await makeSession(fresh);
const failures: string[] = [];
for (const [index, entry] of entries(fresh).entries()) {
const read = await call(entry.read.call, restoredSession);
const value =
read.status === 200 ? entry.read.pick(read) : `status ${read.status}`;
if (!isDeepStrictEqual(value, before.get(index))) {
failures.push(
`${entry.columns.join(", ")}: read ${JSON.stringify(before.get(index))} before, ${JSON.stringify(value)} after`,
);
}
}
const me = await callRoute(getMe, {
method: "GET",
route: "/api/auth/me",
auth: restoredSession,
comparable(stored.get(column)) === comparable(defaults.get(column)),
);
expect(
untouched,
"these columns still hold what a fresh account holds",
).toEqual([]);
});
for (const [index, entry] of entries(fresh).entries()) {
if (!entry.me) continue;
const value = entry.me.pick(me.data);
if (!isDeepStrictEqual(value, meBefore.get(index))) {
failures.push(
`${entry.columns.join(", ")}: /api/auth/me had ${JSON.stringify(meBefore.get(index))}, now ${JSON.stringify(value)}`,
);
it("comes back from a backup restored into a recreated account", async () => {
const exported = await callRoute(exportFullBackup, {
method: "GET",
route: "/api/export/full-backup",
auth: session,
});
expect(exported.status).toBe(200);
// The account is recreated under the same id, the way an operator
// brings an account back on a new host, and nothing else of it remains.
await prisma.user.delete({ where: { id: owner.id } });
const fresh = await makeUser({
id: owner.id,
username: owner.username,
email: owner.email,
onboardingCompletedAt: null,
});
const admin = await makeAdmin();
const operator = await makeSession(admin);
const uploaded = await callRoute<{ id: string }>(uploadBackup, {
method: "POST",
route: "/api/admin/backups/upload",
body: exported.body,
auth: operator,
});
expect(
uploaded.status,
JSON.stringify(uploaded.body).slice(0, 400),
).toBeLessThan(300);
const backupId = uploaded.data.id;
const restored = await callRoute(restoreBackup, {
method: "POST",
route: "/api/admin/backups/{id}/restore",
params: { id: backupId },
body: { confirm: "RESTORE" },
auth: operator,
contract: {
skip: "the restore driver answers 200 with the job's report in place of the route's queued 202",
},
});
expect(
restored.status,
JSON.stringify(restored.body).slice(0, 400),
).toBe(200);
const after = columnsOf(
await prisma.user.findUniqueOrThrow({ where: { id: fresh.id } }),
);
const lostInStorage = SETTING_COLUMNS.filter(
(column) =>
!SEALED.has(column) &&
comparable(after.get(column)) !== comparable(stored.get(column)),
);
expect(
lostInStorage.map(
(column) =>
`${column}: ${comparable(stored.get(column))} -> ${comparable(after.get(column))}`,
),
"these columns came back with a different stored value",
).toEqual([]);
const restoredSession = await makeSession(fresh);
const failures: string[] = [];
for (const [index, entry] of entries(fresh).entries()) {
const read = await call(entry.read.call, restoredSession);
const value =
read.status === 200
? entry.read.pick(read)
: `status ${read.status}`;
if (!isDeepStrictEqual(value, before.get(index))) {
failures.push(
`${entry.columns.join(", ")}: read ${JSON.stringify(before.get(index))} before, ${JSON.stringify(value)} after`,
);
}
}
}
expect(failures).toEqual([]);
});
});
const me = await callRoute(getMe, {
method: "GET",
route: "/api/auth/me",
auth: restoredSession,
});
for (const [index, entry] of entries(fresh).entries()) {
if (!entry.me) continue;
const value = entry.me.pick(me.data);
if (!isDeepStrictEqual(value, meBefore.get(index))) {
failures.push(
`${entry.columns.join(", ")}: /api/auth/me had ${JSON.stringify(meBefore.get(index))}, now ${JSON.stringify(value)}`,
);
}
}
expect(failures).toEqual([]);
});
},
);
});
@@ -33,7 +33,7 @@
* the generation leg asserts both halves — nothing enqueued for the manager,
* something enqueued for the owner doing the identical thing.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
import { NextRequest } from "next/server";
@@ -272,6 +272,8 @@ const STRICT_DRIVER_KEYS = new Set<string>();
* It is the executable answer to "did this route actually do its work?".
*/
const REAL_EFFECT_KEYS = new Set<string>();
/** The drivers whose effect case started in this run, filtered or not. */
const EFFECT_CASES_STARTED = new Set<string>();
const ROUTE_IMPORTS = import.meta.glob<Record<string, unknown>>(
"/src/app/api/**/route.ts",
);
@@ -305,6 +307,7 @@ function manageContract<C>(name: string, c: ManageCase<C>) {
STRICT_DRIVER_KEYS.add(matrixKey(c.contract));
describe(name, () => {
it("changes the owner's record and names the manager as actor", async () => {
EFFECT_CASES_STARTED.add(matrixKey(c.contract));
const owner = await makeUser("owner");
const manager = await makeUser("manager");
const ctx = await c.prepare(owner.id);
@@ -381,6 +384,7 @@ function writeEffectContract<C>(name: string, c: ManageCase<C>) {
});
it("writes for the owner and audits the WRITE actor", async () => {
EFFECT_CASES_STARTED.add(matrixKey(c.contract));
const owner = await makeUser("owner");
const delegate = await makeUser("writer");
const ctx = await c.prepare(owner.id);
@@ -3019,10 +3023,15 @@ describe("the complete MANAGE handler matrix", () => {
expect(STRICT_DRIVER_KEYS.size).toBe(93);
expect([...STRICT_DRIVER_KEYS].sort()).toEqual(expected);
});
});
it("executes every registered driver through its owned effect and actor audit", () => {
const expected = ADMITTED_MUTATING_HANDLERS.map(matrixKey).sort();
expect(REAL_EFFECT_KEYS.size).toBe(93);
expect([...REAL_EFFECT_KEYS].sort()).toEqual(expected);
});
// Every registered driver went through its owned effect and actor audit.
// After all cases, not as one of them: the set fills as the effect cases
// run, and a case placed last in the file was read first whenever the order
// was shuffled. A run filtered down to some cases has nothing to say here.
afterAll(() => {
if (EFFECT_CASES_STARTED.size !== STRICT_DRIVER_KEYS.size) return;
const expected = ADMITTED_MUTATING_HANDLERS.map(matrixKey).sort();
expect(REAL_EFFECT_KEYS.size).toBe(93);
expect([...REAL_EFFECT_KEYS].sort()).toEqual(expected);
});