fix(housekeeping): address task 14 review round 1

This commit is contained in:
Simo committed 2026-08-30 10:53:50 +02:00
1 parent fd68819d9b
commit d09eaa33d6
23 files changed
+2503 -488

No files matched your search

@@ -0,0 +1,145 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
import { executeContentDatabaseMutation } from "./mutation-runtime-database";
const database = vi.hoisted(() => {
let selected: Record<string, unknown> = { id: 7, title: "Existing" };
const set = vi.fn((values: Record<string, unknown>) => ({
where: vi.fn(async () => undefined),
values,
}));
const update = vi.fn(() => ({ set }));
const limit = vi.fn(async () => [selected]);
const where = vi.fn(() => ({ limit }));
const from = vi.fn(() => ({ where }));
const select = vi.fn(() => ({ from }));
return {
set,
update,
select,
selected(value: Record<string, unknown>) {
selected = value;
},
};
});
vi.mock("drizzle-orm", () => ({
eq: vi.fn(),
sql: Object.assign(vi.fn(), { raw: vi.fn() }),
}));
vi.mock("@/lib/db", () => {
const table = new Proxy({}, { get: (_target, key) => String(key) });
return {
db: {
select: database.select,
update: database.update,
},
EmailTemplates: table,
Taggables: table,
Tags: table,
User: table,
WebsiteAds: table,
WebsiteArticleComments: table,
WebsiteArticleReactions: table,
WebsiteArticles: table,
WebsiteBanner: table,
WebsiteEvent: table,
WebsiteEventPrize: table,
WebsiteEventType: table,
WebsiteEventWinner: table,
WebsiteHelpCenterCategories: table,
WebsitePoll: table,
WebsitePollQuestion: table,
WebsiteWriteableBoxes: table,
};
});
vi.mock("@/lib/services/staff-activity", () => ({
logStaffActivity: vi.fn(async () => undefined),
}));
const capability = {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: () => true,
hasAny: () => true,
hasAll: () => true,
} satisfies HousekeepingCapabilityContext;
const context = {
capability,
correlationId: "database-runtime",
legacy: false,
};
describe("Content database mutation runtime partial updates", () => {
beforeEach(() => {
vi.clearAllMocks();
database.selected({ id: 7, title: "Existing" });
});
it("does not reset omitted banner fields", async () => {
await executeContentDatabaseMutation(
"banner.change",
{ action: "update", id: 7, title: "Renamed" },
context,
undefined,
);
expect(database.set).toHaveBeenCalledWith({ title: "Renamed" });
});
it("does not reset a tag color omitted by the update form", async () => {
await executeContentDatabaseMutation(
"tag.change",
{ action: "update", id: "7", name: "News" },
context,
undefined,
);
const values = database.set.mock.calls[0]?.[0];
expect(values).toMatchObject({ name: "News" });
expect(values).not.toHaveProperty("backgroundColor");
});
it.each([
["help-question.change", { name: "Updated question" }, "name"],
["writeable-box.change", { title: "Updated box" }, "title"],
["email-template.change", { subject: "Updated subject" }, "subject"],
] as const)(
"updates only supplied fields for %s",
async (operation, patch, expectedKey) => {
await executeContentDatabaseMutation(
operation,
{ action: "update", id: "7", ...patch },
context,
undefined,
);
const values = database.set.mock.calls[0]?.[0];
expect(values).toHaveProperty(expectedKey);
for (const destructiveKey of [
"content",
"position",
"isActive",
"body",
"variables",
"imageUrl",
]) {
expect(values).not.toHaveProperty(destructiveKey);
}
},
);
it.each([
["help-question.change", { answer: "Updated answer" }, "content"],
["writeable-box.change", { content: "Updated content" }, "content"],
["email-template.change", { body: "Updated body" }, "body"],
] as const)(
"accepts a non-title partial patch for %s",
async (operation, patch, expectedKey) => {
await executeContentDatabaseMutation(
operation,
{ action: "update", id: "7", ...patch },
context,
undefined,
);
expect(database.set.mock.calls[0]?.[0]).toHaveProperty(expectedKey);
},
);
});