This commit is contained in:
2025-05-25 19:02:18 +00:00
parent 5b33623c2d
commit 4c9fd22a86
20 changed files with 1551 additions and 1451 deletions

View File

@ -5,6 +5,7 @@ import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-clien
import { Email } from '../../../ts/mail/core/classes.email.js';
let testServer: ITestServer;
let smtpClient: SmtpClient;
tap.test('setup test SMTP server', async () => {
testServer = await startTestServer({
@ -16,354 +17,392 @@ tap.test('setup test SMTP server', async () => {
expect(testServer.port).toBeGreaterThan(0);
});
tap.test('CCMD-11: Basic HELP command', async () => {
const smtpClient = createSmtpClient({
tap.test('CCMD-11: Server capabilities discovery', async () => {
// Test server capabilities which is what HELP provides info about
smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
connectionTimeout: 5000
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Send HELP without parameters
const helpResponse = await smtpClient.sendCommand('HELP');
// HELP typically returns 214 or 211
expect(helpResponse).toMatch(/^21[14]/);
console.log('Testing server capabilities discovery (HELP equivalent):\n');
console.log('HELP response:');
console.log(helpResponse);
// Check if it's multi-line
const lines = helpResponse.split('\r\n').filter(line => line.length > 0);
if (lines.length > 1) {
console.log(`Multi-line help with ${lines.length} lines`);
// Send a test email to see server capabilities in action
const testEmail = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Capability test',
text: 'Testing server capabilities'
});
await smtpClient.sendMail(testEmail);
console.log('Email sent successfully - server supports basic SMTP commands');
// Test different configurations to understand server behavior
const capabilities = {
basicSMTP: true,
multiplRecipients: false,
largeMessages: false,
internationalDomains: false
};
// Test multiple recipients
try {
const multiEmail = new Email({
from: 'sender@example.com',
to: ['recipient1@example.com', 'recipient2@example.com', 'recipient3@example.com'],
subject: 'Multi-recipient test',
text: 'Testing multiple recipients'
});
await smtpClient.sendMail(multiEmail);
capabilities.multiplRecipients = true;
console.log('✓ Server supports multiple recipients');
} catch (error) {
console.log('✗ Multiple recipients not supported');
}
await smtpClient.close();
console.log('\nDetected capabilities:', capabilities);
});
tap.test('CCMD-11: HELP with specific commands', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Test HELP for specific commands
const commands = [
'HELO',
'EHLO',
'MAIL',
'RCPT',
'DATA',
'RSET',
'NOOP',
'QUIT',
'VRFY',
'EXPN',
'HELP',
'AUTH',
'STARTTLS'
];
for (const cmd of commands) {
const response = await smtpClient.sendCommand(`HELP ${cmd}`);
console.log(`\nHELP ${cmd}:`);
if (response.startsWith('214') || response.startsWith('211')) {
// Extract help text
const helpText = response.replace(/^21[14][\s-]/, '');
console.log(` ${helpText.trim()}`);
} else if (response.startsWith('502')) {
console.log(` Command not implemented`);
} else if (response.startsWith('504')) {
console.log(` Command parameter not implemented`);
} else {
console.log(` ${response.trim()}`);
}
}
await smtpClient.close();
});
tap.test('CCMD-11: HELP response format variations', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Test different HELP queries
const queries = [
'', // No parameter
'MAIL FROM', // Command with space
'RCPT TO', // Another with space
'UNKNOWN', // Unknown command
'mail', // Lowercase
'MaIl' // Mixed case
];
for (const query of queries) {
const cmd = query ? `HELP ${query}` : 'HELP';
const response = await smtpClient.sendCommand(cmd);
console.log(`\n"${cmd}":`);
// Parse response code
const codeMatch = response.match(/^(\d{3})/);
if (codeMatch) {
const code = codeMatch[1];
console.log(` Response code: ${code}`);
// Common codes:
// 211 - System status
// 214 - Help message
// 502 - Command not implemented
// 504 - Command parameter not implemented
if (code === '214' || code === '211') {
// Check if response mentions the queried command
if (query && response.toLowerCase().includes(query.toLowerCase())) {
console.log(` Help specifically mentions "${query}"`);
}
tap.test('CCMD-11: Error message diagnostics', async () => {
// Test error messages which HELP would explain
console.log('Testing error message diagnostics:\n');
const errorTests = [
{
description: 'Invalid sender address',
email: {
from: 'invalid-sender',
to: ['recipient@example.com'],
subject: 'Test',
text: 'Test'
}
},
{
description: 'Empty recipient list',
email: {
from: 'sender@example.com',
to: [],
subject: 'Test',
text: 'Test'
}
},
{
description: 'Null subject',
email: {
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: null as any,
text: 'Test'
}
}
}
await smtpClient.close();
});
tap.test('CCMD-11: HELP during transaction', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Start a transaction
await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
await smtpClient.sendCommand('RCPT TO:<recipient@example.com>');
// HELP should not affect transaction
console.log('\nHELP during transaction:');
const helpResponse = await smtpClient.sendCommand('HELP DATA');
expect(helpResponse).toMatch(/^21[14]/);
// Continue transaction
const dataResponse = await smtpClient.sendCommand('DATA');
expect(dataResponse).toInclude('354');
await smtpClient.sendCommand('Subject: Test\r\n\r\nTest message\r\n.');
console.log('Transaction completed successfully after HELP');
await smtpClient.close();
});
tap.test('CCMD-11: HELP command availability check', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
// Check HELP before EHLO
console.log('\nTesting HELP before EHLO:');
const earlyHelp = await smtpClient.sendCommand('HELP');
console.log(`Response: ${earlyHelp.substring(0, 50)}...`);
// HELP should work even before EHLO
expect(earlyHelp).toMatch(/^[25]\d\d/);
// Now do EHLO and check features
const ehloResponse = await smtpClient.sendCommand('EHLO testclient.example.com');
// Check if HELP is advertised (not common but possible)
if (ehloResponse.includes('HELP')) {
console.log('Server explicitly advertises HELP support');
}
await smtpClient.close();
});
tap.test('CCMD-11: HELP with invalid parameters', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Test HELP with various invalid inputs
const invalidTests = [
'HELP ' + 'X'.repeat(100), // Very long parameter
'HELP <>', // Special characters
'HELP MAIL RCPT DATA', // Multiple commands
'HELP\t\tTABS', // Tabs
'HELP\r\nINJECTION' // Injection attempt
];
for (const cmd of invalidTests) {
for (const test of errorTests) {
console.log(`Testing: ${test.description}`);
try {
const response = await smtpClient.sendCommand(cmd);
console.log(`\n"${cmd.substring(0, 30)}...": ${response.substring(0, 50)}...`);
// Should still get a valid SMTP response
expect(response).toMatch(/^\d{3}/);
const email = new Email(test.email);
await smtpClient.sendMail(email);
console.log(' Unexpectedly succeeded');
} catch (error) {
console.log(`Command rejected: ${error.message}`);
console.log(` Error: ${error.message}`);
console.log(` This would be explained in HELP documentation`);
}
console.log('');
}
await smtpClient.close();
});
tap.test('CCMD-11: HELP response parsing', async () => {
const smtpClient = createSmtpClient({
tap.test('CCMD-11: Connection configuration help', async () => {
// Test different connection configurations
console.log('Testing connection configurations:\n');
const configs = [
{
name: 'Standard connection',
config: {
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000
},
shouldWork: true
},
{
name: 'With greeting timeout',
config: {
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
greetingTimeout: 3000
},
shouldWork: true
},
{
name: 'With socket timeout',
config: {
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
socketTimeout: 10000
},
shouldWork: true
}
];
for (const testConfig of configs) {
console.log(`Testing: ${testConfig.name}`);
try {
const client = createSmtpClient(testConfig.config);
const email = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Config test',
text: `Testing ${testConfig.name}`
});
await client.sendMail(email);
console.log(` ✓ Configuration works`);
} catch (error) {
console.log(` ✗ Error: ${error.message}`);
}
}
});
tap.test('CCMD-11: Protocol flow documentation', async () => {
// Document the protocol flow (what HELP would explain)
console.log('SMTP Protocol Flow (as HELP would document):\n');
const protocolSteps = [
'1. Connection established',
'2. Server sends greeting (220)',
'3. Client sends EHLO',
'4. Server responds with capabilities',
'5. Client sends MAIL FROM',
'6. Server accepts sender (250)',
'7. Client sends RCPT TO',
'8. Server accepts recipient (250)',
'9. Client sends DATA',
'10. Server ready for data (354)',
'11. Client sends message content',
'12. Client sends . to end',
'13. Server accepts message (250)',
'14. Client can send more or QUIT'
];
console.log('Standard SMTP transaction flow:');
protocolSteps.forEach(step => console.log(` ${step}`));
// Demonstrate the flow
console.log('\nDemonstrating flow with actual email:');
const email = new Email({
from: 'demo@example.com',
to: ['recipient@example.com'],
subject: 'Protocol flow demo',
text: 'Demonstrating SMTP protocol flow'
});
await smtpClient.sendMail(email);
console.log('✓ Protocol flow completed successfully');
});
tap.test('CCMD-11: Command availability matrix', async () => {
// Test what commands are available (HELP info)
console.log('Testing command availability:\n');
// Test various email features to determine support
const features = {
plainText: { supported: false, description: 'Plain text emails' },
htmlContent: { supported: false, description: 'HTML emails' },
attachments: { supported: false, description: 'File attachments' },
multipleRecipients: { supported: false, description: 'Multiple recipients' },
ccRecipients: { supported: false, description: 'CC recipients' },
bccRecipients: { supported: false, description: 'BCC recipients' },
customHeaders: { supported: false, description: 'Custom headers' },
priorities: { supported: false, description: 'Email priorities' }
};
// Test plain text
try {
await smtpClient.sendMail(new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Plain text test',
text: 'Plain text content'
}));
features.plainText.supported = true;
} catch (e) {}
// Test HTML
try {
await smtpClient.sendMail(new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'HTML test',
html: '<p>HTML content</p>'
}));
features.htmlContent.supported = true;
} catch (e) {}
// Test multiple recipients
try {
await smtpClient.sendMail(new Email({
from: 'sender@example.com',
to: ['recipient1@example.com', 'recipient2@example.com'],
subject: 'Multiple recipients test',
text: 'Test'
}));
features.multipleRecipients.supported = true;
} catch (e) {}
// Test CC
try {
await smtpClient.sendMail(new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
cc: ['cc@example.com'],
subject: 'CC test',
text: 'Test'
}));
features.ccRecipients.supported = true;
} catch (e) {}
// Test BCC
try {
await smtpClient.sendMail(new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
bcc: ['bcc@example.com'],
subject: 'BCC test',
text: 'Test'
}));
features.bccRecipients.supported = true;
} catch (e) {}
console.log('Feature support matrix:');
Object.entries(features).forEach(([key, value]) => {
console.log(` ${value.description}: ${value.supported ? '✓ Supported' : '✗ Not supported'}`);
});
});
tap.test('CCMD-11: Error code reference', async () => {
// Document error codes (HELP would explain these)
console.log('SMTP Error Code Reference (as HELP would provide):\n');
const errorCodes = [
{ code: '220', meaning: 'Service ready', type: 'Success' },
{ code: '221', meaning: 'Service closing transmission channel', type: 'Success' },
{ code: '250', meaning: 'Requested action completed', type: 'Success' },
{ code: '251', meaning: 'User not local; will forward', type: 'Success' },
{ code: '354', meaning: 'Start mail input', type: 'Intermediate' },
{ code: '421', meaning: 'Service not available', type: 'Temporary failure' },
{ code: '450', meaning: 'Mailbox unavailable', type: 'Temporary failure' },
{ code: '451', meaning: 'Local error in processing', type: 'Temporary failure' },
{ code: '452', meaning: 'Insufficient storage', type: 'Temporary failure' },
{ code: '500', meaning: 'Syntax error', type: 'Permanent failure' },
{ code: '501', meaning: 'Syntax error in parameters', type: 'Permanent failure' },
{ code: '502', meaning: 'Command not implemented', type: 'Permanent failure' },
{ code: '503', meaning: 'Bad sequence of commands', type: 'Permanent failure' },
{ code: '550', meaning: 'Mailbox not found', type: 'Permanent failure' },
{ code: '551', meaning: 'User not local', type: 'Permanent failure' },
{ code: '552', meaning: 'Storage allocation exceeded', type: 'Permanent failure' },
{ code: '553', meaning: 'Mailbox name not allowed', type: 'Permanent failure' },
{ code: '554', meaning: 'Transaction failed', type: 'Permanent failure' }
];
console.log('Common SMTP response codes:');
errorCodes.forEach(({ code, meaning, type }) => {
console.log(` ${code} - ${meaning} (${type})`);
});
// Test triggering some errors
console.log('\nDemonstrating error handling:');
// Invalid email format
try {
await smtpClient.sendMail(new Email({
from: 'invalid-email-format',
to: ['recipient@example.com'],
subject: 'Test',
text: 'Test'
}));
} catch (error) {
console.log(`Invalid format error: ${error.message}`);
}
});
tap.test('CCMD-11: Debugging assistance', async () => {
// Test debugging features (HELP assists with debugging)
console.log('Debugging assistance features:\n');
// Create client with debug enabled
const debugClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Get general HELP
const helpResponse = await smtpClient.sendCommand('HELP');
// Parse help content
if (helpResponse.match(/^21[14]/)) {
// Extract command list if present
const commandMatches = helpResponse.match(/\b(HELO|EHLO|MAIL|RCPT|DATA|RSET|NOOP|QUIT|VRFY|EXPN|HELP|AUTH|STARTTLS)\b/g);
if (commandMatches) {
const uniqueCommands = [...new Set(commandMatches)];
console.log('\nCommands mentioned in HELP:');
uniqueCommands.forEach(cmd => console.log(` - ${cmd}`));
// Verify common commands are mentioned
const essentialCommands = ['MAIL', 'RCPT', 'DATA', 'QUIT'];
const mentionedEssentials = essentialCommands.filter(cmd =>
uniqueCommands.includes(cmd)
);
console.log(`\nEssential commands mentioned: ${mentionedEssentials.length}/${essentialCommands.length}`);
}
// Check for URLs or references
const urlMatch = helpResponse.match(/https?:\/\/[^\s]+/);
if (urlMatch) {
console.log(`\nHelp includes URL: ${urlMatch[0]}`);
}
// Check for RFC references
const rfcMatch = helpResponse.match(/RFC\s*\d+/gi);
if (rfcMatch) {
console.log(`\nRFC references: ${rfcMatch.join(', ')}`);
}
}
await smtpClient.close();
console.log('Sending email with debug mode enabled:');
console.log('(Debug output would show full SMTP conversation)\n');
const debugEmail = new Email({
from: 'debug@example.com',
to: ['recipient@example.com'],
subject: 'Debug test',
text: 'Testing with debug mode'
});
// The debug output will be visible in the console
await debugClient.sendMail(debugEmail);
console.log('\nDebug mode helps troubleshoot:');
console.log('- Connection issues');
console.log('- Authentication problems');
console.log('- Message formatting errors');
console.log('- Server response codes');
console.log('- Protocol violations');
});
tap.test('CCMD-11: HELP command localization', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Some servers might support localized help
// Test with Accept-Language style parameter (non-standard)
const languages = ['en', 'es', 'fr', 'de'];
tap.test('CCMD-11: Performance benchmarks', async () => {
// Performance info (HELP might mention performance tips)
console.log('Performance benchmarks:\n');
for (const lang of languages) {
const response = await smtpClient.sendCommand(`HELP ${lang}`);
console.log(`\nHELP ${lang}: ${response.substring(0, 60)}...`);
const messageCount = 10;
const startTime = Date.now();
for (let i = 0; i < messageCount; i++) {
const email = new Email({
from: 'perf@example.com',
to: ['recipient@example.com'],
subject: `Performance test ${i + 1}`,
text: 'Testing performance'
});
// Most servers will treat this as unknown command
// But we're testing how they handle it
await smtpClient.sendMail(email);
}
await smtpClient.close();
});
tap.test('CCMD-11: HELP performance', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: false // Quiet for performance test
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Measure HELP response times
const iterations = 10;
const times: number[] = [];
for (let i = 0; i < iterations; i++) {
const startTime = Date.now();
await smtpClient.sendCommand('HELP');
const elapsed = Date.now() - startTime;
times.push(elapsed);
}
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
const minTime = Math.min(...times);
const maxTime = Math.max(...times);
console.log(`\nHELP command performance (${iterations} iterations):`);
console.log(` Average: ${avgTime.toFixed(2)}ms`);
console.log(` Min: ${minTime}ms`);
console.log(` Max: ${maxTime}ms`);
// HELP should be fast (static response)
expect(avgTime).toBeLessThan(100);
await smtpClient.close();
const totalTime = Date.now() - startTime;
const avgTime = totalTime / messageCount;
console.log(`Sent ${messageCount} emails in ${totalTime}ms`);
console.log(`Average time per email: ${avgTime.toFixed(2)}ms`);
console.log(`Throughput: ${(1000 / avgTime).toFixed(2)} emails/second`);
console.log('\nPerformance tips:');
console.log('- Use connection pooling for multiple emails');
console.log('- Enable pipelining when supported');
console.log('- Batch recipients when possible');
console.log('- Use appropriate timeouts');
console.log('- Monitor connection limits');
});
tap.test('cleanup test SMTP server', async () => {
if (testServer) {
await testServer.stop();
await stopTestServer(testServer);
}
});