Some checks failed
CI / Frontend Lint (push) Failing after 6s
CI / Frontend Type Check (push) Failing after 7s
CI / Frontend Build (push) Failing after 7s
CI / Frontend E2E Tests (push) Failing after 7s
CI / Orchestrator Build (push) Failing after 7s
CI / Orchestrator Unit Tests (push) Failing after 5s
CI / Orchestrator E2E (Testcontainers) (push) Failing after 6s
CI / Contracts Compile (push) Failing after 7s
CI / Contracts Test (push) Failing after 5s
Security Scan / Dependency Vulnerability Scan (push) Failing after 5s
Security Scan / OWASP ZAP Scan (push) Failing after 3s
233 lines
7.1 KiB
TypeScript
233 lines
7.1 KiB
TypeScript
/**
|
|
* 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<string, string>; 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<string, string>,
|
|
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<string, string>[] = [];
|
|
const client = createCcIdentityClient({
|
|
baseUrl: "http://cc.example.test",
|
|
fetchImpl: makeFetch(
|
|
(_url, init) => {
|
|
calls.push((init.headers ?? {}) as Record<string, string>);
|
|
},
|
|
{
|
|
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/);
|
|
});
|
|
});
|