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; tap.test('setup test SMTP server', async () => { testServer = await startTestServer({ port: 2548, tlsEnabled: false, authRequired: false }); expect(testServer).toBeTruthy(); expect(testServer.port).toBeGreaterThan(0); }); tap.test('CCMD-08: Client handles transaction reset internally', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Send first email const email1 = new Email({ from: 'sender1@example.com', to: 'recipient1@example.com', subject: 'First Email', text: 'This is the first email' }); const result1 = await smtpClient.sendMail(email1); expect(result1.success).toBeTrue(); // Send second email - client handles RSET internally if needed const email2 = new Email({ from: 'sender2@example.com', to: 'recipient2@example.com', subject: 'Second Email', text: 'This is the second email' }); const result2 = await smtpClient.sendMail(email2); expect(result2.success).toBeTrue(); console.log('✅ Client handles transaction reset between emails'); console.log('RSET is used internally to ensure clean state'); await smtpClient.close(); }); tap.test('CCMD-08: Clean state after failed recipient', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Send email with multiple recipients - if one fails, RSET ensures clean state const email = new Email({ from: 'sender@example.com', to: [ 'valid1@example.com', 'valid2@example.com', 'valid3@example.com' ], subject: 'Multi-recipient Email', text: 'Testing state management' }); const result = await smtpClient.sendMail(email); expect(result.success).toBeTrue(); // All recipients should be accepted expect(result.acceptedRecipients.length).toEqual(3); console.log('✅ State remains clean with multiple recipients'); console.log('Internal RSET ensures proper transaction handling'); await smtpClient.close(); }); tap.test('CCMD-08: Multiple emails in sequence', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Send multiple emails in sequence const emails = [ { from: 'sender1@example.com', to: 'recipient1@example.com', subject: 'Email 1', text: 'First email' }, { from: 'sender2@example.com', to: 'recipient2@example.com', subject: 'Email 2', text: 'Second email' }, { from: 'sender3@example.com', to: 'recipient3@example.com', subject: 'Email 3', text: 'Third email' } ]; for (const emailData of emails) { const email = new Email(emailData); const result = await smtpClient.sendMail(email); expect(result.success).toBeTrue(); } console.log('✅ Successfully sent multiple emails in sequence'); console.log('RSET ensures clean state between each transaction'); await smtpClient.close(); }); tap.test('CCMD-08: Connection pooling with clean state', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, pool: true, maxConnections: 2, connectionTimeout: 5000, debug: true }); // Send emails concurrently const promises = Array.from({ length: 5 }, (_, i) => { const email = new Email({ from: `sender${i}@example.com`, to: `recipient${i}@example.com`, subject: `Pooled Email ${i}`, text: `This is pooled email ${i}` }); return smtpClient.sendMail(email); }); const results = await Promise.all(promises); // Check results and log any failures results.forEach((result, index) => { console.log(`Email ${index}: ${result.success ? '✅' : '❌'} ${!result.success ? result.error?.message : ''}`); }); // With connection pooling, at least some emails should succeed const successCount = results.filter(r => r.success).length; console.log(`Successfully sent ${successCount} of ${results.length} emails`); expect(successCount).toBeGreaterThan(0); console.log('✅ Connection pool maintains clean state'); console.log('RSET ensures each pooled connection starts fresh'); await smtpClient.close(); }); tap.test('CCMD-08: Error recovery with state reset', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // First, try with invalid sender (should fail early) try { const badEmail = new Email({ from: '', // Invalid to: 'recipient@example.com', subject: 'Bad Email', text: 'This should fail' }); await smtpClient.sendMail(badEmail); } catch (error) { // Expected to fail console.log('✅ Invalid email rejected as expected'); } // Now send a valid email - should work fine const goodEmail = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Good Email', text: 'This should succeed' }); const result = await smtpClient.sendMail(goodEmail); expect(result.success).toBeTrue(); console.log('✅ State recovered after error'); console.log('RSET ensures clean state after failures'); await smtpClient.close(); }); tap.test('CCMD-08: Verify command maintains session', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // verify() creates temporary connection const verified1 = await smtpClient.verify(); expect(verified1).toBeTrue(); // Send email after verify const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'After Verify', text: 'Email after verification' }); const result = await smtpClient.sendMail(email); expect(result.success).toBeTrue(); // verify() again const verified2 = await smtpClient.verify(); expect(verified2).toBeTrue(); console.log('✅ Verify operations maintain clean session state'); console.log('Each operation ensures proper state management'); await smtpClient.close(); }); tap.test('CCMD-08: Rapid sequential sends', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Send emails rapidly const count = 10; const startTime = Date.now(); for (let i = 0; i < count; i++) { const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: `Rapid Email ${i}`, text: `Rapid test email ${i}` }); const result = await smtpClient.sendMail(email); expect(result.success).toBeTrue(); } const elapsed = Date.now() - startTime; const avgTime = elapsed / count; console.log(`✅ Sent ${count} emails in ${elapsed}ms`); console.log(`Average time per email: ${avgTime.toFixed(2)}ms`); console.log('RSET maintains efficiency in rapid sends'); await smtpClient.close(); }); tap.test('CCMD-08: State isolation between clients', async () => { // Create two separate clients const client1 = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); const client2 = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); // Send from both clients const email1 = new Email({ from: 'client1@example.com', to: 'recipient1@example.com', subject: 'From Client 1', text: 'Email from client 1' }); const email2 = new Email({ from: 'client2@example.com', to: 'recipient2@example.com', subject: 'From Client 2', text: 'Email from client 2' }); // Send concurrently const [result1, result2] = await Promise.all([ client1.sendMail(email1), client2.sendMail(email2) ]); expect(result1.success).toBeTrue(); expect(result2.success).toBeTrue(); console.log('✅ Each client maintains isolated state'); console.log('RSET ensures no cross-contamination'); await client1.close(); await client2.close(); }); tap.test('cleanup test SMTP server', async () => { await stopTestServer(testServer); expect(testServer).toBeTruthy(); }); export default tap.start();