/* eslint-disable max-lines-per-function -- Test suites naturally have many cases */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; describe("discord service", () => { const ORIGINAL_ENV = process.env; const mockFetch = vi.fn(); beforeEach(() => { process.env = { ...ORIGINAL_ENV }; vi.resetModules(); vi.stubGlobal("fetch", mockFetch); }); afterEach(() => { process.env = ORIGINAL_ENV; vi.unstubAllGlobals(); mockFetch.mockReset(); }); describe("buildOAuthUrl", () => { it("throws when DISCORD_CLIENT_ID is missing", async () => { delete process.env["DISCORD_CLIENT_ID"]; process.env["DISCORD_REDIRECT_URI"] = "http://localhost/callback"; const { buildOAuthUrl } = await import("../../src/services/discord.js"); expect(() => buildOAuthUrl()).toThrow("Discord OAuth environment variables are required"); }); it("throws when DISCORD_REDIRECT_URI is missing", async () => { process.env["DISCORD_CLIENT_ID"] = "client123"; delete process.env["DISCORD_REDIRECT_URI"]; const { buildOAuthUrl } = await import("../../src/services/discord.js"); expect(() => buildOAuthUrl()).toThrow("Discord OAuth environment variables are required"); }); it("returns a URL with correct query params", async () => { process.env["DISCORD_CLIENT_ID"] = "client123"; process.env["DISCORD_REDIRECT_URI"] = "http://localhost/callback"; const { buildOAuthUrl } = await import("../../src/services/discord.js"); const url = buildOAuthUrl(); expect(url).toContain("client_id=client123"); expect(url).toContain("response_type=code"); expect(url).toContain("scope=identify"); }); }); describe("exchangeCode", () => { it("throws when env vars are missing", async () => { delete process.env["DISCORD_CLIENT_ID"]; const { exchangeCode } = await import("../../src/services/discord.js"); await expect(exchangeCode("mycode")).rejects.toThrow("Discord OAuth environment variables are required"); }); it("throws when response is not ok", async () => { process.env["DISCORD_CLIENT_ID"] = "cid"; process.env["DISCORD_CLIENT_SECRET"] = "secret"; process.env["DISCORD_REDIRECT_URI"] = "http://localhost/cb"; mockFetch.mockResolvedValueOnce({ ok: false, statusText: "Unauthorized" }); const { exchangeCode } = await import("../../src/services/discord.js"); await expect(exchangeCode("bad_code")).rejects.toThrow("Discord token exchange failed"); }); it("returns parsed body on success", async () => { process.env["DISCORD_CLIENT_ID"] = "cid"; process.env["DISCORD_CLIENT_SECRET"] = "secret"; process.env["DISCORD_REDIRECT_URI"] = "http://localhost/cb"; const tokenData = { access_token: "tok", token_type: "Bearer", expires_in: 3600, refresh_token: "ref", scope: "identify" }; mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(tokenData) }); const { exchangeCode } = await import("../../src/services/discord.js"); const result = await exchangeCode("good_code"); expect(result.access_token).toBe("tok"); }); }); describe("fetchDiscordUser", () => { it("throws when response is not ok", async () => { mockFetch.mockResolvedValueOnce({ ok: false, statusText: "Forbidden" }); const { fetchDiscordUser } = await import("../../src/services/discord.js"); await expect(fetchDiscordUser("bad_token")).rejects.toThrow("Discord user fetch failed"); }); it("returns parsed user on success", async () => { const user = { id: "123", username: "testuser", discriminator: "0", avatar: null }; mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(user) }); const { fetchDiscordUser } = await import("../../src/services/discord.js"); const result = await fetchDiscordUser("valid_token"); expect(result.id).toBe("123"); expect(result.username).toBe("testuser"); }); }); });