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'; import * as net from 'net'; let testServer: ITestServer; tap.test('setup - start SMTP server for timeout tests', async () => { testServer = await startTestServer({ port: 2532, tlsEnabled: false, authRequired: false }); expect(testServer.port).toEqual(2532); }); tap.test('CCM-06: Connection Timeout - should timeout on unresponsive server', async () => { const startTime = Date.now(); const timeoutClient = createSmtpClient({ host: testServer.hostname, port: 9999, // Non-existent port secure: false, connectionTimeout: 2000, // 2 second timeout debug: true }); // verify() returns false on connection failure, doesn't throw const verified = await timeoutClient.verify(); const duration = Date.now() - startTime; expect(verified).toBeFalse(); expect(duration).toBeLessThan(3000); // Should timeout within 3s console.log(`✅ Connection timeout after ${duration}ms`); }); tap.test('CCM-06: Connection Timeout - should handle slow server response', async () => { // Create a mock slow server const slowServer = net.createServer((socket) => { // Accept connection but delay response setTimeout(() => { socket.write('220 Slow server ready\r\n'); }, 3000); // 3 second delay }); await new Promise((resolve) => { slowServer.listen(2533, () => resolve()); }); const startTime = Date.now(); const slowClient = createSmtpClient({ host: 'localhost', port: 2533, secure: false, connectionTimeout: 1000, // 1 second timeout debug: true }); // verify() should return false when server is too slow const verified = await slowClient.verify(); const duration = Date.now() - startTime; expect(verified).toBeFalse(); // Note: actual timeout might be longer due to system defaults console.log(`✅ Slow server timeout after ${duration}ms`); slowServer.close(); await new Promise(resolve => setTimeout(resolve, 100)); }); tap.test('CCM-06: Connection Timeout - should respect socket timeout during data transfer', async () => { const socketTimeoutClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, socketTimeout: 10000, // 10 second socket timeout debug: true }); await socketTimeoutClient.verify(); // Send a normal email const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Socket Timeout Test', text: 'Testing socket timeout configuration' }); const result = await socketTimeoutClient.sendMail(email); expect(result.success).toBeTrue(); await socketTimeoutClient.close(); console.log('✅ Socket timeout configuration applied'); }); tap.test('CCM-06: Connection Timeout - should handle timeout during TLS handshake', async () => { // Create a server that accepts connections but doesn't complete TLS const badTlsServer = net.createServer((socket) => { // Accept connection but don't respond to TLS socket.on('data', () => { // Do nothing - simulate hung TLS handshake }); }); await new Promise((resolve) => { badTlsServer.listen(2534, () => resolve()); }); const startTime = Date.now(); const tlsTimeoutClient = createSmtpClient({ host: 'localhost', port: 2534, secure: true, // Try TLS connectionTimeout: 2000, tls: { rejectUnauthorized: false } }); // verify() should return false when TLS handshake times out const verified = await tlsTimeoutClient.verify(); const duration = Date.now() - startTime; expect(verified).toBeFalse(); // Note: actual timeout might be longer due to system defaults console.log(`✅ TLS handshake timeout after ${duration}ms`); badTlsServer.close(); await new Promise(resolve => setTimeout(resolve, 100)); }); tap.test('CCM-06: Connection Timeout - should not timeout on successful quick connection', async () => { const quickClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 30000, // Very long timeout debug: true }); const startTime = Date.now(); const isConnected = await quickClient.verify(); const duration = Date.now() - startTime; expect(isConnected).toBeTrue(); expect(duration).toBeLessThan(5000); // Should connect quickly await quickClient.close(); console.log(`✅ Quick connection established in ${duration}ms`); }); tap.test('CCM-06: Connection Timeout - should handle timeout during authentication', async () => { // Start auth server const authServer = await startTestServer({ port: 2535, authRequired: true }); // Create mock auth that delays const authTimeoutClient = createSmtpClient({ host: authServer.hostname, port: authServer.port, secure: false, connectionTimeout: 5000, socketTimeout: 1000, // Very short socket timeout auth: { user: 'testuser', pass: 'testpass' } }); try { await authTimeoutClient.verify(); // If this succeeds, auth was fast enough await authTimeoutClient.close(); console.log('✅ Authentication completed within timeout'); } catch (error) { console.log('✅ Authentication timeout handled'); } await stopTestServer(authServer); }); tap.test('CCM-06: Connection Timeout - should apply different timeouts for different operations', async () => { const multiTimeoutClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, // Connection establishment socketTimeout: 30000, // Data operations debug: true }); // Connection should be quick const connectStart = Date.now(); await multiTimeoutClient.verify(); const connectDuration = Date.now() - connectStart; expect(connectDuration).toBeLessThan(5000); // Send email with potentially longer operation const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Multi-timeout Test', text: 'Testing different timeout values', attachments: [{ filename: 'test.txt', content: Buffer.from('Test content'), contentType: 'text/plain' }] }); const sendStart = Date.now(); const result = await multiTimeoutClient.sendMail(email); const sendDuration = Date.now() - sendStart; expect(result.success).toBeTrue(); console.log(`✅ Different timeouts applied: connect=${connectDuration}ms, send=${sendDuration}ms`); await multiTimeoutClient.close(); }); tap.test('CCM-06: Connection Timeout - should retry after timeout with pooled connections', async () => { const retryClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, pool: true, maxConnections: 2, connectionTimeout: 5000, debug: true }); // First connection should succeed const email1 = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Pre-timeout Email', text: 'Before any timeout' }); const result1 = await retryClient.sendMail(email1); expect(result1.success).toBeTrue(); // Pool should handle connection management const poolStatus = retryClient.getPoolStatus(); console.log('📊 Pool status:', poolStatus); await retryClient.close(); console.log('✅ Connection pool handles timeouts gracefully'); }); tap.test('cleanup - stop SMTP server', async () => { await stopTestServer(testServer); }); export default tap.start();