mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
The defect is about rows that survive, so a mocked client proves nothing. The new integration test seeds a row into every table in the schema, driven by information_schema and pg_constraint rather than a fixture, runs the wipe and asks the database what is left. A table added later is seeded and asserted without anyone remembering to. It holds all four halves: nothing in the wipe list survives, everything in the exemption list does, everything reached only by a database cascade is gone, and the account row comes back reset but able to sign in. A second file pins the allergy and checkup-reminder deletes actually removing their row. The reproductive-health note gains a Deletion section. It taught people to reason carefully about a host being imaged and never said what pressing delete does; now it says which of the three deletions removes the row and which only hides it.
121 lines
4.1 KiB
TypeScript
121 lines
4.1 KiB
TypeScript
/**
|
|
* Two confirmation dialogs promised permanence over a soft delete.
|
|
*
|
|
* `records.allergies.deleteConfirmDescription` says it can't be undone and
|
|
* `measurementReminders.deleteConfirmDescription` says the reminder is
|
|
* permanently deleted, while both handlers stamped `deletedAt` and kept the
|
|
* row — with no restore route, no undo affordance, and no purge job to give
|
|
* the tombstone a horizon. The row then rode the nightly disaster-recovery
|
|
* backup off the host, indefinitely.
|
|
*
|
|
* Neither tombstone bought anything: neither model is in the
|
|
* `/api/sync/changes` delta feed, nothing references either table, and every
|
|
* read already filtered `deletedAt: null`. So the behaviour moved to match
|
|
* the copy rather than the copy retreating.
|
|
*
|
|
* Asserted against the real database because the whole question is whether a
|
|
* row is still there. `src/__tests__/copy-matches-behaviour-guard.test.ts`
|
|
* holds the other end — that the copy keeps claiming permanence.
|
|
*
|
|
* Mutation check: put either handler back on `update({ data: { deletedAt } })`
|
|
* and the matching test goes red on the surviving row.
|
|
*/
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { cookieJar, headerJar } from "./mock-next-headers";
|
|
import { getPrismaClient, truncateAllTables } from "./setup";
|
|
|
|
vi.mock("next/headers", async () => {
|
|
const { cookieJar, headerJar } = await import("./mock-next-headers");
|
|
return {
|
|
headers: vi.fn(async () => ({
|
|
get: (name: string) => headerJar.get(name.toLowerCase()) ?? null,
|
|
})),
|
|
cookies: vi.fn(async () => ({
|
|
get: (name: string) => {
|
|
const value = cookieJar.get(name);
|
|
return value ? { name, value } : undefined;
|
|
},
|
|
set: (name: string, value: string) => {
|
|
cookieJar.set(name, value);
|
|
},
|
|
delete: (name: string) => {
|
|
cookieJar.delete(name);
|
|
},
|
|
})),
|
|
};
|
|
});
|
|
|
|
vi.mock("@/lib/db-compat", () => ({
|
|
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
async function signIn(username: string): Promise<string> {
|
|
const prisma = getPrismaClient();
|
|
const user = await prisma.user.create({
|
|
data: { username, email: `${username}@example.test` },
|
|
});
|
|
const session = await prisma.session.create({
|
|
data: { userId: user.id, expiresAt: new Date(Date.now() + 60_000) },
|
|
});
|
|
cookieJar.set("healthlog_session", session.id);
|
|
return user.id;
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await truncateAllTables(getPrismaClient());
|
|
cookieJar.clear();
|
|
headerJar.clear();
|
|
});
|
|
|
|
describe("deletes that promise permanence remove the row", () => {
|
|
it("an allergy is gone from the database, not hidden", async () => {
|
|
const prisma = getPrismaClient();
|
|
const userId = await signIn("allergy-deleter");
|
|
|
|
const allergy = await prisma.allergy.create({
|
|
data: { userId, substance: "Penicillin" },
|
|
});
|
|
|
|
const { DELETE } = await import("@/app/api/allergies/[id]/route");
|
|
const response = await DELETE(
|
|
new Request(`http://localhost/api/allergies/${allergy.id}`, {
|
|
method: "DELETE",
|
|
}) as never,
|
|
{ params: Promise.resolve({ id: allergy.id }) } as never,
|
|
);
|
|
expect(response.status).toBe(200);
|
|
|
|
expect(await prisma.allergy.count({ where: { id: allergy.id } })).toBe(0);
|
|
});
|
|
|
|
it("a checkup reminder is gone from the database, not hidden", async () => {
|
|
const prisma = getPrismaClient();
|
|
const userId = await signIn("reminder-deleter");
|
|
|
|
const reminder = await prisma.measurementReminder.create({
|
|
data: {
|
|
userId,
|
|
label: "Dental checkup",
|
|
intervalDays: 180,
|
|
notifyHour: 9,
|
|
nextDueAt: new Date(Date.now() + 86_400_000),
|
|
},
|
|
});
|
|
|
|
const { DELETE } =
|
|
await import("@/app/api/measurement-reminders/[id]/route");
|
|
const response = await DELETE(
|
|
new Request(`http://localhost/api/measurement-reminders/${reminder.id}`, {
|
|
method: "DELETE",
|
|
}) as never,
|
|
{ params: Promise.resolve({ id: reminder.id }) } as never,
|
|
);
|
|
expect(response.status).toBe(200);
|
|
|
|
expect(
|
|
await prisma.measurementReminder.count({ where: { id: reminder.id } }),
|
|
).toBe(0);
|
|
});
|
|
});
|