import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestSmtpServer } from '../../helpers/server.loader.js'; import { createSmtpClient } from '../../helpers/smtp.client.js'; import { Email } from '../../../ts/mail/core/classes.email.js'; let testServer: any; tap.test('setup test SMTP server', async () => { testServer = await startTestSmtpServer(); expect(testServer).toBeTruthy(); expect(testServer.port).toBeGreaterThan(0); }); tap.test('CEP-04: Basic BCC handling', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Create email with BCC recipients const email = new Email({ from: 'sender@example.com', to: ['visible@example.com'], cc: ['copied@example.com'], bcc: ['hidden1@example.com', 'hidden2@example.com'], subject: 'Test BCC Handling', text: 'This message has BCC recipients' }); // Send the email const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); expect(result.accepted).toBeArray(); // All recipients (including BCC) should be accepted const totalRecipients = [...email.to, ...email.cc, ...email.bcc]; expect(result.accepted.length).toEqual(totalRecipients.length); console.log('BCC recipients processed:', email.bcc.length); console.log('Total recipients:', totalRecipients.length); await smtpClient.close(); }); tap.test('CEP-04: BCC header exclusion', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Create email with BCC const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], bcc: ['secret@example.com'], subject: 'BCC Header Test', text: 'Testing BCC header exclusion' }); // Monitor the actual SMTP commands let dataContent = ''; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); let inDataPhase = false; smtpClient.sendCommand = async (command: string) => { if (command === 'DATA') { inDataPhase = true; } else if (inDataPhase && command === '.') { inDataPhase = false; } else if (inDataPhase) { dataContent += command + '\n'; } return originalSendCommand(command); }; await smtpClient.sendMail(email); // Verify BCC header is not in the message expect(dataContent.toLowerCase()).not.toInclude('bcc:'); console.log('Verified: BCC header not included in message data'); // Verify other headers are present expect(dataContent.toLowerCase()).toInclude('to:'); expect(dataContent.toLowerCase()).toInclude('from:'); expect(dataContent.toLowerCase()).toInclude('subject:'); await smtpClient.close(); }); tap.test('CEP-04: Large BCC list handling', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, debug: true }); await smtpClient.connect(); // Create email with many BCC recipients const bccCount = 50; const bccRecipients = Array.from({ length: bccCount }, (_, i) => `bcc${i + 1}@example.com` ); const email = new Email({ from: 'sender@example.com', to: ['visible@example.com'], bcc: bccRecipients, subject: 'Large BCC List Test', text: `This message has ${bccCount} BCC recipients` }); console.log(`Sending email with ${bccCount} BCC recipients...`); const startTime = Date.now(); const result = await smtpClient.sendMail(email); const elapsed = Date.now() - startTime; expect(result).toBeTruthy(); expect(result.accepted).toBeArray(); // All BCC recipients should be processed expect(result.accepted).toIncludeAllMembers(bccRecipients); console.log(`Processed ${bccCount} BCC recipients in ${elapsed}ms`); console.log(`Average time per recipient: ${(elapsed / bccCount).toFixed(2)}ms`); await smtpClient.close(); }); tap.test('CEP-04: BCC-only email', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Create email with only BCC recipients (no TO or CC) const email = new Email({ from: 'sender@example.com', bcc: ['hidden1@example.com', 'hidden2@example.com', 'hidden3@example.com'], subject: 'BCC-Only Email', text: 'This email has only BCC recipients' }); const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); expect(result.accepted.length).toEqual(email.bcc.length); console.log('Successfully sent BCC-only email to', email.bcc.length, 'recipients'); // Verify the email has appropriate headers let hasToHeader = false; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.toLowerCase().includes('to:')) { hasToHeader = true; } return originalSendCommand(command); }; // Send another BCC-only email to check headers await smtpClient.sendMail(new Email({ from: 'sender@example.com', bcc: ['test@example.com'], subject: 'Header Check', text: 'Checking headers' })); // Some implementations add "To: undisclosed-recipients:;" for BCC-only emails console.log('Email has TO header:', hasToHeader); await smtpClient.close(); }); tap.test('CEP-04: Mixed recipient types', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Create email with all recipient types const email = new Email({ from: 'sender@example.com', to: ['to1@example.com', 'to2@example.com'], cc: ['cc1@example.com', 'cc2@example.com', 'cc3@example.com'], bcc: ['bcc1@example.com', 'bcc2@example.com', 'bcc3@example.com', 'bcc4@example.com'], subject: 'Mixed Recipients Test', text: 'Testing all recipient types together' }); // Track RCPT TO commands const rcptCommands: string[] = []; const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.startsWith('RCPT TO:')) { rcptCommands.push(command); } return originalSendCommand(command); }; const result = await smtpClient.sendMail(email); // Verify all recipients received RCPT TO const totalExpected = email.to.length + email.cc.length + email.bcc.length; expect(rcptCommands.length).toEqual(totalExpected); console.log('Recipient breakdown:'); console.log(` TO: ${email.to.length} recipients`); console.log(` CC: ${email.cc.length} recipients`); console.log(` BCC: ${email.bcc.length} recipients`); console.log(` Total RCPT TO commands: ${rcptCommands.length}`); // Verify each recipient type for (const recipient of email.to) { expect(rcptCommands).toIncludeAnyMembers([`RCPT TO:<${recipient}>`]); } for (const recipient of email.cc) { expect(rcptCommands).toIncludeAnyMembers([`RCPT TO:<${recipient}>`]); } for (const recipient of email.bcc) { expect(rcptCommands).toIncludeAnyMembers([`RCPT TO:<${recipient}>`]); } await smtpClient.close(); }); tap.test('CEP-04: BCC with special characters', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // BCC addresses with special characters const specialBccAddresses = [ 'user+tag@example.com', 'first.last@example.com', 'user_name@example.com', '"quoted string"@example.com', 'user@sub.domain.example.com' ]; const email = new Email({ from: 'sender@example.com', to: ['visible@example.com'], bcc: specialBccAddresses, subject: 'BCC Special Characters Test', text: 'Testing BCC with special character addresses' }); const result = await smtpClient.sendMail(email); expect(result).toBeTruthy(); console.log('BCC addresses with special characters processed:'); specialBccAddresses.forEach((addr, i) => { const accepted = result.accepted.includes(addr); console.log(` ${i + 1}. ${addr} - ${accepted ? 'Accepted' : 'Rejected'}`); }); await smtpClient.close(); }); tap.test('CEP-04: BCC duplicate handling', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Create email with duplicate addresses across recipient types const email = new Email({ from: 'sender@example.com', to: ['shared@example.com', 'unique1@example.com'], cc: ['shared@example.com', 'unique2@example.com'], bcc: ['shared@example.com', 'unique3@example.com', 'unique3@example.com'], // Duplicate in BCC subject: 'Duplicate Recipients Test', text: 'Testing duplicate handling across recipient types' }); // Track unique RCPT TO commands const rcptSet = new Set(); const originalSendCommand = smtpClient.sendCommand.bind(smtpClient); smtpClient.sendCommand = async (command: string) => { if (command.startsWith('RCPT TO:')) { rcptSet.add(command); } return originalSendCommand(command); }; const result = await smtpClient.sendMail(email); console.log('Duplicate handling results:'); console.log(` Total addresses provided: ${email.to.length + email.cc.length + email.bcc.length}`); console.log(` Unique RCPT TO commands: ${rcptSet.size}`); console.log(` Duplicates detected: ${(email.to.length + email.cc.length + email.bcc.length) - rcptSet.size}`); // The client should handle duplicates appropriately expect(rcptSet.size).toBeLessThanOrEqual(email.to.length + email.cc.length + email.bcc.length); await smtpClient.close(); }); tap.test('CEP-04: BCC performance impact', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, debug: false // Quiet for performance test }); await smtpClient.connect(); // Test performance with different BCC counts const bccCounts = [0, 10, 25, 50]; const results: { count: number; time: number }[] = []; for (const count of bccCounts) { const bccRecipients = Array.from({ length: count }, (_, i) => `bcc${i}@example.com` ); const email = new Email({ from: 'sender@example.com', to: ['recipient@example.com'], bcc: bccRecipients, subject: `Performance Test - ${count} BCCs`, text: 'Performance testing' }); const startTime = Date.now(); await smtpClient.sendMail(email); const elapsed = Date.now() - startTime; results.push({ count, time: elapsed }); } console.log('\nBCC Performance Impact:'); console.log('BCC Count | Time (ms) | Per-recipient (ms)'); console.log('----------|-----------|-------------------'); results.forEach(r => { const perRecipient = r.count > 0 ? (r.time / r.count).toFixed(2) : 'N/A'; console.log(`${r.count.toString().padEnd(9)} | ${r.time.toString().padEnd(9)} | ${perRecipient}`); }); // Performance should scale linearly with BCC count if (results.length >= 2) { const timeIncrease = results[results.length - 1].time - results[0].time; const countIncrease = results[results.length - 1].count - results[0].count; const msPerBcc = countIncrease > 0 ? timeIncrease / countIncrease : 0; console.log(`\nAverage time per BCC recipient: ${msPerBcc.toFixed(2)}ms`); } await smtpClient.close(); }); tap.test('cleanup test SMTP server', async () => { if (testServer) { await testServer.stop(); } }); export default tap.start();