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(); expect(testServer).toBeTruthy(); expect(testServer.port).toBeGreaterThan(0); }); tap.test('CCMD-07: Parse single-line responses', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Test various single-line responses const testCases = [ { command: 'NOOP', expectedCode: '250', expectedText: /OK/ }, { command: 'RSET', expectedCode: '250', expectedText: /Reset/ }, { command: 'HELP', expectedCode: '214', expectedText: /Help/ } ]; for (const test of testCases) { const response = await smtpClient.sendCommand(test.command); // Parse response code and text const codeMatch = response.match(/^(\d{3})\s+(.*)$/m); expect(codeMatch).toBeTruthy(); if (codeMatch) { const [, code, text] = codeMatch; expect(code).toEqual(test.expectedCode); expect(text).toMatch(test.expectedText); console.log(`${test.command}: ${code} ${text}`); } } await smtpClient.close(); }); tap.test('CCMD-07: Parse multi-line responses', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // EHLO typically returns multi-line response const ehloResponse = await smtpClient.sendCommand('EHLO testclient.example.com'); // Parse multi-line response const lines = ehloResponse.split('\r\n').filter(line => line.length > 0); let capabilities: string[] = []; let finalCode = ''; lines.forEach((line, index) => { const multiLineMatch = line.match(/^(\d{3})-(.*)$/); // 250-CAPABILITY const finalLineMatch = line.match(/^(\d{3})\s+(.*)$/); // 250 CAPABILITY if (multiLineMatch) { const [, code, capability] = multiLineMatch; expect(code).toEqual('250'); capabilities.push(capability); } else if (finalLineMatch) { const [, code, capability] = finalLineMatch; expect(code).toEqual('250'); finalCode = code; capabilities.push(capability); } }); expect(finalCode).toEqual('250'); expect(capabilities.length).toBeGreaterThan(0); console.log('Parsed capabilities:', capabilities); // Common capabilities to check for const commonCapabilities = ['PIPELINING', 'SIZE', '8BITMIME']; const foundCapabilities = commonCapabilities.filter(cap => capabilities.some(c => c.includes(cap)) ); console.log('Found common capabilities:', foundCapabilities); await smtpClient.close(); }); tap.test('CCMD-07: Parse error response codes', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); await smtpClient.sendCommand('EHLO testclient.example.com'); // Test various error conditions const errorTests = [ { command: 'RCPT TO:', // Without MAIL FROM expectedCodeRange: [500, 599], description: 'RCPT without MAIL FROM' }, { command: 'INVALID_COMMAND', expectedCodeRange: [500, 502], description: 'Invalid command' }, { command: 'MAIL FROM:', expectedCodeRange: [501, 553], description: 'Invalid email format' } ]; for (const test of errorTests) { try { const response = await smtpClient.sendCommand(test.command); const codeMatch = response.match(/^(\d{3})/); if (codeMatch) { const code = parseInt(codeMatch[1]); console.log(`${test.description}: ${code} ${response.trim()}`); expect(code).toBeGreaterThanOrEqual(test.expectedCodeRange[0]); expect(code).toBeLessThanOrEqual(test.expectedCodeRange[1]); } } catch (error) { console.log(`${test.description}: Error caught - ${error.message}`); } } await smtpClient.sendCommand('RSET'); await smtpClient.close(); }); tap.test('CCMD-07: Parse enhanced status codes', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); await smtpClient.sendCommand('EHLO testclient.example.com'); // Send commands that might return enhanced status codes await smtpClient.sendCommand('MAIL FROM:'); // Try to send to a potentially problematic address const response = await smtpClient.sendCommand('RCPT TO:'); // Parse for enhanced status codes (X.Y.Z format) const enhancedMatch = response.match(/\b(\d\.\d+\.\d+)\b/); if (enhancedMatch) { const [, enhancedCode] = enhancedMatch; console.log(`Found enhanced status code: ${enhancedCode}`); // Parse enhanced code components const [classCode, subjectCode, detailCode] = enhancedCode.split('.').map(Number); expect(classCode).toBeGreaterThanOrEqual(2); expect(classCode).toBeLessThanOrEqual(5); expect(subjectCode).toBeGreaterThanOrEqual(0); expect(detailCode).toBeGreaterThanOrEqual(0); // Interpret the enhanced code const classDescriptions = { 2: 'Success', 3: 'Temporary Failure', 4: 'Persistent Transient Failure', 5: 'Permanent Failure' }; console.log(`Enhanced code ${enhancedCode} means: ${classDescriptions[classCode] || 'Unknown'}`); } else { console.log('No enhanced status code found in response'); } await smtpClient.sendCommand('RSET'); await smtpClient.close(); }); tap.test('CCMD-07: Parse response timing and delays', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, debug: true }); await smtpClient.connect(); await smtpClient.sendCommand('EHLO testclient.example.com'); // Measure response times for different commands const timingTests = [ 'NOOP', 'HELP', 'MAIL FROM:', 'RSET' ]; const timings: { command: string; time: number; code: string }[] = []; for (const command of timingTests) { const startTime = Date.now(); const response = await smtpClient.sendCommand(command); const elapsed = Date.now() - startTime; const codeMatch = response.match(/^(\d{3})/); const code = codeMatch ? codeMatch[1] : 'unknown'; timings.push({ command, time: elapsed, code }); } // Analyze timings console.log('\nCommand response times:'); timings.forEach(t => { console.log(` ${t.command}: ${t.time}ms (${t.code})`); }); const avgTime = timings.reduce((sum, t) => sum + t.time, 0) / timings.length; console.log(`Average response time: ${avgTime.toFixed(2)}ms`); // All commands should respond quickly (under 1 second) timings.forEach(t => { expect(t.time).toBeLessThan(1000); }); await smtpClient.close(); }); tap.test('CCMD-07: Parse continuation responses', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, debug: true }); await smtpClient.connect(); await smtpClient.sendCommand('EHLO testclient.example.com'); await smtpClient.sendCommand('MAIL FROM:'); await smtpClient.sendCommand('RCPT TO:'); // DATA command returns a continuation response (354) const dataResponse = await smtpClient.sendCommand('DATA'); // Parse continuation response const contMatch = dataResponse.match(/^(\d{3})[\s-](.*)$/); expect(contMatch).toBeTruthy(); if (contMatch) { const [, code, text] = contMatch; expect(code).toEqual('354'); expect(text).toMatch(/mail input|end with/i); console.log(`Continuation response: ${code} ${text}`); } // Send message data const messageData = 'Subject: Test\r\n\r\nTest message\r\n.'; const finalResponse = await smtpClient.sendCommand(messageData); // Parse final response const finalMatch = finalResponse.match(/^(\d{3})/); expect(finalMatch).toBeTruthy(); expect(finalMatch![1]).toEqual('250'); await smtpClient.close(); }); tap.test('CCMD-07: Parse response text variations', async () => { const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000, debug: true }); await smtpClient.connect(); // Different servers may have different response text const response = await smtpClient.sendCommand('EHLO testclient.example.com'); // Extract server identification from first line const firstLineMatch = response.match(/^250[\s-](.+?)(?:\r?\n|$)/); if (firstLineMatch) { const serverIdent = firstLineMatch[1]; console.log(`Server identification: ${serverIdent}`); // Check for common patterns const patterns = [ { pattern: /ESMTP/, description: 'Extended SMTP' }, { pattern: /ready|ok|hello/i, description: 'Greeting' }, { pattern: /\d+\.\d+/, description: 'Version number' }, { pattern: /[a-zA-Z0-9.-]+/, description: 'Hostname' } ]; patterns.forEach(p => { if (p.pattern.test(serverIdent)) { console.log(` Found: ${p.description}`); } }); } // Test QUIT response variations const quitResponse = await smtpClient.sendCommand('QUIT'); const quitMatch = quitResponse.match(/^(\d{3})\s+(.*)$/); if (quitMatch) { const [, code, text] = quitMatch; expect(code).toEqual('221'); // Common QUIT response patterns const quitPatterns = ['bye', 'closing', 'goodbye', 'terminating']; const foundPattern = quitPatterns.some(p => text.toLowerCase().includes(p)); console.log(`QUIT response: ${text} (matches pattern: ${foundPattern})`); } }); tap.test('cleanup test SMTP server', async () => { if (testServer) { await testServer.stop(); } }); export default tap.start();