728 lines
25 KiB
TypeScript
728 lines
25 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
import { createTestServer } from '../../helpers/server.loader.js';
|
||
import { createTestSmtpClient } from '../../helpers/smtp.client.js';
|
||
import { Email } from '../../../ts/index.js';
|
||
|
||
tap.test('CRFC-07: should ensure SMTP interoperability (RFC 5321)', async (tools) => {
|
||
const testId = 'CRFC-07-interoperability';
|
||
console.log(`\n${testId}: Testing SMTP interoperability compliance...`);
|
||
|
||
let scenarioCount = 0;
|
||
|
||
// Scenario 1: Different server implementations compatibility
|
||
await (async () => {
|
||
scenarioCount++;
|
||
console.log(`\nScenario ${scenarioCount}: Testing different server implementations`);
|
||
|
||
const serverImplementations = [
|
||
{
|
||
name: 'Sendmail-style',
|
||
greeting: '220 mail.example.com ESMTP Sendmail 8.15.2/8.15.2; Date Time',
|
||
ehloResponse: [
|
||
'250-mail.example.com Hello client.example.com [192.168.1.100]',
|
||
'250-ENHANCEDSTATUSCODES',
|
||
'250-PIPELINING',
|
||
'250-8BITMIME',
|
||
'250-SIZE 36700160',
|
||
'250-DSN',
|
||
'250-ETRN',
|
||
'250-DELIVERBY',
|
||
'250 HELP'
|
||
],
|
||
quirks: { verboseResponses: true, includesTimestamp: true }
|
||
},
|
||
{
|
||
name: 'Postfix-style',
|
||
greeting: '220 mail.example.com ESMTP Postfix',
|
||
ehloResponse: [
|
||
'250-mail.example.com',
|
||
'250-PIPELINING',
|
||
'250-SIZE 10240000',
|
||
'250-VRFY',
|
||
'250-ETRN',
|
||
'250-STARTTLS',
|
||
'250-ENHANCEDSTATUSCODES',
|
||
'250-8BITMIME',
|
||
'250-DSN',
|
||
'250 SMTPUTF8'
|
||
],
|
||
quirks: { shortResponses: true, strictSyntax: true }
|
||
},
|
||
{
|
||
name: 'Exchange-style',
|
||
greeting: '220 mail.example.com Microsoft ESMTP MAIL Service ready',
|
||
ehloResponse: [
|
||
'250-mail.example.com Hello [192.168.1.100]',
|
||
'250-SIZE 37748736',
|
||
'250-PIPELINING',
|
||
'250-DSN',
|
||
'250-ENHANCEDSTATUSCODES',
|
||
'250-STARTTLS',
|
||
'250-8BITMIME',
|
||
'250-BINARYMIME',
|
||
'250-CHUNKING',
|
||
'250 OK'
|
||
],
|
||
quirks: { windowsLineEndings: true, detailedErrors: true }
|
||
}
|
||
];
|
||
|
||
for (const impl of serverImplementations) {
|
||
console.log(`\n Testing with ${impl.name} server...`);
|
||
|
||
const testServer = await createTestServer({
|
||
onConnection: async (socket) => {
|
||
console.log(` [${impl.name}] Client connected`);
|
||
socket.write(impl.greeting + '\r\n');
|
||
|
||
socket.on('data', (data) => {
|
||
const command = data.toString().trim();
|
||
console.log(` [${impl.name}] Received: ${command}`);
|
||
|
||
if (command.startsWith('EHLO')) {
|
||
impl.ehloResponse.forEach(line => {
|
||
socket.write(line + '\r\n');
|
||
});
|
||
} else if (command.startsWith('MAIL FROM:')) {
|
||
if (impl.quirks.strictSyntax && !command.includes('<')) {
|
||
socket.write('501 5.5.4 Syntax error in MAIL command\r\n');
|
||
} else {
|
||
const response = impl.quirks.verboseResponses ?
|
||
'250 2.1.0 Sender OK' : '250 OK';
|
||
socket.write(response + '\r\n');
|
||
}
|
||
} else if (command.startsWith('RCPT TO:')) {
|
||
const response = impl.quirks.verboseResponses ?
|
||
'250 2.1.5 Recipient OK' : '250 OK';
|
||
socket.write(response + '\r\n');
|
||
} else if (command === 'DATA') {
|
||
const response = impl.quirks.detailedErrors ?
|
||
'354 Start mail input; end with <CRLF>.<CRLF>' :
|
||
'354 Enter message, ending with "." on a line by itself';
|
||
socket.write(response + '\r\n');
|
||
} else if (command === '.') {
|
||
const timestamp = impl.quirks.includesTimestamp ?
|
||
` at ${new Date().toISOString()}` : '';
|
||
socket.write(`250 2.0.0 Message accepted for delivery${timestamp}\r\n`);
|
||
} else if (command === 'QUIT') {
|
||
const response = impl.quirks.verboseResponses ?
|
||
'221 2.0.0 Service closing transmission channel' :
|
||
'221 Bye';
|
||
socket.write(response + '\r\n');
|
||
socket.end();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
const smtpClient = createTestSmtpClient({
|
||
host: testServer.hostname,
|
||
port: testServer.port,
|
||
secure: false
|
||
});
|
||
|
||
const email = new Email({
|
||
from: 'sender@example.com',
|
||
to: ['recipient@example.com'],
|
||
subject: `Interoperability test with ${impl.name}`,
|
||
text: `Testing compatibility with ${impl.name} server implementation`
|
||
});
|
||
|
||
const result = await smtpClient.sendMail(email);
|
||
console.log(` ${impl.name} compatibility: Success`);
|
||
expect(result).toBeDefined();
|
||
expect(result.messageId).toBeDefined();
|
||
|
||
await testServer.server.close();
|
||
}
|
||
})();
|
||
|
||
// Scenario 2: Character encoding and internationalization
|
||
await (async () => {
|
||
scenarioCount++;
|
||
console.log(`\nScenario ${scenarioCount}: Testing character encoding interoperability`);
|
||
|
||
const testServer = await createTestServer({
|
||
onConnection: async (socket) => {
|
||
console.log(' [Server] Client connected');
|
||
socket.write('220 international.example.com ESMTP\r\n');
|
||
|
||
let supportsUTF8 = false;
|
||
|
||
socket.on('data', (data) => {
|
||
const command = data.toString();
|
||
console.log(` [Server] Received (${data.length} bytes): ${command.trim()}`);
|
||
|
||
if (command.startsWith('EHLO')) {
|
||
socket.write('250-international.example.com\r\n');
|
||
socket.write('250-8BITMIME\r\n');
|
||
socket.write('250-SMTPUTF8\r\n');
|
||
socket.write('250 OK\r\n');
|
||
supportsUTF8 = true;
|
||
} else if (command.startsWith('MAIL FROM:')) {
|
||
// Check for non-ASCII characters
|
||
const hasNonASCII = /[^\x00-\x7F]/.test(command);
|
||
const hasUTF8Param = command.includes('SMTPUTF8');
|
||
|
||
console.log(` [Server] Non-ASCII: ${hasNonASCII}, UTF8 param: ${hasUTF8Param}`);
|
||
|
||
if (hasNonASCII && !hasUTF8Param) {
|
||
socket.write('553 5.6.7 Non-ASCII addresses require SMTPUTF8\r\n');
|
||
} else {
|
||
socket.write('250 OK\r\n');
|
||
}
|
||
} else if (command.startsWith('RCPT TO:')) {
|
||
socket.write('250 OK\r\n');
|
||
} else if (command.trim() === 'DATA') {
|
||
socket.write('354 Start mail input\r\n');
|
||
} else if (command.trim() === '.') {
|
||
socket.write('250 OK: International message accepted\r\n');
|
||
} else if (command.trim() === 'QUIT') {
|
||
socket.write('221 Bye\r\n');
|
||
socket.end();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
const smtpClient = createTestSmtpClient({
|
||
host: testServer.hostname,
|
||
port: testServer.port,
|
||
secure: false
|
||
});
|
||
|
||
// Test various international character sets
|
||
const internationalTests = [
|
||
{
|
||
desc: 'Latin characters with accents',
|
||
from: 'sénder@éxample.com',
|
||
to: 'récipient@éxample.com',
|
||
subject: 'Tëst with açcénts',
|
||
text: 'Café, naïve, résumé, piñata'
|
||
},
|
||
{
|
||
desc: 'Cyrillic characters',
|
||
from: 'отправитель@пример.com',
|
||
to: 'получатель@пример.com',
|
||
subject: 'Тест с кириллицей',
|
||
text: 'Привет мир! Это тест с русскими буквами.'
|
||
},
|
||
{
|
||
desc: 'Chinese characters',
|
||
from: 'sender@example.com', // ASCII for compatibility
|
||
to: 'recipient@example.com',
|
||
subject: '测试中文字符',
|
||
text: '你好世界!这是一个中文测试。'
|
||
},
|
||
{
|
||
desc: 'Arabic characters',
|
||
from: 'sender@example.com',
|
||
to: 'recipient@example.com',
|
||
subject: 'اختبار النص العربي',
|
||
text: 'مرحبا بالعالم! هذا اختبار باللغة العربية.'
|
||
},
|
||
{
|
||
desc: 'Emoji and symbols',
|
||
from: 'sender@example.com',
|
||
to: 'recipient@example.com',
|
||
subject: '🎉 Test with emojis 🌟',
|
||
text: 'Hello 👋 World 🌍! Testing emojis: 🚀 📧 ✨'
|
||
}
|
||
];
|
||
|
||
for (const test of internationalTests) {
|
||
console.log(` Testing: ${test.desc}`);
|
||
|
||
const email = new Email({
|
||
from: test.from,
|
||
to: [test.to],
|
||
subject: test.subject,
|
||
text: test.text
|
||
});
|
||
|
||
try {
|
||
const result = await smtpClient.sendMail(email);
|
||
console.log(` ${test.desc}: Success`);
|
||
expect(result).toBeDefined();
|
||
} catch (error) {
|
||
console.log(` ${test.desc}: Failed - ${error.message}`);
|
||
// Some may fail if server doesn't support international addresses
|
||
}
|
||
}
|
||
|
||
await testServer.server.close();
|
||
})();
|
||
|
||
// Scenario 3: Message format compatibility
|
||
await (async () => {
|
||
scenarioCount++;
|
||
console.log(`\nScenario ${scenarioCount}: Testing message format compatibility`);
|
||
|
||
const testServer = await createTestServer({
|
||
onConnection: async (socket) => {
|
||
console.log(' [Server] Client connected');
|
||
socket.write('220 formats.example.com ESMTP\r\n');
|
||
|
||
let inData = false;
|
||
let messageContent = '';
|
||
|
||
socket.on('data', (data) => {
|
||
if (inData) {
|
||
messageContent += data.toString();
|
||
if (messageContent.includes('\r\n.\r\n')) {
|
||
inData = false;
|
||
|
||
// Analyze message format
|
||
const headers = messageContent.substring(0, messageContent.indexOf('\r\n\r\n'));
|
||
const body = messageContent.substring(messageContent.indexOf('\r\n\r\n') + 4);
|
||
|
||
console.log(' [Server] Message analysis:');
|
||
console.log(` Header count: ${(headers.match(/\r\n/g) || []).length + 1}`);
|
||
console.log(` Body size: ${body.length} bytes`);
|
||
|
||
// Check for proper header folding
|
||
const longHeaders = headers.split('\r\n').filter(h => h.length > 78);
|
||
if (longHeaders.length > 0) {
|
||
console.log(` Long headers detected: ${longHeaders.length}`);
|
||
}
|
||
|
||
// Check for MIME structure
|
||
if (headers.includes('Content-Type:')) {
|
||
console.log(' MIME message detected');
|
||
}
|
||
|
||
socket.write('250 OK: Message format validated\r\n');
|
||
messageContent = '';
|
||
}
|
||
return;
|
||
}
|
||
|
||
const command = data.toString().trim();
|
||
console.log(` [Server] Received: ${command}`);
|
||
|
||
if (command.startsWith('EHLO')) {
|
||
socket.write('250-formats.example.com\r\n');
|
||
socket.write('250-8BITMIME\r\n');
|
||
socket.write('250-BINARYMIME\r\n');
|
||
socket.write('250 SIZE 52428800\r\n');
|
||
} else if (command.startsWith('MAIL FROM:')) {
|
||
socket.write('250 OK\r\n');
|
||
} else if (command.startsWith('RCPT TO:')) {
|
||
socket.write('250 OK\r\n');
|
||
} else if (command === 'DATA') {
|
||
socket.write('354 Start mail input\r\n');
|
||
inData = true;
|
||
} else if (command === 'QUIT') {
|
||
socket.write('221 Bye\r\n');
|
||
socket.end();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
const smtpClient = createTestSmtpClient({
|
||
host: testServer.hostname,
|
||
port: testServer.port,
|
||
secure: false
|
||
});
|
||
|
||
// Test different message formats
|
||
const formatTests = [
|
||
{
|
||
desc: 'Plain text message',
|
||
email: new Email({
|
||
from: 'sender@example.com',
|
||
to: ['recipient@example.com'],
|
||
subject: 'Plain text test',
|
||
text: 'This is a simple plain text message.'
|
||
})
|
||
},
|
||
{
|
||
desc: 'HTML message',
|
||
email: new Email({
|
||
from: 'sender@example.com',
|
||
to: ['recipient@example.com'],
|
||
subject: 'HTML test',
|
||
html: '<h1>HTML Message</h1><p>This is an <strong>HTML</strong> message.</p>'
|
||
})
|
||
},
|
||
{
|
||
desc: 'Multipart alternative',
|
||
email: new Email({
|
||
from: 'sender@example.com',
|
||
to: ['recipient@example.com'],
|
||
subject: 'Multipart test',
|
||
text: 'Plain text version',
|
||
html: '<p>HTML version</p>'
|
||
})
|
||
},
|
||
{
|
||
desc: 'Message with attachment',
|
||
email: new Email({
|
||
from: 'sender@example.com',
|
||
to: ['recipient@example.com'],
|
||
subject: 'Attachment test',
|
||
text: 'Message with attachment',
|
||
attachments: [{
|
||
filename: 'test.txt',
|
||
content: 'This is a test attachment'
|
||
}]
|
||
})
|
||
},
|
||
{
|
||
desc: 'Message with custom headers',
|
||
email: new Email({
|
||
from: 'sender@example.com',
|
||
to: ['recipient@example.com'],
|
||
subject: 'Custom headers test',
|
||
text: 'Message with custom headers',
|
||
headers: {
|
||
'X-Custom-Header': 'Custom value',
|
||
'X-Mailer': 'Test Mailer 1.0',
|
||
'Message-ID': '<test123@example.com>',
|
||
'References': '<ref1@example.com> <ref2@example.com>'
|
||
}
|
||
})
|
||
}
|
||
];
|
||
|
||
for (const test of formatTests) {
|
||
console.log(` Testing: ${test.desc}`);
|
||
|
||
const result = await smtpClient.sendMail(test.email);
|
||
console.log(` ${test.desc}: Success`);
|
||
expect(result).toBeDefined();
|
||
expect(result.messageId).toBeDefined();
|
||
}
|
||
|
||
await testServer.server.close();
|
||
})();
|
||
|
||
// Scenario 4: Error handling interoperability
|
||
await (async () => {
|
||
scenarioCount++;
|
||
console.log(`\nScenario ${scenarioCount}: Testing error handling interoperability`);
|
||
|
||
const testServer = await createTestServer({
|
||
onConnection: async (socket) => {
|
||
console.log(' [Server] Client connected');
|
||
socket.write('220 errors.example.com ESMTP\r\n');
|
||
|
||
socket.on('data', (data) => {
|
||
const command = data.toString().trim();
|
||
console.log(` [Server] Received: ${command}`);
|
||
|
||
if (command.startsWith('EHLO')) {
|
||
socket.write('250-errors.example.com\r\n');
|
||
socket.write('250-ENHANCEDSTATUSCODES\r\n');
|
||
socket.write('250 OK\r\n');
|
||
} else if (command.startsWith('MAIL FROM:')) {
|
||
const address = command.match(/<(.+)>/)?.[1] || '';
|
||
|
||
if (address.includes('temp-fail')) {
|
||
// Temporary failure - client should retry
|
||
socket.write('451 4.7.1 Temporary system problem, try again later\r\n');
|
||
} else if (address.includes('perm-fail')) {
|
||
// Permanent failure - client should not retry
|
||
socket.write('550 5.1.8 Invalid sender address format\r\n');
|
||
} else if (address.includes('syntax-error')) {
|
||
// Syntax error
|
||
socket.write('501 5.5.4 Syntax error in MAIL command\r\n');
|
||
} else {
|
||
socket.write('250 OK\r\n');
|
||
}
|
||
} else if (command.startsWith('RCPT TO:')) {
|
||
const address = command.match(/<(.+)>/)?.[1] || '';
|
||
|
||
if (address.includes('unknown')) {
|
||
socket.write('550 5.1.1 User unknown in local recipient table\r\n');
|
||
} else if (address.includes('temp-reject')) {
|
||
socket.write('450 4.2.1 Mailbox temporarily unavailable\r\n');
|
||
} else if (address.includes('quota-exceeded')) {
|
||
socket.write('552 5.2.2 Mailbox over quota\r\n');
|
||
} else {
|
||
socket.write('250 OK\r\n');
|
||
}
|
||
} else if (command === 'DATA') {
|
||
socket.write('354 Start mail input\r\n');
|
||
} else if (command === '.') {
|
||
socket.write('250 OK\r\n');
|
||
} else if (command === 'QUIT') {
|
||
socket.write('221 Bye\r\n');
|
||
socket.end();
|
||
} else {
|
||
// Unknown command
|
||
socket.write('500 5.5.1 Command unrecognized\r\n');
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
const smtpClient = createTestSmtpClient({
|
||
host: testServer.hostname,
|
||
port: testServer.port,
|
||
secure: false
|
||
});
|
||
|
||
// Test various error scenarios
|
||
const errorTests = [
|
||
{
|
||
desc: 'Temporary sender failure',
|
||
from: 'temp-fail@example.com',
|
||
to: 'valid@example.com',
|
||
expectError: true,
|
||
errorType: '4xx'
|
||
},
|
||
{
|
||
desc: 'Permanent sender failure',
|
||
from: 'perm-fail@example.com',
|
||
to: 'valid@example.com',
|
||
expectError: true,
|
||
errorType: '5xx'
|
||
},
|
||
{
|
||
desc: 'Unknown recipient',
|
||
from: 'valid@example.com',
|
||
to: 'unknown@example.com',
|
||
expectError: true,
|
||
errorType: '5xx'
|
||
},
|
||
{
|
||
desc: 'Mixed valid/invalid recipients',
|
||
from: 'valid@example.com',
|
||
to: ['valid@example.com', 'unknown@example.com', 'temp-reject@example.com'],
|
||
expectError: false, // Partial success
|
||
errorType: 'mixed'
|
||
}
|
||
];
|
||
|
||
for (const test of errorTests) {
|
||
console.log(` Testing: ${test.desc}`);
|
||
|
||
const email = new Email({
|
||
from: test.from,
|
||
to: Array.isArray(test.to) ? test.to : [test.to],
|
||
subject: `Error test: ${test.desc}`,
|
||
text: `Testing error handling for ${test.desc}`
|
||
});
|
||
|
||
try {
|
||
const result = await smtpClient.sendMail(email);
|
||
|
||
if (test.expectError && test.errorType !== 'mixed') {
|
||
console.log(` Unexpected success for ${test.desc}`);
|
||
} else {
|
||
console.log(` ${test.desc}: Handled correctly`);
|
||
if (result.rejected && result.rejected.length > 0) {
|
||
console.log(` Rejected: ${result.rejected.length} recipients`);
|
||
}
|
||
if (result.accepted && result.accepted.length > 0) {
|
||
console.log(` Accepted: ${result.accepted.length} recipients`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
if (test.expectError) {
|
||
console.log(` ${test.desc}: Failed as expected (${error.responseCode})`);
|
||
if (test.errorType === '4xx') {
|
||
expect(error.responseCode).toBeGreaterThanOrEqual(400);
|
||
expect(error.responseCode).toBeLessThan(500);
|
||
} else if (test.errorType === '5xx') {
|
||
expect(error.responseCode).toBeGreaterThanOrEqual(500);
|
||
expect(error.responseCode).toBeLessThan(600);
|
||
}
|
||
} else {
|
||
console.log(` Unexpected error for ${test.desc}: ${error.message}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
await testServer.server.close();
|
||
})();
|
||
|
||
// Scenario 5: Connection management interoperability
|
||
await (async () => {
|
||
scenarioCount++;
|
||
console.log(`\nScenario ${scenarioCount}: Testing connection management interoperability`);
|
||
|
||
const testServer = await createTestServer({
|
||
onConnection: async (socket) => {
|
||
console.log(' [Server] Client connected');
|
||
|
||
let commandCount = 0;
|
||
let idleTime = Date.now();
|
||
const maxIdleTime = 5000; // 5 seconds for testing
|
||
const maxCommands = 10;
|
||
|
||
socket.write('220 connection.example.com ESMTP\r\n');
|
||
|
||
// Set up idle timeout
|
||
const idleCheck = setInterval(() => {
|
||
if (Date.now() - idleTime > maxIdleTime) {
|
||
console.log(' [Server] Idle timeout - closing connection');
|
||
socket.write('421 4.4.2 Idle timeout, closing connection\r\n');
|
||
socket.end();
|
||
clearInterval(idleCheck);
|
||
}
|
||
}, 1000);
|
||
|
||
socket.on('data', (data) => {
|
||
const command = data.toString().trim();
|
||
commandCount++;
|
||
idleTime = Date.now();
|
||
|
||
console.log(` [Server] Command ${commandCount}: ${command}`);
|
||
|
||
if (commandCount > maxCommands) {
|
||
console.log(' [Server] Too many commands - closing connection');
|
||
socket.write('421 4.7.0 Too many commands, closing connection\r\n');
|
||
socket.end();
|
||
clearInterval(idleCheck);
|
||
return;
|
||
}
|
||
|
||
if (command.startsWith('EHLO')) {
|
||
socket.write('250-connection.example.com\r\n');
|
||
socket.write('250-PIPELINING\r\n');
|
||
socket.write('250 OK\r\n');
|
||
} else if (command.startsWith('MAIL FROM:')) {
|
||
socket.write('250 OK\r\n');
|
||
} else if (command.startsWith('RCPT TO:')) {
|
||
socket.write('250 OK\r\n');
|
||
} else if (command === 'DATA') {
|
||
socket.write('354 Start mail input\r\n');
|
||
} else if (command === '.') {
|
||
socket.write('250 OK\r\n');
|
||
} else if (command === 'RSET') {
|
||
socket.write('250 OK\r\n');
|
||
} else if (command === 'NOOP') {
|
||
socket.write('250 OK\r\n');
|
||
} else if (command === 'QUIT') {
|
||
socket.write('221 Bye\r\n');
|
||
socket.end();
|
||
clearInterval(idleCheck);
|
||
}
|
||
});
|
||
|
||
socket.on('close', () => {
|
||
clearInterval(idleCheck);
|
||
console.log(` [Server] Connection closed after ${commandCount} commands`);
|
||
});
|
||
}
|
||
});
|
||
|
||
const smtpClient = createTestSmtpClient({
|
||
host: testServer.hostname,
|
||
port: testServer.port,
|
||
secure: false,
|
||
pool: true,
|
||
maxConnections: 1
|
||
});
|
||
|
||
// Test connection reuse
|
||
console.log(' Testing connection reuse...');
|
||
|
||
for (let i = 1; i <= 3; i++) {
|
||
const email = new Email({
|
||
from: 'sender@example.com',
|
||
to: [`recipient${i}@example.com`],
|
||
subject: `Connection test ${i}`,
|
||
text: `Testing connection management - email ${i}`
|
||
});
|
||
|
||
const result = await smtpClient.sendMail(email);
|
||
console.log(` Email ${i} sent successfully`);
|
||
expect(result).toBeDefined();
|
||
|
||
// Small delay to test connection persistence
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
}
|
||
|
||
// Test NOOP for keeping connection alive
|
||
console.log(' Testing connection keep-alive...');
|
||
|
||
await smtpClient.verify(); // This might send NOOP
|
||
console.log(' Connection verified (keep-alive)');
|
||
|
||
await smtpClient.close();
|
||
await testServer.server.close();
|
||
})();
|
||
|
||
// Scenario 6: Legacy SMTP compatibility
|
||
await (async () => {
|
||
scenarioCount++;
|
||
console.log(`\nScenario ${scenarioCount}: Testing legacy SMTP compatibility`);
|
||
|
||
const testServer = await createTestServer({
|
||
onConnection: async (socket) => {
|
||
console.log(' [Server] Legacy SMTP server');
|
||
|
||
// Old-style greeting without ESMTP
|
||
socket.write('220 legacy.example.com Simple Mail Transfer Service Ready\r\n');
|
||
|
||
socket.on('data', (data) => {
|
||
const command = data.toString().trim();
|
||
console.log(` [Server] Received: ${command}`);
|
||
|
||
if (command.startsWith('EHLO')) {
|
||
// Legacy server doesn't understand EHLO
|
||
socket.write('500 Command unrecognized\r\n');
|
||
} else if (command.startsWith('HELO')) {
|
||
socket.write('250 legacy.example.com\r\n');
|
||
} else if (command.startsWith('MAIL FROM:')) {
|
||
// Very strict syntax checking
|
||
if (!command.match(/^MAIL FROM:\s*<[^>]+>\s*$/)) {
|
||
socket.write('501 Syntax error\r\n');
|
||
} else {
|
||
socket.write('250 Sender OK\r\n');
|
||
}
|
||
} else if (command.startsWith('RCPT TO:')) {
|
||
if (!command.match(/^RCPT TO:\s*<[^>]+>\s*$/)) {
|
||
socket.write('501 Syntax error\r\n');
|
||
} else {
|
||
socket.write('250 Recipient OK\r\n');
|
||
}
|
||
} else if (command === 'DATA') {
|
||
socket.write('354 Enter mail, end with "." on a line by itself\r\n');
|
||
} else if (command === '.') {
|
||
socket.write('250 Message accepted for delivery\r\n');
|
||
} else if (command === 'QUIT') {
|
||
socket.write('221 Service closing transmission channel\r\n');
|
||
socket.end();
|
||
} else if (command === 'HELP') {
|
||
socket.write('214-Commands supported:\r\n');
|
||
socket.write('214-HELO MAIL RCPT DATA QUIT HELP\r\n');
|
||
socket.write('214 End of HELP info\r\n');
|
||
} else {
|
||
socket.write('500 Command unrecognized\r\n');
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// Test with client that can fall back to basic SMTP
|
||
const legacyClient = createTestSmtpClient({
|
||
host: testServer.hostname,
|
||
port: testServer.port,
|
||
secure: false,
|
||
disableESMTP: true // Force HELO mode
|
||
});
|
||
|
||
const email = new Email({
|
||
from: 'sender@example.com',
|
||
to: ['recipient@example.com'],
|
||
subject: 'Legacy compatibility test',
|
||
text: 'Testing compatibility with legacy SMTP servers'
|
||
});
|
||
|
||
const result = await legacyClient.sendMail(email);
|
||
console.log(' Legacy SMTP compatibility: Success');
|
||
expect(result).toBeDefined();
|
||
expect(result.messageId).toBeDefined();
|
||
|
||
await testServer.server.close();
|
||
})();
|
||
|
||
console.log(`\n${testId}: All ${scenarioCount} interoperability scenarios tested ✓`);
|
||
});
|
||
|
||
tap.start(); |