/** * 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:', '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:', '250'); await sendSmtpCommand(conn, 'RCPT TO:', '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, });