Menu
Public repository / 7a284f40b2637792d0599c38c4cac0342da676d7
AkurAI-Mail / src/lib/api.test.ts
Latest change 1c4949c9fc865519b48f48b69196534c6a80e8b4 - OLI-13: add test script + api client tests; bump nanoid/postcss via audit fix by Claude Code
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { api, apiPost } from "./api";
// api.ts is browser code: it talks to the global fetch and redirects via window.location on 401.
// Both globals are replaced with recording fakes for the duration of each test.
type Call = { path: string; init: RequestInit | undefined };
const realFetch = globalThis.fetch;
const realWindow = (globalThis as { window?: unknown }).window;
let calls: Call[];
let location: { pathname: string; href: string };
function stubFetch(status: number, body: unknown = {}) {
globalThis.fetch = (async (path: string | URL | Request, init?: RequestInit) => {
calls.push({ path: String(path), init });
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
}) as typeof fetch;
}
beforeEach(() => {
calls = [];
location = { pathname: "/", href: "" };
(globalThis as { window?: unknown }).window = { location };
});
afterEach(() => {
globalThis.fetch = realFetch;
(globalThis as { window?: unknown }).window = realWindow;
});
describe("api", () => {
test("sends same-origin credentials and returns the parsed JSON body", async () => {
stubFetch(200, { domains: ["example.com"] });
const result = await api("/api/domains");
expect(result).toEqual({ domains: ["example.com"] });
expect(calls).toHaveLength(1);
expect(calls[0]!.path).toBe("/api/domains");
expect(calls[0]!.init).toMatchObject({ credentials: "same-origin" });
});
test("caller options are merged over the defaults", async () => {
stubFetch(200);
await api("/api/x", { method: "DELETE", credentials: "include" });
expect(calls[0]!.init).toMatchObject({ method: "DELETE", credentials: "include" });
});
test("401 on an admin page redirects to /login with the current path as next", async () => {
stubFetch(401);
location.pathname = "/domains";
await expect(api("/api/domains")).rejects.toThrow("authentication required");
expect(location.href).toBe("/login?next=%2Fdomains");
});
test("401 on a webmail page redirects to /webmail/login", async () => {
stubFetch(401);
location.pathname = "/webmail/inbox?x=1";
await expect(api("/api/messages")).rejects.toThrow("authentication required");
expect(location.href).toBe(`/webmail/login?next=${encodeURIComponent("/webmail/inbox?x=1")}`);
});
test("non-401 errors are not treated as an auth failure", async () => {
stubFetch(500, { error: "boom" });
expect(await api("/api/x")).toEqual({ error: "boom" });
expect(location.href).toBe("");
});
});
describe("apiPost", () => {
test("POSTs the body as JSON with a JSON content type", async () => {
stubFetch(200, { ok: true });
const result = await apiPost("/api/mailboxes", { address: "a@example.com", quota: 5 });
expect(result).toEqual({ ok: true });
const init = calls[0]!.init!;
expect(init.method).toBe("POST");
expect(init.headers).toEqual({ "content-type": "application/json" });
expect(JSON.parse(init.body as string)).toEqual({ address: "a@example.com", quota: 5 });
});
});