236 lines
6.1 KiB
TypeScript
236 lines
6.1 KiB
TypeScript
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<string>(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(); |