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 { Email } from '../../../ts/mail/core/classes.email.js'; let testServer: ITestServer; tap.test('setup test SMTP server', async () => { testServer = await startTestServer({ port: 2600, tlsEnabled: false, authRequired: false }); expect(testServer).toBeTruthy(); expect(testServer.port).toEqual(2600); }); tap.test('CREL-01: Basic reconnection after close', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // First verify connection works const result1 = await smtpClient.verify(); expect(result1).toBeTrue(); console.log('Initial connection verified'); // Close connection await smtpClient.close(); console.log('Connection closed'); // Verify again - should reconnect automatically const result2 = await smtpClient.verify(); expect(result2).toBeTrue(); console.log('Reconnection successful'); await smtpClient.close(); }); tap.test('CREL-01: Multiple sequential connections', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Send multiple emails with closes in between for (let i = 0; i < 3; i++) { const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Sequential Test ${i + 1}`, text: 'Testing sequential connections' }); const result = await smtpClient.sendMail(email); expect(result.success).toBeTrue(); console.log(`Email ${i + 1} sent successfully`); // Close connection after each send await smtpClient.close(); console.log(`Connection closed after email ${i + 1}`); } }); tap.test('CREL-01: Recovery from server restart', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Send first email const email1 = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Before Server Restart', text: 'Testing server restart recovery' }); const result1 = await smtpClient.sendMail(email1); expect(result1.success).toBeTrue(); console.log('First email sent successfully'); // Simulate server restart by creating a brief interruption console.log('Simulating server restart...'); // The SMTP client should handle the disconnection gracefully // and reconnect for the next operation // Wait a moment await new Promise(resolve => setTimeout(resolve, 1000)); // Try to send another email const email2 = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'After Server Restart', text: 'Testing recovery after restart' }); const result2 = await smtpClient.sendMail(email2); expect(result2.success).toBeTrue(); console.log('Second email sent successfully after simulated restart'); await smtpClient.close(); }); tap.test('CREL-01: Connection pool reliability', async () => { const pooledClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, pool: true, maxConnections: 3, maxMessages: 10, connectionTimeout: 5000, debug: true }); // Send multiple emails concurrently const emails = Array.from({ length: 10 }, (_, i) => new Email({ from: 'sender@example.com', to: [`recipient${i}@example.com`], subject: `Pool Test ${i}`, text: 'Testing connection pool' })); console.log('Sending 10 emails through connection pool...'); const results = await Promise.allSettled( emails.map(email => pooledClient.sendMail(email)) ); const successful = results.filter(r => r.status === 'fulfilled').length; const failed = results.filter(r => r.status === 'rejected').length; console.log(`Pool results: ${successful} successful, ${failed} failed`); expect(successful).toBeGreaterThan(0); // Most should succeed expect(successful).toBeGreaterThanOrEqual(8); await pooledClient.close(); }); tap.test('CREL-01: Rapid connection cycling', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Rapidly open and close connections console.log('Testing rapid connection cycling...'); for (let i = 0; i < 5; i++) { const result = await smtpClient.verify(); expect(result).toBeTrue(); await smtpClient.close(); console.log(`Cycle ${i + 1} completed`); } console.log('Rapid cycling completed successfully'); }); tap.test('CREL-01: Error recovery', async () => { // Test with invalid server first const smtpClient = createSmtpClient({ host: 'invalid.host.local', port: 9999, secure: false, connectionTimeout: 1000, debug: true }); // First attempt should fail const result1 = await smtpClient.verify(); expect(result1).toBeFalse(); console.log('Connection to invalid host failed as expected'); // Now update to valid server (simulating failover) // Since we can't update options, create a new client const recoveredClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Should connect successfully const result2 = await recoveredClient.verify(); expect(result2).toBeTrue(); console.log('Connection to valid host succeeded'); // Send email to verify full functionality const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Recovery Test', text: 'Testing error recovery' }); const sendResult = await recoveredClient.sendMail(email); expect(sendResult.success).toBeTrue(); console.log('Email sent successfully after recovery'); await recoveredClient.close(); }); tap.test('CREL-01: Long-lived connection', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 30000, // 30 second timeout socketTimeout: 30000, debug: true }); console.log('Testing long-lived connection...'); // Send emails over time for (let i = 0; i < 3; i++) { const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Long-lived Test ${i + 1}`, text: `Email ${i + 1} over long-lived connection` }); const result = await smtpClient.sendMail(email); expect(result.success).toBeTrue(); console.log(`Email ${i + 1} sent at ${new Date().toISOString()}`); // Wait between sends if (i < 2) { await new Promise(resolve => setTimeout(resolve, 2000)); } } console.log('Long-lived connection test completed'); await smtpClient.close(); }); tap.test('CREL-01: Concurrent operations', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, pool: true, maxConnections: 5, connectionTimeout: 5000, debug: true }); console.log('Testing concurrent operations...'); // Mix verify and send operations const operations = [ smtpClient.verify(), smtpClient.sendMail(new Email({ from: 'sender@example.com', to: ['recipient1@example.com'], subject: 'Concurrent 1', text: 'First concurrent email' })), smtpClient.verify(), smtpClient.sendMail(new Email({ from: 'sender@example.com', to: ['recipient2@example.com'], subject: 'Concurrent 2', text: 'Second concurrent email' })), smtpClient.verify() ]; const results = await Promise.allSettled(operations); const successful = results.filter(r => r.status === 'fulfilled').length; console.log(`Concurrent operations: ${successful}/${results.length} successful`); expect(successful).toEqual(results.length); await smtpClient.close(); }); tap.test('cleanup test SMTP server', async () => { if (testServer) { await stopTestServer(testServer); } }); export default tap.start();