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 60a1d538..bf3bc854 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 @@ -17,6 +17,8 @@ const sqlMocks = vi.hoisted(() => ({ const database = vi.hoisted(() => { let selected: Record = { id: 7, title: "Existing" }; let selectedQueue: Record[] = []; + let selectedTable: string | undefined; + let lockedTables: Array = []; const insertedValues = vi.fn(async (_values: Record) => [ { insertId: 8 }, ]); @@ -28,9 +30,15 @@ const database = vi.hoisted(() => { const update = vi.fn(() => ({ set })); const nextSelected = () => [selectedQueue.shift() ?? selected]; const limit = vi.fn(async () => nextSelected()); - const lock = vi.fn(async () => nextSelected()); + const lock = vi.fn(async (strength: string) => { + lockedTables.push([selectedTable, strength]); + return nextSelected(); + }); const where = vi.fn(() => ({ for: lock, limit })); - const from = vi.fn(() => ({ where })); + const from = vi.fn((table: unknown) => { + selectedTable = (table as { tableName?: string }).tableName; + return { where }; + }); const select = vi.fn(() => ({ from })); const execute = vi.fn(async () => undefined); const deleteWhere = vi.fn(async () => undefined); @@ -40,6 +48,9 @@ const database = vi.hoisted(() => { insert, insertedValues, lock, + lockedTables() { + return [...lockedTables]; + }, set, update, select, @@ -53,6 +64,8 @@ const database = vi.hoisted(() => { selected(value: Record) { selected = value; selectedQueue = []; + selectedTable = undefined; + lockedTables = []; }, queueSelected(...values: Record[]) { selectedQueue = [...values]; @@ -173,6 +186,7 @@ describe("Content database mutation runtime partial updates", () => { "WebsitePollQuestion", "WebsitePoll", ]); + expect(database.lockedTables()).toEqual([["WebsitePoll", "update"]]); }); it("deletes question votes before the question and retains its audit snapshot", async () => { @@ -202,6 +216,9 @@ describe("Content database mutation runtime partial updates", () => { question: "Favourite colour?", }); expect(snapshot?.after).toBeNull(); + expect(database.lockedTables()).toEqual([ + ["WebsitePollQuestion", "update"], + ]); }); it("rejects moving a question to another poll during update", async () => { @@ -246,6 +263,7 @@ describe("Content database mutation runtime partial updates", () => { expect.objectContaining({ options: "" }), ); expect(database.lock).toHaveBeenCalledWith("update"); + expect(database.lockedTables()).toEqual([["WebsitePoll", "update"]]); }); it("maps duplicate question options to an options field error", async () => { @@ -332,6 +350,7 @@ describe("Content database mutation runtime partial updates", () => { ]) { expect(values).not.toHaveProperty(omitted); } + expect(database.lockedTables()).toEqual([["WebsitePoll", "update"]]); }); it("rejects question 101 without inserting it", async () => { @@ -418,6 +437,9 @@ describe("Content database mutation runtime partial updates", () => { ); expect(database.set).toHaveBeenCalledWith({ options: "Green\nYellow" }); + expect(database.lockedTables()).toEqual([ + ["WebsitePollQuestion", "update"], + ]); }); it("blocks deletion of an event type referenced by an event", 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 b9dd75dd..cf202ab1 100644 --- a/src/features/housekeeping/domains/content/services/mutation-runtime-database.ts +++ b/src/features/housekeeping/domains/content/services/mutation-runtime-database.ts @@ -632,7 +632,7 @@ async function pollChange( }) .from(WebsitePoll) .where(eq(WebsitePoll.id, id)) - .limit(1); + .for("update"); if (!existing) throw notFound(); if (action === "delete") { await connection @@ -735,7 +735,7 @@ async function pollQuestionChange( }) .from(WebsitePollQuestion) .where(eq(WebsitePollQuestion.id, id)) - .limit(1); + .for("update"); if (!existing) throw notFound(); if (action === "delete") { await connection