299 lines
7.6 KiB
TypeScript
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(); |