/** * Message Framing Test Suite * Tests length-prefix-4be framing for ISO 20022 messages */ import { LengthPrefixFramer } from '@/transport/framing/length-prefix'; import { readFileSync } from 'fs'; import { join } from 'path'; describe('Message Framing Tests', () => { const pacs008Template = readFileSync( join(__dirname, '../../../docs/examples/pacs008-template-a.xml'), 'utf-8' ); describe('Length-Prefix-4BE Framing', () => { it('should frame message with 4-byte big-endian length prefix', () => { const message = Buffer.from('Hello, World!', 'utf-8'); const framed = LengthPrefixFramer.frame(message); expect(framed.length).toBe(4 + message.length); expect(framed.readUInt32BE(0)).toBe(message.length); }); it('should correctly frame ISO 20022 pacs.008 message', () => { const message = Buffer.from(pacs008Template, 'utf-8'); const framed = LengthPrefixFramer.frame(message); expect(framed.length).toBe(4 + message.length); expect(framed.readUInt32BE(0)).toBe(message.length); // Verify message content is preserved const unframed = framed.slice(4); expect(unframed.toString('utf-8')).toBe(pacs008Template); }); it('should handle empty message', () => { const message = Buffer.alloc(0); const framed = LengthPrefixFramer.frame(message); expect(framed.length).toBe(4); expect(framed.readUInt32BE(0)).toBe(0); }); it('should handle large messages (up to 4GB)', () => { const largeMessage = Buffer.alloc(1024 * 1024); // 1MB largeMessage.fill('A'); const framed = LengthPrefixFramer.frame(largeMessage); expect(framed.length).toBe(4 + largeMessage.length); expect(framed.readUInt32BE(0)).toBe(largeMessage.length); }); it('should handle messages with maximum 32-bit length', () => { const maxLength = 0xFFFFFFFF; // Max 32-bit unsigned int const lengthBuffer = Buffer.allocUnsafe(4); lengthBuffer.writeUInt32BE(maxLength, 0); expect(lengthBuffer.readUInt32BE(0)).toBe(maxLength); }); }); describe('Length-Prefix Unframing', () => { it('should unframe message correctly', () => { const original = Buffer.from('Test message', 'utf-8'); const framed = LengthPrefixFramer.frame(original); const { message, remaining } = LengthPrefixFramer.unframe(framed); expect(message).not.toBeNull(); expect(message!.toString('utf-8')).toBe('Test message'); expect(remaining.length).toBe(0); }); it('should handle partial frames (need more data)', () => { const partialFrame = Buffer.alloc(2); // Only 2 bytes, need 4 for length partialFrame.writeUInt16BE(100, 0); const { message, remaining } = LengthPrefixFramer.unframe(partialFrame); expect(message).toBeNull(); expect(remaining.length).toBe(2); }); it('should handle incomplete message payload', () => { const message = Buffer.from('Hello', 'utf-8'); const framed = LengthPrefixFramer.frame(message); const partial = framed.slice(0, 6); // Only length + 2 bytes of message const { message: unframed, remaining } = LengthPrefixFramer.unframe(partial); expect(unframed).toBeNull(); expect(remaining.length).toBe(6); }); it('should handle multiple messages in buffer', () => { const msg1 = Buffer.from('First message', 'utf-8'); const msg2 = Buffer.from('Second message', 'utf-8'); const framed1 = LengthPrefixFramer.frame(msg1); const framed2 = LengthPrefixFramer.frame(msg2); const combined = Buffer.concat([framed1, framed2]); // Unframe first message const { message: first, remaining: afterFirst } = LengthPrefixFramer.unframe(combined); expect(first!.toString('utf-8')).toBe('First message'); // Unframe second message const { message: second, remaining: afterSecond } = LengthPrefixFramer.unframe(afterFirst); expect(second!.toString('utf-8')).toBe('Second message'); expect(afterSecond.length).toBe(0); }); it('should correctly unframe ISO 20022 message', () => { const message = Buffer.from(pacs008Template, 'utf-8'); const framed = LengthPrefixFramer.frame(message); const { message: unframed, remaining } = LengthPrefixFramer.unframe(framed); expect(unframed).not.toBeNull(); expect(unframed!.toString('utf-8')).toBe(pacs008Template); expect(remaining.length).toBe(0); }); }); describe('Expected Length Detection', () => { it('should get expected length from buffer', () => { const message = Buffer.from('Test', 'utf-8'); const framed = LengthPrefixFramer.frame(message); const expectedLength = LengthPrefixFramer.getExpectedLength(framed); expect(expectedLength).toBe(message.length); }); it('should return null for incomplete length prefix', () => { const partial = Buffer.alloc(2); const expectedLength = LengthPrefixFramer.getExpectedLength(partial); expect(expectedLength).toBeNull(); }); it('should handle zero-length message', () => { const empty = Buffer.alloc(4); empty.writeUInt32BE(0, 0); const expectedLength = LengthPrefixFramer.getExpectedLength(empty); expect(expectedLength).toBe(0); }); }); describe('Framing Edge Cases', () => { it('should handle Unicode characters correctly', () => { const unicodeMessage = Buffer.from('测试消息 🚀', 'utf-8'); const framed = LengthPrefixFramer.frame(unicodeMessage); const { message } = LengthPrefixFramer.unframe(framed); expect(message!.toString('utf-8')).toBe('测试消息 🚀'); }); it('should handle binary data', () => { const binaryData = Buffer.from([0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD]); const framed = LengthPrefixFramer.frame(binaryData); const { message } = LengthPrefixFramer.unframe(framed); expect(Buffer.compare(message!, binaryData)).toBe(0); }); it('should maintain message integrity through frame/unframe cycle', () => { const testCases = [ 'Simple message', pacs008Template, 'A'.repeat(1000), 'Multi\nline\nmessage', 'Message with special chars: !@#$%^&*()', ]; for (const testCase of testCases) { const original = Buffer.from(testCase, 'utf-8'); const framed = LengthPrefixFramer.frame(original); const { message } = LengthPrefixFramer.unframe(framed); expect(message!.toString('utf-8')).toBe(testCase); } }); }); });