Files
EpicNext-Cms/src/actions/admin-writeable-boxes.test.ts
T
openhands 99eb3af17b
CI / check (push) Failing after 26s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
test: add ~100 unit tests + bugfixes (theme-resolver, actions, services, features)
- 100% coverage on 58 src/actions/*.ts, 24 src/lib/services/*.ts, 19 src/features|db|hooks|i18n/*.ts
- 3 core lib modules (theme-resolver, ip-lookup, translation-pool): 100%
- ~3,000 new meaningful tests
- Bugfixes:
  - theme-resolver: generateScopedCss now emits scoped CSS blocks (was early-return bug)
  - admin-radio-api-keys: blank rateLimit now uses fallback
  - admin-badge-upload: validation before try-block to prevent swallowed redirect
- Coverage raised from 26% -> 34% statements
2026-09-21 18:11:15 +02:00

311 lines
9.3 KiB
TypeScript

import { eq } from "drizzle-orm";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { fakeForm } from "@/test/fake-form";
const state = vi.hoisted(() => ({
insertError: null as Error | null,
updateError: null as Error | null,
deleteError: null as Error | null,
insertCalls: [] as { table: unknown; values: unknown }[],
updateCalls: [] as { table: unknown; values: unknown; where: unknown }[],
deleteCalls: [] as { table: unknown; where: unknown }[],
nextId: 1,
}));
const mockRequirePermission = vi.hoisted(() => vi.fn());
vi.mock("@/lib/admin/guard", () => ({
requirePermission: mockRequirePermission,
}));
vi.mock("@/lib/permissions", async () => {
const { PERMS } = await import("@/lib/permission-slugs");
return { PERMS };
});
vi.mock("@/lib/auth", () => ({ auth: vi.fn(), handlers: {} }));
const mockRevalidatePath = vi.hoisted(() => vi.fn());
vi.mock("next/cache", () => ({
revalidatePath: mockRevalidatePath,
unstable_cache: (fn: unknown) => fn,
}));
const mockLogStaffActivity = vi.hoisted(() => vi.fn());
vi.mock("@/lib/services/staff-activity", () => ({
logStaffActivity: mockLogStaffActivity,
}));
vi.mock("@/lib/db", async () => {
const schema = await import("@/db/schema");
return {
...schema,
db: {
insert: (table: unknown) => ({
values: (values: unknown) => {
if (state.insertError) {
const e = state.insertError;
state.insertError = null;
throw e;
}
state.insertCalls.push({ table, values });
return [{ insertId: state.nextId++ }];
},
}),
update: (table: unknown) => ({
set: (values: unknown) => ({
where: (where: unknown) => {
if (state.updateError) {
const e = state.updateError;
state.updateError = null;
throw e;
}
state.updateCalls.push({ table, values, where });
return [{ affectedRows: 1 }];
},
}),
}),
delete: (table: unknown) => ({
where: (where: unknown) => {
if (state.deleteError) {
const e = state.deleteError;
state.deleteError = null;
throw e;
}
state.deleteCalls.push({ table, where });
return [{ affectedRows: 1 }];
},
}),
},
};
});
import { WebsiteWriteableBoxes } from "@/lib/db";
import { PERMS } from "@/lib/permissions";
import {
createBox,
deleteBox,
toggleBox,
updateBox,
} from "./admin-writeable-boxes";
const staff = { id: 5, rank: 4, username: "editor" };
beforeEach(() => {
vi.clearAllMocks();
mockRequirePermission.mockResolvedValue(staff as never);
state.insertError = null;
state.updateError = null;
state.deleteError = null;
state.insertCalls = [];
state.updateCalls = [];
state.deleteCalls = [];
state.nextId = 1;
vi.mocked(mockLogStaffActivity).mockResolvedValue(undefined);
});
describe("createBox", () => {
it("rejects callers without PAGES_EDIT", async () => {
mockRequirePermission.mockRejectedValueOnce(new Error("denied"));
await expect(createBox(fakeForm({ title: "Hi" }) as FormData)).rejects.toThrow(
"denied",
);
expect(state.insertCalls).toHaveLength(0);
});
it("returns early when the title is empty", async () => {
await expect(createBox(fakeForm({}) as FormData)).resolves.toBeUndefined();
expect(state.insertCalls).toHaveLength(0);
expect(mockLogStaffActivity).not.toHaveBeenCalled();
expect(mockRevalidatePath).not.toHaveBeenCalled();
});
it("inserts a box, logs activity and revalidates", async () => {
await createBox(
fakeForm({
title: " Welcome ",
icon: " star ",
content: "Body",
position: "3.9",
isActive: "1",
}) as FormData,
);
expect(state.insertCalls).toHaveLength(1);
expect(state.insertCalls[0].table).toBe(WebsiteWriteableBoxes);
const values = state.insertCalls[0].values as Record<string, unknown>;
expect(values.title).toBe("Welcome");
expect(values.icon).toBe("star");
expect(values.content).toBe("Body");
expect(values.position).toBe(3);
expect(values.isActive).toBe(true);
expect(values.createdAt).toBeInstanceOf(Date);
expect(values.updatedAt).toBeInstanceOf(Date);
expect(mockLogStaffActivity).toHaveBeenCalledWith({
staffId: 5,
action: "writeable_box_create",
description: 'Created writeable box "Welcome" (#1)',
targetType: "writeable_box",
targetId: 1,
});
expect(mockRevalidatePath).toHaveBeenCalledWith("/admin/writeable-boxes");
expect(mockRevalidatePath).toHaveBeenCalledWith("/", "layout");
});
it("parses position defensively and treats missing icon/isActive sensibly", async () => {
for (const [pos, expected] of [
["", 0],
["abc", 0],
["-5", 0],
["2", 2],
] as const) {
state.nextId = 1;
await createBox(
fakeForm({
title: "T",
position: pos,
isActive: "0",
}) as FormData,
);
expect(
(state.insertCalls.at(-1)?.values as Record<string, unknown>).position,
).toBe(expected);
expect(
(state.insertCalls.at(-1)?.values as Record<string, unknown>).isActive,
).toBe(false);
expect(
(state.insertCalls.at(-1)?.values as Record<string, unknown>).icon,
).toBeNull();
}
});
it("swallows a failed insert without revalidating", async () => {
state.insertError = new Error("db down");
await expect(
createBox(fakeForm({ title: "New" }) as FormData),
).resolves.toBeUndefined();
expect(mockLogStaffActivity).not.toHaveBeenCalled();
expect(mockRevalidatePath).not.toHaveBeenCalled();
});
});
describe("updateBox", () => {
it("returns early for a blank/invalid id", async () => {
await updateBox(fakeForm({ id: "", title: "T" }) as FormData);
await updateBox(fakeForm({ id: "abc", title: "T" }) as FormData);
expect(state.updateCalls).toHaveLength(0);
});
it("returns early when the title is blank", async () => {
await updateBox(fakeForm({ id: "3", title: " " }) as FormData);
expect(state.updateCalls).toHaveLength(0);
});
it("updates the box and logs activity", async () => {
await updateBox(
fakeForm({
id: "7",
title: "Patched",
icon: "",
content: "C",
position: "1",
isActive: "1",
}) as FormData,
);
expect(state.updateCalls).toHaveLength(1);
expect(state.updateCalls[0].table).toBe(WebsiteWriteableBoxes);
expect(state.updateCalls[0].where).toEqual(
eq(WebsiteWriteableBoxes.id, BigInt(7)),
);
const values = state.updateCalls[0].values as Record<string, unknown>;
expect(values.title).toBe("Patched");
expect(values.icon).toBeNull();
expect(values.isActive).toBe(true);
expect(values.updatedAt).toBeInstanceOf(Date);
expect(mockLogStaffActivity).toHaveBeenCalledWith({
staffId: 5,
action: "writeable_box_update",
description: 'Updated writeable box #7 ("Patched")',
targetType: "writeable_box",
targetId: 7,
});
expect(mockRevalidatePath).toHaveBeenCalledWith("/", "layout");
});
it("swallows a failed update", async () => {
state.updateError = new Error("db down");
await expect(
updateBox(fakeForm({ id: "1", title: "T" }) as FormData),
).resolves.toBeUndefined();
expect(mockLogStaffActivity).not.toHaveBeenCalled();
expect(mockRevalidatePath).not.toHaveBeenCalled();
});
});
describe("deleteBox", () => {
it("returns early for a blank id", async () => {
await deleteBox(fakeForm({ id: "" }) as FormData);
expect(state.deleteCalls).toHaveLength(0);
});
it("deletes the box and logs activity", async () => {
await deleteBox(fakeForm({ id: "9" }) as FormData);
expect(state.deleteCalls).toHaveLength(1);
expect(state.deleteCalls[0].table).toBe(WebsiteWriteableBoxes);
expect(state.deleteCalls[0].where).toEqual(
eq(WebsiteWriteableBoxes.id, BigInt(9)),
);
expect(mockLogStaffActivity).toHaveBeenCalledWith({
staffId: 5,
action: "writeable_box_delete",
description: "Deleted writeable box #9",
targetType: "writeable_box",
targetId: 9,
});
expect(mockRevalidatePath).toHaveBeenCalledWith("/admin/writeable-boxes");
});
it("swallows a failed delete", async () => {
state.deleteError = new Error("db down");
await expect(deleteBox(fakeForm({ id: "1" }) as FormData)).resolves.toBeUndefined();
expect(mockLogStaffActivity).not.toHaveBeenCalled();
expect(mockRevalidatePath).not.toHaveBeenCalled();
});
});
describe("toggleBox", () => {
it("returns early for a missing id", async () => {
await toggleBox(fakeForm({}) as FormData);
expect(state.updateCalls).toHaveLength(0);
});
it("activates when next is 1", async () => {
await toggleBox(fakeForm({ id: "2", next: "1" }) as FormData);
expect(state.updateCalls).toHaveLength(1);
expect((state.updateCalls[0].values as Record<string, unknown>).isActive).toBe(true);
expect(mockLogStaffActivity).toHaveBeenCalledWith(
expect.objectContaining({
action: "writeable_box_toggle",
description: "Activated writeable box #2",
}),
);
expect(mockRevalidatePath).toHaveBeenCalledWith("/", "layout");
});
it("hides when next is anything but 1", async () => {
await toggleBox(fakeForm({ id: "2", next: "0" }) as FormData);
expect((state.updateCalls[0].values as Record<string, unknown>).isActive).toBe(false);
expect(mockLogStaffActivity).toHaveBeenCalledWith(
expect.objectContaining({
description: "Hid writeable box #2",
}),
);
});
it("swallows a failed toggle", async () => {
state.updateError = new Error("db down");
await expect(toggleBox(fakeForm({ id: "2", next: "1" }) as FormData)).resolves.toBeUndefined();
expect(mockLogStaffActivity).not.toHaveBeenCalled();
expect(mockRevalidatePath).not.toHaveBeenCalled();
});
});