import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as plugins from './helpers/server.loader.js'; import { createTestSmtpClient } from './helpers/smtp.client.js'; import { SmtpClient } from '../ts/mail/delivery/smtpclient/smtp-client.js'; const TEST_PORT = 2525; // Test email configuration with rate limits const testEmailConfig = { ports: [TEST_PORT], hostname: 'localhost', domains: [ { domain: 'test.local', dnsMode: 'forward' as const, rateLimits: { inbound: { messagesPerMinute: 3, // Very low limit for testing recipientsPerMessage: 2, connectionsPerIp: 5 } } } ], routes: [ { name: 'test-route', match: { recipients: '*@test.local' }, action: { type: 'process' as const, process: { scan: false, queue: 'normal' } } } ], rateLimits: { global: { maxMessagesPerMinute: 10, maxConnectionsPerIP: 10, maxErrorsPerIP: 3, maxAuthFailuresPerIP: 2, blockDuration: 5000 // 5 seconds for testing } } }; tap.test('prepare server with rate limiting', async () => { await plugins.startTestServer(testEmailConfig); // Give server time to start await new Promise(resolve => setTimeout(resolve, 1000)); }); tap.test('should enforce connection rate limits', async (tools) => { const done = tools.defer(); const clients: SmtpClient[] = []; try { // Try to create many connections quickly for (let i = 0; i < 12; i++) { const client = createTestSmtpClient(); clients.push(client); // Connection should fail after limit is exceeded const verified = await client.verify().catch(() => false); if (i < 10) { // First 10 should succeed (global limit) expect(verified).toBeTrue(); } else { // After 10, should be rate limited expect(verified).toBeFalse(); } } done.resolve(); } catch (error) { done.reject(error); } finally { // Clean up connections for (const client of clients) { await client.close().catch(() => {}); } } }); tap.test('should enforce message rate limits per domain', async (tools) => { const done = tools.defer(); const client = createTestSmtpClient(); try { // Send messages rapidly to test domain-specific rate limit for (let i = 0; i < 5; i++) { const email = { from: `sender${i}@example.com`, to: 'recipient@test.local', subject: `Test ${i}`, text: 'Test message' }; const result = await client.sendMail(email).catch(err => err); if (i < 3) { // First 3 should succeed (domain limit is 3 per minute) expect(result.accepted).toBeDefined(); expect(result.accepted.length).toEqual(1); } else { // After 3, should be rate limited expect(result.code).toEqual('EENVELOPE'); expect(result.response).toContain('try again later'); } } done.resolve(); } catch (error) { done.reject(error); } finally { await client.close(); } }); tap.test('should enforce recipient limits', async (tools) => { const done = tools.defer(); const client = createTestSmtpClient(); try { // Try to send to many recipients (domain limit is 2 per message) const email = { from: 'sender@example.com', to: ['user1@test.local', 'user2@test.local', 'user3@test.local'], subject: 'Test with multiple recipients', text: 'Test message' }; const result = await client.sendMail(email).catch(err => err); // Should fail due to recipient limit expect(result.code).toEqual('EENVELOPE'); expect(result.response).toContain('try again later'); done.resolve(); } catch (error) { done.reject(error); } finally { await client.close(); } }); tap.test('should enforce error rate limits', async (tools) => { const done = tools.defer(); const client = createTestSmtpClient(); try { // Send multiple invalid commands to trigger error rate limit const socket = (client as any).socket; // Wait for connection await new Promise(resolve => setTimeout(resolve, 100)); // Send invalid commands for (let i = 0; i < 5; i++) { socket.write('INVALID_COMMAND\r\n'); // Wait for response await new Promise(resolve => { socket.once('data', resolve); }); } // After 3 errors, connection should be blocked const lastResponse = await new Promise(resolve => { socket.once('data', (data: Buffer) => resolve(data.toString())); socket.write('NOOP\r\n'); }); expect(lastResponse).toContain('421 Too many errors'); done.resolve(); } catch (error) { done.reject(error); } finally { await client.close().catch(() => {}); } }); tap.test('should enforce authentication failure limits', async (tools) => { const done = tools.defer(); // Create config with auth required const authConfig = { ...testEmailConfig, auth: { required: true, methods: ['PLAIN' as const] } }; // Restart server with auth config await plugins.stopTestServer(); await plugins.startTestServer(authConfig); await new Promise(resolve => setTimeout(resolve, 1000)); const client = createTestSmtpClient(); try { // Try multiple failed authentications for (let i = 0; i < 3; i++) { const result = await client.sendMail({ from: 'sender@example.com', to: 'recipient@test.local', subject: 'Test', text: 'Test' }, { auth: { user: 'wronguser', pass: 'wrongpass' } }).catch(err => err); if (i < 2) { // First 2 should fail with auth error expect(result.code).toEqual('EAUTH'); } else { // After 2 failures, should be blocked expect(result.code).toEqual('ECONNECTION'); } } done.resolve(); } catch (error) { done.reject(error); } finally { await client.close().catch(() => {}); } }); tap.test('cleanup server', async () => { await plugins.stopTestServer(); }); tap.start();