fix(housekeeping): lock poll mutation snapshots

This commit is contained in:
Simo committed 2026-09-02 18:38:25 +02:00
1 parent 3e52965292
commit b4193bea6c
2 files changed
+26 -4

No files matched your search

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