/* 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"]; process.env["DISCORD_GUILD_ID"] = "guild123"; process.env["DISCORD_APOTHEOSIS_ROLE_ID"] = "role123"; const { grantApotheosisRole } = await import("../../src/services/webhook.js"); await grantApotheosisRole("user123"); expect(mockFetch).not.toHaveBeenCalled(); }); it("does nothing when guild id is missing", async () => { process.env["DISCORD_BOT_TOKEN"] = "token"; delete process.env["DISCORD_GUILD_ID"]; process.env["DISCORD_APOTHEOSIS_ROLE_ID"] = "role123"; const { grantApotheosisRole } = await import("../../src/services/webhook.js"); await grantApotheosisRole("user123"); expect(mockFetch).not.toHaveBeenCalled(); }); it("does nothing when role id is missing", async () => { process.env["DISCORD_BOT_TOKEN"] = "token"; process.env["DISCORD_GUILD_ID"] = "guild123"; delete process.env["DISCORD_APOTHEOSIS_ROLE_ID"]; 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 env vars are set", async () => { process.env["DISCORD_BOT_TOKEN"] = "bot_token"; process.env["DISCORD_GUILD_ID"] = "guild123"; process.env["DISCORD_APOTHEOSIS_ROLE_ID"] = "role456"; mockFetch.mockResolvedValueOnce({ ok: true }); const { grantApotheosisRole } = await import("../../src/services/webhook.js"); await grantApotheosisRole("user789"); expect(mockFetch).toHaveBeenCalledWith( "https://discord.com/api/v10/guilds/guild123/members/user789/roles/role456", expect.objectContaining({ method: "PUT", headers: expect.objectContaining({ Authorization: "Bot bot_token" }), }), ); }); it("swallows fetch errors gracefully", async () => { process.env["DISCORD_BOT_TOKEN"] = "tok"; process.env["DISCORD_GUILD_ID"] = "g"; process.env["DISCORD_APOTHEOSIS_ROLE_ID"] = "r"; 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"; process.env["DISCORD_GUILD_ID"] = "g"; process.env["DISCORD_APOTHEOSIS_ROLE_ID"] = "r"; 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"]; process.env["DISCORD_GUILD_ID"] = "guild123"; process.env["DISCORD_ELYSIAN_ROLE_ID"] = "role123"; const { grantElysianRole } = await import("../../src/services/webhook.js"); const result = await grantElysianRole("user123"); expect(mockFetch).not.toHaveBeenCalled(); expect(result).toBe(false); }); it("does nothing when guild id is missing", async () => { process.env["DISCORD_BOT_TOKEN"] = "token"; delete process.env["DISCORD_GUILD_ID"]; process.env["DISCORD_ELYSIAN_ROLE_ID"] = "role123"; 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"; process.env["DISCORD_GUILD_ID"] = "guild123"; 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/guild123/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"; process.env["DISCORD_GUILD_ID"] = "g"; 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"; process.env["DISCORD_GUILD_ID"] = "g"; 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"; process.env["DISCORD_GUILD_ID"] = "g"; 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"; process.env["DISCORD_GUILD_ID"] = "g"; 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(); }); }); });