| import { describe, expect, it } from "vitest"; |
| import { withFetchPreconnect } from "../test-utils/fetch-mock.js"; |
| import { resolveDiscordUserAllowlist } from "./resolve-users.js"; |
| import { jsonResponse, urlToString } from "./test-http-helpers.js"; |
|
|
| function createGuildListProbeFetcher() { |
| let guildsCalled = false; |
| const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => { |
| const url = urlToString(input); |
| if (url.endsWith("/users/@me/guilds")) { |
| guildsCalled = true; |
| return jsonResponse([]); |
| } |
| return new Response("not found", { status: 404 }); |
| }); |
| return { |
| fetcher, |
| wasGuildsCalled: () => guildsCalled, |
| }; |
| } |
|
|
| function createGuildsForbiddenFetcher() { |
| return withFetchPreconnect(async (input: RequestInfo | URL) => { |
| const url = urlToString(input); |
| if (url.endsWith("/users/@me/guilds")) { |
| throw new Error("Forbidden: Missing Access"); |
| } |
| return new Response("not found", { status: 404 }); |
| }); |
| } |
|
|
| describe("resolveDiscordUserAllowlist", () => { |
| it("resolves plain user ids without calling listGuilds", async () => { |
| const { fetcher, wasGuildsCalled } = createGuildListProbeFetcher(); |
|
|
| const results = await resolveDiscordUserAllowlist({ |
| token: "test", |
| entries: ["123456789012345678"], |
| fetcher, |
| }); |
|
|
| expect(results).toEqual([ |
| { |
| input: "123456789012345678", |
| resolved: true, |
| id: "123456789012345678", |
| }, |
| ]); |
| expect(wasGuildsCalled()).toBe(false); |
| }); |
|
|
| it("resolves mention-format ids without calling listGuilds", async () => { |
| const { fetcher, wasGuildsCalled } = createGuildListProbeFetcher(); |
|
|
| const results = await resolveDiscordUserAllowlist({ |
| token: "test", |
| entries: ["<@!123456789012345678>"], |
| fetcher, |
| }); |
|
|
| expect(results).toEqual([ |
| { |
| input: "<@!123456789012345678>", |
| resolved: true, |
| id: "123456789012345678", |
| }, |
| ]); |
| expect(wasGuildsCalled()).toBe(false); |
| }); |
|
|
| it("resolves prefixed ids (user:, discord:) without calling listGuilds", async () => { |
| const { fetcher, wasGuildsCalled } = createGuildListProbeFetcher(); |
|
|
| const results = await resolveDiscordUserAllowlist({ |
| token: "test", |
| entries: ["user:111", "discord:222"], |
| fetcher, |
| }); |
|
|
| expect(results).toHaveLength(2); |
| expect(results[0]).toMatchObject({ resolved: true, id: "111" }); |
| expect(results[1]).toMatchObject({ resolved: true, id: "222" }); |
| expect(wasGuildsCalled()).toBe(false); |
| }); |
|
|
| it("resolves user ids even when listGuilds would fail", async () => { |
| const fetcher = createGuildsForbiddenFetcher(); |
|
|
| |
| const results = await resolveDiscordUserAllowlist({ |
| token: "test", |
| entries: ["994979735488692324"], |
| fetcher, |
| }); |
|
|
| expect(results).toEqual([ |
| { |
| input: "994979735488692324", |
| resolved: true, |
| id: "994979735488692324", |
| }, |
| ]); |
| }); |
|
|
| it("calls listGuilds lazily when resolving usernames", async () => { |
| let guildsCalled = false; |
| const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => { |
| const url = urlToString(input); |
| if (url.endsWith("/users/@me/guilds")) { |
| guildsCalled = true; |
| return jsonResponse([{ id: "g1", name: "Test Guild" }]); |
| } |
| if (url.includes("/guilds/g1/members/search")) { |
| return jsonResponse([ |
| { |
| user: { id: "u1", username: "alice", bot: false }, |
| nick: null, |
| }, |
| ]); |
| } |
| return new Response("not found", { status: 404 }); |
| }); |
|
|
| const results = await resolveDiscordUserAllowlist({ |
| token: "test", |
| entries: ["alice"], |
| fetcher, |
| }); |
|
|
| expect(guildsCalled).toBe(true); |
| expect(results).toHaveLength(1); |
| expect(results[0]).toMatchObject({ |
| input: "alice", |
| resolved: true, |
| id: "u1", |
| name: "alice", |
| }); |
| }); |
|
|
| it("fetches guilds only once for multiple username entries", async () => { |
| let guildsCallCount = 0; |
| const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => { |
| const url = urlToString(input); |
| if (url.endsWith("/users/@me/guilds")) { |
| guildsCallCount++; |
| return jsonResponse([{ id: "g1", name: "Test Guild" }]); |
| } |
| if (url.includes("/guilds/g1/members/search")) { |
| const params = new URL(url).searchParams; |
| const query = params.get("query") ?? ""; |
| return jsonResponse([ |
| { |
| user: { id: `u-${query}`, username: query, bot: false }, |
| nick: null, |
| }, |
| ]); |
| } |
| return new Response("not found", { status: 404 }); |
| }); |
|
|
| const results = await resolveDiscordUserAllowlist({ |
| token: "test", |
| entries: ["alice", "bob"], |
| fetcher, |
| }); |
|
|
| expect(guildsCallCount).toBe(1); |
| expect(results).toHaveLength(2); |
| expect(results[0]).toMatchObject({ resolved: true, id: "u-alice" }); |
| expect(results[1]).toMatchObject({ resolved: true, id: "u-bob" }); |
| }); |
|
|
| it("handles mixed ids and usernames — ids resolve even if guilds fail", async () => { |
| const fetcher = createGuildsForbiddenFetcher(); |
|
|
| |
| await expect( |
| resolveDiscordUserAllowlist({ |
| token: "test", |
| entries: ["123456789012345678", "alice"], |
| fetcher, |
| }), |
| ).rejects.toThrow("Forbidden"); |
|
|
| |
| const results = await resolveDiscordUserAllowlist({ |
| token: "test", |
| entries: ["123456789012345678", "<@999>"], |
| fetcher, |
| }); |
|
|
| expect(results).toHaveLength(2); |
| expect(results[0]).toMatchObject({ resolved: true, id: "123456789012345678" }); |
| expect(results[1]).toMatchObject({ resolved: true, id: "999" }); |
| }); |
|
|
| it("returns unresolved for empty/blank entries", async () => { |
| const fetcher = withFetchPreconnect(async () => { |
| return new Response("not found", { status: 404 }); |
| }); |
|
|
| const results = await resolveDiscordUserAllowlist({ |
| token: "test", |
| entries: ["", " "], |
| fetcher, |
| }); |
|
|
| expect(results).toHaveLength(2); |
| expect(results[0]).toMatchObject({ resolved: false }); |
| expect(results[1]).toMatchObject({ resolved: false }); |
| }); |
|
|
| it("returns all unresolved when token is empty", async () => { |
| const results = await resolveDiscordUserAllowlist({ |
| token: "", |
| entries: ["123456789012345678", "alice"], |
| }); |
|
|
| expect(results).toHaveLength(2); |
| expect(results.every((r) => !r.resolved)).toBe(true); |
| }); |
| }); |
|
|