186 lines
6.6 KiB
TypeScript
186 lines
6.6 KiB
TypeScript
/**
|
|
* 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);
|
|
}
|
|
});
|
|
});
|
|
});
|