/** * Unit tests for the Complete Credential (DBIS cc-*) adapters. * * All tests are headless — they either exercise the embedded mock / * matrix or stub `fetch` directly. No network, no UI. */ import { createCcIdentityClient, loadControlsMatrix, findControl, type CcIdentityClient, } from "../../src/services/completeCredential"; describe("completeCredential.createCcIdentityClient() — mock mode", () => { let client: CcIdentityClient; beforeAll(() => { delete process.env.CC_IDENTITY_URL; delete process.env.CC_IDENTITY_API_KEY; client = createCcIdentityClient(); }); it("reports mock mode", () => { expect(client.mode).toBe("mock"); }); it("returns ok for health()", async () => { const h = await client.health(); expect(h.status).toBe("ok"); expect(h.service).toBe("cc-identity-core"); }); it("returns a ready response with persistence=false in mock", async () => { const r = await client.ready(); expect(r.status).toBe("ok"); expect(r.persistence).toBe(false); }); it("creates a subject with a uuid and defaulted tenant/entity", async () => { const s = await client.createSubject({}); expect(s.subjectId).toMatch(/^[0-9a-f-]{36}$/); expect(s.tenantId).toBe("tenant-demo"); expect(s.entityId).toBe("entity-demo"); }); it("passes tenant/entity through when provided", async () => { const s = await client.createSubject({ tenantId: "t-acme", entityId: "e-bank-1", }); expect(s.tenantId).toBe("t-acme"); expect(s.entityId).toBe("e-bank-1"); }); }); describe("completeCredential.createCcIdentityClient() — live mode (stubbed fetch)", () => { function makeFetch( record: (url: string, init: RequestInit) => void, responseBody: unknown, status = 200, ): typeof fetch { return (async (input: string | URL | Request, init?: RequestInit) => { record(String(input), init ?? {}); return new Response(JSON.stringify(responseBody), { status, headers: { "Content-Type": "application/json" }, }); }) as typeof fetch; } it("reports live mode when baseUrl is set", () => { const client = createCcIdentityClient({ baseUrl: "http://cc.example.test", fetchImpl: makeFetch(() => undefined, { status: "ok", service: "x" }), }); expect(client.mode).toBe("live"); }); it("hits GET /health", async () => { const calls: string[] = []; const client = createCcIdentityClient({ baseUrl: "http://cc.example.test", fetchImpl: makeFetch( (url) => { calls.push(url); }, { status: "ok", service: "cc-identity-core" }, ), }); const h = await client.health(); expect(h.status).toBe("ok"); expect(calls[0]).toBe("http://cc.example.test/health"); }); it("posts to /v1/subjects with X-Correlation-Id + api key header", async () => { const calls: { url: string; headers: Record; body?: string }[] = []; const client = createCcIdentityClient({ baseUrl: "http://cc.example.test", apiKey: "k-1", fetchImpl: makeFetch( (url, init) => { calls.push({ url, headers: (init.headers ?? {}) as Record, body: init.body as string, }); }, { subjectId: "11111111-2222-3333-4444-555555555555", tenantId: "t-1", entityId: "e-1", createdAt: "2026-01-01T00:00:00Z", }, ), }); const s = await client.createSubject({ tenantId: "t-1", entityId: "e-1" }, "corr-42"); expect(s.subjectId).toContain("-"); expect(calls[0].url).toBe("http://cc.example.test/v1/subjects"); expect(calls[0].headers["X-API-Key"]).toBe("k-1"); expect(calls[0].headers["X-Correlation-Id"]).toBe("corr-42"); expect(JSON.parse(calls[0].body ?? "{}")).toEqual({ tenantId: "t-1", entityId: "e-1", }); }); it("auto-generates a correlation id when not provided", async () => { const calls: Record[] = []; const client = createCcIdentityClient({ baseUrl: "http://cc.example.test", fetchImpl: makeFetch( (_url, init) => { calls.push((init.headers ?? {}) as Record); }, { subjectId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", tenantId: "t", entityId: "e", createdAt: "2026-01-01T00:00:00Z", }, ), }); await client.createSubject({}); expect(calls[0]["X-Correlation-Id"]).toMatch(/^[0-9a-f-]{36}$/); }); it("throws a descriptive error on non-2xx", async () => { const client = createCcIdentityClient({ baseUrl: "http://cc.example.test", fetchImpl: makeFetch(() => undefined, { error: "boom" }, 500), }); await expect(client.health()).rejects.toThrow(/HTTP 500/); }); }); describe("completeCredential.loadControlsMatrix() — embedded mode", () => { beforeAll(() => { delete process.env.CC_CONTROLS_MATRIX_URL; }); it("returns the embedded v0 matrix when no URL is set", async () => { const m = await loadControlsMatrix(); expect(m.source).toBe("embedded"); expect(m.version).toBe(0); expect(m.domains.length).toBeGreaterThan(0); }); it("exposes expected control ids", async () => { const m = await loadControlsMatrix(); const ids = m.domains.flatMap((d) => d.controls.map((c) => c.id)); expect(ids).toEqual(expect.arrayContaining(["IDP-001", "PAY-001", "AUD-001", "REG-001"])); }); it("findControl() resolves by id", async () => { const m = await loadControlsMatrix(); const c = findControl(m, "PAY-001"); expect(c?.title).toContain("PAN"); }); it("findControl() returns undefined for unknown ids", async () => { const m = await loadControlsMatrix(); expect(findControl(m, "NOPE-999")).toBeUndefined(); }); }); describe("completeCredential.loadControlsMatrix() — remote mode", () => { function makeFetch(responseBody: unknown, status = 200): typeof fetch { return (async () => new Response(JSON.stringify(responseBody), { status, headers: { "Content-Type": "application/json" }, })) as typeof fetch; } it("fetches and normalises a JSON matrix", async () => { const matrix = await loadControlsMatrix({ url: "http://cc.example.test/controls/matrix/v0.json", fetchImpl: makeFetch({ version: 1, domains: [ { id: "extra", controls: [ { id: "X-001", title: "Extra", evidence_type: "doc_review", owner_team: "ops", frequency: "monthly", }, ], }, ], }), }); expect(matrix.source).toBe("remote"); expect(matrix.version).toBe(1); expect(matrix.domains[0].controls[0].evidenceType).toBe("doc_review"); expect(matrix.domains[0].controls[0].ownerTeam).toBe("ops"); }); it("throws on non-2xx", async () => { await expect( loadControlsMatrix({ url: "http://cc.example.test/nope", fetchImpl: makeFetch({}, 404), }), ).rejects.toThrow(/HTTP 404/); }); });