// @ts-nocheck import { readFileSync } from "node:fs"; import { redirect } from "next/navigation"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context"; import { requirePermission, requireStaff } from "@/lib/admin/guard"; import { createAd } from "./admin-ads"; import { createArticle } from "./admin-articles"; import { uploadMedia } from "./admin-media"; import { deleteFavicon, saveFavicon } from "./save-favicon"; import { saveLogo } from "./save-logo"; const { execute, auditedBrandExecute } = vi.hoisted(() => ({ execute: vi.fn(async () => ({ ok: true, data: { before: null, after: { id: "1" }, output: { url: "/api/media/x" } }, correlationId: "legacy", })), auditedBrandExecute: vi.fn(async () => ({ before: null, after: { value: "/api/media/x" }, output: { url: "/api/media/x" }, })), })); const { executeLegacyBrandAssetMutation } = vi.hoisted(() => ({ executeLegacyBrandAssetMutation: vi.fn(async () => ({ before: null, after: { value: "/api/media/x" }, output: { url: "/api/media/x" }, })), })); vi.mock("@/features/housekeeping/domains/content/services/mutations", () => ({ contentMutationService: { execute }, createContentMutationInvocation: (actor, correlationId) => ({ expectedActorId: actor.id, correlationId, legacy: true, }), })); vi.mock( "@/features/housekeeping/domains/content/services/mutations-production", () => ({ contentProductionMutationAdapter: { execute: auditedBrandExecute }, }), ); vi.mock("@/features/housekeeping/foundation/server-capability-context", () => ({ getHousekeepingCapabilityContext: vi.fn(async () => ({ actor: { id: 42, username: "operator", rank: 7 }, isSuperAdmin: false, has: () => false, hasAny: () => false, hasAll: () => false, })), })); vi.mock( "@/features/housekeeping/domains/content/services/mutation-runtime-external", () => ({ executeLegacyBrandAssetMutation, }), ); vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn(), requireStaff: vi.fn(), })); vi.mock("@/lib/safe-action", () => ({ adminAction: (_options: unknown, handler: unknown) => handler, })); vi.mock("@/lib/safe-action-shared", () => ({ ActionError: class ActionError extends Error {}, actionOk: (data: unknown = {}) => ({ ok: true, data }), })); vi.mock("@/lib/logger", () => ({ logger: { error: vi.fn() }, })); vi.mock("@/lib/permissions", () => ({ PERMS: { NEWS_EDIT: "news.edit", PAGES_EDIT: "pages.edit", SETTINGS_EDIT: "settings.edit", SETTINGS_VIEW: "settings.view", }, })); vi.mock("@/lib/db", () => ({ db: { select: vi.fn(() => ({ from: vi.fn(() => ({ where: vi.fn(() => ({ limit: vi.fn(async () => []) })), })), })), insert: vi.fn(() => ({ values: vi.fn(async () => [{ insertId: 1 }]), })), }, WebsiteArticles: { id: "id", slug: "slug" }, WebsiteAds: { id: "id" }, WebsiteSetting: { key: "key" }, })); vi.mock("@/lib/services/staff-activity", () => ({ logStaffActivity: vi.fn(), })); vi.mock("@/lib/services/site-settings", () => ({ siteSettings: { get: vi.fn(), reload: vi.fn() }, })); vi.mock("@/lib/media-storage", () => ({ MEDIA_ROOT: "C:\\media", resolveMediaPath: vi.fn((name: string) => `C:\\media\\${name}`), })); vi.mock("node:fs/promises", () => ({ mkdir: vi.fn(), writeFile: vi.fn(), unlink: vi.fn(), })); vi.mock("next/cache", () => ({ revalidatePath: vi.fn() })); vi.mock("next/navigation", () => ({ redirect: vi.fn() })); const staff = { id: 42, rank: 7, username: "operator" }; const form = (data: Record) => ({ get: (key: string) => data[key] ?? null, has: (key: string) => key in data, }); beforeEach(() => { vi.clearAllMocks(); vi.mocked(requirePermission).mockResolvedValue(staff as never); vi.mocked(requireStaff).mockResolvedValue(staff as never); execute.mockResolvedValue({ ok: true, data: { before: null, after: { id: "1" }, output: { url: "/api/media/x" } }, correlationId: "legacy", }); auditedBrandExecute.mockResolvedValue({ before: null, after: { value: "/api/media/x" }, output: { url: "/api/media/x" }, }); }); describe("Content legacy wrappers", () => { it("delegates article creation and preserves redirect ordering", async () => { await createArticle( form({ title: "Launch", shortStory: "Summary", fullStory: "Body", image: "/image.png", }) as FormData, ); expect(execute).toHaveBeenCalledWith( expect.objectContaining({ expectedActorId: 42, legacy: true }), "article.change", expect.objectContaining({ action: "create", title: "Launch" }), ); expect(redirect).toHaveBeenCalledWith("/admin/articles"); }); it("delegates ad creation and keeps the legacy void/redirect contract", async () => { expect( await createAd( form({ image: "https://example.test/ad.png" }) as FormData, ), ).toBeUndefined(); expect(execute).toHaveBeenCalledWith( expect.objectContaining({ expectedActorId: 42, legacy: true }), "ad.change", expect.objectContaining({ action: "create" }), ); expect(redirect).toHaveBeenCalledWith("/admin/ads"); }); it("delegates media and favicon uploads while retaining public result shapes", async () => { const file = new File(["bytes"], "image.png", { type: "image/png" }); const media = await uploadMedia(form({ file }) as FormData); const favicon = await saveFavicon(form({ file }) as FormData); expect(media).toEqual({ ok: true }); expect(favicon).toEqual({ success: true, url: "/api/media/x" }); expect(execute).toHaveBeenCalledWith( expect.anything(), "media.upload", expect.objectContaining({ file }), ); expect(auditedBrandExecute).toHaveBeenCalledWith( "favicon.save", { file }, expect.objectContaining({ capability: expect.objectContaining({ actor: expect.objectContaining({ id: 42 }), }), legacy: true, }), ); expect(executeLegacyBrandAssetMutation).not.toHaveBeenCalled(); }); it("preserves the legacy favicon page gate and establishes a staff logo floor", async () => { vi.clearAllMocks(); const file = new File(["bytes"], "image.png", { type: "image/png" }); await saveFavicon(form({ file }) as FormData); await deleteFavicon(); await saveLogo(form({ file }) as FormData); expect(requirePermission).toHaveBeenNthCalledWith(1, "settings.view"); expect(requirePermission).toHaveBeenNthCalledWith(2, "settings.view"); expect(requirePermission).not.toHaveBeenCalledWith("settings.edit"); expect(requireStaff).toHaveBeenCalledOnce(); expect( auditedBrandExecute.mock.calls.map(([operation]) => operation), ).toEqual(["favicon.save", "favicon.delete", "logo.save"]); expect(executeLegacyBrandAssetMutation).not.toHaveBeenCalled(); }); it("does not mutate brand assets when either legacy guard denies access", async () => { const file = new File(["bytes"], "image.png", { type: "image/png" }); vi.mocked(requirePermission).mockRejectedValueOnce( new Error("favicon denied"), ); await expect(saveFavicon(form({ file }) as FormData)).rejects.toThrow( "favicon denied", ); expect(executeLegacyBrandAssetMutation).not.toHaveBeenCalled(); expect(auditedBrandExecute).not.toHaveBeenCalled(); vi.mocked(requireStaff).mockRejectedValueOnce(new Error("logo denied")); await expect(saveLogo(form({ file }) as FormData)).rejects.toThrow( "logo denied", ); expect(executeLegacyBrandAssetMutation).not.toHaveBeenCalled(); expect(auditedBrandExecute).not.toHaveBeenCalled(); }); it("lets a requireStaff-approved actor without an additional ACL use the audited logo boundary", async () => { vi.clearAllMocks(); vi.mocked(requireStaff).mockResolvedValue(staff as never); const file = new File(["bytes"], "logo.png", { type: "image/png" }); await expect(saveLogo(form({ file }) as FormData)).resolves.toEqual({ success: true, url: "/api/media/x", }); expect(requirePermission).not.toHaveBeenCalled(); expect(requireStaff).toHaveBeenCalledOnce(); expect(auditedBrandExecute).toHaveBeenCalledWith( "logo.save", { file }, expect.objectContaining({ capability: expect.objectContaining({ actor: expect.objectContaining({ id: 42 }), }), legacy: true, }), ); }); it("refuses a brand mutation when the rehydrated actor changes after the legacy guard", async () => { vi.mocked(getHousekeepingCapabilityContext).mockResolvedValueOnce({ actor: { id: 99, username: "other", rank: 7 }, isSuperAdmin: false, has: () => false, hasAny: () => false, hasAll: () => false, } as never); const file = new File(["bytes"], "logo.png", { type: "image/png" }); await expect(saveLogo(form({ file }) as FormData)).resolves.toEqual({ success: false, error: "Authenticated staff changed during logo mutation", }); expect(auditedBrandExecute).not.toHaveBeenCalled(); }); it("maps a favicon audit partial to the truthful legacy result shape", async () => { auditedBrandExecute.mockResolvedValueOnce({ before: { value: "/old.ico" }, after: { value: "/api/media/favicon/new.ico" }, output: { url: "/api/media/favicon/new.ico" }, completion: { status: "partial", external: "completed", audit: "unavailable", }, }); const file = new File(["bytes"], "favicon.png", { type: "image/png" }); await expect(saveFavicon(form({ file }) as FormData)).resolves.toEqual({ success: false, url: "/api/media/favicon/new.ico", error: "Favicon change completed partially; verify storage and audit state", }); }); it("maps a logo audit partial to the truthful legacy result shape", async () => { auditedBrandExecute.mockResolvedValueOnce({ before: { value: "/old.png" }, after: { value: "/api/media/logo/new.png" }, output: { url: "/api/media/logo/new.png" }, completion: { status: "partial", external: "completed", audit: "unavailable", }, }); const file = new File(["bytes"], "logo.png", { type: "image/png" }); await expect(saveLogo(form({ file }) as FormData)).resolves.toEqual({ success: false, url: "/api/media/logo/new.png", error: "Logo change completed partially; verify storage and audit state", }); }); it("keeps every listed legacy action as a thin shared-service wrapper", () => { for (const path of [ "src/actions/admin-ads.ts", "src/actions/admin-articles.ts", "src/actions/admin-banners.ts", "src/actions/admin-email-templates.ts", "src/actions/admin-help.ts", "src/actions/admin-media.ts", "src/actions/admin-nav-menu.ts", "src/actions/admin-photos.ts", "src/actions/admin-tags.ts", "src/actions/admin-theme.ts", "src/actions/admin-writeable-boxes.ts", "src/actions/banners.ts", "src/actions/events.ts", "src/actions/polls.ts", "src/actions/prefixes.ts", "src/actions/save-favicon.ts", "src/actions/save-logo.ts", "src/actions/translations.ts", "src/actions/emulator.ts", ]) { const source = readFileSync(path, "utf8"); expect( source.includes("contentMutationService") || source.includes("contentProductionMutationAdapter") || source.includes('from "./banners"'), path, ).toBe(true); } }); it("routes the real banner manager through the required compatibility action", () => { const source = readFileSync( "src/app/admin/banners/banners-manager.tsx", "utf8", ); expect(source).toContain('from "@/actions/admin-banners"'); expect(source).not.toContain('from "@/actions/banners"'); }); });