177 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			177 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * CMD-13: QUIT Command Tests | ||
|  |  * Tests SMTP QUIT command for graceful connection termination | ||
|  |  */ | ||
|  | 
 | ||
|  | 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 = 25255; | ||
|  | let testServer: ITestServer; | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-13: 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-13: QUIT - gracefully closes connection', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  |       await sendSmtpCommand(conn, 'EHLO test.example.com', '250'); | ||
|  | 
 | ||
|  |       // Send QUIT command
 | ||
|  |       const response = await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       assertMatch(response, /^221/, 'Should respond with 221 Service closing'); | ||
|  |       assert(response.includes('Service closing'), 'Should indicate service is closing'); | ||
|  | 
 | ||
|  |       console.log('✓ QUIT command received 221 response'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Connection may already be closed by server
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-13: QUIT - works 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'); | ||
|  | 
 | ||
|  |       // QUIT should work at any point
 | ||
|  |       const response = await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       assertMatch(response, /^221/, 'Should respond with 221'); | ||
|  | 
 | ||
|  |       console.log('✓ QUIT works after MAIL FROM'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-13: QUIT - works after complete transaction', | ||
|  |   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'); | ||
|  | 
 | ||
|  |       // QUIT should work after a complete transaction setup
 | ||
|  |       const response = await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       assertMatch(response, /^221/, 'Should respond with 221'); | ||
|  | 
 | ||
|  |       console.log('✓ QUIT works after complete transaction'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-13: QUIT - can be called multiple times (idempotent)', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  |       await sendSmtpCommand(conn, 'EHLO test.example.com', '250'); | ||
|  | 
 | ||
|  |       // First QUIT
 | ||
|  |       const response1 = await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       assertMatch(response1, /^221/, 'First QUIT should respond with 221'); | ||
|  | 
 | ||
|  |       // Try second QUIT (connection might be closed, so catch error)
 | ||
|  |       try { | ||
|  |         const response2 = await sendSmtpCommand(conn, 'QUIT'); | ||
|  |         // If we get here, server allowed second QUIT
 | ||
|  |         console.log('⚠️  Server allows multiple QUIT commands'); | ||
|  |       } catch (error) { | ||
|  |         // This is expected - connection should be closed after first QUIT
 | ||
|  |         console.log('✓ Connection closed after first QUIT (expected)'); | ||
|  |       } | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-13: QUIT - works without EHLO (immediate quit)', | ||
|  |   async fn() { | ||
|  |     const conn = await connectToSmtp('localhost', TEST_PORT); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await waitForGreeting(conn); | ||
|  | 
 | ||
|  |       // QUIT immediately after greeting
 | ||
|  |       const response = await sendSmtpCommand(conn, 'QUIT', '221'); | ||
|  |       assertMatch(response, /^221/, 'Should respond with 221 even without EHLO'); | ||
|  | 
 | ||
|  |       console.log('✓ QUIT works without EHLO'); | ||
|  |     } finally { | ||
|  |       try { | ||
|  |         conn.close(); | ||
|  |       } catch { | ||
|  |         // Ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); | ||
|  | 
 | ||
|  | Deno.test({ | ||
|  |   name: 'CMD-13: Cleanup - Stop SMTP server', | ||
|  |   async fn() { | ||
|  |     await stopTestServer(testServer); | ||
|  |   }, | ||
|  |   sanitizeResources: false, | ||
|  |   sanitizeOps: false, | ||
|  | }); |