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 test SMTP server', async () => { testServer = await startTestServer({ port: 2549, tlsEnabled: false, authRequired: false }); expect(testServer).toBeTruthy(); expect(testServer.port).toBeGreaterThan(0); }); tap.test('CCMD-09: Connection keepalive test', async () => { // NOOP is used internally for keepalive - test that connections remain active smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, greetingTimeout: 5000, socketTimeout: 10000 }); // Send an initial email to establish connection const email1 = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Initial connection test', text: 'Testing connection establishment' }); await smtpClient.sendMail(email1); console.log('First email sent successfully'); // Wait 5 seconds (connection should stay alive with internal NOOP) await new Promise(resolve => setTimeout(resolve, 5000)); // Send another email on the same connection const email2 = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Keepalive test', text: 'Testing connection after delay' }); await smtpClient.sendMail(email2); console.log('Second email sent successfully after 5 second delay'); }); tap.test('CCMD-09: Multiple emails in sequence', async () => { // Test that client can handle multiple emails without issues // Internal NOOP commands may be used between transactions const emails = []; for (let i = 0; i < 5; i++) { emails.push(new Email({ from: 'sender@example.com', to: [`recipient${i}@example.com`], subject: `Sequential email ${i + 1}`, text: `This is email number ${i + 1}` })); } console.log('Sending 5 emails in sequence...'); for (let i = 0; i < emails.length; i++) { await smtpClient.sendMail(emails[i]); console.log(`Email ${i + 1} sent successfully`); // Small delay between emails await new Promise(resolve => setTimeout(resolve, 500)); } console.log('All emails sent successfully'); }); tap.test('CCMD-09: Rapid email sending', async () => { // Test rapid email sending without delays // Internal connection management should handle this properly const emailCount = 10; const emails = []; for (let i = 0; i < emailCount; i++) { emails.push(new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Rapid email ${i + 1}`, text: `Rapid fire email number ${i + 1}` })); } console.log(`Sending ${emailCount} emails rapidly...`); const startTime = Date.now(); // Send all emails as fast as possible for (const email of emails) { await smtpClient.sendMail(email); } const elapsed = Date.now() - startTime; console.log(`All ${emailCount} emails sent in ${elapsed}ms`); console.log(`Average: ${(elapsed / emailCount).toFixed(2)}ms per email`); }); tap.test('CCMD-09: Long-lived connection test', async () => { // Test that connection stays alive over extended period // SmtpClient should use internal keepalive mechanisms console.log('Testing connection over 10 seconds with periodic emails...'); const testDuration = 10000; const emailInterval = 2500; const iterations = Math.floor(testDuration / emailInterval); for (let i = 0; i < iterations; i++) { const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Keepalive test ${i + 1}`, text: `Testing connection keepalive - email ${i + 1}` }); const startTime = Date.now(); await smtpClient.sendMail(email); const elapsed = Date.now() - startTime; console.log(`Email ${i + 1} sent in ${elapsed}ms`); if (i < iterations - 1) { await new Promise(resolve => setTimeout(resolve, emailInterval)); } } console.log('Connection remained stable over 10 seconds'); }); tap.test('CCMD-09: Connection pooling behavior', async () => { // Test connection pooling with different email patterns // Internal NOOP may be used to maintain pool connections const testPatterns = [ { count: 3, delay: 0, desc: 'Burst of 3 emails' }, { count: 2, delay: 1000, desc: '2 emails with 1s delay' }, { count: 1, delay: 3000, desc: '1 email after 3s delay' } ]; for (const pattern of testPatterns) { console.log(`\nTesting: ${pattern.desc}`); if (pattern.delay > 0) { await new Promise(resolve => setTimeout(resolve, pattern.delay)); } for (let i = 0; i < pattern.count; i++) { const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `${pattern.desc} - Email ${i + 1}`, text: 'Testing connection pooling behavior' }); await smtpClient.sendMail(email); } console.log(`Completed: ${pattern.desc}`); } }); tap.test('CCMD-09: Email sending performance', async () => { // Measure email sending performance // Connection management (including internal NOOP) affects timing const measurements = 20; const times: number[] = []; console.log(`Measuring performance over ${measurements} emails...`); for (let i = 0; i < measurements; i++) { const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: `Performance test ${i + 1}`, text: 'Measuring email sending performance' }); const startTime = Date.now(); await smtpClient.sendMail(email); const elapsed = Date.now() - startTime; times.push(elapsed); } // Calculate statistics const avgTime = times.reduce((a, b) => a + b, 0) / times.length; const minTime = Math.min(...times); const maxTime = Math.max(...times); // Calculate standard deviation const variance = times.reduce((sum, time) => sum + Math.pow(time - avgTime, 2), 0) / times.length; const stdDev = Math.sqrt(variance); console.log(`\nPerformance analysis (${measurements} emails):`); console.log(` Average: ${avgTime.toFixed(2)}ms`); console.log(` Min: ${minTime}ms`); console.log(` Max: ${maxTime}ms`); console.log(` Std Dev: ${stdDev.toFixed(2)}ms`); // First email might be slower due to connection establishment const avgWithoutFirst = times.slice(1).reduce((a, b) => a + b, 0) / (times.length - 1); console.log(` Average (excl. first): ${avgWithoutFirst.toFixed(2)}ms`); // Performance should be reasonable expect(avgTime).toBeLessThan(200); }); tap.test('CCMD-09: Email with NOOP in content', async () => { // Test that NOOP as email content doesn't affect delivery const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Email containing NOOP', text: `This email contains SMTP commands as content: NOOP HELO test MAIL FROM: These should be treated as plain text, not commands. The word NOOP appears multiple times in this email. NOOP is used internally by SMTP for keepalive.` }); await smtpClient.sendMail(email); console.log('Email with NOOP content sent successfully'); // Send another email to verify connection still works const email2 = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Follow-up email', text: 'Verifying connection still works after NOOP content' }); await smtpClient.sendMail(email2); console.log('Follow-up email sent successfully'); }); tap.test('CCMD-09: Concurrent email sending', async () => { // Test concurrent email sending // Connection pooling and internal management should handle this const concurrentCount = 5; const emails = []; for (let i = 0; i < concurrentCount; i++) { emails.push(new Email({ from: 'sender@example.com', to: [`recipient${i}@example.com`], subject: `Concurrent email ${i + 1}`, text: `Testing concurrent email sending - message ${i + 1}` })); } console.log(`Sending ${concurrentCount} emails concurrently...`); const startTime = Date.now(); // Send all emails concurrently try { await Promise.all(emails.map(email => smtpClient.sendMail(email))); const elapsed = Date.now() - startTime; console.log(`All ${concurrentCount} emails sent concurrently in ${elapsed}ms`); } catch (error) { // Concurrent sending might not be supported - that's OK console.log('Concurrent sending not supported, falling back to sequential'); for (const email of emails) { await smtpClient.sendMail(email); } } }); tap.test('CCMD-09: Connection recovery test', async () => { // Test connection recovery and error handling // SmtpClient should handle connection issues gracefully // Create a new client with shorter timeouts for testing const testClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 3000, socketTimeout: 3000 }); // Send initial email const email1 = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Connection test 1', text: 'Testing initial connection' }); await testClient.sendMail(email1); console.log('Initial email sent'); // Simulate long delay that might timeout connection console.log('Waiting 5 seconds to test connection recovery...'); await new Promise(resolve => setTimeout(resolve, 5000)); // Try to send another email - client should recover if needed const email2 = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Connection test 2', text: 'Testing connection recovery' }); try { await testClient.sendMail(email2); console.log('Email sent successfully after delay - connection recovered'); } catch (error) { console.log('Connection recovery failed (this might be expected):', error.message); } }); tap.test('cleanup test SMTP server', async () => { if (testServer) { await stopTestServer(testServer); } }); export default tap.start();