import { describe, it, expect } from "bun:test"; import { generateJWT, getAuthHeaders } from "../src/auth/index.js"; import type { X3Config } from "../src/config.js"; function makeConfig(overrides: Partial = {}): X3Config { return { url: "https://example.com/api", endpoint: "SEED", clientId: "test-client-id", secret: "test-secret-key-for-hs256", user: "TEST_USER", tokenLifetime: 600, mode: "authenticated", tlsRejectUnauthorized: true, ...overrides, }; } function decodeJwtPayload(token: string): Record { const parts = token.split("."); const payload = Buffer.from(parts[1], "base64url").toString(); return JSON.parse(payload); } describe("generateJWT", () => { it("produces a token with 3 dot-separated parts", async () => { const token = await generateJWT(makeConfig()); const parts = token.split("."); expect(parts).toHaveLength(3); expect(parts.every((p) => p.length > 0)).toBe(true); }); it("encodes correct claims in the payload", async () => { const config = makeConfig({ tokenLifetime: 900 }); const beforeTime = Math.floor(Date.now() / 1000) - 30; const token = await generateJWT(config); const afterTime = Math.floor(Date.now() / 1000) - 30; const claims = decodeJwtPayload(token); expect(claims.iss).toBe("test-client-id"); expect(claims.sub).toBe("TEST_USER"); expect(claims.aud).toBe(""); const iat = claims.iat as number; expect(iat).toBeGreaterThanOrEqual(beforeTime); expect(iat).toBeLessThanOrEqual(afterTime); expect(claims.exp).toBe(iat + 900); }); it("throws when clientId is missing", async () => { expect(generateJWT(makeConfig({ clientId: undefined }))).rejects.toThrow( "clientId, secret, and user are required", ); }); it("throws when secret is missing", async () => { expect(generateJWT(makeConfig({ secret: undefined }))).rejects.toThrow( "clientId, secret, and user are required", ); }); it("throws when user is missing", async () => { expect(generateJWT(makeConfig({ user: undefined }))).rejects.toThrow( "clientId, secret, and user are required", ); }); }); describe("getAuthHeaders", () => { it("returns only Content-Type in sandbox mode", async () => { const headers = await getAuthHeaders(makeConfig({ mode: "sandbox" })); expect(headers["Content-Type"]).toBe("application/json"); expect(headers["Authorization"]).toBeUndefined(); expect(headers["x-xtrem-endpoint"]).toBeUndefined(); }); it("returns Authorization and x-xtrem-endpoint in authenticated mode", async () => { const headers = await getAuthHeaders(makeConfig()); expect(headers["Content-Type"]).toBe("application/json"); expect(headers["Authorization"]).toStartWith("Bearer "); expect(headers["x-xtrem-endpoint"]).toBe("SEED"); }); it("sets x-xtrem-endpoint to empty string when endpoint is undefined", async () => { const headers = await getAuthHeaders( makeConfig({ endpoint: undefined }), ); expect(headers["x-xtrem-endpoint"]).toBe(""); }); });