import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as plugins from './plugins.js'; import { createTestServer } from '../../helpers/server.loader.js'; import { createSmtpClient } from '../../helpers/smtp.client.js'; tap.test('CEDGE-04: should handle resource constraints gracefully', async (tools) => { const testId = 'CEDGE-04-resource-constraints'; console.log(`\n${testId}: Testing resource constraint handling...`); let scenarioCount = 0; // Scenario 1: Very slow server responses await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing very slow server responses`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); // Slow greeting setTimeout(() => { socket.write('220 mail.example.com ESMTP\r\n'); }, 2000); socket.on('data', async (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); // Add delays to all responses const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); if (command.startsWith('EHLO')) { await delay(1500); socket.write('250-mail.example.com\r\n'); await delay(500); socket.write('250 OK\r\n'); } else if (command.startsWith('MAIL FROM:')) { await delay(2000); socket.write('250 OK\r\n'); } else if (command.startsWith('RCPT TO:')) { await delay(1000); socket.write('250 OK\r\n'); } else if (command === 'DATA') { await delay(1500); socket.write('354 Start mail input\r\n'); } else if (command === '.') { await delay(3000); socket.write('250 OK\r\n'); } else if (command === 'QUIT') { await delay(500); socket.write('221 Bye\r\n'); socket.end(); } }); } }); const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 10000, // 10 second timeout greetingTimeout: 5000, socketTimeout: 10000 }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Slow server test', text: 'Testing slow server responses' }); console.log(' Sending email (this will take time due to delays)...'); const start = Date.now(); const result = await smtpClient.sendMail(email); const elapsed = Date.now() - start; console.log(` Result: ${result.messageId ? 'Success' : 'Failed'} (took ${elapsed}ms)`); expect(result).toBeDefined(); expect(result.messageId).toBeDefined(); await testServer.server.close(); })(); // Scenario 2: Server with limited buffer (sends data in small chunks) await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing server sending data in small chunks`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); // Send greeting in small chunks const greeting = '220 mail.example.com ESMTP\r\n'; for (let i = 0; i < greeting.length; i += 5) { socket.write(greeting.slice(i, i + 5)); await new Promise(resolve => setTimeout(resolve, 50)); } socket.on('data', async (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { // Send capabilities in very small chunks const response = '250-mail.example.com\r\n250-SIZE 10485760\r\n250-8BITMIME\r\n250 OK\r\n'; for (let i = 0; i < response.length; i += 3) { socket.write(response.slice(i, i + 3)); await new Promise(resolve => setTimeout(resolve, 20)); } } else if (command.startsWith('MAIL FROM:')) { socket.write('250 OK\r\n'); } else if (command.startsWith('RCPT TO:')) { socket.write('250 OK\r\n'); } else if (command === 'DATA') { socket.write('354 Start mail input\r\n'); } else if (command === '.') { socket.write('250 OK\r\n'); } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } }); } }); const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Chunked response test', text: 'Testing fragmented server responses' }); const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`); expect(result).toBeDefined(); expect(result.messageId).toBeDefined(); await testServer.server.close(); })(); // Scenario 3: Server with connection limit await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing server connection limits`); let connectionCount = 0; const maxConnections = 2; const testServer = await createTestServer({ onConnection: async (socket) => { connectionCount++; console.log(` [Server] Connection ${connectionCount} (max: ${maxConnections})`); if (connectionCount > maxConnections) { socket.write('421 4.3.2 Too many connections, try again later\r\n'); socket.end(); return; } socket.write('220 mail.example.com ESMTP\r\n'); socket.on('close', () => { connectionCount--; console.log(` [Server] Connection closed, count: ${connectionCount}`); }); socket.on('data', (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); if (command.startsWith('EHLO')) { socket.write('250-mail.example.com\r\n'); socket.write('250 OK\r\n'); } else if (command.startsWith('MAIL FROM:')) { socket.write('250 OK\r\n'); } else if (command.startsWith('RCPT TO:')) { socket.write('250 OK\r\n'); } else if (command === 'DATA') { socket.write('354 Start mail input\r\n'); } else if (command === '.') { socket.write('250 OK\r\n'); } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } }); } }); // Try to send multiple emails concurrently const emails = Array(3).fill(null).map((_, i) => new plugins.smartmail.Email({ from: 'sender@example.com', to: [`recipient${i + 1}@example.com`], subject: `Connection limit test ${i + 1}`, text: `Testing connection limits - email ${i + 1}` }) ); const results = await Promise.allSettled( emails.map(async (email, i) => { const client = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); console.log(` Sending email ${i + 1}...`); try { const result = await client.sendMail(email); return { index: i + 1, success: true, result }; } catch (error) { return { index: i + 1, success: false, error: error.message }; } }) ); results.forEach((result, i) => { if (result.status === 'fulfilled') { const { index, success, error } = result.value; console.log(` Email ${index}: ${success ? 'Success' : `Failed - ${error}`}`); } }); // At least some should succeed const successes = results.filter(r => r.status === 'fulfilled' && r.value.success ); expect(successes.length).toBeGreaterThan(0); await testServer.server.close(); })(); // Scenario 4: Server with memory constraints (limited line length) await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing server line length limits`); const maxLineLength = 100; const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); let buffer = ''; socket.on('data', (data) => { buffer += data.toString(); let lines = buffer.split('\r\n'); buffer = lines.pop() || ''; // Keep incomplete line in buffer lines.forEach(line => { if (line.length === 0) return; console.log(` [Server] Received line (${line.length} chars): ${line.substring(0, 50)}...`); if (line.length > maxLineLength && !line.startsWith('DATA')) { socket.write(`500 5.5.2 Line too long (max ${maxLineLength})\r\n`); return; } if (line.startsWith('EHLO')) { socket.write('250-mail.example.com\r\n'); socket.write(`250-SIZE ${maxLineLength}\r\n`); socket.write('250 OK\r\n'); } else if (line.startsWith('MAIL FROM:')) { socket.write('250 OK\r\n'); } else if (line.startsWith('RCPT TO:')) { socket.write('250 OK\r\n'); } else if (line === 'DATA') { socket.write('354 Start mail input\r\n'); } else if (line === '.') { socket.write('250 OK\r\n'); } else if (line === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } }); }); } }); // Test with normal email first const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Line length test', text: 'Testing server line length limits with a reasonably short message that should work fine.' }); const result = await smtpClient.sendMail(email); console.log(` Normal email result: ${result.messageId ? 'Success' : 'Failed'}`); expect(result).toBeDefined(); expect(result.messageId).toBeDefined(); // Test with very long subject const longEmail = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'A'.repeat(150), // Very long subject text: 'Short body' }); try { const longResult = await smtpClient.sendMail(longEmail); console.log(` Long subject email: ${longResult.messageId ? 'Success (folded properly)' : 'Failed'}`); } catch (error) { console.log(` Long subject email failed as expected: ${error.message}`); } await testServer.server.close(); })(); // Scenario 5: Server with CPU constraints (slow command processing) await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing server with slow command processing`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); socket.on('data', async (data) => { const command = data.toString().trim(); console.log(` [Server] Received: ${command}`); // Simulate CPU-intensive processing const busyWait = (ms: number) => { const start = Date.now(); while (Date.now() - start < ms) { // Busy wait } }; if (command.startsWith('EHLO')) { busyWait(500); socket.write('250-mail.example.com\r\n'); busyWait(200); socket.write('250 OK\r\n'); } else if (command.startsWith('MAIL FROM:')) { busyWait(300); socket.write('250 OK\r\n'); } else if (command.startsWith('RCPT TO:')) { busyWait(400); socket.write('250 OK\r\n'); } else if (command === 'DATA') { busyWait(200); socket.write('354 Start mail input\r\n'); } else if (command === '.') { busyWait(1000); // Slow processing of message socket.write('250 OK\r\n'); } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } }); } }); const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, socketTimeout: 10000 // Higher timeout for slow server }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'CPU constraint test', text: 'Testing server with slow processing' }); console.log(' Sending email to slow server...'); const start = Date.now(); const result = await smtpClient.sendMail(email); const elapsed = Date.now() - start; console.log(` Result: ${result.messageId ? 'Success' : 'Failed'} (took ${elapsed}ms)`); expect(result).toBeDefined(); expect(result.messageId).toBeDefined(); expect(elapsed).toBeGreaterThan(2000); // Should take at least 2 seconds await testServer.server.close(); })(); // Scenario 6: Server with limited command buffer await (async () => { scenarioCount++; console.log(`\nScenario ${scenarioCount}: Testing server with limited command buffer`); const testServer = await createTestServer({ onConnection: async (socket) => { console.log(' [Server] Client connected'); socket.write('220 mail.example.com ESMTP\r\n'); const commandQueue: string[] = []; let processing = false; const processCommands = async () => { if (processing || commandQueue.length === 0) return; processing = true; while (commandQueue.length > 0) { const command = commandQueue.shift()!; console.log(` [Server] Processing: ${command}`); // Simulate slow processing await new Promise(resolve => setTimeout(resolve, 100)); if (command.startsWith('EHLO')) { socket.write('250-mail.example.com\r\n'); socket.write('250-PIPELINING\r\n'); // Support pipelining socket.write('250 OK\r\n'); } else if (command.startsWith('MAIL FROM:')) { socket.write('250 OK\r\n'); } else if (command.startsWith('RCPT TO:')) { socket.write('250 OK\r\n'); } else if (command === 'DATA') { socket.write('354 Start mail input\r\n'); } else if (command === '.') { socket.write('250 OK\r\n'); } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } } processing = false; }; socket.on('data', (data) => { const commands = data.toString().split('\r\n').filter(cmd => cmd.length > 0); commands.forEach(cmd => { if (commandQueue.length >= 5) { console.log(' [Server] Command buffer full, rejecting command'); socket.write('421 4.3.2 Command buffer full\r\n'); socket.end(); return; } commandQueue.push(cmd); }); processCommands(); }); } }); const smtpClient = createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false }); const email = new plugins.smartmail.Email({ from: 'sender@example.com', to: ['recipient@example.com'], subject: 'Command buffer test', text: 'Testing limited command buffer' }); const result = await smtpClient.sendMail(email); console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`); expect(result).toBeDefined(); expect(result.messageId).toBeDefined(); await testServer.server.close(); })(); console.log(`\n${testId}: All ${scenarioCount} resource constraint scenarios tested ✓`); });