/** * Mock TLS Receiver Server * Simulates receiver for testing without external dependencies */ import * as tls from 'tls'; import * as fs from 'fs'; import * as path from 'path'; import { LengthPrefixFramer } from '@/transport/framing/length-prefix'; export interface MockReceiverConfig { port: number; host?: string; responseDelay?: number; // ms ackResponse?: boolean; // true for ACK, false for NACK simulateErrors?: boolean; errorRate?: number; // 0-1, probability of error } export class MockReceiverServer { private server: tls.Server | null = null; private config: MockReceiverConfig; private connections: Set = new Set(); private messageCount = 0; private ackCount = 0; private nackCount = 0; constructor(config: MockReceiverConfig) { this.config = { host: '0.0.0.0', responseDelay: 0, ackResponse: true, simulateErrors: false, errorRate: 0, ...config, }; } /** * Start the mock server */ async start(): Promise { return new Promise((resolve, reject) => { try { // Create self-signed certificate for testing const certPath = path.join(__dirname, '../../test-certs/server-cert.pem'); const keyPath = path.join(__dirname, '../../test-certs/server-key.pem'); // Create test certificates if they don't exist if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) { this.createTestCertificates(certPath, keyPath); } const options: tls.TlsOptions = { cert: fs.readFileSync(certPath), key: fs.readFileSync(keyPath), rejectUnauthorized: false, // For testing only }; this.server = tls.createServer(options, (socket) => { this.connections.add(socket); let buffer = Buffer.alloc(0); socket.on('data', async (data) => { buffer = Buffer.concat([buffer, data]); // Try to unframe messages while (buffer.length >= 4) { // Create a proper Buffer to avoid ArrayBufferLike type issue const bufferCopy = Buffer.from(buffer); const { message, remaining } = LengthPrefixFramer.unframe(bufferCopy); if (!message) { // Need more data break; } // Process message await this.handleMessage(socket, message.toString('utf-8')); // Create new Buffer from remaining to avoid type issues buffer = Buffer.from(remaining); } }); socket.on('error', (error) => { console.error('Mock server socket error:', error); }); socket.on('close', () => { this.connections.delete(socket); }); }); this.server.listen(this.config.port, this.config.host, () => { console.log(`Mock receiver server listening on ${this.config.host}:${this.config.port}`); resolve(); }); this.server.on('error', (error) => { reject(error); }); } catch (error) { reject(error); } }); } /** * Stop the mock server */ async stop(): Promise { return new Promise((resolve) => { if (this.server) { // Close all connections for (const socket of this.connections) { socket.destroy(); } this.connections.clear(); this.server.close(() => { this.server = null; resolve(); }); } else { resolve(); } }); } /** * Handle incoming message */ private async handleMessage(socket: tls.TLSSocket, xmlContent: string): Promise { this.messageCount++; // Simulate response delay if (this.config.responseDelay && this.config.responseDelay > 0) { await new Promise((resolve) => setTimeout(resolve, this.config.responseDelay)); } // Simulate errors if (this.config.simulateErrors && Math.random() < this.config.errorRate!) { socket.destroy(); return; } // Generate response const response = this.generateResponse(xmlContent); const responseBuffer = Buffer.from(response, 'utf-8'); // Create new Buffer to avoid ArrayBufferLike type issue const responseBufferCopy = Buffer.allocUnsafe(responseBuffer.length); responseBuffer.copy(responseBufferCopy); const framedResponse = LengthPrefixFramer.frame(responseBufferCopy); socket.write(framedResponse); } /** * Generate ACK/NACK response */ private generateResponse(xmlContent: string): string { // Extract UETR and MsgId from incoming message const uetrMatch = xmlContent.match(/([^<]+)<\/UETR>/); const msgIdMatch = xmlContent.match(/([^<]+)<\/MsgId>/); const uetr = uetrMatch ? uetrMatch[1] : '00000000-0000-0000-0000-000000000000'; const msgId = msgIdMatch ? msgIdMatch[1] : 'TEST-MSG-ID'; if (this.config.ackResponse) { this.ackCount++; return ` ${uetr} ${msgId} ACCEPTED `; } else { this.nackCount++; return ` ${uetr} ${msgId} Test NACK response `; } } /** * Create test certificates (simplified - in production use proper certs) */ private createTestCertificates(certPath: string, keyPath: string): void { const certDir = path.dirname(certPath); if (!fs.existsSync(certDir)) { fs.mkdirSync(certDir, { recursive: true }); } // Note: In a real implementation, use openssl or a proper certificate generator // This is a placeholder - actual certificates should be generated properly const { execSync } = require('child_process'); try { // Generate self-signed certificate for testing execSync( `openssl req -x509 -newkey rsa:2048 -keyout "${keyPath}" -out "${certPath}" -days 365 -nodes -subj "/CN=test-receiver"`, { stdio: 'ignore' } ); } catch (error) { console.warn('Could not generate test certificates. Using placeholder.'); // Create placeholder files fs.writeFileSync(certPath, 'PLACEHOLDER_CERT'); fs.writeFileSync(keyPath, 'PLACEHOLDER_KEY'); } } /** * Get server statistics */ getStats() { return { messageCount: this.messageCount, ackCount: this.ackCount, nackCount: this.nackCount, activeConnections: this.connections.size, }; } /** * Reset statistics */ resetStats(): void { this.messageCount = 0; this.ackCount = 0; this.nackCount = 0; } /** * Configure response behavior */ configure(config: Partial): void { this.config = { ...this.config, ...config }; } }