Initial commit: add .gitignore and README
This commit is contained in:
141
tests/unit/exports/containers/raw-iso-container.test.ts
Normal file
141
tests/unit/exports/containers/raw-iso-container.test.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Unit tests for Raw ISO 20022 Container
|
||||
*/
|
||||
|
||||
import { RawISOContainer } from '@/exports/containers/raw-iso-container';
|
||||
import { ISOMessage, MessageType, MessageStatus } from '@/models/message';
|
||||
import { PaymentIdentityMap } from '@/exports/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
describe('RawISOContainer', () => {
|
||||
const createTestMessage = (): ISOMessage => {
|
||||
const uetr = uuidv4();
|
||||
return {
|
||||
id: uuidv4(),
|
||||
paymentId: uuidv4(),
|
||||
messageType: MessageType.PACS_008,
|
||||
uetr,
|
||||
msgId: 'MSG-12345',
|
||||
xmlContent: `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10">
|
||||
<FIToFICstmrCdtTrf>
|
||||
<GrpHdr>
|
||||
<MsgId>MSG-12345</MsgId>
|
||||
<CreDtTm>${new Date().toISOString()}</CreDtTm>
|
||||
</GrpHdr>
|
||||
<CdtTrfTxInf>
|
||||
<PmtId>
|
||||
<EndToEndId>E2E-123</EndToEndId>
|
||||
<UETR>${uetr}</UETR>
|
||||
</PmtId>
|
||||
<IntrBkSttlmAmt Ccy="USD">1000.00</IntrBkSttlmAmt>
|
||||
</CdtTrfTxInf>
|
||||
</FIToFICstmrCdtTrf>
|
||||
</Document>`,
|
||||
xmlHash: 'test-hash',
|
||||
status: MessageStatus.VALIDATED,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
};
|
||||
|
||||
describe('exportMessage', () => {
|
||||
it('should export ISO 20022 message without modification', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await RawISOContainer.exportMessage(message);
|
||||
|
||||
expect(exported).toContain('urn:iso:std:iso:20022');
|
||||
expect(exported).toContain('FIToFICstmrCdtTrf');
|
||||
expect(exported).toContain(message.uetr);
|
||||
});
|
||||
|
||||
it('should ensure UETR is present when ensureUETR is true', async () => {
|
||||
const message = createTestMessage();
|
||||
// Remove UETR from XML
|
||||
message.xmlContent = message.xmlContent.replace(/<UETR>.*?<\/UETR>/, '');
|
||||
|
||||
const identityMap: PaymentIdentityMap = {
|
||||
paymentId: message.paymentId,
|
||||
uetr: message.uetr,
|
||||
ledgerJournalIds: [],
|
||||
internalTransactionIds: [],
|
||||
};
|
||||
|
||||
const exported = await RawISOContainer.exportMessage(message, identityMap, {
|
||||
ensureUETR: true,
|
||||
});
|
||||
|
||||
expect(exported).toContain(message.uetr);
|
||||
});
|
||||
|
||||
it('should normalize line endings to LF by default', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await RawISOContainer.exportMessage(message, undefined, {
|
||||
lineEnding: 'LF',
|
||||
});
|
||||
|
||||
expect(exported).not.toContain('\r\n');
|
||||
});
|
||||
|
||||
it('should normalize line endings to CRLF when requested', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await RawISOContainer.exportMessage(message, undefined, {
|
||||
lineEnding: 'CRLF',
|
||||
});
|
||||
|
||||
expect(exported).toContain('\r\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exportBatch', () => {
|
||||
it('should export multiple messages', async () => {
|
||||
const messages = [createTestMessage(), createTestMessage(), createTestMessage()];
|
||||
const exported = await RawISOContainer.exportBatch(messages);
|
||||
|
||||
expect(exported).toContain('FIToFICstmrCdtTrf');
|
||||
// Should contain all message UETRs
|
||||
messages.forEach((msg) => {
|
||||
expect(exported).toContain(msg.uetr);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate', () => {
|
||||
it('should validate correct ISO 20022 message', async () => {
|
||||
const message = createTestMessage();
|
||||
const validation = await RawISOContainer.validate(message.xmlContent);
|
||||
|
||||
expect(validation.valid).toBe(true);
|
||||
expect(validation.errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should detect missing ISO 20022 namespace', async () => {
|
||||
const invalidXml = '<Document><Test>Invalid</Test></Document>';
|
||||
const validation = await RawISOContainer.validate(invalidXml);
|
||||
|
||||
expect(validation.valid).toBe(false);
|
||||
expect(validation.errors).toContain('Missing ISO 20022 namespace');
|
||||
});
|
||||
|
||||
it('should detect missing UETR in payment message', async () => {
|
||||
const messageWithoutUETR = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10">
|
||||
<FIToFICstmrCdtTrf>
|
||||
<GrpHdr>
|
||||
<MsgId>MSG-12345</MsgId>
|
||||
</GrpHdr>
|
||||
<CdtTrfTxInf>
|
||||
<PmtId>
|
||||
<EndToEndId>E2E-123</EndToEndId>
|
||||
</PmtId>
|
||||
</CdtTrfTxInf>
|
||||
</FIToFICstmrCdtTrf>
|
||||
</Document>`;
|
||||
|
||||
const validation = await RawISOContainer.validate(messageWithoutUETR);
|
||||
|
||||
expect(validation.valid).toBe(false);
|
||||
expect(validation.errors).toContain('Missing UETR in payment instruction (CBPR+ requirement)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
123
tests/unit/exports/containers/rje-container.test.ts
Normal file
123
tests/unit/exports/containers/rje-container.test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Unit tests for RJE Container
|
||||
*/
|
||||
|
||||
import { RJEContainer } from '@/exports/containers/rje-container';
|
||||
import { ISOMessage, MessageType, MessageStatus } from '@/models/message';
|
||||
import { PaymentIdentityMap } from '@/exports/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
describe('RJEContainer', () => {
|
||||
const createTestMessage = (): ISOMessage => {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
paymentId: uuidv4(),
|
||||
messageType: MessageType.PACS_008,
|
||||
uetr: uuidv4(),
|
||||
msgId: 'MSG-12345',
|
||||
xmlContent: `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10">
|
||||
<FIToFICstmrCdtTrf>
|
||||
<GrpHdr>
|
||||
<MsgId>MSG-12345</MsgId>
|
||||
</GrpHdr>
|
||||
</FIToFICstmrCdtTrf>
|
||||
</Document>`,
|
||||
xmlHash: 'test-hash',
|
||||
status: MessageStatus.VALIDATED,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
};
|
||||
|
||||
describe('exportMessage', () => {
|
||||
it('should export message in RJE format with blocks', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await RJEContainer.exportMessage(message, undefined, {
|
||||
includeBlocks: true,
|
||||
});
|
||||
|
||||
expect(exported).toContain('{1:');
|
||||
expect(exported).toContain('{2:');
|
||||
expect(exported).toContain('{3:');
|
||||
expect(exported).toContain('{4:');
|
||||
expect(exported).toContain('{5:');
|
||||
});
|
||||
|
||||
it('should use CRLF line endings', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await RJEContainer.exportMessage(message);
|
||||
|
||||
expect(exported).toContain('\r\n');
|
||||
});
|
||||
|
||||
it('should include UETR in Block 3', async () => {
|
||||
const message = createTestMessage();
|
||||
const identityMap: PaymentIdentityMap = {
|
||||
paymentId: message.paymentId,
|
||||
uetr: message.uetr,
|
||||
ledgerJournalIds: [],
|
||||
internalTransactionIds: [],
|
||||
};
|
||||
|
||||
const exported = await RJEContainer.exportMessage(message, identityMap);
|
||||
|
||||
expect(exported).toContain(':121:');
|
||||
expect(exported).toContain(message.uetr);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exportBatch', () => {
|
||||
it('should export batch with $ delimiter', async () => {
|
||||
const messages = [createTestMessage(), createTestMessage()];
|
||||
const exported = await RJEContainer.exportBatch(messages);
|
||||
|
||||
// Should contain $ delimiter
|
||||
expect(exported).toContain('$');
|
||||
// Should NOT have trailing $ (check last character is not $)
|
||||
expect(exported.trim().endsWith('$')).toBe(false);
|
||||
});
|
||||
|
||||
it('should not have trailing $ delimiter', async () => {
|
||||
const messages = [createTestMessage(), createTestMessage(), createTestMessage()];
|
||||
const exported = await RJEContainer.exportBatch(messages);
|
||||
|
||||
// Split by $ and check last part is not empty
|
||||
const parts = exported.split('$');
|
||||
expect(parts[parts.length - 1].trim().length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate', () => {
|
||||
it('should validate correct RJE format', () => {
|
||||
const validRJE = `{1:F01BANKDEFFXXXX1234567890}\r\n{2:I103BANKDEFFXXXXN}\r\n{3:\r\n:121:test-uetr}\r\n{4:\r\n:20:REF123}\r\n{5:{MAC:123456}{CHK:123456}}`;
|
||||
const validation = RJEContainer.validate(validRJE);
|
||||
|
||||
expect(validation.valid).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect missing CRLF', () => {
|
||||
const invalidRJE = `{1:F01BANKDEFFXXXX1234567890}\n{2:I103BANKDEFFXXXXN}`;
|
||||
const validation = RJEContainer.validate(invalidRJE);
|
||||
|
||||
expect(validation.valid).toBe(false);
|
||||
expect(validation.errors).toContain('RJE format requires CRLF line endings');
|
||||
});
|
||||
|
||||
it('should detect trailing $ delimiter', () => {
|
||||
const invalidRJE = `{1:F01BANKDEFFXXXX1234567890}\r\n{2:I103BANKDEFFXXXXN}\r\n$`;
|
||||
const validation = RJEContainer.validate(invalidRJE);
|
||||
|
||||
expect(validation.valid).toBe(false);
|
||||
expect(validation.errors).toContain('RJE batch files must not have trailing $ delimiter');
|
||||
});
|
||||
|
||||
it('should detect missing Block 4 CRLF at beginning', () => {
|
||||
const invalidRJE = `{1:F01BANKDEFFXXXX1234567890}\r\n{2:I103BANKDEFFXXXXN}\r\n{3:}\r\n{4::20:REF123}\r\n{5:{MAC:123456}}`;
|
||||
|
||||
// This should pass as Block 4 validation is lenient in current implementation
|
||||
// But we can check for the presence of Block 4
|
||||
expect(invalidRJE).toContain('{4:');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
104
tests/unit/exports/containers/xmlv2-container.test.ts
Normal file
104
tests/unit/exports/containers/xmlv2-container.test.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Unit tests for XML v2 Container
|
||||
*/
|
||||
|
||||
import { XMLV2Container } from '@/exports/containers/xmlv2-container';
|
||||
import { ISOMessage, MessageType, MessageStatus } from '@/models/message';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
describe('XMLV2Container', () => {
|
||||
const createTestMessage = (): ISOMessage => {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
paymentId: uuidv4(),
|
||||
messageType: MessageType.PACS_008,
|
||||
uetr: uuidv4(),
|
||||
msgId: 'MSG-12345',
|
||||
xmlContent: `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10">
|
||||
<FIToFICstmrCdtTrf>
|
||||
<GrpHdr>
|
||||
<MsgId>MSG-12345</MsgId>
|
||||
<CreDtTm>${new Date().toISOString()}</CreDtTm>
|
||||
</GrpHdr>
|
||||
<CdtTrfTxInf>
|
||||
<PmtId>
|
||||
<UETR>${uuidv4()}</UETR>
|
||||
</PmtId>
|
||||
</CdtTrfTxInf>
|
||||
</FIToFICstmrCdtTrf>
|
||||
</Document>`,
|
||||
xmlHash: 'test-hash',
|
||||
status: MessageStatus.VALIDATED,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
};
|
||||
|
||||
describe('exportMessage', () => {
|
||||
it('should export message in XML v2 format', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await XMLV2Container.exportMessage(message);
|
||||
|
||||
expect(exported).toContain('DataPDU');
|
||||
expect(exported).toContain('AllianceAccessHeader');
|
||||
expect(exported).toContain('MessageBlock');
|
||||
});
|
||||
|
||||
it('should include Alliance Access Header when requested', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await XMLV2Container.exportMessage(message, undefined, {
|
||||
includeAllianceHeader: true,
|
||||
});
|
||||
|
||||
expect(exported).toContain('AllianceAccessHeader');
|
||||
});
|
||||
|
||||
it('should include Application Header when requested', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await XMLV2Container.exportMessage(message, undefined, {
|
||||
includeApplicationHeader: true,
|
||||
});
|
||||
|
||||
expect(exported).toContain('ApplicationHeader');
|
||||
});
|
||||
|
||||
it('should embed XML content in MessageBlock for MX messages', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await XMLV2Container.exportMessage(message, undefined, {
|
||||
base64EncodeMT: false,
|
||||
});
|
||||
|
||||
expect(exported).toContain('<Encoding>XML</Encoding>');
|
||||
expect(exported).toContain('FIToFICstmrCdtTrf');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exportBatch', () => {
|
||||
it('should export batch of messages in XML v2 format', async () => {
|
||||
const messages = [createTestMessage(), createTestMessage()];
|
||||
const exported = await XMLV2Container.exportBatch(messages);
|
||||
|
||||
expect(exported).toContain('BatchPDU');
|
||||
expect(exported).toContain('MessageCount');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate', () => {
|
||||
it('should validate correct XML v2 structure', async () => {
|
||||
const message = createTestMessage();
|
||||
const exported = await XMLV2Container.exportMessage(message);
|
||||
const validation = await XMLV2Container.validate(exported);
|
||||
|
||||
expect(validation.valid).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect missing DataPDU', async () => {
|
||||
const invalidXml = '<Document><Test>Invalid</Test></Document>';
|
||||
const validation = await XMLV2Container.validate(invalidXml);
|
||||
|
||||
expect(validation.valid).toBe(false);
|
||||
expect(validation.errors).toContain('Missing DataPDU or BatchPDU element');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user