import { describe, expect, it, vi } from "vitest"; import { PERMS } from "@/lib/permission-slugs"; import type { HousekeepingCapabilityContext } from "../../../foundation/contracts"; import { PEOPLE_ROUTES } from "../routes"; import { createPeopleCommunityQuery } from "./community"; import { createPeopleModerationQuery } from "./moderation"; import { createPeopleStaffQuery } from "./staff"; import { createPeopleSupportQuery } from "./support"; import { createPeopleUsersQuery } from "./users"; function context(granted: readonly string[]): HousekeepingCapabilityContext { const permissions = new Set(granted); return { actor: { id: 42, username: "operator", rank: 99 }, isSuperAdmin: false, has: (slug) => permissions.has(slug), hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)), hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)), }; } const rawUser = (id: number, username: string) => ({ id, username, rank: 5, online: "1", mail: `${username.toLowerCase()}@example.test`, ipCurrent: `203.0.113.${id}`, bannedUntil: null, }); describe("People users query", () => { it("includes watched state and permission-rank data required by detail and edit", async () => { const query = createPeopleUsersQuery({ loadUsers: async () => ({ rows: [], total: 0 }), loadUser: async () => ({ ...rawUser(9, "Nine"), motto: "", look: "hd-180-1", accountCreated: 1_700_000_000, lastLogin: 1_700_000_100, }), loadMultiAccounts: async () => ({ rows: [], total: 0 }), loadSanctions: async () => [], loadWatchedUserIds: async () => new Set([9]), loadPermissionRanks: async () => [ { id: 1, name: "User" }, { id: 5, name: "Moderator" }, ], } as never); const result = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.users.detail", id: 9, }); expect(result).toMatchObject({ ok: true, data: { user: { watched: true, permission: { rankName: "Moderator", ranks: [ { id: 1, name: "User" }, { id: 5, name: "Moderator" }, ], }, }, }, }); }); it("preserves a DB-paged username window without locale re-sort or offset slicing", async () => { const query = createPeopleUsersQuery({ loadUsers: async () => ({ rows: [rawUser(10, "user10"), rawUser(2, "user2")], total: 20, }), loadUser: async () => null, loadMultiAccounts: async () => ({ rows: [], total: 0 }), loadSanctions: async () => [], loadWatchedUserIds: async () => new Set(), loadPermissionRanks: async () => [{ id: 5, name: "Moderator" }], }); const result = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.users.list", list: { sort: "username", order: "asc", offset: 2, pageSize: 2 }, }); expect(result).toMatchObject({ ok: true, data: { kind: "users", page: { items: [{ id: 10 }, { id: 2 }], offset: 2, pageSize: 2 }, }, }); }); it("normalizes list input and projects PII for admin users view", async () => { const loadUsers = vi.fn(async () => ({ rows: [rawUser(1, "Alice"), rawUser(2, "alice"), rawUser(3, "alice")], total: 3, })); const query = createPeopleUsersQuery({ loadUsers, loadUser: async () => null, loadMultiAccounts: async () => ({ rows: [], total: 0 }), loadSanctions: async () => [], loadWatchedUserIds: async () => new Set(), loadPermissionRanks: async () => [{ id: 5, name: "Moderator" }], }); const result = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.users.list", list: { pageSize: 5_000, offset: -9, search: ` ali\u0000${"x".repeat(200)} `, sort: "username", order: "asc", }, }); expect(loadUsers).toHaveBeenCalledWith( expect.objectContaining({ pageSize: 100, offset: 0, search: `alix${"x".repeat(124)}`, sort: "username", }), { includeMail: true, includeIp: true }, ); expect(result).toMatchObject({ ok: true, data: { kind: "users", page: { items: [ { id: 1, mail: "alice@example.test", ipCurrent: "203.0.113.1", href: "/ase/people/users/1", }, { id: 2 }, { id: 3 }, ], total: 3, pageSize: 100, offset: 0, }, }, }); }); it("returns a base-only projection to mod users and fails closed for neither permission", async () => { const loadUsers = vi.fn(async () => ({ rows: [rawUser(1, "Alice")], total: 1, })); const query = createPeopleUsersQuery({ loadUsers, loadUser: async () => null, loadMultiAccounts: async () => ({ rows: [], total: 0 }), loadSanctions: async () => [], loadWatchedUserIds: async () => new Set(), loadPermissionRanks: async () => [{ id: 5, name: "Moderator" }], }); const moderator = await query.run(context([PERMS.MOD_USERS_VIEW]), { routeId: "people.users.list", list: {}, }); expect(loadUsers).toHaveBeenLastCalledWith(expect.anything(), { includeMail: false, includeIp: false, }); expect(moderator).toMatchObject({ ok: true, data: { page: { items: [{ mail: null, ipCurrent: null }] }, }, }); loadUsers.mockClear(); const forbidden = await query.run(context([]), { routeId: "people.users.list", list: {}, }); expect(forbidden).toMatchObject({ ok: false, error: { code: "FORBIDDEN" }, }); expect(loadUsers).not.toHaveBeenCalled(); }); it("maps missing users, invalid ids, and adapter failures to stable errors", async () => { const loadUser = vi.fn(async () => null); const query = createPeopleUsersQuery({ loadUsers: async () => { throw new Error("count unavailable"); }, loadUser, loadMultiAccounts: async () => ({ rows: [], total: 0 }), loadSanctions: async () => [], loadWatchedUserIds: async () => new Set(), loadPermissionRanks: async () => [{ id: 5, name: "Moderator" }], }); const invalid = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.users.detail", id: -1, }); expect(invalid).toMatchObject({ ok: false, error: { code: "VALIDATION", fieldErrors: { id: ["invalid"] } }, }); expect(loadUser).not.toHaveBeenCalled(); const missing = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.users.detail", id: 404, }); expect(missing).toMatchObject({ ok: false, error: { code: "NOT_FOUND" }, }); const unavailable = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.users.list", list: {}, }); expect(unavailable).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE" }, }); }); it("returns JSON-safe user details with sanctions and canonical links", async () => { const query = createPeopleUsersQuery({ loadUsers: async () => ({ rows: [], total: 0 }), loadUser: async () => ({ ...rawUser(9, "Nine"), motto: "Hello", look: "hd-180-1", accountCreated: 1_700_000_000, lastLogin: new Date("2026-08-29T09:00:00.000Z"), }), loadMultiAccounts: async () => ({ rows: [], total: 0 }), loadSanctions: async () => [ { id: 8, kind: "account", reason: "test", createdAt: "2026-08-20T00:00:00.000Z", expiresAt: null, active: true, }, ], loadWatchedUserIds: async () => new Set(), loadPermissionRanks: async () => [{ id: 5, name: "Moderator" }], }); const result = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.users.detail", id: 9, }); expect(result).toMatchObject({ ok: true, data: { kind: "user", user: { id: 9, href: "/ase/people/users/9", accountCreated: "2023-11-14T22:13:20.000Z", lastLogin: "2026-08-29T09:00:00.000Z", sanctions: [{ id: 8 }], }, }, }); expect(() => JSON.stringify(result)).not.toThrow(); }); }); describe("People community and staff queries", () => { it("accepts only the guild user and room zero sentinels", async () => { const guild = { id: 12, name: "Unassigned guild", description: "", userId: 0, ownerUsername: null, roomId: 0, memberCount: 0, threadCount: 0, createdAt: null, href: "/ase/people/community/guilds/12" as const, members: [], }; const query = createPeopleCommunityQuery({ loadOnline: async () => ({ rows: [], total: 0 }), loadGuilds: async () => ({ rows: [], total: 0 }), loadGuild: async () => guild as never, }); expect( await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.community.guild-detail", id: 12, }), ).toMatchObject({ ok: true, data: { guild: { id: 12, userId: 0, roomId: 0 } }, }); const invalidQuery = createPeopleCommunityQuery({ loadOnline: async () => ({ rows: [], total: 0 }), loadGuilds: async () => ({ rows: [], total: 0 }), loadGuild: async () => ({ ...guild, userId: -1 }) as never, }); expect( await invalidQuery.run(context([PERMS.USERS_VIEW]), { routeId: "people.community.guild-detail", id: 12, }), ).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE" }, }); }); it("fails closed when a community adapter returns a corrupt entity id", async () => { const query = createPeopleCommunityQuery({ loadOnline: async () => ({ rows: [], total: 0 }), loadGuilds: async () => ({ rows: [], total: 0 }), loadGuild: async () => ({ id: 0, name: "Corrupt", description: "", userId: -1, ownerUsername: null, memberCount: 0, createdAt: null, href: "/ase/people/community/guilds/0", roomId: 0, threadCount: 0, members: [], }) as never, }); const result = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.community.guild-detail", id: 1, }); expect(result).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE" }, }); }); it("loads online, guild list, and guild detail workflows through narrow adapters", async () => { const loadOnline = vi.fn(async () => ({ rows: [], total: 0 })); const loadGuilds = vi.fn(async () => ({ rows: [ { id: 2, name: "Builders", description: "Build", userId: 4, ownerUsername: "Owner", roomId: 7, memberCount: 3, createdAt: "2026-08-01T00:00:00.000Z", href: "/ase/people/community/guilds/2" as const, }, ], total: 1, })); const loadGuild = vi.fn(async (id: number) => id === 2 ? { id: 2, name: "Builders", description: "Build", userId: 4, ownerUsername: "Owner", memberCount: 3, createdAt: "2026-08-01T00:00:00.000Z", href: "/ase/people/community/guilds/2" as const, roomId: 7, threadCount: 1, members: [], } : null, ); const query = createPeopleCommunityQuery({ loadOnline, loadGuilds, loadGuild, }); expect( await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.community.online", list: {}, }), ).toMatchObject({ ok: true, data: { kind: "online" } }); expect( await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.community.guilds", list: {}, }), ).toMatchObject({ ok: true, data: { kind: "guilds", page: { items: [{ id: 2 }] } }, }); expect( await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.community.guild-detail", id: 404, }), ).toMatchObject({ ok: false, error: { code: "NOT_FOUND" } }); }); it("loads applications, teams, and moderation-team data with serializable DTOs", async () => { const query = createPeopleStaffQuery({ loadApplications: async () => ({ rows: [ { id: 11, userId: 5, username: "Applicant", rankId: 3, content: "Why me", createdAt: "2026-08-29T00:00:00.000Z", }, ], total: 1, }), loadTeams: async () => ({ rows: [], total: 0 }), loadModerationTeam: async () => ({ rows: [], total: 0 }), }); const applications = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.staff.applications", list: {}, }); const teams = await query.run(context([PERMS.USERS_VIEW]), { routeId: "people.staff.teams", list: {}, }); const moderationTeam = await query.run(context([PERMS.MOD_TEAM_VIEW]), { routeId: "people.staff.moderation-team", list: {}, }); expect(applications).toMatchObject({ ok: true, data: { kind: "applications", page: { items: [{ id: 11 }] } }, }); expect(teams).toMatchObject({ ok: true, data: { kind: "teams" } }); expect(moderationTeam).toMatchObject({ ok: true, data: { kind: "moderation-team" }, }); expect(() => JSON.stringify(applications)).not.toThrow(); }); }); describe("People support query", () => { it("routes the ticket desk to its CMS-only context loader", async () => { const loadTickets = vi.fn(async () => ({ rows: [ { source: "help" as const, id: 12, subject: "Help row", status: "open", priority: "normal", creatorId: 7, creatorUsername: "Seven", updatedAt: null, href: "/ase/people/support/help-tickets/12" as const, }, ], total: 1, })); const loadTicketDesk = vi.fn(async () => ({ rows: [ { source: "cms" as const, id: 91, subject: "Urgent CMS ticket", status: "open", priority: "urgent", creatorId: 7, creatorUsername: "Seven", updatedAt: null, href: "/ase/people/support/tickets/91" as const, category: "safety", assigneeId: 8, messageCount: 4, }, { source: "cms" as const, id: 92, subject: "High-priority CMS ticket", status: "open", priority: "high", creatorId: 9, creatorUsername: "Nine", updatedAt: null, href: "/ase/people/support/tickets/92" as const, category: "account", assigneeId: null, messageCount: 2, }, ], total: 2, })); const query = createPeopleSupportQuery({ loadQueue: async () => ({ tickets: 1, helpTickets: 1, cfh: 0, activeBans: 0, }), loadTickets, loadTicketDesk, loadTicket: async () => null, loadTemplates: async () => ({ rows: [], total: 0 }), loadHelpTickets: async () => ({ rows: [], total: 0 }), loadHelpTicket: async () => null, loadSupportStaff: async () => [], loadActiveBan: async () => null, } as never); const result = await query.run(context([PERMS.TICKETS_VIEW]), { routeId: "people.support.ticket-desk", list: {}, }); expect(result).toMatchObject({ ok: true, data: { page: { items: [ { id: 91, priority: "urgent", category: "safety", assigneeId: 8, messageCount: 4, }, { id: 92, priority: "high", category: "account", assigneeId: null, messageCount: 2, }, ], }, }, }); expect(loadTicketDesk).toHaveBeenCalledOnce(); expect(loadTickets).not.toHaveBeenCalled(); }); it("hydrates desk/detail support context and the help-ticket active ban", async () => { const queue = { tickets: 2, helpTickets: 1, cfh: 3, activeBans: 4 }; const staff = [ { id: 8, username: "Helper", rank: 5, href: "/ase/people/users/8" as const, }, ]; const activeBan = { id: 31, userId: 7, username: "Seven", staffId: 8, staffUsername: "Helper", type: "account", reason: "permanent", createdAt: "2026-08-01T00:00:00.000Z", expiresAt: 0 as const, active: true, }; const query = createPeopleSupportQuery({ loadQueue: async () => queue, loadTickets: async () => ({ rows: [], total: 0 }), loadTicketDesk: async () => ({ rows: [], total: 0 }), loadTicket: async () => ({ id: 11, subject: "CMS ticket", status: "open", priority: "normal", creatorId: 7, creatorUsername: "Seven", updatedAt: "2026-08-20T00:00:00.000Z", replyCount: 0, href: "/ase/people/support/tickets/11", category: "general", assigneeId: null, messages: [], }), loadTemplates: async () => ({ rows: [], total: 0 }), loadHelpTickets: async () => ({ rows: [], total: 0 }), loadHelpTicket: async () => ({ id: 12, title: "Help ticket", open: true, userId: 7, username: "Seven", updatedAt: "2026-08-20T00:00:00.000Z", href: "/ase/people/support/help-tickets/12", categoryId: 2, categoryName: "Help", content: "Body", replies: [], }), loadSupportStaff: async () => staff, loadActiveBan: async () => activeBan, } as never); const desk = await query.run(context([PERMS.TICKETS_VIEW]), { routeId: "people.support.ticket-desk", list: {}, }); const detail = await query.run(context([PERMS.TICKETS_VIEW]), { routeId: "people.support.ticket-detail", id: 11, }); const help = await query.run(context([PERMS.TICKETS_VIEW]), { routeId: "people.support.help-ticket-detail", id: 12, }); expect(desk).toMatchObject({ ok: true, data: { kind: "ticket-desk", queue, staff }, }); expect(detail).toMatchObject({ ok: true, data: { ticket: { queue, staff } }, }); expect(help).toMatchObject({ ok: true, data: { ticket: { queue, staff, activeBan } }, }); }); it("fails closed when support returns a corrupt detail shape", async () => { const query = createPeopleSupportQuery({ loadQueue: async () => ({ tickets: 0, helpTickets: 0, cfh: 0, activeBans: 0, }), loadTickets: async () => ({ rows: [], total: 0 }), loadTicketDesk: async () => ({ rows: [], total: 0 }), loadTicket: async () => ({ id: 0, href: "/ase/people/support/tickets/0" }) as never, loadTemplates: async () => ({ rows: [], total: 0 }), loadHelpTickets: async () => ({ rows: [], total: 0 }), loadHelpTicket: async () => null, loadSupportStaff: async () => [], loadActiveBan: async () => null, }); const result = await query.run(context([PERMS.TICKETS_VIEW]), { routeId: "people.support.ticket-detail", id: 1, }); expect(result).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE" }, }); }); it.each([ ["status", [7, 3]], ["updatedAt", [8, 4]], ] as const)( "preserves the DB-paged ticket window for %s ordering", async (sort, ids) => { const rows = ids.map((id, index) => ({ source: "cms" as const, id, subject: `Ticket ${id}`, status: index === 0 ? "open" : "pending", priority: "normal", creatorId: 1, creatorUsername: "Reporter", updatedAt: `2026-08-${20 - index}T00:00:00.000Z`, href: `/ase/people/support/tickets/${id}` as const, })); const query = createPeopleSupportQuery({ loadQueue: async () => ({ tickets: 0, helpTickets: 0, cfh: 0, activeBans: 0, }), loadTickets: async () => ({ rows, total: 40 }), loadTicketDesk: async () => ({ rows: rows.map((row) => ({ ...row, category: "general", assigneeId: null, messageCount: 0, })), total: 40, }), loadTicket: async () => null, loadTemplates: async () => ({ rows: [], total: 0 }), loadHelpTickets: async () => ({ rows: [], total: 0 }), loadHelpTicket: async () => null, loadSupportStaff: async () => [], loadActiveBan: async () => null, }); const result = await query.run(context([PERMS.TICKETS_VIEW]), { routeId: "people.support.ticket-desk", list: { sort, order: "desc", offset: 10, pageSize: 2 }, }); expect(result).toMatchObject({ ok: true, data: { page: { items: ids.map((id) => ({ id })), offset: 10 } }, }); }, ); it("returns the four-source queue snapshot and fails closed when a count fails", async () => { const loadQueue = vi .fn() .mockResolvedValueOnce({ tickets: 2, helpTickets: 3, cfh: 4, activeBans: 5, }) .mockRejectedValueOnce(new Error("count failed")); const query = createPeopleSupportQuery({ loadQueue, loadTickets: async () => ({ rows: [], total: 0 }), loadTicketDesk: async () => ({ rows: [], total: 0 }), loadTicket: async () => null, loadTemplates: async () => ({ rows: [], total: 0 }), loadHelpTickets: async () => ({ rows: [], total: 0 }), loadHelpTicket: async () => null, loadSupportStaff: async () => [], loadActiveBan: async () => null, }); expect( await query.run(context([PERMS.TICKETS_VIEW]), { routeId: "people.support.queue", }), ).toMatchObject({ ok: true, data: { kind: "queue", queue: { tickets: 2, helpTickets: 3, cfh: 4, activeBans: 5 }, }, }); expect( await query.run(context([PERMS.TICKETS_VIEW]), { routeId: "people.support.queue", }), ).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE" }, }); }); it("covers ticket desk/templates/detail and help-ticket list/detail reads", async () => { const query = createPeopleSupportQuery({ loadQueue: async () => ({ tickets: 0, helpTickets: 0, cfh: 0, activeBans: 0, }), loadTickets: async () => ({ rows: [], total: 0 }), loadTicketDesk: async () => ({ rows: [], total: 0 }), loadTicket: async () => null, loadTemplates: async () => ({ rows: [], total: 0 }), loadHelpTickets: async () => ({ rows: [], total: 0 }), loadHelpTicket: async () => null, loadSupportStaff: async () => [], loadActiveBan: async () => null, }); for (const routeId of [ "people.support.tickets", "people.support.ticket-desk", "people.support.help-tickets", ] as const) { expect( await query.run(context([PERMS.TICKETS_VIEW, PERMS.MOD_TICKETS_VIEW]), { routeId, list: {}, }), ).toMatchObject({ ok: true }); } expect( await query.run(context([PERMS.TICKETS_EDIT]), { routeId: "people.support.ticket-templates", list: {}, }), ).toMatchObject({ ok: true, data: { kind: "ticket-templates" } }); for (const input of [ { routeId: "people.support.ticket-detail" as const, id: 91 }, { routeId: "people.support.help-ticket-detail" as const, id: 92 }, ]) { expect( await query.run(context([PERMS.TICKETS_VIEW]), input), ).toMatchObject({ ok: false, error: { code: "NOT_FOUND" } }); } }); }); describe("People moderation query", () => { it("accepts only the CFH participant and room zero sentinels", async () => { const ticket = { id: 41, state: 0, senderId: 0, senderUsername: null, reportedId: 0, reportedUsername: null, moderatorId: 0, issue: "unassigned", createdAt: null, href: "/ase/people/moderation/cfh/41" as const, roomId: 0, activeBan: null, }; const adapters = { loadOverview: async () => ({ tickets: 0, helpTickets: 0, cfh: 0, activeBans: 0, staffOnline: 0, recentActions: 0, }), loadCfh: async () => ({ rows: [], total: 0 }), loadCfhDetail: async () => ticket, loadBans: async () => ({ rows: [], total: 0 }), loadIpRules: async () => ({ blacklist: [], whitelist: [] }), loadVpnSettings: async () => [], loadWordFilter: async () => ({ rows: [], total: 0 }), }; const query = createPeopleModerationQuery(adapters); expect( await query.run(context([PERMS.MOD_CFH_VIEW]), { routeId: "people.moderation.cfh-detail", id: 41, }), ).toMatchObject({ ok: true, data: { ticket: { id: 41, senderId: 0, reportedId: 0, moderatorId: 0, roomId: 0, }, }, }); const invalidQuery = createPeopleModerationQuery({ ...adapters, loadCfhDetail: async () => ({ ...ticket, reportedId: -1 }), }); expect( await invalidQuery.run(context([PERMS.MOD_CFH_VIEW]), { routeId: "people.moderation.cfh-detail", id: 41, }), ).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE" }, }); }); it("declares exactly the same eleven-permission union as the moderation overview route", () => { const query = createPeopleModerationQuery({ loadOverview: async () => ({ tickets: 0, helpTickets: 0, cfh: 0, activeBans: 0, staffOnline: 0, recentActions: 0, }), loadCfh: async () => ({ rows: [], total: 0 }), loadCfhDetail: async () => null, loadBans: async () => ({ rows: [], total: 0 }), loadIpRules: async () => ({ blacklist: [], whitelist: [] }), loadVpnSettings: async () => [], loadWordFilter: async () => ({ rows: [], total: 0 }), }); const route = PEOPLE_ROUTES.find( (candidate) => candidate.id === "people.moderation.overview", ); expect(route).toBeDefined(); expect(query.capability).toEqual(route?.capability); expect(query.capability.slugs).toHaveLength(11); }); it("fails closed when moderation returns a corrupt detail shape", async () => { const query = createPeopleModerationQuery({ loadOverview: async () => ({ tickets: 0, helpTickets: 0, cfh: 0, activeBans: 0, staffOnline: 0, recentActions: 0, }), loadCfh: async () => ({ rows: [], total: 0 }), loadCfhDetail: async () => ({ id: -1, href: "/ase/people/moderation/cfh/-1" }) as never, loadBans: async () => ({ rows: [], total: 0 }), loadIpRules: async () => ({ blacklist: [], whitelist: [] }), loadVpnSettings: async () => [], loadWordFilter: async () => ({ rows: [], total: 0 }), }); const result = await query.run(context([PERMS.MOD_CFH_VIEW]), { routeId: "people.moderation.cfh-detail", id: 1, }); expect(result).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE" }, }); }); it("covers overview, CFH, bans, IP, VPN, and word-filter read workflows", async () => { const query = createPeopleModerationQuery({ loadOverview: async () => ({ tickets: 1, helpTickets: 2, cfh: 3, activeBans: 4, staffOnline: 5, recentActions: 6, }), loadCfh: async () => ({ rows: [], total: 0 }), loadCfhDetail: async () => null, loadBans: async () => ({ rows: [], total: 0 }), loadIpRules: async () => ({ blacklist: [], whitelist: [] }), loadVpnSettings: async () => [], loadWordFilter: async () => ({ rows: [], total: 0 }), }); expect( await query.run(context([PERMS.MODERATION_VIEW]), { routeId: "people.moderation.overview", }), ).toMatchObject({ ok: true, data: { kind: "overview", snapshot: { cfh: 3, activeBans: 4 } }, }); expect( await query.run(context([PERMS.MOD_CFH_VIEW]), { routeId: "people.moderation.cfh", list: {}, }), ).toMatchObject({ ok: true, data: { kind: "cfh" } }); expect( await query.run(context([PERMS.MOD_CFH_VIEW]), { routeId: "people.moderation.cfh-detail", id: 77, }), ).toMatchObject({ ok: false, error: { code: "NOT_FOUND" } }); expect( await query.run(context([PERMS.MOD_BANS_VIEW]), { routeId: "people.moderation.bans", list: {}, }), ).toMatchObject({ ok: true, data: { kind: "bans" } }); for (const input of [ { routeId: "people.moderation.ip" as const }, { routeId: "people.moderation.vpn" as const }, ]) { expect( await query.run(context([PERMS.SETTINGS_VIEW]), input), ).toMatchObject({ ok: true }); } expect( await query.run(context([PERMS.WORDFILTER_VIEW]), { routeId: "people.moderation.word-filter", list: {}, }), ).toMatchObject({ ok: true, data: { kind: "word-filter" } }); }); it("maps adapter exceptions to dependency unavailable without partial data", async () => { const down = async () => { throw new Error("database unavailable"); }; const query = createPeopleModerationQuery({ loadOverview: down, loadCfh: down, loadCfhDetail: down, loadBans: down, loadIpRules: down, loadVpnSettings: down, loadWordFilter: down, }); expect( await query.run(context([PERMS.MOD_BANS_VIEW]), { routeId: "people.moderation.bans", list: {}, }), ).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE", messageKey: "errors.housekeeping.dependencyUnavailable", }, }); }); });