diff --git a/src/utils/twitch-server.ts b/src/utils/twitch-server.ts index 5fb5577..fde4c18 100644 --- a/src/utils/twitch-server.ts +++ b/src/utils/twitch-server.ts @@ -11,20 +11,23 @@ export const generateTwitchRequestHeaders = (accessToken: string) => { }; export const getTwitchUserId = async (userName: string, token: string) => { - const res = await fetch( - `https://api.twitch.tv/helix/users?login=${userName}`, + const response = await fetch( + `https://api.twitch.tv/helix/users?${new URLSearchParams({ + login: userName, + })}`, { method: "GET", headers: generateTwitchRequestHeaders(token), - next: { revalidate: Infinity }, // These should never change + // Twitch logins can change or be reassigned to another account. + next: { revalidate: 60 }, } - ).then((response) => response.json()); - if (res.error === "Unauthorized") throw new Error("Unauthorized"); - - const responseId = (res as any)?.data[0]?.id as string; - if (!responseId) return null; + ); + if (!response.ok) { + throw new Error(`Could not look up Twitch user: ${response.status}`); + } - return responseId; + const result = (await response.json()) as { data?: { id: string }[] }; + return result.data?.[0]?.id ?? null; }; export type VOD = { diff --git a/src/utils/twitch-user.test.ts b/src/utils/twitch-user.test.ts new file mode 100644 index 0000000..036e7a3 --- /dev/null +++ b/src/utils/twitch-user.test.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { getTwitchUserId } from "./twitch-server"; + +vi.mock("@clerk/nextjs/server", () => ({ clerkClient: vi.fn() })); + +afterEach(() => vi.unstubAllGlobals()); + +describe("getTwitchUserId", () => { + it("encodes the login and returns the matching ID", async () => { + const fetchMock = vi + .fn() + .mockResolvedValue( + new Response(JSON.stringify({ data: [{ id: "123" }] }), { status: 200 }) + ); + vi.stubGlobal("fetch", fetchMock); + + await expect(getTwitchUserId("name&other", "app-token")).resolves.toBe( + "123" + ); + expect(fetchMock.mock.calls[0]![0]).toBe( + "https://api.twitch.tv/helix/users?login=name%26other" + ); + }); + + it("returns null when Twitch has no account for the login", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue(new Response(JSON.stringify({ data: [] }))) + ); + + await expect(getTwitchUserId("missing", "app-token")).resolves.toBeNull(); + }); + + it("throws a clear error when the Twitch request fails", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue(new Response(null, { status: 401 })) + ); + + await expect(getTwitchUserId("creator", "bad-token")).rejects.toThrow( + "Could not look up Twitch user: 401" + ); + }); +});