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 { EmailValidator } from '../../../ts/mail/core/classes.emailvalidator.js'; let testServer: ITestServer; let smtpClient: SmtpClient; tap.test('setup test SMTP server', async () => { testServer = await startTestServer({ port: 2550, tlsEnabled: false, authRequired: false }); expect(testServer).toBeTruthy(); expect(testServer.port).toBeGreaterThan(0); smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); }); tap.test('CCMD-10: Email address validation', async () => { // Test email address validation which is what VRFY conceptually does const validator = new EmailValidator(); const testAddresses = [ { address: 'user@example.com', expected: true }, { address: 'postmaster@example.com', expected: true }, { address: 'admin@example.com', expected: true }, { address: 'user.name+tag@example.com', expected: true }, { address: 'test@sub.domain.example.com', expected: true }, { address: 'invalid@', expected: false }, { address: '@example.com', expected: false }, { address: 'not-an-email', expected: false }, { address: '', expected: false }, { address: 'user@', expected: false } ]; console.log('Testing email address validation (VRFY equivalent):\n'); for (const test of testAddresses) { const isValid = validator.isValidFormat(test.address); expect(isValid).toEqual(test.expected); console.log(`Address: "${test.address}" - Valid: ${isValid} (expected: ${test.expected})`); } // Test sending to valid addresses const validEmail = new Email({ from: 'sender@example.com', to: ['user@example.com'], subject: 'Address validation test', text: 'Testing address validation' }); await smtpClient.sendMail(validEmail); console.log('\nEmail sent successfully to validated address'); }); tap.test('CCMD-10: Multiple recipient handling (EXPN equivalent)', async () => { // Test multiple recipients which is conceptually similar to mailing list expansion console.log('Testing multiple recipient handling (EXPN equivalent):\n'); // Create email with multiple recipients (like a mailing list) const multiRecipientEmail = new Email({ from: 'sender@example.com', to: [ 'user1@example.com', 'user2@example.com', 'user3@example.com' ], cc: [ 'cc1@example.com', 'cc2@example.com' ], bcc: [ 'bcc1@example.com' ], subject: 'Multi-recipient test (mailing list)', text: 'Testing email distribution to multiple recipients' }); const toAddresses = multiRecipientEmail.getToAddresses(); const ccAddresses = multiRecipientEmail.getCcAddresses(); const bccAddresses = multiRecipientEmail.getBccAddresses(); console.log(`To recipients: ${toAddresses.length}`); toAddresses.forEach(addr => console.log(` - ${addr}`)); console.log(`\nCC recipients: ${ccAddresses.length}`); ccAddresses.forEach(addr => console.log(` - ${addr}`)); console.log(`\nBCC recipients: ${bccAddresses.length}`); bccAddresses.forEach(addr => console.log(` - ${addr}`)); console.log(`\nTotal recipients: ${toAddresses.length + ccAddresses.length + bccAddresses.length}`); // Send the email await smtpClient.sendMail(multiRecipientEmail); console.log('\nEmail sent successfully to all recipients'); }); tap.test('CCMD-10: Email addresses with display names', async () => { // Test email addresses with display names (full names) console.log('Testing email addresses with display names:\n'); const fullNameTests = [ { from: '"John Doe" ', expectedAddress: 'john@example.com' }, { from: '"Smith, John" ', expectedAddress: 'john.smith@example.com' }, { from: 'Mary Johnson ', expectedAddress: 'mary@example.com' }, { from: '', expectedAddress: 'bob@example.com' } ]; for (const test of fullNameTests) { const email = new Email({ from: test.from, to: ['recipient@example.com'], subject: 'Display name test', text: `Testing from: ${test.from}` }); const fromAddress = email.getFromAddress(); console.log(`Full: "${test.from}"`); console.log(`Extracted: "${fromAddress}"`); expect(fromAddress).toEqual(test.expectedAddress); await smtpClient.sendMail(email); console.log('Email sent successfully\n'); } }); tap.test('CCMD-10: Email validation security', async () => { // Test security aspects of email validation console.log('Testing email validation security considerations:\n'); // Test common system/role addresses that should be handled carefully const systemAddresses = [ 'root@example.com', 'admin@example.com', 'administrator@example.com', 'webmaster@example.com', 'hostmaster@example.com', 'abuse@example.com', 'postmaster@example.com', 'noreply@example.com' ]; const validator = new EmailValidator(); console.log('Checking if addresses are role accounts:'); for (const addr of systemAddresses) { const validationResult = await validator.validate(addr, { checkRole: true, checkMx: false }); console.log(` ${addr}: ${validationResult.details?.role ? 'Role account' : 'Not a role account'} (format valid: ${validationResult.details?.formatValid})`); } // Test that we don't expose information about which addresses exist console.log('\nTesting information disclosure prevention:'); try { // Try sending to a non-existent address const testEmail = new Email({ from: 'sender@example.com', to: ['definitely-does-not-exist-12345@example.com'], subject: 'Test', text: 'Test' }); await smtpClient.sendMail(testEmail); console.log('Server accepted email (does not disclose non-existence)'); } catch (error) { console.log('Server rejected email:', error.message); } console.log('\nSecurity best practice: Servers should not disclose address existence'); }); tap.test('CCMD-10: Validation during email sending', async () => { // Test that validation doesn't interfere with email sending console.log('Testing validation during email transaction:\n'); const validator = new EmailValidator(); // Create a series of emails with validation between them const emails = [ { from: 'sender1@example.com', to: ['recipient1@example.com'], subject: 'First email', text: 'Testing validation during transaction' }, { from: 'sender2@example.com', to: ['recipient2@example.com', 'recipient3@example.com'], subject: 'Second email', text: 'Multiple recipients' }, { from: '"Test User" ', to: ['recipient4@example.com'], subject: 'Third email', text: 'Display name test' } ]; for (let i = 0; i < emails.length; i++) { const emailData = emails[i]; // Validate addresses before sending console.log(`Email ${i + 1}:`); const fromAddr = emailData.from.includes('<') ? emailData.from.match(/<([^>]+)>/)?.[1] || emailData.from : emailData.from; console.log(` From: ${emailData.from} - Valid: ${validator.isValidFormat(fromAddr)}`); for (const to of emailData.to) { console.log(` To: ${to} - Valid: ${validator.isValidFormat(to)}`); } // Create and send email const email = new Email(emailData); await smtpClient.sendMail(email); console.log(` Sent successfully\n`); } console.log('All emails sent successfully with validation'); }); tap.test('CCMD-10: Special characters in email addresses', async () => { // Test email addresses with special characters console.log('Testing email addresses with special characters:\n'); const validator = new EmailValidator(); const specialAddresses = [ { address: 'user+tag@example.com', shouldBeValid: true, description: 'Plus addressing' }, { address: 'first.last@example.com', shouldBeValid: true, description: 'Dots in local part' }, { address: 'user_name@example.com', shouldBeValid: true, description: 'Underscore' }, { address: 'user-name@example.com', shouldBeValid: true, description: 'Hyphen' }, { address: '"quoted string"@example.com', shouldBeValid: true, description: 'Quoted string' }, { address: 'user@sub.domain.example.com', shouldBeValid: true, description: 'Subdomain' }, { address: 'user@example.co.uk', shouldBeValid: true, description: 'Multi-part TLD' }, { address: 'user..name@example.com', shouldBeValid: false, description: 'Double dots' }, { address: '.user@example.com', shouldBeValid: false, description: 'Leading dot' }, { address: 'user.@example.com', shouldBeValid: false, description: 'Trailing dot' } ]; for (const test of specialAddresses) { const isValid = validator.isValidFormat(test.address); console.log(`${test.description}:`); console.log(` Address: "${test.address}"`); console.log(` Valid: ${isValid} (expected: ${test.shouldBeValid})`); if (test.shouldBeValid && isValid) { // Try sending an email with this address try { const email = new Email({ from: 'sender@example.com', to: [test.address], subject: 'Special character test', text: `Testing special characters in: ${test.address}` }); await smtpClient.sendMail(email); console.log(` Email sent successfully`); } catch (error) { console.log(` Failed to send: ${error.message}`); } } console.log(''); } }); tap.test('CCMD-10: Large recipient lists', async () => { // Test handling of large recipient lists (similar to EXPN multi-line) console.log('Testing large recipient lists:\n'); // Create email with many recipients const recipientCount = 20; const toRecipients = []; const ccRecipients = []; for (let i = 1; i <= recipientCount; i++) { if (i <= 10) { toRecipients.push(`user${i}@example.com`); } else { ccRecipients.push(`user${i}@example.com`); } } console.log(`Creating email with ${recipientCount} total recipients:`); console.log(` To: ${toRecipients.length} recipients`); console.log(` CC: ${ccRecipients.length} recipients`); const largeListEmail = new Email({ from: 'sender@example.com', to: toRecipients, cc: ccRecipients, subject: 'Large distribution list test', text: `This email is being sent to ${recipientCount} recipients total` }); // Show extracted addresses const allTo = largeListEmail.getToAddresses(); const allCc = largeListEmail.getCcAddresses(); console.log('\nExtracted addresses:'); console.log(`To (first 3): ${allTo.slice(0, 3).join(', ')}...`); console.log(`CC (first 3): ${allCc.slice(0, 3).join(', ')}...`); // Send the email const startTime = Date.now(); await smtpClient.sendMail(largeListEmail); const elapsed = Date.now() - startTime; console.log(`\nEmail sent to all ${recipientCount} recipients in ${elapsed}ms`); console.log(`Average: ${(elapsed / recipientCount).toFixed(2)}ms per recipient`); }); tap.test('CCMD-10: Email validation performance', async () => { // Test validation performance console.log('Testing email validation performance:\n'); const validator = new EmailValidator(); const testCount = 1000; // Generate test addresses const testAddresses = []; for (let i = 0; i < testCount; i++) { testAddresses.push(`user${i}@example${i % 10}.com`); } // Time validation const startTime = Date.now(); let validCount = 0; for (const address of testAddresses) { if (validator.isValidFormat(address)) { validCount++; } } const elapsed = Date.now() - startTime; const rate = (testCount / elapsed) * 1000; console.log(`Validated ${testCount} addresses in ${elapsed}ms`); console.log(`Rate: ${rate.toFixed(0)} validations/second`); console.log(`Valid addresses: ${validCount}/${testCount}`); // Test rapid email sending to see if there's rate limiting console.log('\nTesting rapid email sending:'); const emailCount = 10; const sendStartTime = Date.now(); let sentCount = 0; for (let i = 0; i < emailCount; i++) { try { const email = new Email({ from: 'sender@example.com', to: [`recipient${i}@example.com`], subject: `Rate test ${i + 1}`, text: 'Testing rate limits' }); await smtpClient.sendMail(email); sentCount++; } catch (error) { console.log(`Rate limit hit at email ${i + 1}: ${error.message}`); break; } } const sendElapsed = Date.now() - sendStartTime; const sendRate = (sentCount / sendElapsed) * 1000; console.log(`Sent ${sentCount}/${emailCount} emails in ${sendElapsed}ms`); console.log(`Rate: ${sendRate.toFixed(2)} emails/second`); }); tap.test('CCMD-10: Email validation error handling', async () => { // Test error handling for invalid email addresses console.log('Testing email validation error handling:\n'); const validator = new EmailValidator(); const errorTests = [ { address: null, description: 'Null address' }, { address: undefined, description: 'Undefined address' }, { address: '', description: 'Empty string' }, { address: ' ', description: 'Whitespace only' }, { address: '@', description: 'Just @ symbol' }, { address: 'user@', description: 'Missing domain' }, { address: '@domain.com', description: 'Missing local part' }, { address: 'user@@domain.com', description: 'Double @ symbol' }, { address: 'user@domain@com', description: 'Multiple @ symbols' }, { address: 'user space@domain.com', description: 'Space in local part' }, { address: 'user@domain .com', description: 'Space in domain' }, { address: 'x'.repeat(256) + '@domain.com', description: 'Very long local part' }, { address: 'user@' + 'x'.repeat(256) + '.com', description: 'Very long domain' } ]; for (const test of errorTests) { console.log(`${test.description}:`); console.log(` Input: "${test.address}"`); // Test validation let isValid = false; try { isValid = validator.isValidFormat(test.address as any); } catch (error) { console.log(` Validation threw: ${error.message}`); } if (!isValid) { console.log(` Correctly rejected as invalid`); } else { console.log(` WARNING: Accepted as valid!`); } // Try to send email with invalid address if (test.address) { try { const email = new Email({ from: 'sender@example.com', to: [test.address], subject: 'Error test', text: 'Testing invalid address' }); await smtpClient.sendMail(email); console.log(` WARNING: Email sent with invalid address!`); } catch (error) { console.log(` Email correctly rejected: ${error.message}`); } } console.log(''); } }); tap.test('cleanup test SMTP server', async () => { if (testServer) { await stopTestServer(testServer); } }); export default tap.start();