226 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			226 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * CMD-06: RSET Command Tests | ||
|  |  * Tests SMTP RSET command for transaction reset | ||
|  |  */ | ||
|  | 
 | ||
|  | 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 = 25259; | ||
|  | let testServer: ITestServer; | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-06: 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-06: RSET - resets transaction after MAIL FROM', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  |       await sendSmtpCommand(conn, 'EHLO test.example.com', '250'); | ||
|  |       await sendSmtpCommand(conn, 'MAIL FROM:<sender@example.com>', '250'); | ||
|  | 
 | ||
|  |       // Send RSET
 | ||
|  |       const rsetResponse = await sendSmtpCommand(conn, 'RSET', '250'); | ||
|  |       assert(rsetResponse.includes('250'), 'RSET should respond with 250 OK'); | ||
|  | 
 | ||
|  |       // After RSET, should be able to send new MAIL FROM
 | ||
|  |       const mailFromResponse = await sendSmtpCommand(conn, 'MAIL FROM:<newsender@example.com>', '250'); | ||
|  |       assert(mailFromResponse.includes('250'), 'Should accept MAIL FROM after RSET'); | ||
|  | 
 | ||
|  |       await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       console.log('✓ RSET successfully resets transaction after MAIL FROM'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-06: RSET - resets transaction after RCPT TO', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  |       await sendSmtpCommand(conn, 'EHLO test.example.com', '250'); | ||
|  |       await sendSmtpCommand(conn, 'MAIL FROM:<sender@example.com>', '250'); | ||
|  |       await sendSmtpCommand(conn, 'RCPT TO:<recipient@example.com>', '250'); | ||
|  | 
 | ||
|  |       // Send RSET
 | ||
|  |       const rsetResponse = await sendSmtpCommand(conn, 'RSET', '250'); | ||
|  |       assert(rsetResponse.includes('250'), 'RSET should respond with 250 OK'); | ||
|  | 
 | ||
|  |       // After RSET, RCPT TO should fail (need MAIL FROM first)
 | ||
|  |       const rcptToResponse = await sendSmtpCommand(conn, 'RCPT TO:<newrecipient@example.com>'); | ||
|  |       assertMatch(rcptToResponse, /^503/, 'Should reject RCPT TO without MAIL FROM after RSET'); | ||
|  | 
 | ||
|  |       await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       console.log('✓ RSET clears transaction state requiring new MAIL FROM'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-06: RSET - handles multiple consecutive RSET commands', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  |       await sendSmtpCommand(conn, 'EHLO test.example.com', '250'); | ||
|  |       await sendSmtpCommand(conn, 'MAIL FROM:<sender@example.com>', '250'); | ||
|  | 
 | ||
|  |       // Send multiple RSETs
 | ||
|  |       const rset1 = await sendSmtpCommand(conn, 'RSET', '250'); | ||
|  |       assert(rset1.includes('250'), 'First RSET should succeed'); | ||
|  | 
 | ||
|  |       const rset2 = await sendSmtpCommand(conn, 'RSET', '250'); | ||
|  |       assert(rset2.includes('250'), 'Second RSET should succeed'); | ||
|  | 
 | ||
|  |       const rset3 = await sendSmtpCommand(conn, 'RSET', '250'); | ||
|  |       assert(rset3.includes('250'), 'Third RSET should succeed'); | ||
|  | 
 | ||
|  |       await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       console.log('✓ Multiple consecutive RSET commands work (idempotent)'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-06: RSET - works without active transaction', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  |       await sendSmtpCommand(conn, 'EHLO test.example.com', '250'); | ||
|  | 
 | ||
|  |       // Send RSET without any transaction
 | ||
|  |       const rsetResponse = await sendSmtpCommand(conn, 'RSET', '250'); | ||
|  |       assert(rsetResponse.includes('250'), 'RSET should work even without active transaction'); | ||
|  | 
 | ||
|  |       await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       console.log('✓ RSET works without active transaction'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-06: RSET - clears all recipients', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  |       await sendSmtpCommand(conn, 'EHLO test.example.com', '250'); | ||
|  |       await sendSmtpCommand(conn, 'MAIL FROM:<sender@example.com>', '250'); | ||
|  | 
 | ||
|  |       // Add multiple recipients
 | ||
|  |       await sendSmtpCommand(conn, 'RCPT TO:<recipient1@example.com>', '250'); | ||
|  |       await sendSmtpCommand(conn, 'RCPT TO:<recipient2@example.com>', '250'); | ||
|  |       await sendSmtpCommand(conn, 'RCPT TO:<recipient3@example.com>', '250'); | ||
|  | 
 | ||
|  |       // Send RSET
 | ||
|  |       const rsetResponse = await sendSmtpCommand(conn, 'RSET', '250'); | ||
|  |       assert(rsetResponse.includes('250'), 'RSET should respond with 250 OK'); | ||
|  | 
 | ||
|  |       // After RSET, DATA should fail (no recipients)
 | ||
|  |       const dataResponse = await sendSmtpCommand(conn, 'DATA'); | ||
|  |       assertMatch(dataResponse, /^503/, 'DATA should fail without recipients after RSET'); | ||
|  | 
 | ||
|  |       await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       console.log('✓ RSET clears all recipients from transaction'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-06: RSET - ignores parameters', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  |       await sendSmtpCommand(conn, 'EHLO test.example.com', '250'); | ||
|  | 
 | ||
|  |       // Send RSET with parameters (should be ignored)
 | ||
|  |       const rsetResponse = await sendSmtpCommand(conn, 'RSET ignored parameter', '250'); | ||
|  |       assert(rsetResponse.includes('250'), 'RSET should ignore parameters and respond with 250 OK'); | ||
|  | 
 | ||
|  |       await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       console.log('✓ RSET ignores parameters as per RFC'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-06: Cleanup - Stop SMTP server', | ||
|  |   async fn() { | ||
|  |     await stopTestServer(testServer); | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); |