import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestSmtpServer } from '../../helpers/server.loader.js'; import { createSmtpClient } from '../../helpers/smtp.client.js'; import { Email } from '../../../ts/mail/core/classes.email.js'; import * as net from 'net'; let testServer: any; tap.test('setup test SMTP server', async () => { testServer = await startTestSmtpServer(); expect(testServer).toBeTruthy(); expect(testServer.port).toBeGreaterThan(0); }); tap.test('CREL-02: Handle sudden connection drop', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, socketTimeout: 10000, debug: true }); let connectionDropped = false; let errorReceived = false; smtpClient.on('error', (error) => { errorReceived = true; console.log('Error event received:', error.message); }); smtpClient.on('close', () => { connectionDropped = true; console.log('Connection closed unexpectedly'); }); await smtpClient.connect(); // Get the underlying socket const connectionInfo = smtpClient.getConnectionInfo(); const socket = connectionInfo?.socket as net.Socket; if (socket) { // Simulate sudden network drop console.log('Simulating sudden network disconnection...'); socket.destroy(); // Wait for events to fire await new Promise(resolve => setTimeout(resolve, 1000)); expect(connectionDropped || errorReceived).toBeTruthy(); expect(smtpClient.isConnected()).toBeFalsy(); } console.log(`Connection dropped: ${connectionDropped}, Error received: ${errorReceived}`); }); tap.test('CREL-02: Network timeout handling', async () => { // Create a server that accepts connections but doesn't respond const silentServer = net.createServer((socket) => { console.log('Silent server: Client connected, not responding...'); // Don't send any data }); await new Promise((resolve) => { silentServer.listen(0, '127.0.0.1', () => { resolve(); }); }); const silentPort = (silentServer.address() as net.AddressInfo).port; const smtpClient = createSmtpClient({ host: '127.0.0.1', port: silentPort, secure: false, connectionTimeout: 2000, // 2 second timeout debug: true }); const startTime = Date.now(); let timeoutError = false; try { await smtpClient.connect(); } catch (error) { const elapsed = Date.now() - startTime; timeoutError = true; console.log(`Connection timed out after ${elapsed}ms`); console.log('Error:', error.message); expect(elapsed).toBeGreaterThanOrEqual(1900); // Allow small margin expect(elapsed).toBeLessThan(3000); } expect(timeoutError).toBeTruthy(); silentServer.close(); }); tap.test('CREL-02: Packet loss simulation', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, commandTimeout: 3000, debug: true }); await smtpClient.connect(); // Create a proxy that randomly drops packets let packetDropRate = 0.3; // 30% packet loss const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); let droppedCommands = 0; let totalCommands = 0; smtpClient.sendCommand = async (command: string) => { totalCommands++; if (Math.random() < packetDropRate && !command.startsWith('QUIT')) { droppedCommands++; console.log(`Simulating packet loss for: ${command.trim()}`); // Simulate timeout return new Promise((_, reject) => { setTimeout(() => reject(new Error('Command timeout')), 3000); }); } return originalSendCommand(command); }; // Try to send email with simulated packet loss const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Packet Loss Test', text: 'Testing reliability with packet loss' }); let retries = 0; let success = false; const maxRetries = 3; while (retries < maxRetries && !success) { try { console.log(`\nAttempt ${retries + 1}/${maxRetries}`); const result = await smtpClient.sendMail(email); success = true; console.log('Email sent successfully despite packet loss'); } catch (error) { retries++; console.log(`Attempt failed: ${error.message}`); if (retries < maxRetries) { console.log('Retrying...'); // Reset connection for retry if (!smtpClient.isConnected()) { await smtpClient.connect(); } } } } console.log(`\nPacket loss simulation results:`); console.log(` Total commands: ${totalCommands}`); console.log(` Dropped: ${droppedCommands} (${(droppedCommands/totalCommands*100).toFixed(1)}%)`); console.log(` Success after ${retries} retries: ${success}`); await smtpClient.close(); }); tap.test('CREL-02: Bandwidth throttling', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, debug: true }); await smtpClient.connect(); // Simulate bandwidth throttling by adding delays const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); const bytesPerSecond = 1024; // 1KB/s throttle smtpClient.sendCommand = async (command: string) => { const commandBytes = Buffer.byteLength(command, 'utf8'); const delay = (commandBytes / bytesPerSecond) * 1000; console.log(`Throttling: ${commandBytes} bytes, ${delay.toFixed(0)}ms delay`); await new Promise(resolve => setTimeout(resolve, delay)); return originalSendCommand(command); }; // Send email with large content const largeText = 'x'.repeat(10000); // 10KB of text const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Bandwidth Throttling Test', text: largeText }); console.log('\nSending large email with bandwidth throttling...'); const startTime = Date.now(); const result = await smtpClient.sendMail(email); const elapsed = Date.now() - startTime; const effectiveSpeed = (largeText.length / elapsed) * 1000; console.log(`Email sent in ${elapsed}ms`); console.log(`Effective speed: ${effectiveSpeed.toFixed(0)} bytes/second`); expect(result).toBeTruthy(); expect(elapsed).toBeGreaterThan(5000); // Should take several seconds await smtpClient.close(); }); tap.test('CREL-02: Connection stability monitoring', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, keepAlive: true, keepAliveInterval: 1000, debug: true }); // Track connection stability const metrics = { keepAlivesSent: 0, keepAlivesSuccessful: 0, errors: 0, latencies: [] as number[] }; smtpClient.on('keepalive', () => { metrics.keepAlivesSent++; }); await smtpClient.connect(); // Monitor connection for 10 seconds console.log('Monitoring connection stability for 10 seconds...'); const monitoringDuration = 10000; const checkInterval = 2000; const endTime = Date.now() + monitoringDuration; while (Date.now() < endTime) { const startTime = Date.now(); try { // Send NOOP to check connection await smtpClient.sendCommand('NOOP'); const latency = Date.now() - startTime; metrics.latencies.push(latency); metrics.keepAlivesSuccessful++; console.log(`Connection check OK, latency: ${latency}ms`); } catch (error) { metrics.errors++; console.log(`Connection check failed: ${error.message}`); } await new Promise(resolve => setTimeout(resolve, checkInterval)); } // Calculate stability metrics const avgLatency = metrics.latencies.reduce((a, b) => a + b, 0) / metrics.latencies.length; const maxLatency = Math.max(...metrics.latencies); const minLatency = Math.min(...metrics.latencies); const successRate = (metrics.keepAlivesSuccessful / (metrics.keepAlivesSuccessful + metrics.errors)) * 100; console.log('\nConnection Stability Report:'); console.log(` Success rate: ${successRate.toFixed(1)}%`); console.log(` Average latency: ${avgLatency.toFixed(1)}ms`); console.log(` Min/Max latency: ${minLatency}ms / ${maxLatency}ms`); console.log(` Errors: ${metrics.errors}`); expect(successRate).toBeGreaterThan(90); // Expect high reliability await smtpClient.close(); }); tap.test('CREL-02: Intermittent network issues', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Simulate intermittent network issues let issueActive = false; let issueCount = 0; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); // Create intermittent issues every few seconds const issueInterval = setInterval(() => { issueActive = !issueActive; if (issueActive) { issueCount++; console.log(`\nNetwork issue ${issueCount} started`); } else { console.log(`Network issue ${issueCount} resolved`); } }, 3000); smtpClient.sendCommand = async (command: string) => { if (issueActive && Math.random() > 0.5) { console.log(`Command affected by network issue: ${command.trim()}`); throw new Error('Network unreachable'); } return originalSendCommand(command); }; // Send multiple emails during intermittent issues const emails = Array.from({ length: 5 }, (_, i) => new Email({ from: 'sender@example.com', to: [`recipient${i}@example.com`], subject: `Intermittent Network Test ${i}`, text: 'Testing with intermittent network issues' })); const results = await Promise.allSettled( emails.map(async (email, i) => { // Add random delay to spread out sends await new Promise(resolve => setTimeout(resolve, i * 1000)); return smtpClient.sendMail(email); }) ); clearInterval(issueInterval); const successful = results.filter(r => r.status === 'fulfilled').length; const failed = results.filter(r => r.status === 'rejected').length; console.log(`\nResults with intermittent issues:`); console.log(` Successful: ${successful}/${emails.length}`); console.log(` Failed: ${failed}/${emails.length}`); console.log(` Network issues encountered: ${issueCount}`); // Some should succeed despite issues expect(successful).toBeGreaterThan(0); await smtpClient.close(); }); tap.test('CREL-02: DNS resolution failures', async () => { // Test handling of DNS resolution failures const invalidHosts = [ 'non.existent.domain.invalid', 'another.fake.domain.test', '...', 'domain with spaces.com' ]; for (const host of invalidHosts) { console.log(`\nTesting DNS resolution for: ${host}`); const smtpClient = createSmtpClient({ host: host, port: 25, secure: false, connectionTimeout: 3000, dnsTimeout: 2000, debug: true }); const startTime = Date.now(); let errorType = ''; try { await smtpClient.connect(); } catch (error) { const elapsed = Date.now() - startTime; errorType = error.code || 'unknown'; console.log(` Failed after ${elapsed}ms`); console.log(` Error type: ${errorType}`); console.log(` Error message: ${error.message}`); } expect(errorType).toBeTruthy(); } }); tap.test('CREL-02: Network latency spikes', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, commandTimeout: 5000, debug: true }); await smtpClient.connect(); // Simulate latency spikes const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); let spikeCount = 0; smtpClient.sendCommand = async (command: string) => { // Random latency spikes if (Math.random() < 0.2) { // 20% chance of spike spikeCount++; const spikeDelay = 1000 + Math.random() * 3000; // 1-4 second spike console.log(`Latency spike ${spikeCount}: ${spikeDelay.toFixed(0)}ms delay`); await new Promise(resolve => setTimeout(resolve, spikeDelay)); } return originalSendCommand(command); }; // Send email with potential latency spikes const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Latency Spike Test', text: 'Testing behavior during network latency spikes' }); console.log('\nSending email with potential latency spikes...'); const startTime = Date.now(); try { const result = await smtpClient.sendMail(email); const elapsed = Date.now() - startTime; console.log(`\nEmail sent successfully in ${elapsed}ms`); console.log(`Latency spikes encountered: ${spikeCount}`); expect(result).toBeTruthy(); if (spikeCount > 0) { expect(elapsed).toBeGreaterThan(1000); // Should show impact of spikes } } catch (error) { console.log('Send failed due to timeout:', error.message); // This is acceptable if spike exceeded timeout } await smtpClient.close(); }); tap.test('cleanup test SMTP server', async () => { if (testServer) { await testServer.stop(); } }); export default tap.start();