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('CEDGE-03: should handle protocol violations gracefully', async (tools) => { const testId = 'CEDGE-03-protocol-violations'; console.log(`\n${testId}: Testing protocol violation handling...`); let scenarioCount = 0; // Scenario 1: Server closes connection unexpectedly await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing unexpected connection closure`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); let commandCount = 0; socket.on('data', (data) => { commandCount++; const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { socket.write('250-mail.example.com\r\n'); socket.write('250 OK\r\n'); } else if (command.startsWith('MAIL FROM:')) { // Abruptly close connection console.log(' [Server] Closing connection unexpectedly'); socket.destroy(); } }); } }); 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: 'Connection closure test', text: 'Testing unexpected disconnection' }); try { await smtpClient.sendMail(email); console.log(' Unexpected success'); } catch (error) { console.log(` Expected error: ${error.message}`); expect(error).toBeDefined(); } await testServer.server.close(); })(); // Scenario 2: Server sends data without CRLF await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing responses without proper CRLF`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); // Send greeting without CRLF socket.write('220 mail.example.com ESMTP'); // Then send proper CRLF setTimeout(() => socket.write('\r\n'), 100); socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { // Mix responses with and without CRLF socket.write('250-mail.example.com\n'); // Just LF socket.write('250-SIZE 10485760\r'); // Just CR socket.write('250 OK\r\n'); // Proper CRLF } 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 === 'QUIT') { socket.write('221 Bye\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: 'Line ending test', text: 'Testing non-standard line endings' }); const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success (handled gracefully)' : 'Failed'}`); expect(result).toBeDefined(); await testServer.server.close(); })(); // Scenario 3: Server sends responses in wrong order await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing out-of-order responses`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); const pendingResponses: Array<() => void> = []; socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { socket.write('250-mail.example.com\r\n'); socket.write('250 OK\r\n'); } else if (command.startsWith('MAIL FROM:')) { // Delay response pendingResponses.push(() => { socket.write('250 OK\r\n'); }); } else if (command.startsWith('RCPT TO:')) { // Send this response first, then the MAIL response socket.write('250 OK\r\n'); if (pendingResponses.length > 0) { pendingResponses.forEach(fn => fn()); pendingResponses.length = 0; } } 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(); } }); } }); 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: 'Response order test', text: 'Testing out-of-order responses' }); try { const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`); } catch (error) { console.log(` Expected possible error: ${error.message}`); expect(error).toBeDefined(); } await testServer.server.close(); })(); // Scenario 4: Server sends unsolicited responses await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing unsolicited server responses`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); // Send unsolicited responses periodically const interval = setInterval(() => { if (!socket.destroyed) { console.log(' [Server] Sending unsolicited response'); socket.write('250-NOTICE: Server status update\r\n'); } }, 500); socket.on('close', () => clearInterval(interval)); socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { socket.write('250-mail.example.com\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 === 'QUIT') { clearInterval(interval); socket.write('221 Bye\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: 'Unsolicited response test', text: 'Testing unsolicited server messages' }); const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`); expect(result).toBeDefined(); await testServer.server.close(); })(); // Scenario 5: Server violates response code format await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing invalid response codes`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); // Invalid response code (should be 3 digits) socket.write('22 mail.example.com ESMTP\r\n'); setTimeout(() => { // Send correct response socket.write('220 mail.example.com ESMTP\r\n'); }, 100); socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { // Mix valid and invalid response codes socket.write('250-mail.example.com\r\n'); socket.write('25O-TYPO IN CODE\r\n'); // Letter O instead of zero socket.write('250 OK\r\n'); } else if (command.startsWith('MAIL FROM:')) { socket.write('2.5.0 OK\r\n'); // Wrong format setTimeout(() => { socket.write('250 OK\r\n'); }, 50); } 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 === 'QUIT') { socket.write('221 Bye\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: 'Response code test', text: 'Testing invalid response codes' }); try { const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success (handled invalid codes)' : 'Failed'}`); } catch (error) { console.log(` Expected possible error: ${error.message}`); expect(error).toBeDefined(); } await testServer.server.close(); })(); // Scenario 6: Server sends binary data await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing binary data in responses`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); // Send greeting with some binary data socket.write('220 mail.example.com ESMTP\r\n'); socket.write(Buffer.from([0x00, 0x01, 0x02, 0x03])); // Binary data socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { socket.write('250-mail.example.com\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 === '.') { // Include binary in response socket.write('250 OK '); socket.write(Buffer.from([0xFF, 0xFE])); socket.write('\r\n'); } else if (command === 'QUIT') { socket.write('221 Bye\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: 'Binary data test', text: 'Testing binary data handling' }); try { const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`); expect(result).toBeDefined(); } catch (error) { console.log(` Error handling binary data: ${error.message}`); expect(error).toBeDefined(); } await testServer.server.close(); })(); console.log(`\n${testId}: All ${scenarioCount} protocol violation scenarios tested ✓`); });