247 lines
6.9 KiB
TypeScript
247 lines
6.9 KiB
TypeScript
/**
|
|
* 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<tls.TLSSocket> = 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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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>([^<]+)<\/UETR>/);
|
|
const msgIdMatch = xmlContent.match(/<MsgId>([^<]+)<\/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 `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.002.001.10">
|
|
<Ack>
|
|
<UETR>${uetr}</UETR>
|
|
<MsgId>${msgId}</MsgId>
|
|
<Status>ACCEPTED</Status>
|
|
</Ack>
|
|
</Document>`;
|
|
} else {
|
|
this.nackCount++;
|
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.002.001.10">
|
|
<Nack>
|
|
<UETR>${uetr}</UETR>
|
|
<MsgId>${msgId}</MsgId>
|
|
<Reason>Test NACK response</Reason>
|
|
</Nack>
|
|
</Document>`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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<MockReceiverConfig>): void {
|
|
this.config = { ...this.config, ...config };
|
|
}
|
|
}
|