generated from nhcarrigan/template
d48b53eecd
- Add full test suite for frontend.ts (POST /log and POST /error) - Add error-path tests to all route handlers to cover catch blocks triggered by Prisma rejections - Add non-Error throw tests to cover the `new Error(String(error))` ternary false branch in middleware, services, and route catch handlers - Suppress unreachable outer catch in about.ts with v8 ignore (fetchReleases swallows all errors internally, making the outer catch genuinely dead code)
108 lines
4.8 KiB
TypeScript
108 lines
4.8 KiB
TypeScript
/* 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");
|
|
});
|
|
|
|
it("re-throws when fetch rejects with a non-Error value", async () => {
|
|
mockFetch.mockRejectedValueOnce("raw string error");
|
|
const { fetchDiscordUser } = await import("../../src/services/discord.js");
|
|
await expect(fetchDiscordUser("some_token")).rejects.toBe("raw string error");
|
|
});
|
|
});
|
|
|
|
describe("exchangeCode non-Error throw", () => {
|
|
it("re-throws when fetch rejects with a non-Error value", async () => {
|
|
process.env["DISCORD_CLIENT_ID"] = "cid";
|
|
process.env["DISCORD_CLIENT_SECRET"] = "secret";
|
|
process.env["DISCORD_REDIRECT_URI"] = "http://localhost/cb";
|
|
mockFetch.mockRejectedValueOnce("raw string error");
|
|
const { exchangeCode } = await import("../../src/services/discord.js");
|
|
await expect(exchangeCode("some_code")).rejects.toBe("raw string error");
|
|
});
|
|
});
|
|
});
|