- 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.
		
			
				
	
	
		
			170 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * CMD-02: MAIL FROM Command Tests
 | |
|  * Tests SMTP MAIL FROM command validation and handling
 | |
|  */
 | |
| 
 | |
| import { assert, 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 = 25252;
 | |
| let testServer: ITestServer;
 | |
| 
 | |
| Deno.test({
 | |
|   name: 'CMD-02: 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-02: MAIL FROM - accepts valid sender addresses',
 | |
|   async fn() {
 | |
|     const conn = await connectToSmtp('localhost', TEST_PORT);
 | |
| 
 | |
|     try {
 | |
|       await waitForGreeting(conn);
 | |
|       await sendSmtpCommand(conn, 'EHLO test.example.com', '250');
 | |
| 
 | |
|       const validAddresses = [
 | |
|         'sender@example.com',
 | |
|         'test.user+tag@example.com',
 | |
|         'user@[192.168.1.1]',  // IP literal
 | |
|         'user@subdomain.example.com',
 | |
|         'user@very-long-domain-name-that-is-still-valid.example.com',
 | |
|         'test_user@example.com',  // underscore in local part
 | |
|       ];
 | |
| 
 | |
|       for (const address of validAddresses) {
 | |
|         console.log(`✓ Testing valid address: ${address}`);
 | |
|         const response = await sendSmtpCommand(conn, `MAIL FROM:<${address}>`, '250');
 | |
|         assert(response.startsWith('250'), `Should accept valid address: ${address}`);
 | |
| 
 | |
|         // Reset for next test
 | |
|         await sendSmtpCommand(conn, 'RSET', '250');
 | |
|       }
 | |
|     } finally {
 | |
|       await closeSmtpConnection(conn);
 | |
|     }
 | |
|   },
 | |
|   sanitizeResources: false,
 | |
|   sanitizeOps: false,
 | |
| });
 | |
| 
 | |
| Deno.test({
 | |
|   name: 'CMD-02: MAIL FROM - rejects invalid sender addresses',
 | |
|   async fn() {
 | |
|     const conn = await connectToSmtp('localhost', TEST_PORT);
 | |
| 
 | |
|     try {
 | |
|       await waitForGreeting(conn);
 | |
|       await sendSmtpCommand(conn, 'EHLO test.example.com', '250');
 | |
| 
 | |
|       const invalidAddresses = [
 | |
|         'notanemail',               // No @ symbol
 | |
|         '@example.com',             // Missing local part
 | |
|         'user@',                    // Missing domain
 | |
|         'user@.com',                // Invalid domain
 | |
|         'user@domain..com',         // Double dot
 | |
|         'user space@example.com',   // Space in address
 | |
|       ];
 | |
| 
 | |
|       for (const address of invalidAddresses) {
 | |
|         console.log(`✗ Testing invalid address: ${address}`);
 | |
|         try {
 | |
|           const response = await sendSmtpCommand(conn, `MAIL FROM:<${address}>`);
 | |
|           // Should get 5xx error
 | |
|           assertMatch(response, /^5\d\d/, `Should reject invalid address with 5xx: ${address}`);
 | |
|         } catch (error) {
 | |
|           // Connection might be dropped for really bad input, which is acceptable
 | |
|           console.log(`  Address "${address}" caused error (acceptable):`, error.message);
 | |
|         }
 | |
| 
 | |
|         // Try to reset (may fail if connection dropped)
 | |
|         try {
 | |
|           await sendSmtpCommand(conn, 'RSET', '250');
 | |
|         } catch {
 | |
|           // Reset after connection closed, reconnect for next test
 | |
|           conn.close();
 | |
|           return; // Exit test early if connection was dropped
 | |
|         }
 | |
|       }
 | |
|     } finally {
 | |
|       try {
 | |
|         await closeSmtpConnection(conn);
 | |
|       } catch {
 | |
|         // Ignore errors if connection already closed
 | |
|       }
 | |
|     }
 | |
|   },
 | |
|   sanitizeResources: false,
 | |
|   sanitizeOps: false,
 | |
| });
 | |
| 
 | |
| Deno.test({
 | |
|   name: 'CMD-02: MAIL FROM - supports SIZE parameter',
 | |
|   async fn() {
 | |
|     const conn = await connectToSmtp('localhost', TEST_PORT);
 | |
| 
 | |
|     try {
 | |
|       await waitForGreeting(conn);
 | |
|       const caps = await sendSmtpCommand(conn, 'EHLO test.example.com', '250');
 | |
| 
 | |
|       // Verify SIZE is advertised
 | |
|       assert(caps.includes('SIZE'), 'Server should advertise SIZE capability');
 | |
| 
 | |
|       // Try MAIL FROM with SIZE parameter
 | |
|       const response = await sendSmtpCommand(
 | |
|         conn,
 | |
|         'MAIL FROM:<sender@example.com> SIZE=5000',
 | |
|         '250'
 | |
|       );
 | |
|       assert(response.startsWith('250'), 'Should accept SIZE parameter');
 | |
|     } finally {
 | |
|       await closeSmtpConnection(conn);
 | |
|     }
 | |
|   },
 | |
|   sanitizeResources: false,
 | |
|   sanitizeOps: false,
 | |
| });
 | |
| 
 | |
| Deno.test({
 | |
|   name: 'CMD-02: MAIL FROM - enforces correct sequence',
 | |
|   async fn() {
 | |
|     const conn = await connectToSmtp('localhost', TEST_PORT);
 | |
| 
 | |
|     try {
 | |
|       await waitForGreeting(conn);
 | |
| 
 | |
|       // Try MAIL FROM before EHLO - should fail
 | |
|       const response = await sendSmtpCommand(conn, 'MAIL FROM:<sender@example.com>');
 | |
|       assertMatch(response, /^5\d\d/, 'Should reject MAIL FROM before EHLO/HELO');
 | |
|     } finally {
 | |
|       try {
 | |
|         conn.close();
 | |
|       } catch {
 | |
|         // Ignore close errors
 | |
|       }
 | |
|     }
 | |
|   },
 | |
|   sanitizeResources: false,
 | |
|   sanitizeOps: false,
 | |
| });
 | |
| 
 | |
| Deno.test({
 | |
|   name: 'CMD-02: Cleanup - Stop SMTP server',
 | |
|   async fn() {
 | |
|     await stopTestServer(testServer);
 | |
|   },
 | |
|   sanitizeResources: false,
 | |
|   sanitizeOps: false,
 | |
| });
 |