Files
CurrenciCombo/orchestrator/tests/unit/swift.test.ts
nsatoshi fd575000fe
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 / 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 E: SWIFT gateway (MT760, pacs.009, MT202, camt.025/054) (#9)
2026-04-22 17:17:51 +00:00

170 lines
6.3 KiB
TypeScript

import { describe, it, expect } from "@jest/globals";
import {
generateMt760,
messageHash,
generatePacs009,
generateMt202,
parseCamt025,
parseCamt054,
parseCamt,
reconcileCamt054,
} from "../../src/services/swift";
import type { InstrumentTerms, Plan } from "../../src/types/plan";
const TERMS: InstrumentTerms = {
applicant: "ACME TRADING FZE",
issuingBankBIC: "EBILAEAD",
beneficiaryBankBIC: "EMBKAEAD",
beneficiaryName: "BLUE OCEAN SHIPPING LLC",
beneficiaryAccount: "AE070260001015104203701",
amount: 1_500_000,
currency: "USD",
tenor: "365D",
expiryDate: "2027-04-18",
placeOfPresentation: "DUBAI",
governingLaw: "URDG 758",
templateRef: "EIB-SBLC-2024-01",
templateHash: "a".repeat(64),
};
const PLAN: Plan = {
plan_id: "11111111-2222-3333-4444-555555555555",
creator: "0xabc",
steps: [{ type: "pay", asset: "USD", amount: 1_500_000 }],
};
describe("SWIFT gateway — MT760", () => {
it("renders all 12 required tags", () => {
const msg = generateMt760(TERMS, { transactionReference: "TXN1", issueDate: "2026-04-18" });
expect(msg.sender).toBe("EBILAEAD");
expect(msg.receiver).toBe("EMBKAEAD");
expect(msg.fields["20"]).toBe("TXN1");
expect(msg.fields["30"]).toBe("260418");
expect(msg.fields["32B"]).toBe("USD1500000,00");
expect(msg.fields["31D"]).toBe("270418DUBAI");
expect(msg.fin).toContain("{1:F01EBILAEADXXXX0000000000}");
expect(msg.fin).toContain("{2:I760EMBKAEADXXXXN}");
expect(msg.fin).toContain(":32B:USD1500000,00");
});
it("rejects malformed expiry date", () => {
expect(() =>
generateMt760({ ...TERMS, expiryDate: "not-a-date" }, { transactionReference: "T", issueDate: "2026-04-18" }),
).toThrow(/YYYY-MM-DD/);
});
it("rejects negative amount", () => {
expect(() =>
generateMt760({ ...TERMS, amount: -1 }, { transactionReference: "T", issueDate: "2026-04-18" }),
).toThrow(/non-negative/);
});
it("messageHash is deterministic", () => {
const a = generateMt760(TERMS, { transactionReference: "T", issueDate: "2026-04-18" });
const b = generateMt760(TERMS, { transactionReference: "T", issueDate: "2026-04-18" });
expect(messageHash(a)).toBe(messageHash(b));
expect(messageHash(a)).toMatch(/^[0-9a-f]{64}$/);
});
});
describe("SWIFT gateway — pacs.009", () => {
const opts = {
messageId: "MSG-1",
creationDateTime: "2026-04-18T10:00:00Z",
instructingAgentBIC: "EBILAEAD",
instructedAgentBIC: "EMBKAEAD",
debtorAgentBIC: "EBILAEAD",
creditorAgentBIC: "EMBKAEAD",
};
it("emits well-formed pacs.009.001.08 XML", () => {
const result = generatePacs009(PLAN, opts);
expect(result.messageId).toBe("MSG-1");
expect(result.xml).toContain("urn:iso:std:iso:20022:tech:xsd:pacs.009.001.08");
expect(result.xml).toContain("<IntrBkSttlmAmt Ccy=\"USD\">1500000.00</IntrBkSttlmAmt>");
expect(result.xml).toContain("<BICFI>EBILAEAD</BICFI>");
expect(result.xml).toContain("<BICFI>EMBKAEAD</BICFI>");
expect(result.endToEndId).toBe(`E2E-${PLAN.plan_id}`);
});
it("rejects invalid BIC", () => {
expect(() => generatePacs009(PLAN, { ...opts, instructingAgentBIC: "BAD" })).toThrow(/BIC/);
});
it("requires a pay step", () => {
expect(() =>
generatePacs009({ ...PLAN, steps: [{ type: "borrow", amount: 1, asset: "USD" }] }, opts),
).toThrow(/pay/);
});
});
describe("SWIFT gateway — MT202", () => {
it("renders the 6 required tags", () => {
const msg = generateMt202(PLAN, {
transactionReference: "TXN-1",
valueDate: "2026-04-18",
sendingInstitution: "EBILAEAD",
receivingInstitution: "EMBKAEAD",
beneficiaryInstitution: "EMBKAEAD",
});
expect(msg.fields["20"]).toBe("TXN-1");
expect(msg.fields["32A"]).toBe("260418USD1500000,00");
expect(msg.fields["58A"]).toBe("EMBKAEAD");
expect(msg.fin).toContain(":20:TXN-1");
});
});
describe("SWIFT gateway — camt parsers", () => {
it("parseCamt025 extracts status + ids", () => {
const xml = `<?xml version="1.0"?><Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.025.001.05"><Rct><MsgId>R1</MsgId><OrgnlMsgId>MSG-1</OrgnlMsgId><Cd>ACSC</Cd><CreDtTm>2026-04-18T10:01:00Z</CreDtTm></Rct></Document>`;
const r = parseCamt025(xml);
expect(r.type).toBe("camt.025");
expect(r.originalMessageId).toBe("MSG-1");
expect(r.status).toBe("ACSC");
});
it("parseCamt054 extracts credit amount + endToEndId", () => {
const xml = `<?xml version="1.0"?><Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.054.001.08"><BkToCstmrDbtCdtNtfctn><MsgId>N1</MsgId><Ntfctn><Ntry><Amt Ccy="USD">1500000.00</Amt><CdtDbtInd>CRDT</CdtDbtInd><BookgDt><Dt>2026-04-18</Dt></BookgDt><ValDt><Dt>2026-04-18</Dt></ValDt><NtryDtls><TxDtls><Refs><EndToEndId>E2E-plan-1</EndToEndId></Refs></TxDtls></NtryDtls></Ntry></Ntfctn></BkToCstmrDbtCdtNtfctn></Document>`;
const r = parseCamt054(xml);
expect(r.type).toBe("camt.054");
expect(r.creditDebitIndicator).toBe("CRDT");
expect(r.amount).toBe(1_500_000);
expect(r.currency).toBe("USD");
expect(r.endToEndId).toBe("E2E-plan-1");
});
it("parseCamt dispatches on xmlns marker", () => {
const xml025 = `<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.025.001.05"><Rct><MsgId>R</MsgId><OrgnlMsgId>O</OrgnlMsgId><Cd>ACSC</Cd></Rct></Document>`;
expect(parseCamt(xml025).type).toBe("camt.025");
});
it("parseCamt rejects unknown xmlns", () => {
expect(() => parseCamt('<Document xmlns="urn:other"/>')).toThrow(/unsupported/);
});
it("reconcileCamt054 returns empty array when everything matches", () => {
const msg = {
type: "camt.054" as const,
messageId: "N1",
creditDebitIndicator: "CRDT" as const,
amount: 1_500_000,
currency: "USD",
endToEndId: "E2E-1",
};
expect(reconcileCamt054(msg, { amount: 1_500_000, currency: "USD", endToEndId: "E2E-1" })).toEqual([]);
});
it("reconcileCamt054 reports amount + currency + direction mismatches", () => {
const msg = {
type: "camt.054" as const,
messageId: "N1",
creditDebitIndicator: "DBIT" as const,
amount: 1_400_000,
currency: "EUR",
endToEndId: "E2E-2",
};
const result = reconcileCamt054(msg, { amount: 1_500_000, currency: "USD", endToEndId: "E2E-1" });
expect(result.map((m) => m.field).sort()).toEqual(["amount", "creditDebitIndicator", "currency", "endToEndId"]);
});
});