/** * 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: 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:'); 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, });