import { describe, expect, it } from "vitest"; import { assertPeopleSerializable, createPeoplePage, normalizePeopleListInput, normalizePeopleUser, type PeopleUserDetail, peopleCfhHref, peopleGuildHref, peopleHelpTicketHref, peopleTicketHref, peopleUserHref, toPeopleIsoDate, } from "./models"; describe("People list normalization", () => { it("clamps adversarial page size and offset values and bounds search text", () => { expect( normalizePeopleListInput( { pageSize: 99_999, offset: -50, search: ` alice\u0000${"x".repeat(300)} `, sort: "not-a-column", order: "sideways", }, ["id", "username"], "username", ), ).toEqual({ pageSize: 100, offset: 0, search: `alicex${"x".repeat(122)}`, sort: "username", order: "asc", }); expect( normalizePeopleListInput( { pageSize: Number.NaN, offset: Number.POSITIVE_INFINITY }, ["id"], "id", ), ).toMatchObject({ pageSize: 20, offset: 0 }); expect( normalizePeopleListInput({ offset: 999_999 }, ["id"], "id").offset, ).toBe(10_000); }); it("wraps rows already sorted and paged by the adapter", () => { const input = normalizePeopleListInput( { pageSize: 2, offset: 1, sort: "username", order: "asc" }, ["id", "username"], "id", ); const page = createPeoplePage( [ { id: 3, username: "alice" }, { id: 1, username: "Alice" }, ], 4, input, ); expect(page).toEqual({ items: [ { id: 3, username: "alice" }, { id: 1, username: "Alice" }, ], total: 4, pageSize: 2, offset: 1, }); }); }); describe("People canonical models", () => { const rawUser = { id: 7n, username: "Alice", rank: "5", online: "1", mail: "alice@example.test", ipCurrent: "203.0.113.9", bannedUntil: "1700000000", }; it("projects mail and current IP independently and never substitutes a redaction", () => { expect( normalizePeopleUser(rawUser, { includeMail: false, includeIp: false }), ).toEqual({ id: 7, username: "Alice", rank: 5, online: true, mail: null, ipCurrent: null, bannedUntil: 1_700_000_000, href: "/ase/people/users/7", }); expect( normalizePeopleUser(rawUser, { includeMail: true, includeIp: false }), ).toMatchObject({ mail: "alice@example.test", ipCurrent: null }); expect( normalizePeopleUser(rawUser, { includeMail: false, includeIp: true }), ).toMatchObject({ mail: null, ipCurrent: "203.0.113.9" }); }); it("normalizes BigInt and Date values to JSON-safe DTO primitives", () => { const detail: PeopleUserDetail = { ...normalizePeopleUser(rawUser, { includeMail: true, includeIp: true, }), motto: "Hello", look: "hd-180-1", accountCreated: toPeopleIsoDate(new Date("2026-08-29T10:20:30.000Z")), lastLogin: toPeopleIsoDate(1_700_000_000), sanctions: [], watched: false, permission: { rankName: "Moderator", ranks: [{ id: 5, name: "Moderator" }], }, }; expect(JSON.parse(JSON.stringify(detail))).toEqual({ id: 7, username: "Alice", rank: 5, online: true, mail: "alice@example.test", ipCurrent: "203.0.113.9", bannedUntil: 1_700_000_000, href: "/ase/people/users/7", motto: "Hello", look: "hd-180-1", accountCreated: "2026-08-29T10:20:30.000Z", lastLogin: "2023-11-14T22:13:20.000Z", sanctions: [], watched: false, permission: { rankName: "Moderator", ranks: [{ id: 5, name: "Moderator" }], }, }); }); it("builds canonical People entity links only", () => { expect([ peopleUserHref(4), peopleGuildHref(5), peopleTicketHref(6), peopleHelpTicketHref(7), peopleCfhHref(8), ]).toEqual([ "/ase/people/users/4", "/ase/people/community/guilds/5", "/ase/people/support/tickets/6", "/ase/people/support/help-tickets/7", "/ase/people/moderation/cfh/8", ]); }); it("rejects corrupt identifiers and non-serializable date values", () => { expect(() => normalizePeopleUser( { ...rawUser, id: 0 }, { includeMail: false, includeIp: false }, ), ).toThrow(); expect(() => peopleUserHref(Number.MAX_SAFE_INTEGER + 1)).toThrow(); expect(() => peopleGuildHref(-4)).toThrow(); expect(() => toPeopleIsoDate("not-a-date")).toThrow(); expect(() => assertPeopleSerializable({ id: 7, userId: 0, href: "/ase/people/users/7", }), ).toThrow(); }); });