import { beforeEach, describe, expect, it, vi } from "vitest"; const state = vi.hoisted(() => ({ rows: [] as unknown[][], insert: vi.fn(), update: vi.fn(), invalidate: vi.fn(), log: vi.fn(() => "news-error-1"), notify: vi.fn(), })); vi.mock("@/lib/admin/guard", () => ({ requirePermission: async () => ({ id: 1, username: "staff" }), })); vi.mock("@/lib/permissions", () => ({ PERMS: { NEWS_EDIT: "news.edit" } })); vi.mock("@/lib/services/webhook", () => ({ notify: state.notify })); vi.mock("@/lib/services/news-cache", () => ({ invalidateNewsCache: state.invalidate, })); vi.mock("@/lib/logger", () => ({ logger: { error: state.log } })); vi.mock("next-intl/server", () => ({ getTranslations: async () => (key: string, args?: { reference: string }) => key + (args?.reference ?? ""), })); vi.mock("next/cache", () => ({ revalidatePath: vi.fn() })); vi.mock("next/navigation", () => ({ redirect: (url: string) => { throw Error(`redirect ${url}`); }, })); vi.mock("@/lib/db", async () => ({ ...(await import("@/db/schema")), db: { select: () => ({ from: () => ({ where: () => ({ limit: async () => state.rows.shift() ?? [] }), }), }), insert: () => ({ values: state.insert }), update: () => ({ set: (value: unknown) => { state.update(value); return { where: async () => [{ affectedRows: 1 }] }; }, }), }, })); import { createArticle, updateArticle } from "./admin-articles"; function form(extra: Record = {}) { const f = new FormData(); for (const [k, v] of Object.entries({ title: "Test news", slug: "test-news", shortStory: "Summary", fullStory: "Body", status: "published", ...extra, })) f.set(k, v); return f; } describe("news saves", () => { beforeEach(() => { vi.clearAllMocks(); state.rows = []; state.insert.mockResolvedValue([{ insertId: 1 }]); }); it("preserves a recoverable error and logs its reference", async () => { state.insert.mockRejectedValueOnce(Error("Unknown column status")); const result = await createArticle(form()); expect(result).toEqual({ ok: false, error: "saveFailedReferencenews-error-1", }); expect(state.invalidate).not.toHaveBeenCalled(); expect(state.log).toHaveBeenCalled(); }); it("saves drafts without publication notifications", async () => { expect((await createArticle(form({ status: "draft" }))).ok).toBe(true); expect(state.insert).toHaveBeenCalledWith( expect.objectContaining({ status: "draft", publishAt: null, publishedAt: null, }), ); expect(state.notify).not.toHaveBeenCalled(); expect(state.invalidate).toHaveBeenCalledOnce(); }); it("preserves the slug and clears old scheduling for immediate publication", async () => { state.rows = [[{ slug: "test-news", status: "draft", publishedAt: null }]]; expect( ( await updateArticle( form({ id: "1", publishAt: "2099-01-01T00:00:00Z" }), ) ).ok, ).toBe(true); expect(state.update).toHaveBeenCalledWith( expect.objectContaining({ slug: "test-news", publishAt: null, status: "published", }), ); expect(state.invalidate).toHaveBeenCalledOnce(); }); it("rejects invalid scheduled dates before writing", async () => { expect( (await createArticle(form({ status: "scheduled", publishAt: "invalid" }))) .ok, ).toBe(false); expect(state.insert).not.toHaveBeenCalled(); }); });