155 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			155 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * CMD-01: EHLO Command Tests | ||
|  |  * Tests SMTP EHLO command and server capabilities advertisement | ||
|  |  */ | ||
|  | 
 | ||
|  | import { assert, assertEquals, assertMatch } from '@std/assert'; | ||
|  | import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts'; | ||
|  | import { | ||
|  |   connectToSmtp, | ||
|  |   waitForGreeting, | ||
|  |   sendSmtpCommand, | ||
|  |   closeSmtpConnection, | ||
|  | } from '../../helpers/utils.ts'; | ||
|  | 
 | ||
|  | const TEST_PORT = 25251; | ||
|  | let testServer: ITestServer; | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-01: Setup - Start SMTP server', | ||
|  |   async fn() { | ||
|  |     testServer = await startTestServer({ port: TEST_PORT }); | ||
|  |     assert(testServer, 'Test server should be created'); | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-01: EHLO Command - server responds with proper capabilities', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       // Wait for greeting
 | ||
|  |       const greeting = await waitForGreeting(conn); | ||
|  |       assert(greeting.includes('220'), 'Should receive 220 greeting'); | ||
|  | 
 | ||
|  |       // Send EHLO
 | ||
|  |       const ehloResponse = await sendSmtpCommand(conn, 'EHLO test.example.com', '250'); | ||
|  | 
 | ||
|  |       // Parse capabilities
 | ||
|  |       const lines = ehloResponse | ||
|  |         .split('\r\n') | ||
|  |         .filter((line) => line.startsWith('250')) | ||
|  |         .filter((line) => line.length > 0); | ||
|  | 
 | ||
|  |       const capabilities = lines.map((line) => line.substring(4).trim()); | ||
|  |       console.log('📋 Server capabilities:', capabilities); | ||
|  | 
 | ||
|  |       // Verify essential capabilities
 | ||
|  |       assert( | ||
|  |         capabilities.some((cap) => cap.includes('SIZE')), | ||
|  |         'Should advertise SIZE capability' | ||
|  |       ); | ||
|  |       assert( | ||
|  |         capabilities.some((cap) => cap.includes('8BITMIME')), | ||
|  |         'Should advertise 8BITMIME capability' | ||
|  |       ); | ||
|  | 
 | ||
|  |       // The last line should be "250 " (without hyphen)
 | ||
|  |       const lastLine = lines[lines.length - 1]; | ||
|  |       assert(lastLine.startsWith('250 '), 'Last line should start with "250 " (space, not hyphen)'); | ||
|  |     } finally { | ||
|  |       await closeSmtpConnection(conn); | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-01: EHLO with invalid hostname - server handles gracefully', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  | 
 | ||
|  |       const invalidHostnames = [ | ||
|  |         '',                    // Empty hostname
 | ||
|  |         ' ',                   // Whitespace only
 | ||
|  |         'invalid..hostname',   // Double dots
 | ||
|  |         '.invalid',            // Leading dot
 | ||
|  |         'invalid.',            // Trailing dot
 | ||
|  |         'very-long-hostname-that-exceeds-reasonable-limits-' + 'x'.repeat(200), | ||
|  |       ]; | ||
|  | 
 | ||
|  |       for (const hostname of invalidHostnames) { | ||
|  |         console.log(`Testing invalid hostname: "${hostname}"`); | ||
|  | 
 | ||
|  |         try { | ||
|  |           const response = await sendSmtpCommand(conn, `EHLO ${hostname}`); | ||
|  |           // Server should either accept with warning or reject with 5xx
 | ||
|  |           assertMatch(response, /^(250|5\d\d)/, 'Server should respond with 250 or 5xx'); | ||
|  | 
 | ||
|  |           // Reset session for next test
 | ||
|  |           if (response.startsWith('250')) { | ||
|  |             await sendSmtpCommand(conn, 'RSET', '250'); | ||
|  |           } | ||
|  |         } catch (error) { | ||
|  |           // Some invalid hostnames might cause connection issues, which is acceptable
 | ||
|  |           console.log(`  Hostname "${hostname}" caused error (acceptable):`, error.message); | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       // Send QUIT
 | ||
|  |       await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore close errors
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-01: EHLO command pipelining - multiple EHLO commands', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  | 
 | ||
|  |       // First EHLO
 | ||
|  |       const ehlo1Response = await sendSmtpCommand(conn, 'EHLO first.example.com', '250'); | ||
|  |       assert(ehlo1Response.startsWith('250'), 'First EHLO should succeed'); | ||
|  | 
 | ||
|  |       // Second EHLO (should reset session)
 | ||
|  |       const ehlo2Response = await sendSmtpCommand(conn, 'EHLO second.example.com', '250'); | ||
|  |       assert(ehlo2Response.startsWith('250'), 'Second EHLO should succeed'); | ||
|  | 
 | ||
|  |       // Verify session was reset by trying MAIL FROM
 | ||
|  |       const mailResponse = await sendSmtpCommand(conn, 'MAIL FROM:<test@example.com>', '250'); | ||
|  |       assert(mailResponse.startsWith('250'), 'MAIL FROM should work after second EHLO'); | ||
|  |     } finally { | ||
|  |       await closeSmtpConnection(conn); | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-01: Cleanup - Stop SMTP server', | ||
|  |   async fn() { | ||
|  |     await stopTestServer(testServer); | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); |