- Implemented SMTP client utilities in `test/helpers/smtp.client.ts` for creating test clients, sending emails, and testing connections. - Developed SMTP protocol test utilities in `test/helpers/utils.ts` for managing TCP connections, sending commands, and handling responses. - Created a detailed README in `test/readme.md` outlining the test framework, infrastructure, organization, and running instructions. - Ported CMD-01: EHLO Command tests in `test/suite/smtpserver_commands/test.cmd-01.ehlo-command.test.ts` with multiple scenarios including valid and invalid hostnames. - Ported CMD-02: MAIL FROM Command tests in `test/suite/smtpserver_commands/test.cmd-02.mail-from.test.ts` covering valid address acceptance, invalid address rejection, SIZE parameter support, and command sequence enforcement.
		
			
				
	
	
		
			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,
 | |
| });
 |