Some checks failed
CI / Frontend Lint (pull_request) Failing after 5s
CI / Frontend Type Check (pull_request) Failing after 7s
CI / Frontend Build (pull_request) Failing after 6s
CI / Frontend E2E Tests (pull_request) Failing after 8s
CI / Orchestrator Build (pull_request) Failing after 6s
CI / Contracts Compile (pull_request) Failing after 7s
CI / Contracts Test (pull_request) Failing after 6s
Code Quality / SonarQube Analysis (pull_request) Failing after 19s
Code Quality / Code Quality Checks (pull_request) Failing after 5s
Security Scan / Dependency Vulnerability Scan (pull_request) Failing after 5s
Security Scan / OWASP ZAP Scan (pull_request) Failing after 4s
Outbound generators: - swift/mt760.ts: SBLC issuance (FIN Cat-7). 12-tag message built from InstrumentTerms with deterministic messageHash() for planHash anchoring. URDG 758 / UCP 600 aware. - swift/pacs009.ts: FI-to-FI credit transfer (ISO 20022 XML, pacs.009.001.08). Fixes the pacs.008 mis-routing flagged in the gap-analysis (pacs.008 is customer-to-bank; pacs.009 is bank-to-bank). BIC validation on all four agents. - swift/mt202.ts: FIN equivalent of pacs.009 for non-migrated corridors. 32A amount formatted with SWIFT decimal comma. Inbound parsers: - swift/camt.ts: parseCamt025 (receipt / status), parseCamt054 (credit/debit notification), reconcileCamt054 (diffs amount, ccy, direction, endToEndId so VALIDATING can feed mismatches into Data.valueMismatch()), parseCamt dispatcher on xmlns marker. Public surface in swift/index.ts documents channel selection: pacs.008 stays on the PSP customer leg; pacs.009/MT202 is the interbank leg; COMMIT requires camt.025 ACSC or camt.054 CRDT (arch §9.2 accepted !== settled). Tests: swift.test.ts — 14 cases covering the happy path, validation errors (bad BIC, malformed date, negative amount, missing pay step), determinism of messageHash, camt parser + reconciliation. tsc clean. 74 tests pass across 6 suites.
170 lines
6.3 KiB
TypeScript
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"]);
|
|
});
|
|
});
|