import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; import { createSmtpClient, createPooledSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js'; import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.js'; import { Email } from '../../../ts/mail/core/classes.email.js'; import * as net from 'net'; let testServer: ITestServer; tap.test('setup - start SMTP server for reconnection tests', async () => { testServer = await startTestServer({ port: 2533, tlsEnabled: false, authRequired: false }); expect(testServer.port).toEqual(2533); }); tap.test('CCM-07: Automatic Reconnection - should reconnect after connection loss', async () => { const client = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // First connection and email const email1 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Before Disconnect', text: 'First email before connection loss' }); const result1 = await client.sendMail(email1); expect(result1.success).toBeTrue(); expect(client.isConnected()).toBeTrue(); // Force disconnect await client.close(); expect(client.isConnected()).toBeFalse(); // Try to send another email - should auto-reconnect const email2 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'After Reconnect', text: 'Email after automatic reconnection' }); const result2 = await client.sendMail(email2); expect(result2.success).toBeTrue(); expect(client.isConnected()).toBeTrue(); await client.close(); console.log('✅ Automatic reconnection successful'); }); tap.test('CCM-07: Automatic Reconnection - pooled client should reconnect failed connections', async () => { const pooledClient = createPooledSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, maxConnections: 3, connectionTimeout: 5000, debug: true }); // Send emails to establish pool connections const promises = []; for (let i = 0; i < 3; i++) { const email = new Email({ from: 'sender@example.com', to: `recipient${i}@example.com`, subject: `Pool Test ${i}`, text: 'Testing connection pool' }); promises.push(pooledClient.sendMail(email)); } await Promise.all(promises); const poolStatus1 = pooledClient.getPoolStatus(); console.log('📊 Pool status before disruption:', poolStatus1); // Send more emails - pool should handle any connection issues const promises2 = []; for (let i = 0; i < 5; i++) { const email = new Email({ from: 'sender@example.com', to: `recipient${i}@example.com`, subject: `Pool Recovery ${i}`, text: 'Testing pool recovery' }); promises2.push(pooledClient.sendMail(email)); } const results = await Promise.all(promises2); results.forEach(result => { expect(result.success).toBeTrue(); }); const poolStatus2 = pooledClient.getPoolStatus(); console.log('📊 Pool status after recovery:', poolStatus2); await pooledClient.close(); console.log('✅ Connection pool handles reconnection automatically'); }); tap.test('CCM-07: Automatic Reconnection - should handle server restart', async () => { // Create client const client = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); // Send first email const email1 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Before Server Restart', text: 'Email before server restart' }); const result1 = await client.sendMail(email1); expect(result1.success).toBeTrue(); // Simulate server restart console.log('🔄 Simulating server restart...'); await stopTestServer(testServer); await new Promise(resolve => setTimeout(resolve, 1000)); // Restart server on same port testServer = await startTestServer({ port: 2533, tlsEnabled: false, authRequired: false }); // Try to send another email const email2 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'After Server Restart', text: 'Email after server restart' }); const result2 = await client.sendMail(email2); expect(result2.success).toBeTrue(); await client.close(); console.log('✅ Client recovered from server restart'); }); tap.test('CCM-07: Automatic Reconnection - should handle network interruption', async () => { const client = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, socketTimeout: 10000 }); // Establish connection await client.verify(); // Send emails with simulated network issues for (let i = 0; i < 3; i++) { const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: `Network Test ${i}`, text: `Testing network resilience ${i}` }); try { const result = await client.sendMail(email); expect(result.success).toBeTrue(); console.log(`✅ Email ${i + 1} sent successfully`); } catch (error) { console.log(`⚠️ Email ${i + 1} failed, will retry`); // Client should recover on next attempt } // Add small delay between sends await new Promise(resolve => setTimeout(resolve, 100)); } await client.close(); }); tap.test('CCM-07: Automatic Reconnection - should limit reconnection attempts', async () => { // Connect to a port that will be closed const tempServer = net.createServer(); await new Promise((resolve) => { tempServer.listen(2534, () => resolve()); }); const client = createSmtpClient({ host: 'localhost', port: 2534, secure: false, connectionTimeout: 2000 }); // Close the server to simulate failure tempServer.close(); await new Promise(resolve => setTimeout(resolve, 100)); let errorCount = 0; const maxAttempts = 3; // Try multiple times for (let i = 0; i < maxAttempts; i++) { try { await client.verify(); } catch (error) { errorCount++; } } expect(errorCount).toEqual(maxAttempts); console.log('✅ Reconnection attempts are limited to prevent infinite loops'); }); tap.test('CCM-07: Automatic Reconnection - should maintain state after reconnect', async () => { const client = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); // Send email with specific settings const email1 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'State Test 1', text: 'Testing state persistence', priority: 'high', headers: { 'X-Test-ID': 'test-123' } }); const result1 = await client.sendMail(email1); expect(result1.success).toBeTrue(); // Force reconnection await client.close(); // Send another email - client state should be maintained const email2 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'State Test 2', text: 'After reconnection', priority: 'high', headers: { 'X-Test-ID': 'test-456' } }); const result2 = await client.sendMail(email2); expect(result2.success).toBeTrue(); await client.close(); console.log('✅ Client state maintained after reconnection'); }); tap.test('CCM-07: Automatic Reconnection - should handle rapid reconnections', async () => { const client = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); // Rapid connect/disconnect cycles for (let i = 0; i < 5; i++) { const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: `Rapid Test ${i}`, text: 'Testing rapid reconnections' }); const result = await client.sendMail(email); expect(result.success).toBeTrue(); // Force disconnect await client.close(); // No delay - immediate next attempt } console.log('✅ Rapid reconnections handled successfully'); }); tap.test('cleanup - stop SMTP server', async () => { await stopTestServer(testServer); }); tap.start();