import { readFileSync } from "node:fs"; import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it, vi } from "vitest"; import { executeHousekeepingCommand } from "@/actions/housekeeping-command"; import { PERMS } from "@/lib/permission-slugs"; vi.mock("@/actions/housekeeping-command", () => ({ executeHousekeepingCommand: vi.fn(), })); import { fail, type HousekeepingCapabilityContext, type HousekeepingDomainManifest, type HousekeepingResult, ok, } from "../../../foundation/contracts"; import { HOUSEKEEPING_MANIFESTS } from "../../../manifests"; import { HOUSEKEEPING_ROUTE_HANDLERS } from "../../../route-handlers"; import { peopleManifest } from "../manifest"; import type { PeopleCommunityQueryData } from "../queries/community"; import type { PeopleStaffQueryData } from "../queries/staff"; import type { PeopleUsersQueryData } from "../queries/users"; import { PEOPLE_PRIMARY_ROUTE_HANDLERS, PEOPLE_PRIMARY_ROUTE_IDS, } from "../route-handlers"; import { PEOPLE_ROUTE_IDS } from "../routes"; import { PeopleCommunityPage } from "./community"; import { PeopleMultiAccountsPage } from "./multi-accounts"; import { parsePeopleListInput } from "./page-state"; import { PeopleCommandResult, submitPeopleCommandForm, } from "./people-command-form"; import { PeopleStaffPage } from "./staff"; import { PeopleUserDetailPage } from "./user-detail"; import { PeopleUserEditPage } from "./user-edit"; import { PeopleUsersPage } from "./users"; const correlationId = "people-pages-test"; function capabilityContext( granted: readonly string[], ): HousekeepingCapabilityContext { const permissions = new Set(granted); return { actor: { id: 42, username: "operator", rank: 6 }, isSuperAdmin: false, has: (slug) => permissions.has(slug), hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)), hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)), }; } const readContext = capabilityContext([PERMS.USERS_VIEW]); const modReadContext = capabilityContext([PERMS.MOD_USERS_VIEW]); const user = { id: 7, username: "Alice", rank: 2, online: true, mail: "alice@example.test", ipCurrent: "198.51.100.7", bannedUntil: null, href: "/ase/people/users/7" as const, }; const detail = { ...user, motto: "Ready", look: "hd-180-1", accountCreated: "2026-08-01T00:00:00.000Z", lastLogin: "2026-08-28T00:00:00.000Z", sanctions: [], watched: false, permission: { rankName: "Member", ranks: [{ id: 2, name: "Member" }] }, }; type PageCase = { readonly name: string; readonly render: ( result?: HousekeepingResult, partialDependencies?: readonly string[], ) => string; readonly empty: unknown; readonly ready: unknown; }; const cases: readonly PageCase[] = [ { name: "users", render: (result) => renderToStaticMarkup( } />, ), empty: { kind: "users", page: { items: [], total: 0, pageSize: 20, offset: 0 }, }, ready: { kind: "users", page: { items: [user], total: 1, pageSize: 20, offset: 0 }, }, }, { name: "user-detail", render: (result) => renderToStaticMarkup( } />, ), empty: { kind: "user", user: { ...detail, sanctions: [] } }, ready: { kind: "user", user: { ...detail, sanctions: [ { id: 3, kind: "account", reason: "spam", createdAt: null, expiresAt: 0, active: true, }, ], }, }, }, { name: "user-edit", render: (result) => renderToStaticMarkup( } />, ), empty: fail("NOT_FOUND", "errors.housekeeping.notFound", correlationId), ready: { kind: "user", user: { ...detail, watched: true } }, }, { name: "multi-accounts", render: (result) => renderToStaticMarkup( } />, ), empty: { kind: "multi-accounts", page: { items: [], total: 0, pageSize: 20, offset: 0 }, }, ready: { kind: "multi-accounts", page: { items: [{ key: "198.51.100.7", accountCount: 2, accounts: [user] }], total: 1, pageSize: 20, offset: 0, }, }, }, { name: "community", render: (result) => renderToStaticMarkup( } />, ), empty: { kind: "guilds", page: { items: [], total: 0, pageSize: 20, offset: 0 }, }, ready: { kind: "guilds", page: { items: [ { id: 9, name: "Builders", description: "Rooms", userId: 7, ownerUsername: "Alice", roomId: 4, memberCount: 2, createdAt: null, href: "/ase/people/community/guilds/9", }, ], total: 1, pageSize: 20, offset: 0, }, }, }, { name: "staff", render: (result) => renderToStaticMarkup( } />, ), empty: { kind: "applications", page: { items: [], total: 0, pageSize: 20, offset: 0 }, }, ready: { kind: "applications", page: { items: [ { id: "5", userId: 7, username: "Alice", rankId: 4, content: "Experienced", createdAt: null, }, ], total: 1, pageSize: 20, offset: 0, }, }, }, ]; describe.each(cases)("People $name page", ({ render, empty, ready }) => { it("renders loading, forbidden, dependency error, empty, and ready states", () => { expect(render()).toContain('data-housekeeping-state="loading"'); expect( render(fail("FORBIDDEN", "errors.housekeeping.forbidden", correlationId)), ).toContain('data-housekeeping-state="forbidden"'); expect( render( fail( "DEPENDENCY_UNAVAILABLE", "errors.housekeeping.dependencyUnavailable", correlationId, ), ), ).toContain('data-housekeeping-state="error"'); const emptyResult = typeof empty === "object" && empty !== null && "ok" in empty ? (empty as HousekeepingResult) : ok(empty, correlationId); expect(render(emptyResult)).toContain('data-housekeeping-state="empty"'); expect(render(ok(ready, correlationId))).toContain( 'data-housekeeping-state="ready"', ); }); }); describe("People primary route registration", () => { it("keeps the nine primary handlers and registers the complete People catalog", () => { const expected = [ "people.users.list", "people.users.edit", "people.users.multi-accounts", "people.users.detail", "people.community.online", "people.community.guilds", "people.community.guild-detail", "people.staff.applications", "people.staff.teams", ]; expect(PEOPLE_PRIMARY_ROUTE_IDS).toEqual(expected); expect(peopleManifest.routes.map((route) => route.id)).toEqual( PEOPLE_ROUTE_IDS, ); expect( PEOPLE_PRIMARY_ROUTE_HANDLERS.map((handler) => handler.routeId), ).toEqual(expected); expect( HOUSEKEEPING_ROUTE_HANDLERS.map((handler) => handler.routeId), ).toEqual(expect.arrayContaining(expected)); expect( ( HOUSEKEEPING_MANIFESTS as readonly HousekeepingDomainManifest[] ).flatMap((manifest) => manifest.routes.map((route) => route.id)), ).toEqual(HOUSEKEEPING_ROUTE_HANDLERS.map((handler) => handler.routeId)); }); it("uses canonical People links and exposes PII and actions only with exact capabilities", () => { const redacted = { ...detail, mail: null, ipCurrent: null, }; const modHtml = renderToStaticMarkup( , ); expect(modHtml).not.toContain("alice@example.test"); expect(modHtml).not.toContain("198.51.100.7"); expect(modHtml).not.toContain("people.user.update"); const editorHtml = renderToStaticMarkup( , ); expect(editorHtml).toContain('href="/ase/people/users/7/edit"'); expect(editorHtml).toContain(", ); expect(editHtml).toContain("people.user.update"); expect(editHtml).toContain(" { it("bounds and normalizes search, page, pageSize, sort, and direction", () => { expect( parsePeopleListInput({ search: [" Alice ", "ignored"], page: "999999999999", pageSize: "5000", sort: "username", direction: "desc", }), ).toEqual({ search: "Alice", pageSize: 100, offset: 100000, sort: "username", order: "desc", }); }); }); describe("People actionable form contract", () => { it("submits bounded command input and reason through the actual form action adapter", async () => { vi.mocked(executeHousekeepingCommand).mockResolvedValue( ok({ before: null, after: { amount: 100 } }, "form-action"), ); const formData = new FormData(); formData.set("amount", "999"); formData.set("reason", " Planned grant "); const result = await submitPeopleCommandForm( { commandId: "people.user.send-currency", input: { userId: 7 }, fields: [ { name: "amount", label: "Amount", type: "number", min: 1, max: 100, }, ], requiresReason: true, includeReasonInInput: true, }, null, formData, ); expect(executeHousekeepingCommand).toHaveBeenCalledWith({ commandId: "people.user.send-currency", input: { userId: 7, amount: 100, reason: "Planned grant" }, reason: "Planned grant", }); expect(result).toMatchObject({ ok: true }); }); it("renders a generated reset credential once in the current accessible result", () => { const password = "one-time-secret"; const html = renderToStaticMarkup( , ); expect(html).toContain('aria-label="Generated temporary password"'); expect(html.match(new RegExp(password, "gu"))).toHaveLength(1); }); it("renders typed external sync debt with an explicit do-not-retry warning", () => { const completion = { status: "partial", external: "failed", audit: "persisted", } as const; const html = renderToStaticMarkup( , ); expect(html).toContain('role="alert"'); expect(html).toContain("Do not retry automatically."); expect(html).toContain('aria-label="External synchronization debt"'); expect(html).toContain("User #7"); expect(html).toContain("Database applied; emulator sync failed."); expect(html).not.toContain("unsafe payload from an untyped result"); expect(html).not.toContain("sensitive internal payload"); }); it("keeps form-returned external sync debt visible in the operator result surface", async () => { const completion = { status: "partial", external: "failed", audit: "persisted", } as const; vi.mocked(executeHousekeepingCommand).mockResolvedValue( ok( { before: { completed: 0, total: 1, failedIds: [] }, after: { completed: 1, total: 1, failedIds: [], externalSyncFailures: [ { userId: 23, reason: "Database applied; emulator sync failed. Do not retry automatically.", }, ], }, completion, }, "form-sync-debt", completion, ), ); const formData = new FormData(); formData.set("userIds", "23"); formData.set("amount", "100"); formData.set("type", "credits"); formData.set("reason", "Planned grant"); const result = await submitPeopleCommandForm( { commandId: "people.users.bulk-currency", input: {}, fields: [ { name: "userIds", label: "User IDs", type: "number-list" }, { name: "amount", label: "Amount", type: "number", min: 1 }, { name: "type", label: "Currency", type: "select", options: [{ value: "credits", label: "Credits" }], }, ], requiresReason: true, }, null, formData, ); const html = renderToStaticMarkup(); expect(executeHousekeepingCommand).toHaveBeenCalledWith({ commandId: "people.users.bulk-currency", input: { userIds: [23], amount: 100, type: "credits" }, reason: "Planned grant", }); expect(html).toContain("Do not retry automatically."); expect(html).toContain("User #23"); }); it("provides a real Next loading boundary", () => { const source = readFileSync( "src/app/ase-next/[domain]/[[...segments]]/loading.tsx", "utf8", ); expect(source).toContain('data-housekeeping-state="loading"'); expect(source).toContain('state="loading"'); }); });