dcrouter/test/suite/smtpclient_connection/test.ccm-04.connection-pooling.ts
2025-05-25 19:05:43 +00:00

250 lines
7.8 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
import { createPooledSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js';
import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.js';
import { Email } from '../../../ts/mail/core/classes.email.js';
let testServer: ITestServer;
let pooledClient: SmtpClient;
tap.test('setup - start SMTP server for pooling test', async () => {
testServer = await startTestServer({
port: 2530,
tlsEnabled: false,
authRequired: false,
maxConnections: 10
});
expect(testServer.port).toEqual(2530);
});
tap.test('CCM-04: Connection Pooling - should create pooled client', async () => {
const startTime = Date.now();
try {
// Create pooled SMTP client
pooledClient = createPooledSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
maxConnections: 5,
maxMessages: 100,
connectionTimeout: 5000,
debug: true
});
// Verify connection pool is working
const isConnected = await pooledClient.verify();
expect(isConnected).toBeTrue();
const poolStatus = pooledClient.getPoolStatus();
console.log('📊 Initial pool status:', poolStatus);
expect(poolStatus.total).toBeGreaterThanOrEqual(0);
const duration = Date.now() - startTime;
console.log(`✅ Connection pool created in ${duration}ms`);
} catch (error) {
const duration = Date.now() - startTime;
console.error(`❌ Connection pool creation failed after ${duration}ms:`, error);
throw error;
}
});
tap.test('CCM-04: Connection Pooling - should handle concurrent connections', async () => {
// Send multiple emails concurrently
const emailPromises = [];
const concurrentCount = 5;
for (let i = 0; i < concurrentCount; i++) {
const email = new Email({
from: 'test@example.com',
to: `recipient${i}@example.com`,
subject: `Concurrent Email ${i}`,
text: `This is concurrent email number ${i}`
});
emailPromises.push(
pooledClient.sendMail(email).catch(error => {
console.error(`❌ Failed to send email ${i}:`, error);
return { success: false, error: error.message, acceptedRecipients: [] };
})
);
}
// Wait for all emails to be sent
const results = await Promise.all(emailPromises);
// Check results and count successes
let successCount = 0;
results.forEach((result, index) => {
if (result.success) {
successCount++;
expect(result.acceptedRecipients).toContain(`recipient${index}@example.com`);
} else {
console.log(`Email ${index} failed:`, result.error);
}
});
// At least some emails should succeed with pooling
expect(successCount).toBeGreaterThan(0);
console.log(`✅ Sent ${successCount}/${concurrentCount} emails successfully`);
// Check pool status after concurrent sends
const poolStatus = pooledClient.getPoolStatus();
console.log('📊 Pool status after concurrent sends:', poolStatus);
expect(poolStatus.total).toBeGreaterThanOrEqual(1);
expect(poolStatus.total).toBeLessThanOrEqual(5); // Should not exceed max
});
tap.test('CCM-04: Connection Pooling - should reuse connections', async () => {
// Get initial pool status
const initialStatus = pooledClient.getPoolStatus();
console.log('📊 Initial status:', initialStatus);
// Send emails sequentially to test connection reuse
const emailCount = 10;
const connectionCounts = [];
for (let i = 0; i < emailCount; i++) {
const email = new Email({
from: 'test@example.com',
to: 'recipient@example.com',
subject: `Sequential Email ${i}`,
text: `Testing connection reuse - email ${i}`
});
await pooledClient.sendMail(email);
const status = pooledClient.getPoolStatus();
connectionCounts.push(status.total);
}
// Check that connections were reused (total shouldn't grow linearly)
const maxConnections = Math.max(...connectionCounts);
expect(maxConnections).toBeLessThan(emailCount); // Should reuse connections
console.log(`✅ Sent ${emailCount} emails using max ${maxConnections} connections`);
console.log('📊 Connection counts:', connectionCounts);
});
tap.test('CCM-04: Connection Pooling - should respect max connections limit', async () => {
// Create a client with small pool
const limitedClient = createPooledSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
maxConnections: 2, // Very small pool
connectionTimeout: 5000
});
// Send many concurrent emails
const emailPromises = [];
for (let i = 0; i < 10; i++) {
const email = new Email({
from: 'test@example.com',
to: `test${i}@example.com`,
subject: `Pool Limit Test ${i}`,
text: 'Testing pool limits'
});
emailPromises.push(limitedClient.sendMail(email));
}
// Monitor pool during sending
const checkInterval = setInterval(() => {
const status = limitedClient.getPoolStatus();
console.log('📊 Pool status during load:', status);
expect(status.total).toBeLessThanOrEqual(2); // Should never exceed max
}, 100);
await Promise.all(emailPromises);
clearInterval(checkInterval);
await limitedClient.close();
console.log('✅ Connection pool respected max connections limit');
});
tap.test('CCM-04: Connection Pooling - should handle connection failures in pool', async () => {
// Create a new pooled client
const resilientClient = createPooledSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
maxConnections: 3,
connectionTimeout: 5000
});
// Send some emails successfully
for (let i = 0; i < 3; i++) {
const email = new Email({
from: 'test@example.com',
to: 'recipient@example.com',
subject: `Pre-failure Email ${i}`,
text: 'Before simulated failure'
});
const result = await resilientClient.sendMail(email);
expect(result.success).toBeTrue();
}
// Pool should recover and continue working
const poolStatus = resilientClient.getPoolStatus();
console.log('📊 Pool status after recovery test:', poolStatus);
expect(poolStatus.total).toBeGreaterThanOrEqual(1);
await resilientClient.close();
console.log('✅ Connection pool handled failures gracefully');
});
tap.test('CCM-04: Connection Pooling - should clean up idle connections', async () => {
// Create client with specific idle settings
const idleClient = createPooledSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
maxConnections: 5,
connectionTimeout: 5000
});
// Send burst of emails
const promises = [];
for (let i = 0; i < 5; i++) {
const email = new Email({
from: 'test@example.com',
to: 'recipient@example.com',
subject: `Idle Test ${i}`,
text: 'Testing idle cleanup'
});
promises.push(idleClient.sendMail(email));
}
await Promise.all(promises);
const activeStatus = idleClient.getPoolStatus();
console.log('📊 Pool status after burst:', activeStatus);
// Wait for connections to become idle
await new Promise(resolve => setTimeout(resolve, 2000));
const idleStatus = idleClient.getPoolStatus();
console.log('📊 Pool status after idle period:', idleStatus);
await idleClient.close();
console.log('✅ Idle connection management working');
});
tap.test('cleanup - close pooled client', async () => {
if (pooledClient && pooledClient.isConnected()) {
await pooledClient.close();
// Verify pool is cleaned up
const finalStatus = pooledClient.getPoolStatus();
console.log('📊 Final pool status:', finalStatus);
}
});
tap.test('cleanup - stop SMTP server', async () => {
await stopTestServer(testServer);
});
export default tap.start();