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("1500000.00"); expect(result.xml).toContain("EBILAEAD"); expect(result.xml).toContain("EMBKAEAD"); 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 = `R1MSG-1ACSC2026-04-18T10:01:00Z`; 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 = `N11500000.00CRDT
2026-04-18
2026-04-18
E2E-plan-1
`; 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 = `ROACSC`; expect(parseCamt(xml025).type).toBe("camt.025"); }); it("parseCamt rejects unknown xmlns", () => { expect(() => parseCamt('')).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"]); }); });