feat(housekeeping): deliver people account workflows

This commit is contained in:
Simo committed 2026-08-29 11:45:50 +02:00
1 parent 7920d4f46c
commit e1b31ff773
45 files changed
+4290 -1243

No files matched your search

@@ -0,0 +1,316 @@
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import {
fail,
type HousekeepingCapabilityContext,
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 { PeopleCommunityPage } from "./community";
import { PeopleMultiAccountsPage } from "./multi-accounts";
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: "[email protected]",
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<unknown>,
partialDependencies?: readonly string[],
) => string;
readonly empty: unknown;
readonly ready: unknown;
};
const cases: readonly PageCase[] = [
{
name: "users",
render: (result, partialDependencies) =>
renderToStaticMarkup(
<PeopleUsersPage
context={readContext}
result={result as HousekeepingResult<PeopleUsersQueryData>}
partialDependencies={partialDependencies}
/>,
),
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, partialDependencies) =>
renderToStaticMarkup(
<PeopleUserDetailPage
context={readContext}
result={result as HousekeepingResult<PeopleUsersQueryData>}
partialDependencies={partialDependencies}
/>,
),
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, partialDependencies) =>
renderToStaticMarkup(
<PeopleUserEditPage
context={capabilityContext([PERMS.USERS_EDIT, PERMS.USERS_VIEW])}
result={result as HousekeepingResult<PeopleUsersQueryData>}
partialDependencies={partialDependencies}
/>,
),
empty: fail("NOT_FOUND", "errors.housekeeping.notFound", correlationId),
ready: { kind: "user", user: { ...detail, watched: true } },
},
{
name: "multi-accounts",
render: (result, partialDependencies) =>
renderToStaticMarkup(
<PeopleMultiAccountsPage
context={readContext}
result={result as HousekeepingResult<PeopleUsersQueryData>}
partialDependencies={partialDependencies}
/>,
),
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, partialDependencies) =>
renderToStaticMarkup(
<PeopleCommunityPage
context={readContext}
result={result as HousekeepingResult<PeopleCommunityQueryData>}
partialDependencies={partialDependencies}
/>,
),
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, partialDependencies) =>
renderToStaticMarkup(
<PeopleStaffPage
context={readContext}
result={result as HousekeepingResult<PeopleStaffQueryData>}
partialDependencies={partialDependencies}
/>,
),
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, partial, 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<unknown>)
: ok(empty, correlationId);
expect(render(emptyResult)).toContain('data-housekeeping-state="empty"');
expect(render(ok(ready, correlationId), ["secondary"])).toContain(
'data-housekeeping-state="partial"',
);
expect(render(ok(ready, correlationId))).toContain(
'data-housekeeping-state="ready"',
);
});
});
describe("People primary route registration", () => {
it("registers exactly the nine real primary routes and handlers", () => {
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(expected);
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.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(
<PeopleUserDetailPage
context={modReadContext}
result={ok({ kind: "user", user: redacted }, correlationId)}
/>,
);
expect(modHtml).not.toContain("[email protected]");
expect(modHtml).not.toContain("198.51.100.7");
expect(modHtml).not.toContain("people.user.update");
const editorHtml = renderToStaticMarkup(
<PeopleUserDetailPage
context={capabilityContext([PERMS.USERS_VIEW, PERMS.USERS_EDIT])}
result={ok({ kind: "user", user: detail }, correlationId)}
/>,
);
expect(editorHtml).toContain('href="/ase/people/users/7/edit"');
expect(editorHtml).toContain("people.user.update");
expect(editorHtml).not.toContain("/admin");
});
});