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 - start SMTP server for error handling tests', async () => { testServer = await startTestServer({ port: 2550, tlsEnabled: false, authRequired: false, maxRecipients: 5 // Low limit to trigger errors }); expect(testServer.port).toEqual(2550); }); tap.test('CERR-01: 4xx Errors - should handle invalid recipient (450)', async () => { smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Create email with invalid recipient format const email = new Email({ from: 'test@example.com', to: 'invalid@address@multiple@signs.com', // Invalid format subject: 'Testing 4xx Error', text: 'This should trigger a 4xx error' }); let errorCaught = false; try { await smtpClient.sendMail(email); } catch (error) { errorCaught = true; expect(error).toBeInstanceOf(Error); console.log('✅ Invalid recipient error caught:', error.message); } expect(errorCaught).toBeTrue(); }); tap.test('CERR-01: 4xx Errors - should handle mailbox unavailable (450)', async () => { const email = new Email({ from: 'test@example.com', to: 'nonexistent@localhost', // Local domain should trigger mailbox check subject: 'Mailbox Unavailable Test', text: 'Testing mailbox unavailable error' }); const result = await smtpClient.sendMail(email); // Depending on server configuration, this might be accepted or rejected if (!result.success) { expect(result.error).toBeInstanceOf(Error); console.log('✅ Mailbox unavailable handled:', result.error?.message); } else { // Some test servers accept all recipients expect(result.acceptedRecipients.length).toBeGreaterThan(0); console.log('ℹ️ Test server accepted recipient (common in test environments)'); } }); tap.test('CERR-01: 4xx Errors - should handle quota exceeded (452)', async () => { // Send multiple emails to trigger quota/limit errors const emails = []; for (let i = 0; i < 10; i++) { emails.push(new Email({ from: 'test@example.com', to: `recipient${i}@example.com`, subject: `Quota Test ${i}`, text: 'Testing quota limits' })); } let quotaErrorCount = 0; const results = await Promise.allSettled( emails.map(email => smtpClient.sendMail(email)) ); results.forEach((result, index) => { if (result.status === 'rejected') { quotaErrorCount++; console.log(`Email ${index} rejected:`, result.reason); } }); console.log(`✅ Handled ${quotaErrorCount} quota-related errors`); }); tap.test('CERR-01: 4xx Errors - should handle too many recipients (452)', async () => { // Create email with many recipients to exceed limit const recipients = []; for (let i = 0; i < 10; i++) { recipients.push(`recipient${i}@example.com`); } const email = new Email({ from: 'test@example.com', to: recipients, // Many recipients subject: 'Too Many Recipients Test', text: 'Testing recipient limit' }); const result = await smtpClient.sendMail(email); // Check if some recipients were rejected due to limits if (result.rejectedRecipients.length > 0) { console.log(`✅ Rejected ${result.rejectedRecipients.length} recipients due to limits`); expect(result.rejectedRecipients).toBeArray(); } else { // Server might accept all expect(result.acceptedRecipients.length).toEqual(recipients.length); console.log('ℹ️ Server accepted all recipients'); } }); tap.test('CERR-01: 4xx Errors - should handle authentication required (450)', async () => { // Create new server requiring auth const authServer = await startTestServer({ port: 2551, authRequired: true // This will reject unauthenticated commands }); const unauthClient = createSmtpClient({ host: authServer.hostname, port: authServer.port, secure: false, // No auth credentials provided connectionTimeout: 5000 }); const email = new Email({ from: 'test@example.com', to: 'recipient@example.com', subject: 'Auth Required Test', text: 'Should fail without auth' }); let authError = false; try { await unauthClient.sendMail(email); } catch (error) { authError = true; expect(error).toBeInstanceOf(Error); console.log('✅ Authentication required error caught'); } expect(authError).toBeTrue(); await unauthClient.close(); await stopTestServer(authServer); }); tap.test('CERR-01: 4xx Errors - should parse enhanced status codes', async () => { // 4xx errors often include enhanced status codes (e.g., 4.7.1) const email = new Email({ from: 'test@blocked-domain.com', // Might trigger policy rejection to: 'recipient@example.com', subject: 'Enhanced Status Code Test', text: 'Testing enhanced status codes' }); try { const result = await smtpClient.sendMail(email); if (!result.success && result.error) { console.log('✅ Error details:', { message: result.error.message, response: result.response }); } } catch (error: any) { // Check if error includes status information expect(error.message).toBeTypeofString(); console.log('✅ Error with potential enhanced status:', error.message); } }); tap.test('CERR-01: 4xx Errors - should not retry permanent 4xx errors', async () => { // Track retry attempts let attemptCount = 0; const trackingClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); // Monitor connection attempts trackingClient.on('connect', () => attemptCount++); const email = new Email({ from: 'invalid sender format', // Clearly invalid to: 'recipient@example.com', subject: 'Permanent Error Test', text: 'Should not retry' }); try { await trackingClient.sendMail(email); } catch (error) { console.log('✅ Permanent error not retried'); } // Should not have retried expect(attemptCount).toBeLessThanOrEqual(1); await trackingClient.close(); }); tap.test('cleanup - close SMTP client', async () => { if (smtpClient && smtpClient.isConnected()) { await smtpClient.close(); } }); tap.test('cleanup - stop SMTP server', async () => { await stopTestServer(testServer); }); tap.start();