/* eslint-disable max-lines-per-function -- Test suites naturally have many cases */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; describe("webhook 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("grantApotheosisRole", () => { it("does nothing when bot token is missing", async () => { delete process.env["DISCORD_BOT_TOKEN"]; const { grantApotheosisRole } = await import("../../src/services/webhook.js"); await grantApotheosisRole("user123"); expect(mockFetch).not.toHaveBeenCalled(); }); it("calls Discord API with correct URL and auth when bot token is set", async () => { process.env["DISCORD_BOT_TOKEN"] = "bot_token"; mockFetch.mockResolvedValueOnce({ ok: true }); const { grantApotheosisRole } = await import("../../src/services/webhook.js"); await grantApotheosisRole("user789"); expect(mockFetch).toHaveBeenCalledWith( "https://discord.com/api/v10/guilds/1354624415861833870/members/user789/roles/1479966598210129991", expect.objectContaining({ method: "PUT", headers: expect.objectContaining({ Authorization: "Bot bot_token" }), }), ); }); it("swallows fetch errors gracefully", async () => { process.env["DISCORD_BOT_TOKEN"] = "tok"; mockFetch.mockRejectedValueOnce(new Error("Network error")); const { grantApotheosisRole } = await import("../../src/services/webhook.js"); await expect(grantApotheosisRole("user")).resolves.toBeUndefined(); }); it("swallows non-Error fetch rejections gracefully", async () => { process.env["DISCORD_BOT_TOKEN"] = "tok"; mockFetch.mockRejectedValueOnce("raw string error"); const { grantApotheosisRole } = await import("../../src/services/webhook.js"); await expect(grantApotheosisRole("user")).resolves.toBeUndefined(); }); }); describe("grantElysianRole", () => { it("does nothing when bot token is missing", async () => { delete process.env["DISCORD_BOT_TOKEN"]; const { grantElysianRole } = await import("../../src/services/webhook.js"); const result = await grantElysianRole("user123"); expect(mockFetch).not.toHaveBeenCalled(); expect(result).toBe(false); }); it("returns true when Discord API responds with ok", async () => { process.env["DISCORD_BOT_TOKEN"] = "bot_token"; mockFetch.mockResolvedValueOnce({ ok: true, status: 200 }); const { grantElysianRole } = await import("../../src/services/webhook.js"); const result = await grantElysianRole("user789"); expect(mockFetch).toHaveBeenCalledWith( "https://discord.com/api/v10/guilds/1354624415861833870/members/user789/roles/1486144823684628490", expect.objectContaining({ method: "PUT", headers: expect.objectContaining({ Authorization: "Bot bot_token" }), }), ); expect(result).toBe(true); }); it("returns true when Discord API responds with 204", async () => { process.env["DISCORD_BOT_TOKEN"] = "tok"; mockFetch.mockResolvedValueOnce({ ok: false, status: 204 }); const { grantElysianRole } = await import("../../src/services/webhook.js"); const result = await grantElysianRole("user"); expect(result).toBe(true); }); it("returns false when Discord API responds with an error status", async () => { process.env["DISCORD_BOT_TOKEN"] = "tok"; mockFetch.mockResolvedValueOnce({ ok: false, status: 403 }); const { grantElysianRole } = await import("../../src/services/webhook.js"); const result = await grantElysianRole("user"); expect(result).toBe(false); }); it("returns false and swallows fetch errors gracefully", async () => { process.env["DISCORD_BOT_TOKEN"] = "tok"; mockFetch.mockRejectedValueOnce(new Error("Network error")); const { grantElysianRole } = await import("../../src/services/webhook.js"); const result = await grantElysianRole("user"); expect(result).toBe(false); }); it("returns false and swallows non-Error fetch rejections", async () => { process.env["DISCORD_BOT_TOKEN"] = "tok"; mockFetch.mockRejectedValueOnce("raw string error"); const { grantElysianRole } = await import("../../src/services/webhook.js"); const result = await grantElysianRole("user"); expect(result).toBe(false); }); }); describe("postMilestoneWebhook", () => { const counts = { prestige: 1, transcendence: 0, apotheosis: 0 }; it("does nothing when webhook URL is missing", async () => { delete process.env["DISCORD_MILESTONE_WEBHOOK"]; const { postMilestoneWebhook } = await import("../../src/services/webhook.js"); await postMilestoneWebhook("user123", "prestige", counts); expect(mockFetch).not.toHaveBeenCalled(); }); it("posts prestige message with correct body", async () => { process.env["DISCORD_MILESTONE_WEBHOOK"] = "https://discord.com/webhook/abc"; mockFetch.mockResolvedValueOnce({ ok: true }); const { postMilestoneWebhook } = await import("../../src/services/webhook.js"); await postMilestoneWebhook("user123", "prestige", counts); const [url, options] = mockFetch.mock.calls[0] as [string, RequestInit]; expect(url).toBe("https://discord.com/webhook/abc"); const body = JSON.parse(options.body as string) as { content: string; flags: number }; expect(body.content).toContain("<@user123>"); expect(body.content).toContain("prestiged"); expect(body.flags).toBe(4096); }); it("posts transcendence message correctly", async () => { process.env["DISCORD_MILESTONE_WEBHOOK"] = "https://discord.com/webhook/abc"; mockFetch.mockResolvedValueOnce({ ok: true }); const { postMilestoneWebhook } = await import("../../src/services/webhook.js"); await postMilestoneWebhook("user123", "transcendence", { prestige: 0, transcendence: 1, apotheosis: 0 }); const [, options] = mockFetch.mock.calls[0] as [string, RequestInit]; const body = JSON.parse(options.body as string) as { content: string }; expect(body.content).toContain("transcended"); }); it("posts apotheosis message correctly", async () => { process.env["DISCORD_MILESTONE_WEBHOOK"] = "https://discord.com/webhook/abc"; mockFetch.mockResolvedValueOnce({ ok: true }); const { postMilestoneWebhook } = await import("../../src/services/webhook.js"); await postMilestoneWebhook("user123", "apotheosis", { prestige: 0, transcendence: 0, apotheosis: 1 }); const [, options] = mockFetch.mock.calls[0] as [string, RequestInit]; const body = JSON.parse(options.body as string) as { content: string }; expect(body.content).toContain("reached apotheosis"); }); it("swallows fetch errors gracefully", async () => { process.env["DISCORD_MILESTONE_WEBHOOK"] = "https://discord.com/webhook/abc"; mockFetch.mockRejectedValueOnce(new Error("Network timeout")); const { postMilestoneWebhook } = await import("../../src/services/webhook.js"); await expect(postMilestoneWebhook("user", "prestige", counts)).resolves.toBeUndefined(); }); it("swallows non-Error fetch rejections gracefully", async () => { process.env["DISCORD_MILESTONE_WEBHOOK"] = "https://discord.com/webhook/abc"; mockFetch.mockRejectedValueOnce("raw string error"); const { postMilestoneWebhook } = await import("../../src/services/webhook.js"); await expect(postMilestoneWebhook("user", "prestige", counts)).resolves.toBeUndefined(); }); }); });