/** * TASK-0128 — the API-key UI logic (pure functions, no DOM): * - scope implication: selecting ADMIN derives WRITE+READ; the STORED * set is the minimal one (the acceptance criterion "stored set is * minimal"); * - the curl example generator (correct Authorization header, masked * prefix, the `` placeholder, never the raw key); * - relative-time / expiry-state formatting (the red < 7 d window, * "never", "expired"). */ import { describe, expect, it } from "vitest"; import { SCOPE_IMPLIES, minimalScopes, grants, curlExample, expiryState, keyStatus, relativeTime, shortDate, expiryBounds, } from "../api-keys"; const T0 = Date.parse("2026-01-15T12:00:00Z"); describe("scope implication (the hierarchy)", () => { it("ADMIN implies WRITE and READ; WRITE implies READ; READ implies only itself", () => { expect(SCOPE_IMPLIES.ADMIN).toEqual(["ADMIN", "WRITE", "READ"]); expect(SCOPE_IMPLIES.WRITE).toEqual(["WRITE", "READ"]); expect(SCOPE_IMPLIES.READ).toEqual(["READ"]); }); it("the stored set is MINIMAL — implied scopes are dropped", () => { // The acceptance case: check all three → store only ADMIN. expect(minimalScopes(["ADMIN", "WRITE", "READ"])).toEqual(["ADMIN"]); expect(minimalScopes(["WRITE", "READ"])).toEqual(["WRITE"]); expect(minimalScopes(["READ"])).toEqual(["READ"]); // Order-independent. expect(minimalScopes(["READ", "ADMIN"])).toEqual(["ADMIN"]); }); it("grants() resolves through the chain", () => { expect(grants(["ADMIN"], "READ")).toBe(true); expect(grants(["ADMIN"], "WRITE")).toBe(true); expect(grants(["WRITE"], "READ")).toBe(true); expect(grants(["READ"], "WRITE")).toBe(false); expect(grants([], "READ")).toBe(false); }); }); describe("the curl example generator", () => { const PREFIX = "fk_live_ab12cd34"; it("carries the Bearer placeholder (masked prefix, never a raw key)", () => { const ex = curlExample(PREFIX, "GET", "http://localhost:4000/api/v1/pages/p1"); expect(ex).toContain(`Authorization: Bearer ${PREFIX}…(your key)`); expect(ex).toContain("http://localhost:4000/api/v1/pages/p1"); expect(ex).toContain("curl -X GET"); }); it("adds Content-Type + -d only for a body", () => { const noBody = curlExample(PREFIX, "GET", "http://x/api/v1/pages"); expect(noBody).not.toContain("-d"); expect(noBody).not.toContain("Content-Type"); const body = curlExample( PREFIX, "POST", "http://x/api/v1/workspaces/w1/pages", '{"title":"Hi"}', ); expect(body).toContain("Content-Type: application/json"); expect(body).toContain(`-d '{"title":"Hi"}'`); expect(body).toContain("curl -X POST"); }); it("is syntactically a real command line", () => { const ex = curlExample(PREFIX, "PATCH", "http://x/api/v1/pages/p1", '{"title":"x"}'); // Every continuation line ends with a backslash except the last. const lines = ex.split("\n"); expect(lines.at(-1)).toBe(" http://x/api/v1/pages/p1"); for (let i = 0; i < lines.length - 1; i++) { expect(lines[i]?.endsWith("\\"), `line ${i} must continue`).toBe(true); } }); }); describe("expiry state (the red < 7 d window)", () => { it("no expiry → never", () => { expect(expiryState(null, T0)).toBe("never"); expect(expiryState(undefined, T0)).toBe("never"); }); it("expired when in the past", () => { expect(expiryState("2026-01-01T00:00:00Z", T0)).toBe("expired"); }); it("soon (red) when ≤ 7 days away", () => { expect(expiryState("2026-01-21T12:00:00Z", T0)).toBe("soon"); // 6 days expect(expiryState("2026-01-16T12:00:00Z", T0)).toBe("soon"); // 1 day expect(expiryState("2026-01-22T11:59:00Z", T0)).toBe("soon"); // 6d23h59m }); it("active when > 7 days away", () => { expect(expiryState("2026-02-01T00:00:00Z", T0)).toBe("active"); }); it("keyStatus precedence: Revoked > Expired > Active", () => { expect( keyStatus({ revokedAt: "2026-01-01T00:00:00Z", expiresAt: "2026-01-01T00:00:00Z" }, T0), ).toBe("Revoked"); expect(keyStatus({ revokedAt: null, expiresAt: "2026-01-01T00:00:00Z" }, T0)).toBe("Expired"); expect(keyStatus({ revokedAt: null, expiresAt: "2026-06-01T00:00:00Z" }, T0)).toBe("Active"); expect(keyStatus({ revokedAt: null, expiresAt: null }, T0)).toBe("Active"); }); }); describe("relative time (last used)", () => { it("never / just now", () => { expect(relativeTime(null, T0)).toBe("never"); expect(relativeTime("2026-01-15T11:59:30Z", T0)).toBe("just now"); }); it("minutes / hours / days", () => { expect(relativeTime("2026-01-15T11:55:00Z", T0)).toBe("5 m ago"); expect(relativeTime("2026-01-15T09:00:00Z", T0)).toBe("3 h ago"); expect(relativeTime("2026-01-12T12:00:00Z", T0)).toBe("3 d ago"); }); }); describe("expiry bounds (the form window)", () => { it("min = now + 1 h, max = now + 1 y", () => { const b = expiryBounds(T0); expect(new Date(b.min).getTime()).toBeCloseTo(T0 + 3_600_000, -6); expect(new Date(b.max).getTime()).toBeCloseTo(T0 + 365 * 24 * 3_600_000, -6); expect(b.min).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/); }); }); describe("short date label", () => { it("formats a readable date", () => { const s = shortDate("2027-01-05T00:00:00Z"); expect(s).toMatch(/2027/); expect(s).toMatch(/Jan/); }); });