import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; import { connectToSmtp, waitForGreeting, sendSmtpCommand, closeSmtpConnection, generateRandomEmail } from '../../helpers/test.utils.js'; let testServer: ITestServer; tap.test('setup - start SMTP server with large size limit', async () => { testServer = await startTestServer({ port: 2532, hostname: 'localhost', size: 100 * 1024 * 1024 // 100MB limit for testing }); expect(testServer).toBeInstanceOf(Object); }); tap.test('EDGE-01: Very Large Email - test size limits and handling', async () => { const testCases = [ { size: 1 * 1024 * 1024, label: '1MB', shouldPass: true }, { size: 10 * 1024 * 1024, label: '10MB', shouldPass: true }, { size: 50 * 1024 * 1024, label: '50MB', shouldPass: true }, { size: 101 * 1024 * 1024, label: '101MB', shouldPass: false } // Over limit ]; for (const testCase of testCases) { console.log(`\nšŸ“§ Testing ${testCase.label} email...`); const socket = await connectToSmtp(testServer.hostname, testServer.port); try { await waitForGreeting(socket); await sendSmtpCommand(socket, 'EHLO test.example.com', '250'); // Check SIZE extension await sendSmtpCommand(socket, `MAIL FROM: SIZE=${testCase.size}`, testCase.shouldPass ? '250' : '552'); if (testCase.shouldPass) { // Continue with transaction await sendSmtpCommand(socket, 'RCPT TO:', '250'); await sendSmtpCommand(socket, 'DATA', '354'); // Send large content in chunks const chunkSize = 65536; // 64KB chunks const totalChunks = Math.ceil(testCase.size / chunkSize); console.log(` Sending ${totalChunks} chunks...`); // Headers socket.write('From: large@example.com\r\n'); socket.write('To: recipient@example.com\r\n'); socket.write(`Subject: ${testCase.label} Test Email\r\n`); socket.write('Content-Type: text/plain\r\n'); socket.write('\r\n'); // Body in chunks let bytesSent = 100; // Approximate header size const startTime = Date.now(); for (let i = 0; i < totalChunks; i++) { const chunk = generateRandomEmail(Math.min(chunkSize, testCase.size - bytesSent)); socket.write(chunk); bytesSent += chunk.length; // Progress indicator every 10% if (i % Math.floor(totalChunks / 10) === 0) { const progress = (i / totalChunks * 100).toFixed(0); console.log(` Progress: ${progress}%`); } // Small delay to avoid overwhelming if (i % 100 === 0) { await new Promise(resolve => setTimeout(resolve, 10)); } } // End of data socket.write('\r\n.\r\n'); // Wait for response with longer timeout for large emails const response = await new Promise((resolve, reject) => { let buffer = ''; const timeout = setTimeout(() => reject(new Error('Timeout')), 60000); const onData = (data: Buffer) => { buffer += data.toString(); if (buffer.includes('250') || buffer.includes('5')) { clearTimeout(timeout); socket.removeListener('data', onData); resolve(buffer); } }; socket.on('data', onData); }); const duration = Date.now() - startTime; const throughputMBps = (testCase.size / 1024 / 1024) / (duration / 1000); expect(response).toInclude('250'); console.log(` āœ… ${testCase.label} email accepted in ${duration}ms`); console.log(` Throughput: ${throughputMBps.toFixed(2)} MB/s`); } else { console.log(` āœ… ${testCase.label} email properly rejected (over size limit)`); } } catch (error) { if (!testCase.shouldPass && error.message.includes('552')) { console.log(` āœ… ${testCase.label} email properly rejected: ${error.message}`); } else { throw error; } } finally { await closeSmtpConnection(socket).catch(() => {}); } } }); tap.test('EDGE-01: Email size enforcement - SIZE parameter', async () => { const socket = await connectToSmtp(testServer.hostname, testServer.port); try { await waitForGreeting(socket); const ehloResponse = await sendSmtpCommand(socket, 'EHLO test.example.com', '250'); // Extract SIZE limit from capabilities const sizeMatch = ehloResponse.match(/250[- ]SIZE (\d+)/); const sizeLimit = sizeMatch ? parseInt(sizeMatch[1]) : 0; console.log(`šŸ“ Server advertises SIZE limit: ${sizeLimit} bytes`); expect(sizeLimit).toBeGreaterThan(0); // Test SIZE parameter enforcement const testSizes = [ { size: 1000, shouldPass: true }, { size: sizeLimit - 1000, shouldPass: true }, { size: sizeLimit + 1000, shouldPass: false } ]; for (const test of testSizes) { try { const response = await sendSmtpCommand( socket, `MAIL FROM: SIZE=${test.size}` ); if (test.shouldPass) { expect(response).toInclude('250'); console.log(` āœ… SIZE=${test.size} accepted`); await sendSmtpCommand(socket, 'RSET', '250'); } else { expect(response).toInclude('552'); console.log(` āœ… SIZE=${test.size} rejected`); } } catch (error) { if (!test.shouldPass) { console.log(` āœ… SIZE=${test.size} rejected: ${error.message}`); } else { throw error; } } } } finally { await closeSmtpConnection(socket); } }); tap.test('EDGE-01: Memory efficiency with large emails', async () => { // Get initial memory usage const initialMemory = process.memoryUsage(); console.log('šŸ“Š Initial memory usage:', { heapUsed: `${(initialMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`, rss: `${(initialMemory.rss / 1024 / 1024).toFixed(2)} MB` }); // Send a moderately large email const socket = await connectToSmtp(testServer.hostname, testServer.port); try { await waitForGreeting(socket); await sendSmtpCommand(socket, 'EHLO test.example.com', '250'); await sendSmtpCommand(socket, 'MAIL FROM:', '250'); await sendSmtpCommand(socket, 'RCPT TO:', '250'); await sendSmtpCommand(socket, 'DATA', '354'); // Send 20MB email const size = 20 * 1024 * 1024; const chunkSize = 1024 * 1024; // 1MB chunks socket.write('From: memory@test.com\r\n'); socket.write('To: recipient@example.com\r\n'); socket.write('Subject: Memory Test\r\n\r\n'); for (let i = 0; i < size / chunkSize; i++) { socket.write(generateRandomEmail(chunkSize)); // Force garbage collection if available if (global.gc) { global.gc(); } } socket.write('\r\n.\r\n'); // Wait for response await new Promise((resolve) => { const onData = (data: Buffer) => { if (data.toString().includes('250')) { socket.removeListener('data', onData); resolve(); } }; socket.on('data', onData); }); // Check memory after processing const finalMemory = process.memoryUsage(); const memoryIncrease = (finalMemory.heapUsed - initialMemory.heapUsed) / 1024 / 1024; console.log('šŸ“Š Final memory usage:', { heapUsed: `${(finalMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`, rss: `${(finalMemory.rss / 1024 / 1024).toFixed(2)} MB`, increase: `${memoryIncrease.toFixed(2)} MB` }); // Memory increase should be reasonable (not storing entire email in memory) expect(memoryIncrease).toBeLessThan(50); // Less than 50MB increase for 20MB email console.log('āœ… Memory efficiency test passed'); } finally { await closeSmtpConnection(socket); } }); tap.test('cleanup - stop SMTP server', async () => { await stopTestServer(testServer); console.log('āœ… Test server stopped'); }); tap.start();