import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestSmtpServer } from '../../helpers/server.loader.js'; import { createSmtpClient } from '../../helpers/smtp.client.js'; let testServer: any; tap.test('setup test SMTP server', async () => { testServer = await startTestSmtpServer({ features: ['PIPELINING'] // Ensure server advertises PIPELINING }); expect(testServer).toBeTruthy(); expect(testServer.port).toBeGreaterThan(0); }); tap.test('CCMD-06: Check PIPELINING capability', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Send EHLO to get capabilities const ehloResponse = await smtpClient.sendCommand('EHLO testclient.example.com'); expect(ehloResponse).toInclude('250'); // Check if PIPELINING is advertised const supportsPipelining = ehloResponse.includes('PIPELINING'); console.log(`Server supports PIPELINING: ${supportsPipelining}`); if (supportsPipelining) { expect(ehloResponse).toInclude('PIPELINING'); } await smtpClient.close(); }); tap.test('CCMD-06: Basic command pipelining', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, enablePipelining: true, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Send EHLO first await smtpClient.sendCommand('EHLO testclient.example.com'); // Pipeline multiple commands console.log('Sending pipelined commands...'); const startTime = Date.now(); // Send commands without waiting for responses const promises = [ smtpClient.sendCommand('MAIL FROM:'), smtpClient.sendCommand('RCPT TO:'), smtpClient.sendCommand('RCPT TO:') ]; // Wait for all responses const responses = await Promise.all(promises); const elapsed = Date.now() - startTime; console.log(`Pipelined commands completed in ${elapsed}ms`); // Verify all responses are successful responses.forEach((response, index) => { expect(response).toInclude('250'); console.log(`Response ${index + 1}: ${response.trim()}`); }); // Reset for cleanup await smtpClient.sendCommand('RSET'); await smtpClient.close(); }); tap.test('CCMD-06: Pipelining with DATA command', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, enablePipelining: true, connectionTimeout: 10000, debug: true }); await smtpClient.connect(); await smtpClient.sendCommand('EHLO testclient.example.com'); // Pipeline commands up to DATA console.log('Pipelining commands before DATA...'); const setupPromises = [ smtpClient.sendCommand('MAIL FROM:'), smtpClient.sendCommand('RCPT TO:') ]; const setupResponses = await Promise.all(setupPromises); setupResponses.forEach(response => { expect(response).toInclude('250'); }); // DATA command should not be pipelined const dataResponse = await smtpClient.sendCommand('DATA'); expect(dataResponse).toInclude('354'); // Send message data const messageData = [ 'Subject: Test Pipelining', 'From: sender@example.com', 'To: recipient@example.com', '', 'This is a test message sent with pipelining.', '.' ].join('\r\n'); const messageResponse = await smtpClient.sendCommand(messageData); expect(messageResponse).toInclude('250'); await smtpClient.close(); }); tap.test('CCMD-06: Pipelining error handling', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, enablePipelining: true, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); await smtpClient.sendCommand('EHLO testclient.example.com'); // Pipeline commands with an invalid one console.log('Testing pipelining with invalid command...'); const mixedPromises = [ smtpClient.sendCommand('MAIL FROM:'), smtpClient.sendCommand('RCPT TO:'), // Invalid format smtpClient.sendCommand('RCPT TO:') ]; const responses = await Promise.allSettled(mixedPromises); // Check responses responses.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`Command ${index + 1} response: ${result.value.trim()}`); if (index === 1) { // Invalid email might get rejected expect(result.value).toMatch(/[45]\d\d/); } } else { console.log(`Command ${index + 1} failed: ${result.reason}`); } }); // Reset await smtpClient.sendCommand('RSET'); await smtpClient.close(); }); tap.test('CCMD-06: Pipelining performance comparison', async () => { // Test without pipelining const clientNoPipeline = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, enablePipelining: false, connectionTimeout: 10000, debug: false }); await clientNoPipeline.connect(); await clientNoPipeline.sendCommand('EHLO testclient.example.com'); const startNoPipeline = Date.now(); // Send commands sequentially await clientNoPipeline.sendCommand('MAIL FROM:'); await clientNoPipeline.sendCommand('RCPT TO:'); await clientNoPipeline.sendCommand('RCPT TO:'); await clientNoPipeline.sendCommand('RCPT TO:'); await clientNoPipeline.sendCommand('RSET'); const timeNoPipeline = Date.now() - startNoPipeline; await clientNoPipeline.close(); // Test with pipelining const clientPipeline = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, enablePipelining: true, connectionTimeout: 10000, debug: false }); await clientPipeline.connect(); await clientPipeline.sendCommand('EHLO testclient.example.com'); const startPipeline = Date.now(); // Send commands pipelined await Promise.all([ clientPipeline.sendCommand('MAIL FROM:'), clientPipeline.sendCommand('RCPT TO:'), clientPipeline.sendCommand('RCPT TO:'), clientPipeline.sendCommand('RCPT TO:'), clientPipeline.sendCommand('RSET') ]); const timePipeline = Date.now() - startPipeline; await clientPipeline.close(); console.log(`Sequential: ${timeNoPipeline}ms, Pipelined: ${timePipeline}ms`); console.log(`Speedup: ${(timeNoPipeline / timePipeline).toFixed(2)}x`); // Pipelining should be faster (but might not be in local testing) expect(timePipeline).toBeLessThanOrEqual(timeNoPipeline * 1.1); // Allow 10% margin }); tap.test('CCMD-06: Pipelining with multiple recipients', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, enablePipelining: true, connectionTimeout: 10000, debug: true }); await smtpClient.connect(); await smtpClient.sendCommand('EHLO testclient.example.com'); // Create many recipients const recipientCount = 10; const recipients = Array.from({ length: recipientCount }, (_, i) => `recipient${i + 1}@example.com` ); console.log(`Pipelining ${recipientCount} recipients...`); // Pipeline MAIL FROM and all RCPT TO commands const commands = [ smtpClient.sendCommand('MAIL FROM:'), ...recipients.map(rcpt => smtpClient.sendCommand(`RCPT TO:<${rcpt}>`)) ]; const startTime = Date.now(); const responses = await Promise.all(commands); const elapsed = Date.now() - startTime; console.log(`Sent ${commands.length} pipelined commands in ${elapsed}ms`); // Verify all succeeded responses.forEach((response, index) => { expect(response).toInclude('250'); }); // Calculate average time per command const avgTime = elapsed / commands.length; console.log(`Average time per command: ${avgTime.toFixed(2)}ms`); await smtpClient.sendCommand('RSET'); await smtpClient.close(); }); tap.test('CCMD-06: Pipelining limits and buffering', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, enablePipelining: true, pipelineMaxCommands: 5, // Limit pipeline size connectionTimeout: 10000, debug: true }); await smtpClient.connect(); await smtpClient.sendCommand('EHLO testclient.example.com'); // Try to pipeline more than the limit const commandCount = 8; const commands = [ smtpClient.sendCommand('MAIL FROM:'), ...Array.from({ length: commandCount - 1 }, (_, i) => smtpClient.sendCommand(`RCPT TO:`) ) ]; console.log(`Attempting to pipeline ${commandCount} commands with limit of 5...`); const responses = await Promise.all(commands); // All should still succeed, even if sent in batches responses.forEach(response => { expect(response).toInclude('250'); }); console.log('All commands processed successfully despite pipeline limit'); await smtpClient.sendCommand('RSET'); await smtpClient.close(); }); tap.test('cleanup test SMTP server', async () => { if (testServer) { await testServer.stop(); } }); export default tap.start();