mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
Move the contract tests to the end that now writes
Three integration cases pinned the old behaviour: the sync handshake stamping the checkpoint, and the calendar read persisting the prediction. Both are the faults this release removes, so the cases were asserting a bug. Each file now asserts the read writes nothing AND that the other end does. The second half is not decoration: without it, "nothing was written" would pass just as well against a column nothing writes at all. The cycle case seeds its own history and its own profile row rather than reaching a route to get one, because reaching the route is exactly what the prediction must no longer depend on.
This commit is contained in:
1 parent
c9ee84e5e4
commit
03d2a712d0
2 files changed
+81
-10
No files matched your search
@@ -480,16 +480,67 @@ describe("cycle calendar", () => {
|
||||
// GENERAL_HEALTH default → fertile window suppressed.
|
||||
expect(json.data.prediction.fertileWindowStart).toBeNull();
|
||||
|
||||
// The cache row is persisted fire-and-forget (the route never blocks
|
||||
// the read on it), so poll briefly for it to land.
|
||||
let cache = null;
|
||||
for (let i = 0; i < 20 && cache === null; i++) {
|
||||
cache = await prisma.cyclePrediction.findUnique({
|
||||
// The read persists nothing. The prediction row exists for the reminder
|
||||
// job, and writing it here made the job's behaviour depend on whether
|
||||
// anyone had opened this page. Poll anyway rather than reading once, so
|
||||
// that a write which merely arrives late still fails the case.
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const cache = await prisma.cyclePrediction.findUnique({
|
||||
where: { userId: FEMALE_USER_ID },
|
||||
});
|
||||
if (cache === null) await new Promise((r) => setTimeout(r, 25));
|
||||
expect(cache).toBeNull();
|
||||
await new Promise((r) => setTimeout(r, 25));
|
||||
}
|
||||
expect(cache).not.toBeNull();
|
||||
});
|
||||
|
||||
it("the nightly refresh writes the prediction the read no longer writes", async () => {
|
||||
// The other end of the move. Without this the case above would pass
|
||||
// against a prediction nothing writes at all.
|
||||
//
|
||||
// `beforeEach` truncates, so this case seeds its own history rather than
|
||||
// inheriting the one above. It also never touches the calendar route: the
|
||||
// point is that the prediction lands without anyone opening that page.
|
||||
//
|
||||
// The profile row is seeded directly for the same reason. Every
|
||||
// `/api/cycle/*` route creates one on the way past, so in production the
|
||||
// cohort is "has ever used cycle tracking" rather than "opened the
|
||||
// calendar recently". Calling a route to get one here would defeat the
|
||||
// case.
|
||||
const prisma = getPrismaClient();
|
||||
await prisma.cycleProfile.create({ data: { userId: FEMALE_USER_ID } });
|
||||
for (const [start, end, len] of [
|
||||
["2026-01-01", "2026-01-28", 28],
|
||||
["2026-01-29", "2026-02-25", 28],
|
||||
] as const) {
|
||||
await prisma.menstrualCycle.create({
|
||||
data: {
|
||||
userId: FEMALE_USER_ID,
|
||||
startDate: start,
|
||||
endDate: end,
|
||||
lengthDays: len,
|
||||
periodEndDate: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
await prisma.menstrualCycle.create({
|
||||
data: { userId: FEMALE_USER_ID, startDate: "2026-02-26" },
|
||||
});
|
||||
|
||||
expect(
|
||||
await prisma.cyclePrediction.findUnique({
|
||||
where: { userId: FEMALE_USER_ID },
|
||||
}),
|
||||
).toBeNull();
|
||||
|
||||
const { refreshPredictionCacheForUser } =
|
||||
await import("@/lib/cycle/prediction-refresh");
|
||||
await refreshPredictionCacheForUser(FEMALE_USER_ID);
|
||||
|
||||
expect(
|
||||
await prisma.cyclePrediction.findUnique({
|
||||
where: { userId: FEMALE_USER_ID },
|
||||
}),
|
||||
).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -103,21 +103,41 @@ describe("GET /api/sync/state (real Postgres)", () => {
|
||||
expect(json.data.intakes.liveCount).toBe(0);
|
||||
expect(json.data.intakes.tombstonedCount).toBe(0);
|
||||
|
||||
// Server-side, lastSyncedAt now carries a value.
|
||||
// The handshake asks where to resume. It does not deliver anything, so it
|
||||
// must not move the resume point: advancing past a window the client has
|
||||
// not drained is how rows go missing.
|
||||
const after = await getPrismaClient().user.findUnique({
|
||||
where: { id: TEST_USER_ID },
|
||||
select: { lastSyncedAt: true },
|
||||
});
|
||||
expect(after?.lastSyncedAt).not.toBeNull();
|
||||
expect(after?.lastSyncedAt).toBeNull();
|
||||
});
|
||||
|
||||
it("returns the previous checkpoint on the second call", async () => {
|
||||
it("still reports null on the second call, because asking twice is not syncing", async () => {
|
||||
// The old contract stamped the checkpoint on the first call, so the second
|
||||
// read found one. Nothing has arrived between these two calls, so the
|
||||
// honest answer is still null.
|
||||
const { GET } = await import("@/app/api/sync/state/route");
|
||||
await GET(makeRequest());
|
||||
const res = await GET(makeRequest());
|
||||
const json = (await res.json()) as {
|
||||
data: { lastSyncedAt: string | null };
|
||||
};
|
||||
expect(json.data.lastSyncedAt).toBeNull();
|
||||
});
|
||||
|
||||
it("reports a checkpoint once data has actually arrived", async () => {
|
||||
// The other half of the move: the mark now comes from an ingest boundary.
|
||||
// Without this case the two above would pass just as well against a column
|
||||
// nothing writes at all, which is the failure they exist to rule out.
|
||||
const { markSyncCheckpoint } = await import("@/lib/sync/checkpoint");
|
||||
await markSyncCheckpoint(TEST_USER_ID);
|
||||
|
||||
const { GET } = await import("@/app/api/sync/state/route");
|
||||
const res = await GET(makeRequest());
|
||||
const json = (await res.json()) as {
|
||||
data: { lastSyncedAt: string | null };
|
||||
};
|
||||
expect(json.data.lastSyncedAt).not.toBeNull();
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user