import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; import { 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'; let testServer: ITestServer; let pooledClient: SmtpClient; tap.test('setup - start SMTP server for pooling test', async () => { testServer = await startTestServer({ port: 2530, tlsEnabled: false, authRequired: false, maxConnections: 10 }); expect(testServer.port).toEqual(2530); }); tap.test('CCM-04: Connection Pooling - should create pooled client', async () => { const startTime = Date.now(); try { // Create pooled SMTP client pooledClient = createPooledSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, maxConnections: 5, maxMessages: 100, connectionTimeout: 5000, debug: true }); // Verify connection pool is working const isConnected = await pooledClient.verify(); expect(isConnected).toBeTrue(); const poolStatus = pooledClient.getPoolStatus(); console.log('📊 Initial pool status:', poolStatus); expect(poolStatus.total).toBeGreaterThanOrEqual(0); const duration = Date.now() - startTime; console.log(`✅ Connection pool created in ${duration}ms`); } catch (error) { const duration = Date.now() - startTime; console.error(`❌ Connection pool creation failed after ${duration}ms:`, error); throw error; } }); tap.test('CCM-04: Connection Pooling - should handle concurrent connections', async () => { // Send multiple emails concurrently const emailPromises = []; const concurrentCount = 5; for (let i = 0; i < concurrentCount; i++) { const email = new Email({ from: 'test@example.com', to: `recipient${i}@example.com`, subject: `Concurrent Email ${i}`, text: `This is concurrent email number ${i}` }); emailPromises.push( pooledClient.sendMail(email).catch(error => { console.error(`❌ Failed to send email ${i}:`, error); return { success: false, error: error.message, acceptedRecipients: [] }; }) ); } // Wait for all emails to be sent const results = await Promise.all(emailPromises); // Check results and count successes let successCount = 0; results.forEach((result, index) => { if (result.success) { successCount++; expect(result.acceptedRecipients).toContain(`recipient${index}@example.com`); } else { console.log(`Email ${index} failed:`, result.error); } }); // At least some emails should succeed with pooling expect(successCount).toBeGreaterThan(0); console.log(`✅ Sent ${successCount}/${concurrentCount} emails successfully`); // Check pool status after concurrent sends const poolStatus = pooledClient.getPoolStatus(); console.log('📊 Pool status after concurrent sends:', poolStatus); expect(poolStatus.total).toBeGreaterThanOrEqual(1); expect(poolStatus.total).toBeLessThanOrEqual(5); // Should not exceed max }); tap.test('CCM-04: Connection Pooling - should reuse connections', async () => { // Get initial pool status const initialStatus = pooledClient.getPoolStatus(); console.log('📊 Initial status:', initialStatus); // Send emails sequentially to test connection reuse const emailCount = 10; const connectionCounts = []; for (let i = 0; i < emailCount; i++) { const email = new Email({ from: 'test@example.com', to: 'recipient@example.com', subject: `Sequential Email ${i}`, text: `Testing connection reuse - email ${i}` }); await pooledClient.sendMail(email); const status = pooledClient.getPoolStatus(); connectionCounts.push(status.total); } // Check that connections were reused (total shouldn't grow linearly) const maxConnections = Math.max(...connectionCounts); expect(maxConnections).toBeLessThan(emailCount); // Should reuse connections console.log(`✅ Sent ${emailCount} emails using max ${maxConnections} connections`); console.log('📊 Connection counts:', connectionCounts); }); tap.test('CCM-04: Connection Pooling - should respect max connections limit', async () => { // Create a client with small pool const limitedClient = createPooledSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, maxConnections: 2, // Very small pool connectionTimeout: 5000 }); // Send many concurrent emails const emailPromises = []; for (let i = 0; i < 10; i++) { const email = new Email({ from: 'test@example.com', to: `test${i}@example.com`, subject: `Pool Limit Test ${i}`, text: 'Testing pool limits' }); emailPromises.push(limitedClient.sendMail(email)); } // Monitor pool during sending const checkInterval = setInterval(() => { const status = limitedClient.getPoolStatus(); console.log('📊 Pool status during load:', status); expect(status.total).toBeLessThanOrEqual(2); // Should never exceed max }, 100); await Promise.all(emailPromises); clearInterval(checkInterval); await limitedClient.close(); console.log('✅ Connection pool respected max connections limit'); }); tap.test('CCM-04: Connection Pooling - should handle connection failures in pool', async () => { // Create a new pooled client const resilientClient = createPooledSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, maxConnections: 3, connectionTimeout: 5000 }); // Send some emails successfully for (let i = 0; i < 3; i++) { const email = new Email({ from: 'test@example.com', to: 'recipient@example.com', subject: `Pre-failure Email ${i}`, text: 'Before simulated failure' }); const result = await resilientClient.sendMail(email); expect(result.success).toBeTrue(); } // Pool should recover and continue working const poolStatus = resilientClient.getPoolStatus(); console.log('📊 Pool status after recovery test:', poolStatus); expect(poolStatus.total).toBeGreaterThanOrEqual(1); await resilientClient.close(); console.log('✅ Connection pool handled failures gracefully'); }); tap.test('CCM-04: Connection Pooling - should clean up idle connections', async () => { // Create client with specific idle settings const idleClient = createPooledSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, maxConnections: 5, connectionTimeout: 5000 }); // Send burst of emails const promises = []; for (let i = 0; i < 5; i++) { const email = new Email({ from: 'test@example.com', to: 'recipient@example.com', subject: `Idle Test ${i}`, text: 'Testing idle cleanup' }); promises.push(idleClient.sendMail(email)); } await Promise.all(promises); const activeStatus = idleClient.getPoolStatus(); console.log('📊 Pool status after burst:', activeStatus); // Wait for connections to become idle await new Promise(resolve => setTimeout(resolve, 2000)); const idleStatus = idleClient.getPoolStatus(); console.log('📊 Pool status after idle period:', idleStatus); await idleClient.close(); console.log('✅ Idle connection management working'); }); tap.test('cleanup - close pooled client', async () => { if (pooledClient && pooledClient.isConnected()) { await pooledClient.close(); // Verify pool is cleaned up const finalStatus = pooledClient.getPoolStatus(); console.log('📊 Final pool status:', finalStatus); } }); tap.test('cleanup - stop SMTP server', async () => { await stopTestServer(testServer); }); export default tap.start();