import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; import { createSmtpClient } 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'; let testServer: ITestServer; let smtpClient: SmtpClient; tap.test('setup - start SMTP server for connection reuse test', async () => { testServer = await startTestServer({ port: 2531, tlsEnabled: false, authRequired: false }); expect(testServer.port).toEqual(2531); }); tap.test('CCM-05: Connection Reuse - should reuse single connection for multiple emails', async () => { const startTime = Date.now(); smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Verify initial connection await smtpClient.verify(); expect(smtpClient.isConnected()).toBeTrue(); // Send multiple emails on same connection const emailCount = 5; const results = []; for (let i = 0; i < emailCount; i++) { const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: `Connection Reuse Test ${i + 1}`, text: `This is email ${i + 1} using the same connection` }); const result = await smtpClient.sendMail(email); results.push(result); // Connection should remain open expect(smtpClient.isConnected()).toBeTrue(); } // All emails should succeed results.forEach((result, index) => { expect(result.success).toBeTrue(); console.log(`✅ Email ${index + 1} sent successfully`); }); const duration = Date.now() - startTime; console.log(`✅ Sent ${emailCount} emails on single connection in ${duration}ms`); }); tap.test('CCM-05: Connection Reuse - should track message count per connection', async () => { // Create a new client with message limit const limitedClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, maxMessages: 3, // Limit messages per connection connectionTimeout: 5000 }); // Send emails up to and beyond the limit for (let i = 0; i < 5; i++) { const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: `Message Limit Test ${i + 1}`, text: `Testing message limits` }); const result = await limitedClient.sendMail(email); expect(result.success).toBeTrue(); // After 3 messages, connection should be refreshed if (i === 2) { console.log('✅ Connection should refresh after message limit'); } } await limitedClient.close(); }); tap.test('CCM-05: Connection Reuse - should handle connection state changes', async () => { // Monitor connection state during reuse let connectionEvents = 0; const eventClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); eventClient.on('connect', () => connectionEvents++); // First email const email1 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'First Email', text: 'Testing connection events' }); await eventClient.sendMail(email1); const firstConnectCount = connectionEvents; // Second email (should reuse connection) const email2 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Second Email', text: 'Should reuse connection' }); await eventClient.sendMail(email2); // Should not have created new connection expect(connectionEvents).toEqual(firstConnectCount); await eventClient.close(); console.log(`✅ Connection reused (${connectionEvents} total connections)`); }); tap.test('CCM-05: Connection Reuse - should handle idle connection timeout', async () => { const idleClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, socketTimeout: 3000 // Short timeout for testing }); // Send first email const email1 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Pre-idle Email', text: 'Before idle period' }); await idleClient.sendMail(email1); expect(idleClient.isConnected()).toBeTrue(); // Wait for potential idle timeout console.log('⏳ Testing idle connection behavior...'); await new Promise(resolve => setTimeout(resolve, 4000)); // Send another email const email2 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Post-idle Email', text: 'After idle period' }); // Should handle reconnection if needed const result = await idleClient.sendMail(email2); expect(result.success).toBeTrue(); await idleClient.close(); console.log('✅ Idle connection handling working correctly'); }); tap.test('CCM-05: Connection Reuse - should optimize performance with reuse', async () => { // Compare performance with and without connection reuse // Test 1: Multiple connections (no reuse) const noReuseStart = Date.now(); for (let i = 0; i < 3; i++) { const tempClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: `No Reuse ${i}`, text: 'Testing without reuse' }); await tempClient.sendMail(email); await tempClient.close(); } const noReuseDuration = Date.now() - noReuseStart; // Test 2: Single connection (with reuse) const reuseStart = Date.now(); const reuseClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); for (let i = 0; i < 3; i++) { const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: `With Reuse ${i}`, text: 'Testing with reuse' }); await reuseClient.sendMail(email); } await reuseClient.close(); const reuseDuration = Date.now() - reuseStart; console.log(`📊 Performance comparison:`); console.log(` Without reuse: ${noReuseDuration}ms`); console.log(` With reuse: ${reuseDuration}ms`); console.log(` Improvement: ${Math.round((1 - reuseDuration/noReuseDuration) * 100)}%`); // Reuse should be faster expect(reuseDuration).toBeLessThan(noReuseDuration); }); tap.test('CCM-05: Connection Reuse - should handle errors without breaking reuse', async () => { const resilientClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); // Send valid email const validEmail = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Valid Email', text: 'This should work' }); const result1 = await resilientClient.sendMail(validEmail); expect(result1.success).toBeTrue(); // Try to send invalid email try { const invalidEmail = new Email({ from: 'invalid sender format', to: 'recipient@example.com', subject: 'Invalid Email', text: 'This should fail' }); await resilientClient.sendMail(invalidEmail); } catch (error) { console.log('✅ Invalid email rejected as expected'); } // Connection should still be usable const validEmail2 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Valid Email After Error', text: 'Connection should still work' }); const result2 = await resilientClient.sendMail(validEmail2); expect(result2.success).toBeTrue(); await resilientClient.close(); console.log('✅ Connection reuse survived error condition'); }); tap.test('cleanup - close SMTP client', async () => { if (smtpClient && smtpClient.isConnected()) { await smtpClient.close(); } }); tap.test('cleanup - stop SMTP server', async () => { await stopTestServer(testServer); }); tap.start();