import { readFileSync } from "node:fs"; import { createRequire } from "node:module"; import { posix, 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": "HK::preview-badge", "preview.commandDisabled": "HK::command-disabled", "preview.backToSite": "HK::back-to-site", "navigation.skipToContent": "HK::skip-to-content", "navigation.primary": "HK::primary-navigation", "navigation.contextual": "HK::contextual-navigation", "commandDeck.placeholder": "HK::command-placeholder", "commandDeck.navigation": "HK::command-navigation", "commandDeck.commands": "HK::command-commands", "commandDeck.entities": "HK::command-entities", "commandDeck.loading": "HK::command-loading", "commandDeck.empty": "HK::command-empty", "commandDeck.partial": "HK::command-partial", "commandDeck.close": "HK::command-close", "domains.people.title": "HK::people-title", "domains.people.description": "Localized People description", "domains.economy.title": "Localized Economy", "domains.economy.description": "Localized Economy description", "domains.operations.title": "Localized Operations", "domains.operations.description": "Localized Operations description", "domains.content.title": "Localized Content", "domains.content.description": "Localized Content description", "domains.hotel.title": "Localized Hotel", "domains.hotel.description": "Localized Hotel description", "domains.system.title": "HK::system-title", "domains.system.description": "Localized System description", "routes.content.editorial.articles": "HK::content-editorial-articles", "routes.content.editorial.article-create": "HK::content-editorial-article-create", "routes.content.editorial.article-detail": "HK::content-editorial-article-detail", "routes.content.media.photos": "HK::content-media-photos", "routes.content.media.library": "HK::content-media-library", "routes.content.media.banners": "HK::content-media-banners", "routes.content.media.ads": "HK::content-media-ads", "routes.content.media.ad-create": "HK::content-media-ad-create", "routes.content.media.ad-detail": "HK::content-media-ad-detail", "routes.content.engagement.events": "HK::content-engagement-events", "routes.content.engagement.event-create": "HK::content-engagement-event-create", "routes.content.engagement.event-types": "HK::content-engagement-event-types", "routes.content.engagement.event-detail": "HK::content-engagement-event-detail", "routes.content.engagement.polls": "HK::content-engagement-polls", "routes.content.engagement.poll-create": "HK::content-engagement-poll-create", "routes.content.engagement.poll-detail": "HK::content-engagement-poll-detail", "routes.content.help.questions": "HK::content-help-questions", "routes.content.help.question-create": "HK::content-help-question-create", "routes.content.help.question-detail": "HK::content-help-question-detail", "routes.content.editorial.tags": "HK::content-editorial-tags", "routes.content.engagement.prefixes": "HK::content-engagement-prefixes", "routes.content.editorial.writeable-boxes": "HK::content-editorial-writeable-boxes", "routes.content.help.email-templates": "HK::content-help-email-templates", "routes.content.brand.theme": "HK::content-brand-theme", "routes.content.brand.favicon": "HK::content-brand-favicon", "routes.content.localization.overview": "HK::content-localization-overview", "routes.content.localization.client": "HK::content-localization-client", "routes.content.localization.cms": "HK::content-localization-cms", "routes.content.localization.emulator": "HK::content-localization-emulator", "routes.system.access.permissions": "HK::system-access-permissions", "routes.system.access.permission-detail": "HK::system-access-permission-detail", "routes.system.configuration.settings": "HK::system-configuration-settings", "routes.system.configuration.emulator": "HK::system-configuration-emulator", "routes.system.observability.analytics": "HK::system-observability-analytics", "routes.system.observability.analytics-activity": "HK::system-observability-analytics-activity", "routes.system.observability.analytics-economy": "HK::system-observability-analytics-economy", "routes.system.observability.devops": "HK::system-observability-devops", "routes.system.observability.devops-errors": "HK::system-observability-devops-errors", "routes.system.observability.logs-staff": "HK::system-observability-logs-staff", "routes.system.observability.logs-audit": "HK::system-observability-logs-audit", "routes.system.observability.logs-chat": "HK::system-observability-logs-chat", "routes.system.observability.logs-commands": "HK::system-observability-logs-commands", "routes.system.observability.logs-trades": "HK::system-observability-logs-trades", "routes.system.operations.alerts": "HK::system-operations-alerts", "routes.system.operations.command-center": "HK::system-operations-command-center", "routes.system.operations.maintenance": "HK::system-operations-maintenance", "routes.economy.catalog.overview": "HK::economy-catalog-overview", "routes.economy.catalog.detail": "HK::economy-catalog-detail", "routes.economy.catalog.builder-club-detail": "HK::economy-catalog-builder-club-detail", "routes.economy.catalog.maintenance": "HK::economy-catalog-maintenance", "routes.economy.items.overview": "HK::economy-items-overview", "routes.economy.items.detail": "HK::economy-items-detail", "routes.economy.commerce.shop": "HK::economy-commerce-shop", "routes.economy.commerce.shop-create": "HK::economy-commerce-shop-create", "routes.economy.commerce.shop-detail": "HK::economy-commerce-shop-detail", "routes.economy.commerce.marketplace": "HK::economy-commerce-marketplace", "routes.economy.commerce.vouchers": "HK::economy-commerce-vouchers", "routes.economy.commerce.subscriptions": "HK::economy-commerce-subscriptions", "routes.economy.history.transactions": "HK::economy-history-transactions", "routes.economy.value.rare-values": "HK::economy-value-rare-values", "routes.economy.rewards.badges": "HK::economy-rewards-badges", "routes.economy.rewards.achievements": "HK::economy-rewards-achievements", "routes.economy.rewards.sounds": "HK::economy-rewards-sounds", "routes.economy.rewards.calendar": "HK::economy-rewards-calendar", "routes.economy.rewards.calendar-detail": "HK::economy-rewards-calendar-detail", "routes.hotel.rooms": "HK::hotel-rooms", "routes.hotel.room-detail": "HK::hotel-room-detail", "routes.hotel.room-furni": "HK::hotel-room-furni", "routes.hotel.navigation": "HK::hotel-navigation", "routes.hotel.radio.overview": "HK::hotel-radio-overview", "routes.hotel.radio.settings": "HK::hotel-radio-settings", "routes.hotel.radio.monitoring": "HK::hotel-radio-monitoring", "routes.hotel.radio.moderation": "HK::hotel-radio-moderation", "routes.hotel.radio.autodj": "HK::hotel-radio-autodj", "routes.hotel.radio.history": "HK::hotel-radio-history", "routes.hotel.radio.points": "HK::hotel-radio-points", "routes.hotel.radio.ranks": "HK::hotel-radio-ranks", "routes.hotel.radio.api-keys": "HK::hotel-radio-api-keys", "routes.hotel.radio.banners": "HK::hotel-radio-banners", "routes.hotel.radio.embed": "HK::hotel-radio-embed", "routes.hotel.studio.audit": "HK::hotel-studio-audit", "routes.hotel.studio.badges": "HK::hotel-studio-badges", "routes.hotel.studio.clone": "HK::hotel-studio-clone", "routes.hotel.studio.clothing": "HK::hotel-studio-clothing", "routes.hotel.studio.effects": "HK::hotel-studio-effects", "routes.hotel.studio.furni": "HK::hotel-studio-furni", "routes.hotel.studio.maintenance": "HK::hotel-studio-maintenance", "routes.hotel.studio.pets": "HK::hotel-studio-pets", "routes.hotel.studio.repair-icons": "HK::hotel-studio-repair-icons", "routes.hotel.studio.sync": "HK::hotel-studio-sync", "routes.hotel.studio.upload": "HK::hotel-studio-upload", "routes.operations.workspace": "HK::operations-workspace", "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}`); }), renderHousekeepingRoute: vi.fn( async (input: { context: { actor: { username: string } }; match: { routeId: string }; }) => `Rendered ${input.match.routeId} for ${input.context.actor.username}`, ), 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"); }); vi.mock("@/features/housekeeping/manifests", async (importOriginal) => { const actual = await importOriginal(); return { HOUSEKEEPING_MANIFESTS: actual.HOUSEKEEPING_MANIFESTS.map((manifest) => manifest.id === "people" ? { ...manifest, routes: [ { id: "people.users", labelKey: "pages.housekeeping.domains.people.title", href: "/ase/people/users" as const, capability: { mode: "any" as const, slugs: ["admin.users.view", "mod.cfh.view"], }, }, { id: "people.user-detail", labelKey: "pages.housekeeping.domains.people.title", href: "/ase/people/users/:id" as const, capability: { mode: "any" as const, slugs: ["admin.users.view", "mod.cfh.view"], }, }, { id: "people.bans", labelKey: "pages.housekeeping.domains.people.title", href: "/ase/people/bans" as const, capability: { mode: "any" as const, slugs: ["admin.bans.view"], }, }, { id: "people.tool-detail", labelKey: "pages.housekeeping.domains.people.title", href: "/ase/people/tools/:id" as const, capability: { mode: "any" as const, slugs: ["admin.users.view"], }, }, { id: "people.literal-tool", labelKey: "pages.housekeeping.domains.people.title", href: "/ase/people/tools/a+b[1]" as const, capability: { mode: "any" as const, slugs: ["admin.users.view"], }, }, ], } : manifest, ), }; }); vi.mock("@/features/housekeeping/route-handlers", () => ({ HOUSEKEEPING_ROUTE_HANDLERS: [ { routeId: "operations.workspace", render: routeMocks.renderHousekeepingRoute, }, { routeId: "people.users", render: routeMocks.renderHousekeepingRoute }, { routeId: "people.user-detail", render: routeMocks.renderHousekeepingRoute, }, { routeId: "people.bans", render: routeMocks.renderHousekeepingRoute }, { routeId: "people.tool-detail", render: routeMocks.renderHousekeepingRoute, }, { routeId: "people.literal-tool", render: routeMocks.renderHousekeepingRoute, }, ], })); import AdminNextDomainPage from "@/app/ase-next/[domain]/[[...segments]]/page"; import AdminNextDomainLayout from "@/app/ase-next/[domain]/layout"; import AdminNextLayout from "@/app/ase-next/layout"; import AdminNextPage from "@/app/ase-next/page"; const routeFiles = [ "src/app/ase-next/layout.tsx", "src/app/ase-next/page.tsx", "src/app/ase-next/[domain]/layout.tsx", "src/app/ase-next/[domain]/[[...segments]]/page.tsx", ] as const; const forbiddenModuleRoots = [ "src/lib/db", "src/lib/auth", "src/lib/permissions", "src/actions", "src/app/actions", "src/app/admin", "src/app/mod", "@prisma/client", "drizzle-orm", "mysql2", ] as const; interface BabelNode { type: string; [key: string]: unknown; } interface BabelParser { parse( source: string, options: { createImportExpressions: boolean; plugins: readonly ["typescript", "jsx"]; sourceType: "module"; }, ): BabelNode; } interface ModuleAccessScan { specifiers: readonly string[]; violations: readonly string[]; } const projectRequire = createRequire(import.meta.url); const requireFromVitest = createRequire( projectRequire.resolve("vitest/package.json"), ); const babelParser = requireFromVitest("@babel/parser") as BabelParser; const expressionWrapperTypes = new Set([ "ParenthesizedExpression", "TSAsExpression", "TSInstantiationExpression", "TSNonNullExpression", "TSSatisfiesExpression", "TSTypeAssertion", "TypeCastExpression", ]); const resolverExtensionPattern = /\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts)$/i; function isBabelNode(value: unknown): value is BabelNode { return ( typeof value === "object" && value !== null && "type" in value && typeof value.type === "string" ); } function unwrapModuleArgument(node: unknown): BabelNode | null { let current = isBabelNode(node) ? node : null; while (current && expressionWrapperTypes.has(current.type)) { current = isBabelNode(current.expression) ? current.expression : null; } return current; } function readLiteralModuleSpecifier(node: unknown): string | null { const literal = unwrapModuleArgument(node); if (!literal) return null; if (literal.type === "StringLiteral" && typeof literal.value === "string") { return literal.value; } if (literal.type !== "TemplateLiteral") return null; const expressions = Array.isArray(literal.expressions) ? literal.expressions : []; const quasis = Array.isArray(literal.quasis) ? literal.quasis : []; if (expressions.length !== 0 || quasis.length !== 1) return null; const quasi = isBabelNode(quasis[0]) ? quasis[0] : null; const value = quasi?.value; if ( typeof value === "object" && value !== null && "cooked" in value && typeof value.cooked === "string" ) { return value.cooked; } return null; } function memberPropertyName(node: BabelNode): string | null { const property = unwrapModuleArgument(node.property); if (!property) return null; if (node.computed === true) return readLiteralModuleSpecifier(property); return property.type === "Identifier" && typeof property.name === "string" ? property.name : null; } function isGuardedRequireCallee(node: unknown): boolean { const callee = unwrapModuleArgument(node); if (!callee) return false; if (callee.type === "Identifier" && callee.name === "require") return true; if ( callee.type !== "MemberExpression" && callee.type !== "OptionalMemberExpression" ) { return false; } const object = unwrapModuleArgument(callee.object); const property = memberPropertyName(callee); return ( (object?.type === "Identifier" && object.name === "module" && property === "require") || (object?.type === "Identifier" && object.name === "require" && property === "resolve") ); } function scanModuleAccesses(source: string): ModuleAccessScan { const root = babelParser.parse(source, { createImportExpressions: true, plugins: ["typescript", "jsx"], sourceType: "module", }); const specifiers: string[] = []; const violations: string[] = []; function recordArgument(argument: unknown, kind: "import" | "require") { const specifier = readLiteralModuleSpecifier(argument); if (specifier === null) { violations.push(``); return; } specifiers.push(specifier); } function visit(value: unknown): void { if (Array.isArray(value)) { for (const item of value) visit(item); return; } if (!isBabelNode(value)) return; if (value.type === "ImportDeclaration") { const specifier = readLiteralModuleSpecifier(value.source); if (specifier !== null) specifiers.push(specifier); } else if ( (value.type === "ExportNamedDeclaration" || value.type === "ExportAllDeclaration") && value.source !== null ) { const specifier = readLiteralModuleSpecifier(value.source); if (specifier !== null) specifiers.push(specifier); } else if (value.type === "ImportExpression") { recordArgument(value.source, "import"); } else if (value.type === "TSImportType") { recordArgument(value.argument, "import"); } else if ( value.type === "CallExpression" || value.type === "OptionalCallExpression" ) { const arguments_ = Array.isArray(value.arguments) ? value.arguments : []; if (isBabelNode(value.callee) && value.callee.type === "Import") { recordArgument(arguments_[0], "import"); } else if (isGuardedRequireCallee(value.callee)) { recordArgument(arguments_[0], "require"); } } else if (value.type === "TSExternalModuleReference") { recordArgument(value.expression, "require"); } for (const [key, child] of Object.entries(value)) { if (key !== "type") visit(child); } } visit(root); return { specifiers, violations }; } interface CanonicalLocalSpecifier { candidates: readonly string[]; violation: string | null; } function canonicalizeLocalSpecifier( routeFile: string, specifier: string, ): CanonicalLocalSpecifier { const suffixIndex = specifier.search(/[?#]/); const withoutSuffix = suffixIndex === -1 ? specifier : specifier.slice(0, suffixIndex); let decoded: string; try { decoded = decodeURIComponent(withoutSuffix).replaceAll("\\", "/"); } catch { return { candidates: [], violation: "" }; } let normalized: string; if (decoded.startsWith("@/")) { normalized = posix.normalize(`src/${decoded.slice(2)}`); } else if (decoded.startsWith(".")) { normalized = posix.normalize(posix.join(posix.dirname(routeFile), decoded)); } else { normalized = posix.normalize(decoded); } const extensionless = normalized.replace(resolverExtensionPattern, ""); return { candidates: decoded.startsWith("@/") || decoded.startsWith(".") ? [...new Set([normalized, extensionless])] : [normalized], violation: null, }; } function isForbiddenModulePath(path: string): boolean { return forbiddenModuleRoots.some( (root) => path === root || path.startsWith(`${root}/`), ); } function findRouteImportBoundaryViolations( source: string, routeFile: string, ): readonly string[] { const scan = scanModuleAccesses(source); const violations = [...scan.violations]; const forbiddenPaths: string[] = []; for (const specifier of scan.specifiers) { const canonical = canonicalizeLocalSpecifier(routeFile, specifier); if (canonical.violation) violations.push(canonical.violation); const forbiddenPath = canonical.candidates.find(isForbiddenModulePath); if (forbiddenPath) forbiddenPaths.push(forbiddenPath); } return [...violations, ...forbiddenPaths]; } 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("/ase-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("/ase-next operational home", () => { beforeEach(() => { vi.clearAllMocks(); }); it("renders the Operations workspace directly for an administrator", async () => { routeMocks.getHousekeepingCapabilityContext.mockResolvedValue( capabilityContext([PERMS.ADMIN_DASHBOARD, PERMS.USERS_VIEW]), ); const html = await renderRoute(AdminNextPage()); expect(html).toContain("Rendered operations.workspace"); expect(html).toContain("HK::operations-workspace"); expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledWith( expect.objectContaining({ match: expect.objectContaining({ routeId: "operations.workspace" }), surface: "preview", }), ); expect(routeMocks.redirect).not.toHaveBeenCalled(); expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( 1, ); expect(routeMocks.getTranslations).toHaveBeenCalledTimes(1); }); 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:/ase-next/people/users", ); expect(routeMocks.redirect).toHaveBeenCalledWith("/ase-next/people/users"); 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("/ase-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("HK::skip-to-content"); expect(html).toContain("HK::primary-navigation"); expect(html).toContain("HK::contextual-navigation"); expect(html).toContain("HK::command-disabled"); expect(html).toContain("HK::preview-badge"); expect(html).toContain("HK::back-to-site"); expect(html).toContain("HK::people-title"); 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); }); it("builds and translates the complete real System navigation", async () => { routeMocks.getHousekeepingCapabilityContext.mockResolvedValue({ ...capabilityContext([]), isSuperAdmin: true, }); const html = await renderRoute( AdminNextDomainLayout({ children: createElement("p", null, "System body"), params: Promise.resolve({ domain: "system" }), }), ); for (const label of [ "HK::system-access-permissions", "HK::system-access-permission-detail", "HK::system-configuration-settings", "HK::system-configuration-emulator", "HK::system-observability-analytics", "HK::system-observability-analytics-activity", "HK::system-observability-analytics-economy", "HK::system-observability-devops", "HK::system-observability-devops-errors", "HK::system-observability-logs-staff", "HK::system-observability-logs-audit", "HK::system-observability-logs-chat", "HK::system-observability-logs-commands", "HK::system-observability-logs-trades", "HK::system-operations-alerts", "HK::system-operations-command-center", "HK::system-operations-maintenance", ]) { expect(html).toContain(label); } expect(html).toContain("HK::system-title"); expect(html).toContain("System body"); }); }); describe("/ase-next/[domain]/[[...segments]] page", () => { beforeEach(() => { vi.clearAllMocks(); }); it("maps a permitted preview path and renders its handler with one context", async () => { const context = capabilityContext([PERMS.MOD_CFH_VIEW]); routeMocks.getHousekeepingCapabilityContext.mockResolvedValue(context); const html = await renderRoute( AdminNextDomainPage({ params: Promise.resolve({ domain: "people", segments: ["users"] }), }), ); expect(html).toContain("Rendered people.users for refreshed-moderator"); expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( 1, ); expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledTimes(1); expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledWith({ context, surface: "preview", match: { routeId: "people.users", domain: "people", params: {}, canonicalHref: "/ase/people/users", }, }); expect(routeMocks.getTranslations).not.toHaveBeenCalled(); }); it("maps nested preview segments to canonical dynamic parameters", async () => { const context = capabilityContext([PERMS.USERS_VIEW]); routeMocks.getHousekeepingCapabilityContext.mockResolvedValue(context); await renderRoute( AdminNextDomainPage({ params: Promise.resolve({ domain: "people", segments: ["users", "42"], }), }), ); expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledWith({ context, surface: "preview", match: { routeId: "people.user-detail", domain: "people", params: { id: "42" }, canonicalHref: "/ase/people/users/42", }, }); }); it("keeps catch-all encoding from changing the literal route handler", async () => { const context = capabilityContext([PERMS.USERS_VIEW]); routeMocks.getHousekeepingCapabilityContext.mockResolvedValue(context); await renderRoute( AdminNextDomainPage({ params: Promise.resolve({ domain: "people", segments: ["tools", "a+b[1]"], }), }), ); expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledWith({ context, surface: "preview", match: { routeId: "people.literal-tool", domain: "people", params: {}, canonicalHref: "/ase/people/tools/a%2Bb%5B1%5D", }, }); }); it("returns 404 for an inaccessible matched route without invoking it", async () => { routeMocks.getHousekeepingCapabilityContext.mockResolvedValue( capabilityContext([PERMS.MOD_CFH_VIEW]), ); await expect( AdminNextDomainPage({ params: Promise.resolve({ domain: "people", segments: ["bans"] }), }), ).rejects.toThrow("NEXT_NOT_FOUND"); expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes( 1, ); expect(routeMocks.renderHousekeepingRoute).not.toHaveBeenCalled(); }); it("rejects an unknown route before loading capability context", async () => { await expect( AdminNextDomainPage({ params: Promise.resolve({ domain: "people", segments: ["unknown"] }), }), ).rejects.toThrow("NEXT_NOT_FOUND"); expect(routeMocks.getHousekeepingCapabilityContext).not.toHaveBeenCalled(); expect(routeMocks.renderHousekeepingRoute).not.toHaveBeenCalled(); }); }); describe("preview route import boundary", () => { it("rejects normalized forbidden imports and legacy chrome in real routes", () => { for (const path of routeFiles) { const source = readFileSync(resolve(process.cwd(), path), "utf8"); expect(findRouteImportBoundaryViolations(source, path), path).toEqual([]); expect(source, path).not.toMatch(/AdminSidebarNav|AdminHubChrome/); } }); const interpolationOpen = "$" + "{"; it.each([ [ "relative database import with resolver extension", "src/app/ase-next/page.tsx", 'import db from "../../lib/db.js";', "src/lib/db", ], [ "aliased database import with resolver extension", "src/app/ase-next/page.tsx", 'import db from "@/lib/db.js";', "src/lib/db", ], [ "action root import with resolver extension", "src/app/ase-next/page.tsx", 'import actions from "../../actions.mjs";', "src/actions", ], [ "legacy mod root import with resolver extension", "src/app/ase-next/layout.tsx", 'import mod from "../mod.cjs";', "src/app/mod", ], [ "database import with query suffix", "src/app/ase-next/page.tsx", 'import db from "../../lib/db?server-only";', "src/lib/db", ], [ "action import with hash suffix", "src/app/ase-next/page.tsx", 'import("../../actions/users#server")', "src/actions/users", ], [ "Windows-style relative database import", "src/app/ase-next/page.tsx", String.raw`import db from "..\\..\\lib\\db";`, "src/lib/db", ], [ "Windows-style aliased database import", "src/app/ase-next/page.tsx", String.raw`import db from "@\\lib\\db";`, "src/lib/db", ], [ "percent-encoded database import", "src/app/ase-next/page.tsx", 'import db from "../../lib/%64%62";', "src/lib/db", ], [ "optional CommonJS database require", "src/app/ase-next/page.tsx", 'require?.("../../lib/db")', "src/lib/db", ], [ "module database require", "src/app/ase-next/page.tsx", 'module.require("../../lib/db")', "src/lib/db", ], [ "require.resolve database access", "src/app/ase-next/page.tsx", 'require.resolve("../../lib/db")', "src/lib/db", ], [ "optional module database require", "src/app/ase-next/page.tsx", 'module.require?.("../../lib/db")', "src/lib/db", ], [ "optional require.resolve database access", "src/app/ase-next/page.tsx", 'require.resolve?.("../../lib/db")', "src/lib/db", ], [ "TypeScript import-equals database access", "src/app/ase-next/page.tsx", 'import db = require("../../lib/db");', "src/lib/db", ], [ "U+2028 line-continuation database import", "src/app/ase-next/page.tsx", 'import db from "../\\' + "\u2028" + '../lib/db";', "src/lib/db", ], [ "U+2029 line-continuation database import", "src/app/ase-next/page.tsx", 'import db from "../\\' + "\u2029" + '../lib/db";', "src/lib/db", ], [ "parenthesized dynamic action import", "src/app/ase-next/page.tsx", 'import(("../../actions/users"))', "src/actions/users", ], [ "regex-brace template-expression action import", "src/app/ase-next/page.tsx", `const x = \`${interpolationOpen}/}/.test(value) ? import("../../actions/users") : null}\`;`, "src/actions/users", ], [ "CommonJS database require", "src/app/ase-next/page.tsx", 'require("../../lib/db")', "src/lib/db", ], [ "template-literal dynamic action import", "src/app/ase-next/page.tsx", "import(`../../actions/users`)", "src/actions/users", ], [ "TypeScript-asserted dynamic action import", "src/app/ase-next/page.tsx", 'import(("../../actions/users" as string))', "src/actions/users", ], [ "template-expression dynamic action import", "src/app/ase-next/page.tsx", `const x = \`${interpolationOpen}import("../../actions/users")}\`;`, "src/actions/users", ], [ "nested template-expression dynamic action import", "src/app/ase-next/[domain]/[[...segments]]/page.tsx", `const x = \`${interpolationOpen}ready ? \`${interpolationOpen}import("../../../../actions/nested")}\` : ""}\`;`, "src/actions/nested", ], [ "unicode escaped dynamic app-action import", "src/app/ase-next/page.tsx", 'import("\\u002e\\u002e/actions/users")', "src/app/actions/users", ], [ "code-point escaped dynamic app-action import", "src/app/ase-next/page.tsx", 'import("\\u{2e}\\u{2e}/actions/users")', "src/app/actions/users", ], [ "hex escaped export-from auth import", "src/app/ase-next/page.tsx", 'export * from "\\x2e\\x2e/\\x2e\\x2e/lib/auth";', "src/lib/auth", ], [ "escaped-slash permissions import", "src/app/ase-next/page.tsx", 'import permissions from "..\\/..\\/lib\\/permissions";', "src/lib/permissions", ], [ "unknown escape database import", "src/app/ase-next/page.tsx", 'import db from "../../\\lib/db";', "src/lib/db", ], [ "line-continuation database import", "src/app/ase-next/page.tsx", 'import db from "../\\' + "\n" + '../lib/db";', "src/lib/db", ], [ "aliased database descendant import", "src/app/ase-next/page.tsx", 'import { query } from "@/lib/db/query";', "src/lib/db/query", ], [ "root relative database import", "src/app/ase-next/page.tsx", 'import { db } from "../../lib/db";', "src/lib/db", ], [ "domain relative auth side-effect import", "src/app/ase-next/[domain]/layout.tsx", 'import "../../../lib/auth";', "src/lib/auth", ], [ "domain relative permissions export", "src/app/ase-next/[domain]/[[...segments]]/page.tsx", 'export { getAdminContext } from "../../../../lib/permissions";', "src/lib/permissions", ], [ "root relative action dynamic import", "src/app/ase-next/page.tsx", 'import("../../actions/users")', "src/actions/users", ], [ "root relative app action export", "src/app/ase-next/page.tsx", 'export * from "../actions";', "src/app/actions", ], [ "domain relative legacy admin import", "src/app/ase-next/[domain]/layout.tsx", 'import page from "../../admin/users/page";', "src/app/admin/users/page", ], [ "root relative legacy mod dynamic import", "src/app/ase-next/layout.tsx", 'import("../mod/users/page")', "src/app/mod/users/page", ], [ "Prisma TypeScript import type", "src/app/ase-next/page.tsx", 'type PrismaClient = import("@prisma/client").PrismaClient;', "@prisma/client", ], [ "Drizzle package import", "src/app/ase-next/page.tsx", 'import { sql } from "drizzle-orm";', "drizzle-orm", ], [ "mysql2 package import", "src/app/ase-next/page.tsx", 'import type { Pool } from "mysql2";', "mysql2", ], ] as const)("detects %s", (_name, routeFile, source, expectedPath) => { expect(findRouteImportBoundaryViolations(source, routeFile)).toContain( expectedPath, ); }); it.each([ [ "optional CommonJS require", "const path = '../../lib/db'; require?.(path)", "", ], [ "malformed local percent escape", 'import db from "../../lib/db%ZZ";', "", ], [ "dynamic import", "const path = '../../actions/users'; import(path)", "", ], [ "CommonJS require", "const path = '../../lib/db'; require(path)", "", ], ] as const)("fails closed for non-literal %s", (_name, source, violation) => { expect( findRouteImportBoundaryViolations(source, "src/app/ase-next/page.tsx"), ).toContain(violation); }); it("does not reject substring lookalikes, comments, or ordinary strings", () => { const source = [ 'import database from "@/lib/database.js?raw";', 'import dbTools from "../../lib/db-tools.ts";', 'import auth from "../../lib/authentication";', 'import preview from "../ase-next-shared";', 'import prismaTools from "@prisma/client-tools";', 'import drizzleTools from "drizzle-orm-kit";', 'import mysqlTools from "mysql2-wrapper";', "const documentation = \"import db from '../../lib/db'\";", 'const rawTemplate = `import("../../actions/users")`;', '// import db from "../../lib/db";', ].join("\n"); expect( findRouteImportBoundaryViolations(source, "src/app/ase-next/page.tsx"), ).toEqual([]); }); });