mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
test(sharing): teach the wipe guard that ownership is not always user_id
The delete-all-data guard counted an account's surviving rows with a literal `WHERE user_id = $1`, which is the same assumption the wipe loop itself used to make. Account grants relate two accounts and carry no such column, so the guard raised `column "user_id" does not exist` and stopped before it could assert anything. The predicate now comes from the wipe plan's own owner-column declaration, so the guard asks the question the route answers instead of its own version of it.
This commit is contained in:
1 parent
33c879dd6b
commit
07e8ca9ef5
1 file changed
+24
-2
@@ -38,6 +38,7 @@ import {
|
||||
USER_RESET,
|
||||
WIPE_EXEMPT,
|
||||
WIPE_MODELS,
|
||||
WIPE_OWNER_FIELDS,
|
||||
} from "@/lib/data-wipe/wipe-plan";
|
||||
import type { PrismaClient } from "@/generated/prisma/client";
|
||||
|
||||
@@ -271,14 +272,35 @@ async function seedEveryTable(
|
||||
return ordered;
|
||||
}
|
||||
|
||||
/**
|
||||
* The columns that bind a row of `model` to an account, as SQL identifiers.
|
||||
*
|
||||
* `user_id` is the convention. A model that relates two accounts cannot use
|
||||
* it, and the wipe plan already declares its columns — read them from there
|
||||
* rather than keeping a second list here, or the two would answer differently
|
||||
* and this file would be asserting against its own idea of ownership instead
|
||||
* of the one the route uses.
|
||||
*/
|
||||
function ownerColumns(model: string | undefined): string[] {
|
||||
const declared = model ? WIPE_OWNER_FIELDS[model] : undefined;
|
||||
if (!declared) return ["user_id"];
|
||||
return declared.map((field) =>
|
||||
field.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`),
|
||||
);
|
||||
}
|
||||
|
||||
async function countRows(
|
||||
prisma: PrismaClient,
|
||||
table: string,
|
||||
userId?: string,
|
||||
model?: string,
|
||||
): Promise<number> {
|
||||
const predicate = ownerColumns(model)
|
||||
.map((column) => `"${column}" = $1`)
|
||||
.join(" OR ");
|
||||
const rows = userId
|
||||
? await prisma.$queryRawUnsafe<Array<{ n: bigint }>>(
|
||||
`SELECT count(*)::bigint AS n FROM "${table}" WHERE user_id = $1`,
|
||||
`SELECT count(*)::bigint AS n FROM "${table}" WHERE ${predicate}`,
|
||||
userId,
|
||||
)
|
||||
: await prisma.$queryRawUnsafe<Array<{ n: bigint }>>(
|
||||
@@ -354,7 +376,7 @@ describe("DELETE /api/settings/data leaves nothing it promised to delete", () =>
|
||||
// one new row after it commits: the receipt for the wipe itself. The
|
||||
// history is gone; the record that the person asked for it is not.
|
||||
const expected = model === "AuditLog" ? 1 : 0;
|
||||
if ((await countRows(prisma, table, user.id)) > expected) {
|
||||
if ((await countRows(prisma, table, user.id, model)) > expected) {
|
||||
survivors.push(`${model} (${table})`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user