import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js'; import { Email } from '../../../ts/mail/core/classes.email.js'; import * as net from 'net'; let testServer: ITestServer; tap.test('setup - start SMTP server for rate limiting tests', async () => { testServer = await startTestServer({ port: 2578, tlsEnabled: false, authRequired: false }); expect(testServer.port).toEqual(2578); }); tap.test('CERR-08: Server rate limiting - 421 too many connections', async () => { // Create server that immediately rejects with rate limit const rateLimitServer = net.createServer((socket) => { socket.write('421 4.7.0 Too many connections, please try again later\r\n'); socket.end(); }); await new Promise((resolve) => { rateLimitServer.listen(2579, () => resolve()); }); const smtpClient = await createSmtpClient({ host: '127.0.0.1', port: 2579, secure: false, connectionTimeout: 5000 }); const result = await smtpClient.verify(); expect(result).toBeFalse(); console.log('✅ 421 rate limit response handled'); await smtpClient.close(); await new Promise((resolve) => { rateLimitServer.close(() => resolve()); }); }); tap.test('CERR-08: Message rate limiting - 452', async () => { // Create server that rate limits at MAIL FROM const messageRateServer = net.createServer((socket) => { socket.write('220 Message Rate Server\r\n'); let buffer = ''; socket.on('data', (data) => { buffer += data.toString(); let lines = buffer.split('\r\n'); buffer = lines.pop() || ''; for (const line of lines) { const command = line.trim(); if (!command) continue; if (command.startsWith('EHLO')) { socket.write('250 OK\r\n'); } else if (command.startsWith('MAIL FROM')) { socket.write('452 4.3.2 Too many messages sent, please try later\r\n'); } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } } }); }); await new Promise((resolve) => { messageRateServer.listen(2580, () => resolve()); }); const smtpClient = await createSmtpClient({ host: '127.0.0.1', port: 2580, secure: false, connectionTimeout: 5000 }); const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Rate Limit Test', text: 'Testing rate limiting' }); const result = await smtpClient.sendMail(email); expect(result.success).toBeFalse(); console.log('Actual error:', result.error?.message); expect(result.error?.message).toMatch(/452|many|messages|rate/i); console.log('✅ 452 message rate limit handled'); await smtpClient.close(); await new Promise((resolve) => { messageRateServer.close(() => resolve()); }); }); tap.test('CERR-08: User rate limiting - 550', async () => { // Create server that permanently blocks user const userRateServer = net.createServer((socket) => { socket.write('220 User Rate Server\r\n'); let buffer = ''; socket.on('data', (data) => { buffer += data.toString(); let lines = buffer.split('\r\n'); buffer = lines.pop() || ''; for (const line of lines) { const command = line.trim(); if (!command) continue; if (command.startsWith('EHLO')) { socket.write('250 OK\r\n'); } else if (command.startsWith('MAIL FROM')) { if (command.includes('blocked@')) { socket.write('550 5.7.1 User sending rate exceeded\r\n'); } else { socket.write('250 OK\r\n'); } } else if (command.startsWith('RCPT TO')) { socket.write('250 OK\r\n'); } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } } }); }); await new Promise((resolve) => { userRateServer.listen(2581, () => resolve()); }); const smtpClient = await createSmtpClient({ host: '127.0.0.1', port: 2581, secure: false, connectionTimeout: 5000 }); const email = new Email({ from: 'blocked@example.com', to: 'recipient@example.com', subject: 'User Rate Test', text: 'Testing user rate limiting' }); const result = await smtpClient.sendMail(email); expect(result.success).toBeFalse(); console.log('Actual error:', result.error?.message); expect(result.error?.message).toMatch(/550|rate|exceeded/i); console.log('✅ 550 user rate limit handled'); await smtpClient.close(); await new Promise((resolve) => { userRateServer.close(() => resolve()); }); }); tap.test('CERR-08: Connection throttling - delayed response', async () => { // Create server that delays responses to simulate throttling const throttleServer = net.createServer((socket) => { // Delay initial greeting setTimeout(() => { socket.write('220 Throttle Server\r\n'); }, 100); let buffer = ''; socket.on('data', (data) => { buffer += data.toString(); let lines = buffer.split('\r\n'); buffer = lines.pop() || ''; for (const line of lines) { const command = line.trim(); if (!command) continue; // Add delay to all responses setTimeout(() => { if (command.startsWith('EHLO')) { socket.write('250 OK\r\n'); } else if (command === 'QUIT') { socket.write('221 Bye\r\n'); socket.end(); } else { socket.write('250 OK\r\n'); } }, 50); } }); }); await new Promise((resolve) => { throttleServer.listen(2582, () => resolve()); }); const smtpClient = await createSmtpClient({ host: '127.0.0.1', port: 2582, secure: false, connectionTimeout: 5000 }); const startTime = Date.now(); const result = await smtpClient.verify(); const duration = Date.now() - startTime; expect(result).toBeTrue(); console.log(`✅ Throttled connection succeeded in ${duration}ms`); await smtpClient.close(); await new Promise((resolve) => { throttleServer.close(() => resolve()); }); }); tap.test('CERR-08: Normal email without rate limiting', async () => { // Test successful email send with working server const smtpClient = await createSmtpClient({ host: testServer.hostname, port: testServer.port, secure: false, connectionTimeout: 5000 }); const email = new Email({ from: 'sender@example.com', to: 'recipient@example.com', subject: 'Normal Test', text: 'Testing normal operation without rate limits' }); const result = await smtpClient.sendMail(email); expect(result.success).toBeTrue(); console.log('✅ Normal email sent successfully'); await smtpClient.close(); }); tap.test('cleanup - stop SMTP server', async () => { await stopTestServer(testServer); }); export default tap.start();