import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as plugins from './plugins.js'; import { createTestServer } from '../../helpers/server.loader.js'; import { createSmtpClient } from '../../helpers/smtp.client.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 .' : '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 = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); const email = new plugins.smartmail.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 = createSmtpClient({ 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 plugins.smartmail.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 = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); // Test different message formats const formatTests = [ { desc: 'Plain text message', email: new plugins.smartmail.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 plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'HTML test', html: '

HTML Message

This is an HTML message.

' }) }, { desc: 'Multipart alternative', email: new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Multipart test', text: 'Plain text version', html: '

HTML version

' }) }, { desc: 'Message with attachment', email: new plugins.smartmail.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 plugins.smartmail.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': '', 'References': ' ' } }) } ]; 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 = createSmtpClient({ 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 plugins.smartmail.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 = createSmtpClient({ 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 plugins.smartmail.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 = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, disableESMTP: true // Force HELO mode }); const email = new plugins.smartmail.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 ✓`); });