import { tap, expect } from '@git.zone/tstest/tapbundle'; import { createTestServer } from '../../helpers/server.loader.js'; import { createTestSmtpClient } from '../../helpers/smtp.client.js'; import { Email } from '../../../ts/mail/core/classes.email.js'; // Helper function to count active resources const getResourceCounts = () => { const usage = process.memoryUsage(); return { memory: Math.round(usage.heapUsed / 1024 / 1024 * 100) / 100, // MB handles: (process as any)._getActiveHandles ? (process as any)._getActiveHandles().length : 0, requests: (process as any)._getActiveRequests ? (process as any)._getActiveRequests().length : 0 }; }; // Scenario 1: Basic Resource Cleanup tap.test('CREL-07: Basic Resource Cleanup', async () => { console.log('\n๐Ÿงน Testing SMTP Client Resource Cleanup'); console.log('=' .repeat(60)); let connections = 0; let disconnections = 0; const testServer = await createTestServer({ onConnection: (socket: any) => { connections++; console.log(` [Server] Connection opened (total: ${connections})`); socket.on('close', () => { disconnections++; console.log(` [Server] Connection closed (total closed: ${disconnections})`); }); } }); try { const initialResources = getResourceCounts(); console.log(` Initial resources: ${initialResources.memory}MB memory, ${initialResources.handles} handles`); console.log(' Creating SMTP clients and sending emails...'); const clients = []; // Create multiple clients for (let i = 0; i < 3; i++) { const smtpClient = createTestSmtpClient({ host: testServer.hostname, port: testServer.port }); clients.push(smtpClient); // Send a test email const email = new Email({ from: `sender${i}@cleanup.test`, to: [`recipient${i}@cleanup.test`], subject: `Cleanup Test ${i + 1}`, text: `Testing connection cleanup ${i + 1}` }); try { await smtpClient.sendMail(email); console.log(` โœ“ Client ${i + 1} email sent`); } catch (error) { console.log(` โœ— Client ${i + 1} failed: ${error.message}`); } } const afterSending = getResourceCounts(); console.log(` After sending: ${afterSending.memory}MB memory, ${afterSending.handles} handles`); console.log(' Closing all clients...'); for (let i = 0; i < clients.length; i++) { console.log(` Closing client ${i + 1}...`); clients[i].close(); await new Promise(resolve => setTimeout(resolve, 100)); } // Wait for cleanup to complete await new Promise(resolve => setTimeout(resolve, 500)); const finalResources = getResourceCounts(); console.log(`\n Resource cleanup assessment:`); console.log(` Initial: ${initialResources.memory}MB memory, ${initialResources.handles} handles`); console.log(` Final: ${finalResources.memory}MB memory, ${finalResources.handles} handles`); console.log(` Connections opened: ${connections}`); console.log(` Connections closed: ${disconnections}`); console.log(` Cleanup: ${disconnections >= connections - 1 ? 'Complete' : 'Incomplete'}`); expect(disconnections).toBeGreaterThanOrEqual(connections - 1); } finally { testServer.server.close(); } }); // Scenario 2: Multiple Close Safety tap.test('CREL-07: Multiple Close Safety', async () => { console.log('\n๐Ÿ” Testing multiple close calls safety...'); const testServer = await createTestServer({}); try { const smtpClient = createTestSmtpClient({ host: testServer.hostname, port: testServer.port }); // Send a test email const email = new Email({ from: 'sender@multiclose.test', to: ['recipient@multiclose.test'], subject: 'Multiple Close Test', text: 'Testing multiple close calls' }); console.log(' Sending test email...'); await smtpClient.sendMail(email); console.log(' โœ“ Email sent successfully'); console.log(' Attempting multiple close calls...'); let closeErrors = 0; for (let i = 0; i < 5; i++) { try { smtpClient.close(); console.log(` โœ“ Close call ${i + 1} completed`); } catch (error) { closeErrors++; console.log(` โœ— Close call ${i + 1} error: ${error.message}`); } } console.log(` Close errors: ${closeErrors}`); console.log(` Safety: ${closeErrors === 0 ? 'Safe' : 'Issues detected'}`); expect(closeErrors).toEqual(0); } finally { testServer.server.close(); } }); // Scenario 3: Error Recovery and Cleanup tap.test('CREL-07: Error Recovery and Cleanup', async () => { console.log('\nโŒ Testing error recovery and cleanup...'); let errorMode = false; let requestCount = 0; const testServer = await createTestServer({ onConnection: (socket: any) => { requestCount++; if (errorMode && requestCount % 2 === 0) { console.log(` [Server] Simulating connection error`); setTimeout(() => socket.destroy(), 50); } } }); try { const smtpClient = createTestSmtpClient({ host: testServer.hostname, port: testServer.port, connectionTimeout: 2000 }); console.log(' Phase 1: Normal operation...'); const normalEmail = new Email({ from: 'sender@test.com', to: ['recipient@test.com'], subject: 'Normal Test', text: 'Testing normal operation' }); let normalResult = false; try { await smtpClient.sendMail(normalEmail); normalResult = true; console.log(' โœ“ Normal operation successful'); } catch (error) { console.log(' โœ— Normal operation failed'); } console.log(' Phase 2: Error injection...'); errorMode = true; let errorCount = 0; for (let i = 0; i < 3; i++) { try { const errorEmail = new Email({ from: 'sender@error.test', to: ['recipient@error.test'], subject: `Error Test ${i + 1}`, text: 'Testing error handling' }); await smtpClient.sendMail(errorEmail); console.log(` โœ“ Email ${i + 1} sent (recovered)`); } catch (error) { errorCount++; console.log(` โœ— Email ${i + 1} failed as expected`); } } console.log(' Phase 3: Recovery...'); errorMode = false; const recoveryEmail = new Email({ from: 'sender@recovery.test', to: ['recipient@recovery.test'], subject: 'Recovery Test', text: 'Testing recovery' }); let recovered = false; try { await smtpClient.sendMail(recoveryEmail); recovered = true; console.log(' โœ“ Recovery successful'); } catch (error) { console.log(' โœ— Recovery failed'); } // Close and cleanup smtpClient.close(); console.log(`\n Error recovery assessment:`); console.log(` Normal operation: ${normalResult ? 'Success' : 'Failed'}`); console.log(` Errors encountered: ${errorCount}`); console.log(` Recovery: ${recovered ? 'Successful' : 'Failed'}`); expect(normalResult).toEqual(true); expect(errorCount).toBeGreaterThan(0); } finally { testServer.server.close(); } }); // Scenario 4: Rapid Connect/Disconnect tap.test('CREL-07: Rapid Connect/Disconnect Cycles', async () => { console.log('\nโšก Testing rapid connect/disconnect cycles...'); const testServer = await createTestServer({}); try { console.log(' Performing rapid connect/disconnect cycles...'); let successful = 0; let failed = 0; for (let cycle = 0; cycle < 5; cycle++) { const smtpClient = createTestSmtpClient({ host: testServer.hostname, port: testServer.port, connectionTimeout: 1000 }); try { // Quick verify to establish connection await smtpClient.verify(); successful++; console.log(` โœ“ Cycle ${cycle + 1}: Connected`); } catch (error) { failed++; console.log(` โœ— Cycle ${cycle + 1}: Failed`); } // Immediately close smtpClient.close(); // Brief pause between cycles await new Promise(resolve => setTimeout(resolve, 50)); } console.log(`\n Rapid cycle results:`); console.log(` Successful connections: ${successful}`); console.log(` Failed connections: ${failed}`); console.log(` Success rate: ${(successful / (successful + failed) * 100).toFixed(1)}%`); expect(successful).toBeGreaterThan(0); } finally { testServer.server.close(); } }); tap.test('CREL-07: Test Summary', async () => { console.log('\nโœ… CREL-07: Resource Cleanup Reliability Tests completed'); console.log('๐Ÿงน All resource cleanup scenarios tested successfully'); }); tap.start();