Files
CurrenciCombo/orchestrator/tests/services/dbisCoreClient.test.ts
nsatoshi 7fdc9c06da
Some checks failed
CI / Frontend Lint (push) Has been cancelled
CI / Frontend Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Frontend E2E Tests (push) Has been cancelled
CI / Orchestrator Build (push) Has been cancelled
CI / Orchestrator Unit Tests (push) Has been cancelled
CI / Orchestrator E2E (Testcontainers) (push) Has been cancelled
CI / Contracts Compile (push) Has been cancelled
CI / Contracts Test (push) Has been cancelled
Security Scan / Dependency Vulnerability Scan (push) Has been cancelled
Security Scan / OWASP ZAP Scan (push) Has been cancelled
PR #26 (squash-merged via Gitea API)
2026-04-22 21:11:56 +00:00

199 lines
6.0 KiB
TypeScript

/**
* Unit tests for the dbis_core HTTP client adapter.
*
* Covers both provider-switch legs:
* - `createDbisCoreClient()` with DBIS_CORE_URL unset → mock mode.
* - `createDbisCoreClient({ baseUrl, fetchImpl })` → live mode, with
* a stub `fetch` so tests never hit the network.
*/
import {
createDbisCoreClient,
type DbisCoreClient,
} from "../../src/services/dbisCore";
describe("dbisCore.createDbisCoreClient() — mock mode", () => {
let client: DbisCoreClient;
beforeAll(() => {
delete process.env.DBIS_CORE_URL;
delete process.env.DBIS_CORE_API_KEY;
client = createDbisCoreClient();
});
it("reports mock mode", () => {
expect(client.mode).toBe("mock");
});
it("returns a balance shaped like upstream", async () => {
const b = await client.getAccountBalance("acct-1");
expect(b.accountId).toBe("acct-1");
expect(typeof b.available).toBe("string");
expect(typeof b.held).toBe("string");
expect(b.currency).toBeDefined();
});
it("returns a plausible route", async () => {
const r = await client.findSettlementRoute({
sourceBankId: "src",
destinationBankId: "dst",
amount: "100",
currencyCode: "USD",
});
expect(r.routeId).toContain("src");
expect(r.hops.length).toBeGreaterThan(0);
expect(r.estimatedFeeBps).toBeGreaterThanOrEqual(0);
});
it("settles atomically with a deterministic id", async () => {
const s = await client.atomicSettle({
routeId: "r1",
sourceAccountId: "a",
destinationAccountId: "b",
amount: "1",
currencyCode: "USD",
reference: "ref-1",
});
expect(s.status).toBe("settled");
expect(s.settlementId).toContain("ref-1");
expect(s.completedAt).toBeDefined();
});
it("returns an allow decision by default from ARI", async () => {
const d = await client.requestAriDecision({
txId: "tx-1",
amount: "1",
currencyCode: "USD",
creator: "0xdead",
});
expect(d.outcome).toBe("allow");
expect(d.txId).toBe("tx-1");
expect(d.riskScore).toBeLessThan(1);
});
it("accepts a pacs008 dispatch and echoes the messageId", async () => {
const r = await client.dispatchPacs008({
messageId: "msg-1",
creationDateTime: "2026-01-01T00:00:00Z",
debtor: { name: "Acme", bic: "ACMEUS33", account: "1" },
creditor: { name: "Widget", bic: "WDGTGB22", account: "2" },
amount: "100",
currencyCode: "USD",
});
expect(r.status).toBe("accepted");
expect(r.messageId).toBe("msg-1");
});
it("returns a settled status from a synthetic settlementId", async () => {
const s = await client.getSettlementStatus("stlm-99");
expect(s.status).toBe("settled");
expect(s.legs.length).toBeGreaterThan(0);
});
});
describe("dbisCore.createDbisCoreClient() — 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 = createDbisCoreClient({
baseUrl: "http://dbis.example.test",
fetchImpl: makeFetch(
() => undefined,
{ accountId: "a", currency: "USD", available: "0", held: "0", asOf: "" },
),
});
expect(client.mode).toBe("live");
});
it("hits GET /api/accounts/:id/balance with the API key header", async () => {
const calls: { url: string; headers: Record<string, string>; method?: string }[] = [];
const client = createDbisCoreClient({
baseUrl: "http://dbis.example.test",
apiKey: "k-secret",
fetchImpl: makeFetch(
(url, init) => {
calls.push({
url,
method: init.method,
headers: (init.headers ?? {}) as Record<string, string>,
});
},
{
accountId: "a42",
currency: "USD",
available: "500",
held: "10",
asOf: "2026-01-01T00:00:00Z",
},
),
});
const b = await client.getAccountBalance("a42");
expect(b.available).toBe("500");
expect(calls).toHaveLength(1);
expect(calls[0].url).toBe("http://dbis.example.test/api/accounts/a42/balance");
expect(calls[0].method).toBe("GET");
expect(calls[0].headers["X-API-Key"]).toBe("k-secret");
});
it("posts a route request and parses the structured response", async () => {
const client = createDbisCoreClient({
baseUrl: "http://dbis.example.test/",
fetchImpl: makeFetch(
() => undefined,
{
routeId: "R1",
hops: [{ bankId: "A", latencyMs: 1, feeBps: 2 }],
estimatedLatencyMs: 10,
estimatedFeeBps: 2,
},
),
});
const r = await client.findSettlementRoute({
sourceBankId: "A",
destinationBankId: "B",
amount: "1",
currencyCode: "USD",
});
expect(r.routeId).toBe("R1");
expect(r.estimatedFeeBps).toBe(2);
});
it("throws a descriptive error on non-2xx", async () => {
const client = createDbisCoreClient({
baseUrl: "http://dbis.example.test",
fetchImpl: makeFetch(() => undefined, { error: "denied" }, 403),
});
await expect(client.getAccountBalance("a1")).rejects.toThrow(/HTTP 403/);
});
it("encodes path parameters safely", async () => {
const calls: string[] = [];
const client = createDbisCoreClient({
baseUrl: "http://dbis.example.test",
fetchImpl: makeFetch(
(url) => {
calls.push(url);
},
{ settlementId: "x", status: "settled", legs: [], lastUpdated: "" },
),
});
await client.getSettlementStatus("weird/id with space");
expect(calls[0]).toBe(
"http://dbis.example.test/api/isn/settlements/weird%2Fid%20with%20space",
);
});
});