From 6e16b948e6499ec26d6eab3135611664ea32b7d4 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Wed, 2 Sep 2026 18:45:50 +0200 Subject: [PATCH] fix(housekeeping): align poll deletion lock order --- .../mutation-runtime-database.test.ts | 30 +++++++++++++++++-- .../services/mutation-runtime-database.ts | 5 ++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/features/housekeeping/domains/content/services/mutation-runtime-database.test.ts b/src/features/housekeeping/domains/content/services/mutation-runtime-database.test.ts index bf3bc854..de00757f 100644 --- a/src/features/housekeeping/domains/content/services/mutation-runtime-database.test.ts +++ b/src/features/housekeeping/domains/content/services/mutation-runtime-database.test.ts @@ -19,6 +19,7 @@ const database = vi.hoisted(() => { let selectedQueue: Record[] = []; let selectedTable: string | undefined; let lockedTables: Array = []; + let mutationSteps: string[] = []; const insertedValues = vi.fn(async (_values: Record) => [ { insertId: 8 }, ]); @@ -32,6 +33,7 @@ const database = vi.hoisted(() => { const limit = vi.fn(async () => nextSelected()); const lock = vi.fn(async (strength: string) => { lockedTables.push([selectedTable, strength]); + mutationSteps.push(`lock:${selectedTable ?? "unknown"}`); return nextSelected(); }); const where = vi.fn(() => ({ for: lock, limit })); @@ -42,7 +44,12 @@ const database = vi.hoisted(() => { const select = vi.fn(() => ({ from })); const execute = vi.fn(async () => undefined); const deleteWhere = vi.fn(async () => undefined); - const remove = vi.fn((_table: unknown) => ({ where: deleteWhere })); + const remove = vi.fn((table: unknown) => { + mutationSteps.push( + `delete:${(table as { tableName?: string }).tableName ?? "unknown"}`, + ); + return { where: deleteWhere }; + }); return { execute, insert, @@ -51,6 +58,9 @@ const database = vi.hoisted(() => { lockedTables() { return [...lockedTables]; }, + mutationSteps() { + return [...mutationSteps]; + }, set, update, select, @@ -66,6 +76,7 @@ const database = vi.hoisted(() => { selectedQueue = []; selectedTable = undefined; lockedTables = []; + mutationSteps = []; }, queueSelected(...values: Record[]) { selectedQueue = [...values]; @@ -186,7 +197,17 @@ describe("Content database mutation runtime partial updates", () => { "WebsitePollQuestion", "WebsitePoll", ]); - expect(database.lockedTables()).toEqual([["WebsitePoll", "update"]]); + expect(database.lockedTables()).toEqual([ + ["WebsitePoll", "update"], + ["WebsitePollQuestion", "update"], + ]); + expect(database.mutationSteps()).toEqual([ + "lock:WebsitePoll", + "lock:WebsitePollQuestion", + "delete:WebsitePollVote", + "delete:WebsitePollQuestion", + "delete:WebsitePoll", + ]); }); it("deletes question votes before the question and retains its audit snapshot", async () => { @@ -219,6 +240,11 @@ describe("Content database mutation runtime partial updates", () => { expect(database.lockedTables()).toEqual([ ["WebsitePollQuestion", "update"], ]); + expect(database.mutationSteps()).toEqual([ + "lock:WebsitePollQuestion", + "delete:WebsitePollVote", + "delete:WebsitePollQuestion", + ]); }); it("rejects moving a question to another poll during update", async () => { diff --git a/src/features/housekeeping/domains/content/services/mutation-runtime-database.ts b/src/features/housekeeping/domains/content/services/mutation-runtime-database.ts index cf202ab1..cfb6a03b 100644 --- a/src/features/housekeeping/domains/content/services/mutation-runtime-database.ts +++ b/src/features/housekeeping/domains/content/services/mutation-runtime-database.ts @@ -635,6 +635,11 @@ async function pollChange( .for("update"); if (!existing) throw notFound(); if (action === "delete") { + await connection + .select({ id: WebsitePollQuestion.id }) + .from(WebsitePollQuestion) + .where(eq(WebsitePollQuestion.pollId, id)) + .for("update"); await connection .delete(WebsitePollVote) .where(