import * as plugins from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as net from 'net'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; const TEST_PORT = 2525; let testServer; const TEST_TIMEOUT = 10000; tap.test('prepare server', async () => { testServer = await startTestServer({ port: TEST_PORT }); await new Promise(resolve => setTimeout(resolve, 100)); }); // Test: Basic NOOP command tap.test('NOOP - should accept NOOP command', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { currentStep = 'noop'; socket.write('NOOP\r\n'); } else if (currentStep === 'noop' && receivedData.includes('250')) { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(receivedData).toInclude('250'); // NOOP response done.resolve(); }, 100); } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Test: Multiple NOOP commands tap.test('NOOP - should handle multiple consecutive NOOP commands', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; let noopCount = 0; const maxNoops = 3; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'ehlo'; receivedData = ''; // Clear buffer after processing socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250 ')) { currentStep = 'noop'; receivedData = ''; // Clear buffer after processing socket.write('NOOP\r\n'); } else if (currentStep === 'noop' && receivedData.includes('250 OK')) { noopCount++; receivedData = ''; // Clear buffer after processing if (noopCount < maxNoops) { // Send another NOOP command socket.write('NOOP\r\n'); } else { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(noopCount).toEqual(maxNoops); done.resolve(); }, 100); } } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Test: NOOP during transaction tap.test('NOOP - should work during email transaction', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { currentStep = 'mail_from'; socket.write('MAIL FROM:\r\n'); } else if (currentStep === 'mail_from' && receivedData.includes('250')) { currentStep = 'noop_after_mail'; socket.write('NOOP\r\n'); } else if (currentStep === 'noop_after_mail' && receivedData.includes('250')) { currentStep = 'rcpt_to'; socket.write('RCPT TO:\r\n'); } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) { currentStep = 'noop_after_rcpt'; socket.write('NOOP\r\n'); } else if (currentStep === 'noop_after_rcpt' && receivedData.includes('250')) { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(receivedData).toInclude('250'); done.resolve(); }, 100); } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Test: NOOP with parameter (should be ignored) tap.test('NOOP - should handle NOOP with parameters', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { currentStep = 'noop_with_param'; socket.write('NOOP ignored parameter\r\n'); // Parameters should be ignored } else if (currentStep === 'noop_with_param' && receivedData.includes('250')) { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(receivedData).toInclude('250'); done.resolve(); }, 100); } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Test: NOOP before EHLO/HELO tap.test('NOOP - should work before EHLO', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'noop_before_ehlo'; socket.write('NOOP\r\n'); } else if (currentStep === 'noop_before_ehlo' && receivedData.includes('250')) { currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(receivedData).toInclude('250'); done.resolve(); }, 100); } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); // Test: Rapid NOOP commands (stress test) tap.test('NOOP - should handle rapid NOOP commands', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); let receivedData = ''; let currentStep = 'connecting'; let noopsSent = 0; let noopsReceived = 0; const rapidNoops = 10; socket.on('data', (data) => { receivedData += data.toString(); if (currentStep === 'connecting' && receivedData.includes('220')) { currentStep = 'ehlo'; socket.write('EHLO test.example.com\r\n'); } else if (currentStep === 'ehlo' && receivedData.includes('250')) { currentStep = 'rapid_noop'; // Send multiple NOOPs rapidly for (let i = 0; i < rapidNoops; i++) { socket.write('NOOP\r\n'); noopsSent++; } } else if (currentStep === 'rapid_noop') { // Count 250 responses const matches = receivedData.match(/250 /g); if (matches) { noopsReceived = matches.length - 1; // -1 for EHLO response } if (noopsReceived >= rapidNoops) { socket.write('QUIT\r\n'); setTimeout(() => { socket.destroy(); expect(noopsReceived).toBeGreaterThan(rapidNoops - 1); done.resolve(); }, 500); } } }); socket.on('error', (error) => { done.reject(error); }); socket.on('timeout', () => { socket.destroy(); done.reject(new Error(`Connection timeout at step: ${currentStep}`)); }); await done.promise; }); tap.test('cleanup server', async () => { await stopTestServer(testServer); }); tap.start();