398 lines
12 KiB
TypeScript
398 lines
12 KiB
TypeScript
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<string>();
|
|
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(); |