import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { createElement, type ReactNode } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { PERMS } from "@/lib/permission-slugs"; import type { HousekeepingCapabilityContext } from "./contracts"; const routeMocks = vi.hoisted(() => { const messages: Record = { "preview.badge": "Localized preview", "preview.commandDisabled": "Localized disabled command", "preview.backToSite": "Localized back to site", "navigation.skipToContent": "Localized skip to content", "navigation.primary": "Localized primary navigation", "navigation.contextual": "Localized contextual navigation", "domains.people.title": "Localized People", "domains.people.description": "Localized People description", "domains.economy.title": "Localized Economy", "domains.economy.description": "Localized Economy description", "states.empty.title": "Localized empty title", "states.empty.description": "Localized empty description", }; const translate = vi.fn((key: string) => { const message = messages[key]; if (message === undefined) throw new Error(`Unexpected translation: ${key}`); return message; }); return { env: { NODE_ENV: "test" as "development" | "test" | "production", HOUSEKEEPING_NEXT_PREVIEW_ENABLED: true, }, getHousekeepingCapabilityContext: vi.fn(), getTranslations: vi.fn(async (namespace: string) => { if (namespace !== "pages.housekeeping") { throw new Error(`Unexpected namespace: ${namespace}`); } return translate; }), notFound: vi.fn((): never => { throw new Error("NEXT_NOT_FOUND"); }), redirect: vi.fn((href: string): never => { throw new Error(`NEXT_REDIRECT:${href}`); }), translate, }; }); vi.mock("@/env", () => ({ env: routeMocks.env })); vi.mock("next/navigation", () => ({ notFound: routeMocks.notFound, redirect: routeMocks.redirect, })); vi.mock("next-intl/server", () => ({ getTranslations: routeMocks.getTranslations, })); vi.mock("@/features/housekeeping/foundation/server-capability-context", () => ({ getHousekeepingCapabilityContext: routeMocks.getHousekeepingCapabilityContext, })); vi.mock("@/lib/db", () => { throw new Error("preview routes must not import the database"); }); vi.mock("@/lib/auth", () => { throw new Error("preview routes must not call auth directly"); }); vi.mock("@/lib/permissions", () => { throw new Error("preview routes must not reload permissions directly"); }); vi.mock("@/actions", () => { throw new Error("preview routes must not import actions"); }); vi.mock("@/app/actions", () => { throw new Error("preview routes must not import actions"); }); import AdminNextDomainLayout from "@/app/admin-next/[domain]/layout"; import AdminNextDomainPage from "@/app/admin-next/[domain]/page"; import AdminNextLayout from "@/app/admin-next/layout"; import AdminNextPage from "@/app/admin-next/page"; const routeFiles = [ "src/app/admin-next/layout.tsx", "src/app/admin-next/page.tsx", "src/app/admin-next/[domain]/layout.tsx", "src/app/admin-next/[domain]/page.tsx", ] as const; function capabilityContext( granted: readonly string[], actor = { id: 42, username: "refreshed-moderator", rank: 3 }, ): HousekeepingCapabilityContext { const capabilities = new Set(granted); return { actor, isSuperAdmin: false, has: (slug) => capabilities.has(slug), hasAny: (...slugs) => slugs.some((slug) => capabilities.has(slug)), hasAll: (...slugs) => slugs.every((slug) => capabilities.has(slug)), }; } async function renderRoute(route: ReactNode | Promise) { return renderToStaticMarkup(await route); } describe("/admin-next preview gate", () => { beforeEach(() => { vi.clearAllMocks(); routeMocks.env.NODE_ENV = "test"; routeMocks.env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED = true; }); it.each([ ["production", true], ["production", false], ["development", false], ] as const)("returns 404 for NODE_ENV=%s flag=%s", async (nodeEnv, flag) => { routeMocks.env.NODE_ENV = nodeEnv; routeMocks.env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED = flag; await expect(async () => renderRoute( AdminNextLayout({ children: createElement("p", null, "Preview child"), }), ), ).rejects.toThrow("NEXT_NOT_FOUND"); expect(routeMocks.notFound).toHaveBeenCalledTimes(1); }); it.each(["development", "test"] as const)( "renders children in %s when explicitly enabled", async (nodeEnv) => { routeMocks.env.NODE_ENV = nodeEnv; const html = await renderRoute( AdminNextLayout({ children: createElement("p", null, "Preview child"), }), ); expect(html).toContain("Preview child"); expect(routeMocks.notFound).not.toHaveBeenCalled(); }, ); }); describe("/admin-next first visible domain", () => { beforeEach(() => { vi.clearAllMocks(); }); it("redirects an administrator to Operations in locked registry order", async () => { routeMocks.getHousekeepingCapabilityContext.mockResolvedValue( capabilityContext([PERMS.ADMIN_DASHBOARD, PERMS.USERS_VIEW]), ); await expect(AdminNextPage()).rejects.toThrow( "NEXT_REDIRECT:/admin-next/operations", ); expect(routeMocks.redirect).toHaveBeenCalledWith("/admin-next/operations"); expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( 1, ); expect(routeMocks.getTranslations).not.toHaveBeenCalled(); }); it("redirects a moderator with only an approved mod view capability to People", async () => { routeMocks.getHousekeepingCapabilityContext.mockResolvedValue( capabilityContext([PERMS.MOD_CFH_VIEW]), ); await expect(AdminNextPage()).rejects.toThrow( "NEXT_REDIRECT:/admin-next/people", ); expect(routeMocks.redirect).toHaveBeenCalledWith("/admin-next/people"); expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( 1, ); }); it("returns 404 when the operator has no visible domain", async () => { routeMocks.getHousekeepingCapabilityContext.mockResolvedValue( capabilityContext([]), ); await expect(AdminNextPage()).rejects.toThrow("NEXT_NOT_FOUND"); expect(routeMocks.notFound).toHaveBeenCalledTimes(1); expect(routeMocks.redirect).not.toHaveBeenCalled(); expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( 1, ); }); }); describe("/admin-next/[domain] layout", () => { beforeEach(() => { vi.clearAllMocks(); }); it("rejects an unknown domain before loading capability context", async () => { await expect( AdminNextDomainLayout({ children: createElement("p", null, "Unknown body"), params: Promise.resolve({ domain: "unknown" }), }), ).rejects.toThrow("NEXT_NOT_FOUND"); expect(routeMocks.getHousekeepingCapabilityContext).not.toHaveBeenCalled(); expect(routeMocks.getTranslations).not.toHaveBeenCalled(); }); it("rejects a known domain that the operator cannot access", async () => { routeMocks.getHousekeepingCapabilityContext.mockResolvedValue( capabilityContext([PERMS.MOD_CFH_VIEW]), ); await expect( AdminNextDomainLayout({ children: createElement("p", null, "Economy body"), params: Promise.resolve({ domain: "economy" }), }), ).rejects.toThrow("NEXT_NOT_FOUND"); expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( 1, ); expect(routeMocks.getTranslations).not.toHaveBeenCalled(); }); it("renders localized People shell from one refreshed capability context", async () => { routeMocks.getHousekeepingCapabilityContext.mockResolvedValue( capabilityContext([PERMS.MOD_CFH_VIEW]), ); const html = await renderRoute( AdminNextDomainLayout({ children: createElement("p", null, "People body"), params: Promise.resolve({ domain: "people" }), }), ); expect(html).toContain("refreshed-moderator"); expect(html).toContain("Localized preview"); expect(html).toContain("Localized primary navigation"); expect(html).toContain("Localized People"); expect(html).toContain("People body"); expect(html).not.toContain("Localized Economy"); expect(routeMocks.translate).not.toHaveBeenCalledWith( "domains.economy.title", ); expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( 1, ); expect(routeMocks.getTranslations).toHaveBeenCalledTimes(1); }); }); describe("/admin-next/[domain] page", () => { beforeEach(() => { vi.clearAllMocks(); }); it("renders the real localized manifest and empty state without reloading access", async () => { const html = await renderRoute( AdminNextDomainPage({ params: Promise.resolve({ domain: "people" }), }), ); expect(html).toContain("Localized People"); expect(html).toContain("Localized People description"); expect(html).toContain("Localized empty title"); expect(html).toContain("Localized empty description"); expect(routeMocks.getHousekeepingCapabilityContext).not.toHaveBeenCalled(); expect(routeMocks.getTranslations).toHaveBeenCalledTimes(1); }); it("rejects an unknown domain before translating", async () => { await expect( AdminNextDomainPage({ params: Promise.resolve({ domain: "unknown" }), }), ).rejects.toThrow("NEXT_NOT_FOUND"); expect(routeMocks.getHousekeepingCapabilityContext).not.toHaveBeenCalled(); expect(routeMocks.getTranslations).not.toHaveBeenCalled(); }); }); describe("preview route import boundary", () => { it("rejects data, actions, direct auth, old chrome, and legacy route imports", () => { for (const path of routeFiles) { const source = readFileSync(resolve(process.cwd(), path), "utf8"); expect(source, path).not.toMatch( /@\/lib\/db|@\/(?:app\/)?actions(?:\/|["'])/, ); expect(source, path).not.toMatch(/AdminSidebarNav|AdminHubChrome/); expect(source, path).not.toMatch(/@\/lib\/(?:auth|permissions)/); expect(source, path).not.toMatch( /(?:@\/app\/(?:admin|mod)|\.\.\/+(?:admin|mod))(?:\/|["'])/, ); } }); });