AkurAI Build
Menu

Public repository / ce5f5ce299b8d0bd0440b215bf3ba322a862942a

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 });
  });
});