250 lines
7.8 KiB
TypeScript
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(); |