import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; // ── DB mock for findUniqueUsername tests ────────────────────────── const dbMock = vi.hoisted(() => { let idx = 0; let results: unknown[][] = []; return { reset(r: unknown[][]) { idx = 0; results = r; }, nextResult() { return results[idx++] ?? []; }, }; }); vi.mock("../../../apps/api/src/db/index.js", () => ({ db: { select: () => ({ from: () => ({ where: () => Promise.resolve(dbMock.nextResult()), }), }), }, schema: { users: { username: "username" }, }, })); // ── Pure function tests (no DB required) ───────────────────────── describe("external auth resolver", () => { it("module exports resolveExternalUser", async () => { const mod = await import("../../../apps/api/src/lib/external-auth-resolver.js"); expect(typeof mod.resolveExternalUser).toBe("function"); }); it("module exports sanitizeUsername", async () => { const mod = await import("../../../apps/api/src/lib/external-auth-resolver.js"); expect(typeof mod.sanitizeUsername).toBe("function"); }); it("module exports findUniqueUsername", async () => { const mod = await import("../../../apps/api/src/lib/external-auth-resolver.js"); expect(typeof mod.findUniqueUsername).toBe("function"); }); }); describe("sanitizeUsername", () => { let sanitizeUsername: (raw: string) => string; beforeAll(async () => { const mod = await import("../../../apps/api/src/lib/external-auth-resolver.js"); sanitizeUsername = mod.sanitizeUsername; }); it("lowercases input", () => { expect(sanitizeUsername("JohnDoe")).toBe("johndoe"); }); it("replaces non-alphanumeric characters with underscores", () => { expect(sanitizeUsername("john doe!")).toBe("john_doe"); }); it("collapses multiple underscores", () => { expect(sanitizeUsername("john___doe")).toBe("john_doe"); }); it("strips leading and trailing separators", () => { expect(sanitizeUsername("_john_")).toBe("john"); expect(sanitizeUsername(".john.")).toBe("john"); expect(sanitizeUsername("-john-")).toBe("john"); }); it("truncates to 46 characters", () => { const long = "a".repeat(60); expect(sanitizeUsername(long).length).toBe(46); }); it("pads short usernames to 3 characters", () => { expect(sanitizeUsername("ab").length).toBe(3); expect(sanitizeUsername("ab")).toBe("ab_"); }); it("preserves dots and hyphens", () => { expect(sanitizeUsername("john.doe")).toBe("john.doe"); expect(sanitizeUsername("john-doe")).toBe("john-doe"); }); it("handles email addresses as input", () => { expect(sanitizeUsername("user@example.com")).toBe("user_example.com"); }); it("handles empty-after-strip edge case", () => { // All characters stripped, then padded const result = sanitizeUsername("___"); expect(result.length).toBeGreaterThanOrEqual(3); }); it("handles pure whitespace input", () => { const result = sanitizeUsername(" "); expect(result).toBe("___"); }); it("handles numeric-only input", () => { expect(sanitizeUsername("12345")).toBe("12345"); }); it("handles single character input by padding to 3", () => { const result = sanitizeUsername("a"); expect(result).toBe("a__"); expect(result.length).toBe(3); }); it("does not truncate input that is exactly 46 characters", () => { const input = "a".repeat(46); const result = sanitizeUsername(input); expect(result).toBe(input); expect(result.length).toBe(46); }); it("truncates input that is exactly 47 characters to 46", () => { const input = "a".repeat(47); const result = sanitizeUsername(input); expect(result.length).toBe(46); }); it("preserves consecutive dots and hyphens", () => { expect(sanitizeUsername("john..--..doe")).toBe("john..--..doe"); }); it("replaces unicode characters with underscores", () => { expect(sanitizeUsername("café")).toBe("caf"); expect(sanitizeUsername("jöhn")).toBe("j_hn"); }); it("strips leading @ from input", () => { expect(sanitizeUsername("@user")).toBe("user"); }); it("passes through admin as a valid username", () => { expect(sanitizeUsername("admin")).toBe("admin"); }); it("allows input that starts with numbers", () => { expect(sanitizeUsername("123abc")).toBe("123abc"); expect(sanitizeUsername("42user")).toBe("42user"); }); }); describe("findUniqueUsername", () => { let findUniqueUsername: (base: string) => Promise; beforeAll(async () => { const mod = await import("../../../apps/api/src/lib/external-auth-resolver.js"); findUniqueUsername = mod.findUniqueUsername; }); beforeEach(() => { dbMock.reset([]); }); it("returns the base username when it is not taken", async () => { dbMock.reset([[]]); const result = await findUniqueUsername("newuser"); expect(result).toBe("newuser"); }); it("appends _2 when the base username is taken", async () => { dbMock.reset([[{ username: "taken" }], []]); const result = await findUniqueUsername("taken"); expect(result).toBe("taken_2"); }); it("appends _3 when both base and _2 are taken", async () => { dbMock.reset([[{ username: "taken" }], [{ username: "taken_2" }], []]); const result = await findUniqueUsername("taken"); expect(result).toBe("taken_3"); }); it("resolves collisions through the loop until a free slot is found", async () => { dbMock.reset([ [{ username: "user" }], [{ username: "user_2" }], [{ username: "user_3" }], [{ username: "user_4" }], [], ]); const result = await findUniqueUsername("user"); expect(result).toBe("user_5"); }); });