dcrouter/test/suite/smtpclient_error-handling/test.cerr-09.connection-pool-errors.ts
2025-05-26 10:35:50 +00:00

299 lines
7.6 KiB
TypeScript

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 connection pool tests', async () => {
testServer = await startTestServer({
port: 2583,
tlsEnabled: false,
authRequired: false
});
expect(testServer.port).toEqual(2583);
});
tap.test('CERR-09: Connection pool with concurrent sends', async () => {
// Test basic connection pooling functionality
const pooledClient = await createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 2,
connectionTimeout: 5000
});
console.log('Testing connection pool with concurrent sends...');
// Send multiple messages concurrently
const emails = [
new Email({
from: 'sender@example.com',
to: 'recipient1@example.com',
subject: 'Pool test 1',
text: 'Testing connection pool'
}),
new Email({
from: 'sender@example.com',
to: 'recipient2@example.com',
subject: 'Pool test 2',
text: 'Testing connection pool'
}),
new Email({
from: 'sender@example.com',
to: 'recipient3@example.com',
subject: 'Pool test 3',
text: 'Testing connection pool'
})
];
const results = await Promise.all(
emails.map(email => pooledClient.sendMail(email))
);
const successful = results.filter(r => r.success).length;
console.log(`✅ Sent ${successful} messages using connection pool`);
expect(successful).toBeGreaterThan(0);
await pooledClient.close();
});
tap.test('CERR-09: Connection pool with server limit', async () => {
// Create server that limits concurrent connections
let activeConnections = 0;
const maxServerConnections = 1;
const limitedServer = net.createServer((socket) => {
activeConnections++;
if (activeConnections > maxServerConnections) {
socket.write('421 4.7.0 Too many connections\r\n');
socket.end();
activeConnections--;
return;
}
socket.write('220 Limited 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 === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
} else {
socket.write('250 OK\r\n');
}
}
});
socket.on('close', () => {
activeConnections--;
});
});
await new Promise<void>((resolve) => {
limitedServer.listen(2584, () => resolve());
});
const pooledClient = await createSmtpClient({
host: '127.0.0.1',
port: 2584,
secure: false,
pool: true,
maxConnections: 3, // Client wants 3 but server only allows 1
connectionTimeout: 5000
});
// Try concurrent connections
const results = await Promise.all([
pooledClient.verify(),
pooledClient.verify(),
pooledClient.verify()
]);
const successful = results.filter(r => r === true).length;
console.log(`${successful} connections succeeded with server limit`);
expect(successful).toBeGreaterThan(0);
await pooledClient.close();
await new Promise<void>((resolve) => {
limitedServer.close(() => resolve());
});
});
tap.test('CERR-09: Connection pool recovery after error', async () => {
// Create server that fails sometimes
let requestCount = 0;
const flakyServer = net.createServer((socket) => {
requestCount++;
// Fail every 3rd connection
if (requestCount % 3 === 0) {
socket.destroy();
return;
}
socket.write('220 Flaky Server\r\n');
let buffer = '';
let inData = false;
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 (inData) {
if (command === '.') {
inData = false;
socket.write('250 OK\r\n');
}
continue;
}
if (command.startsWith('EHLO')) {
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 Send data\r\n');
inData = true;
} else if (command === 'QUIT') {
socket.write('221 Bye\r\n');
socket.end();
}
}
});
});
await new Promise<void>((resolve) => {
flakyServer.listen(2585, () => resolve());
});
const pooledClient = await createSmtpClient({
host: '127.0.0.1',
port: 2585,
secure: false,
pool: true,
maxConnections: 2,
connectionTimeout: 5000
});
// Send multiple messages to test recovery
const results = [];
for (let i = 0; i < 5; i++) {
const email = new Email({
from: 'sender@example.com',
to: 'recipient@example.com',
subject: `Recovery test ${i}`,
text: 'Testing pool recovery'
});
const result = await pooledClient.sendMail(email);
results.push(result.success);
console.log(`Message ${i}: ${result.success ? 'Success' : 'Failed'}`);
}
const successful = results.filter(r => r === true).length;
console.log(`✅ Pool recovered from errors: ${successful}/5 succeeded`);
expect(successful).toBeGreaterThan(2);
await pooledClient.close();
await new Promise<void>((resolve) => {
flakyServer.close(() => resolve());
});
});
tap.test('CERR-09: Connection pool timeout handling', async () => {
// Create very slow server
const slowServer = net.createServer((socket) => {
// Wait 2 seconds before sending greeting
setTimeout(() => {
socket.write('220 Very Slow Server\r\n');
}, 2000);
socket.on('data', () => {
// Don't respond to any commands
});
});
await new Promise<void>((resolve) => {
slowServer.listen(2586, () => resolve());
});
const pooledClient = await createSmtpClient({
host: '127.0.0.1',
port: 2586,
secure: false,
pool: true,
connectionTimeout: 1000 // 1 second timeout
});
const result = await pooledClient.verify();
expect(result).toBeFalse();
console.log('✅ Connection pool handled timeout correctly');
await pooledClient.close();
await new Promise<void>((resolve) => {
slowServer.close(() => resolve());
});
});
tap.test('CERR-09: Normal pooled operation', async () => {
// Test successful pooled operation
const pooledClient = await createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
pool: true,
maxConnections: 2
});
const email = new Email({
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Pool Test',
text: 'Testing normal pooled operation'
});
const result = await pooledClient.sendMail(email);
expect(result.success).toBeTrue();
console.log('✅ Normal pooled email sent successfully');
await pooledClient.close();
});
tap.test('cleanup - stop SMTP server', async () => {
await stopTestServer(testServer);
});
export default tap.start();