AkurAI Build
Menu

Public repository / c3ef99b092caa55fb6a3eed9d3e776734cc99e97

AkurAI-Mail / src/lib/api.test.ts

Latest change 22202d1bde69be4ace8a299dffd91f8ff24378df - Recover deployed webmail frontend (frontend parity with EC2 a6afffc) by Ólafur Búi Ólafsson

import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { api, apiPost, loginUrl } 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 sends the browser to the IdP login with the current path as return_to and never settles", async () => {
    stubFetch(401);
    location.pathname = "/domains";
    const outcome = await Promise.race([api("/api/domains").then(() => "settled"), new Promise((resolve) => setTimeout(() => resolve("pending"), 20))]);
    expect(outcome).toBe("pending");
    expect(location.href).toBe("/auth/login?return_to=%2Fdomains");
  });

  test("401 on a webmail page also uses /auth/login", async () => {
    stubFetch(401);
    location.pathname = "/webmail";
    void api("/api/webmail");
    await new Promise((resolve) => setTimeout(resolve, 0));
    expect(location.href).toBe("/auth/login?return_to=%2Fwebmail");
  });

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

describe("loginUrl", () => {
  test("encodes the return path", () => {
    expect(loginUrl("/webmail?x=1")).toBe("/auth/login?return_to=%2Fwebmail%3Fx%3D1");
  });
});