102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
import { beforeEach, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
existing: vi.fn(),
|
|
insert: vi.fn(),
|
|
update: vi.fn(),
|
|
}));
|
|
vi.mock("@/lib/db", async () => ({
|
|
...(await import("@/db/schema")),
|
|
db: {
|
|
select: () => ({
|
|
from: () => ({ where: () => ({ limit: mocks.existing }) }),
|
|
}),
|
|
insert: () => ({ values: mocks.insert }),
|
|
update: () => ({ set: () => ({ where: mocks.update }) }),
|
|
},
|
|
}));
|
|
vi.mock("@/lib/safe-action", () => ({
|
|
adminAction:
|
|
(
|
|
options: { schema: { parse: (data: unknown) => unknown } },
|
|
handler: (ctx: unknown) => unknown,
|
|
) =>
|
|
(data: unknown) =>
|
|
handler({
|
|
data: options.schema.parse(data),
|
|
session: { user: { id: 7, username: "Staff" } },
|
|
}),
|
|
authAction: () => vi.fn(),
|
|
}));
|
|
vi.mock("@/lib/services/audit", () => ({ logAudit: vi.fn() }));
|
|
vi.mock("@/lib/services/webhook", () => ({ notify: vi.fn() }));
|
|
|
|
vi.mock("@/lib/foundation/action", () => ({ handleActionError: vi.fn() }));
|
|
vi.mock("@/lib/permissions", async () => import("@/lib/permission-slugs"));
|
|
|
|
import { createEvent, updateEvent } from "./events";
|
|
|
|
const base = {
|
|
title: "Event",
|
|
description: "Details",
|
|
typeId: 1,
|
|
startsAt: new Date("2030-01-01"),
|
|
status: "published" as const,
|
|
isRecurring: 0,
|
|
};
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mocks.existing.mockResolvedValue([
|
|
{
|
|
id: 1,
|
|
status: "published",
|
|
title: "Event",
|
|
image: "/cover.png",
|
|
startsAt: new Date("2030-01-01"),
|
|
endsAt: new Date("2030-01-02"),
|
|
},
|
|
]);
|
|
mocks.insert.mockResolvedValue([{ insertId: 1 }]);
|
|
mocks.update.mockResolvedValue(undefined);
|
|
});
|
|
it("rejects invalid published event images before writing", async () => {
|
|
await expect(
|
|
createEvent({ ...base, image: "javascript:alert(1)" }),
|
|
).rejects.toThrow("Check the event image");
|
|
expect(mocks.insert).not.toHaveBeenCalled();
|
|
});
|
|
it("validates an update against existing dates before writing", async () => {
|
|
await expect(
|
|
updateEvent({ id: 1, endsAt: new Date("2029-12-01") }),
|
|
).rejects.toThrow("Check the event image");
|
|
expect(mocks.update).not.toHaveBeenCalled();
|
|
});
|
|
it("allows draft saves and partial updates with unchanged valid dates", async () => {
|
|
await createEvent({ ...base, status: "draft", image: "javascript:alert(1)" });
|
|
expect(mocks.insert).toHaveBeenCalledOnce();
|
|
await updateEvent({ id: 1, title: "Changed", startsAt: undefined });
|
|
expect(mocks.update).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("does not bypass published preflight when a partial update omits status", async () => {
|
|
await expect(
|
|
updateEvent({ id: 1, image: "javascript:alert(1)" }),
|
|
).rejects.toThrow("Check the event image");
|
|
expect(mocks.update).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("allows explicitly removing an invalid image while publishing the draft", async () => {
|
|
mocks.existing.mockResolvedValue([
|
|
{
|
|
id: 1,
|
|
status: "draft",
|
|
title: "Draft",
|
|
image: "javascript:alert(1)",
|
|
startsAt: new Date("2030-01-01"),
|
|
endsAt: null,
|
|
},
|
|
]);
|
|
await updateEvent({ id: 1, status: "published", image: "" });
|
|
expect(mocks.update).toHaveBeenCalledOnce();
|
|
});
|