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, | ||
|  | }); |