324 lines
9.0 KiB
TypeScript
324 lines
9.0 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
|
import { createSmtpClient, 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';
|
|
import * as net from 'net';
|
|
|
|
let testServer: ITestServer;
|
|
|
|
tap.test('setup - start SMTP server for reconnection tests', async () => {
|
|
testServer = await startTestServer({
|
|
port: 2533,
|
|
tlsEnabled: false,
|
|
authRequired: false
|
|
});
|
|
|
|
expect(testServer.port).toEqual(2533);
|
|
});
|
|
|
|
tap.test('CCM-07: Automatic Reconnection - should reconnect after connection loss', async () => {
|
|
const client = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
// First connection and email
|
|
const email1 = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: 'Before Disconnect',
|
|
text: 'First email before connection loss'
|
|
});
|
|
|
|
const result1 = await client.sendMail(email1);
|
|
expect(result1.success).toBeTrue();
|
|
// Note: Connection state may vary after sending
|
|
|
|
// Force disconnect
|
|
await client.close();
|
|
expect(client.isConnected()).toBeFalse();
|
|
|
|
// Try to send another email - should auto-reconnect
|
|
const email2 = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: 'After Reconnect',
|
|
text: 'Email after automatic reconnection'
|
|
});
|
|
|
|
const result2 = await client.sendMail(email2);
|
|
expect(result2.success).toBeTrue();
|
|
// Connection successfully handled reconnection
|
|
|
|
await client.close();
|
|
console.log('✅ Automatic reconnection successful');
|
|
});
|
|
|
|
tap.test('CCM-07: Automatic Reconnection - pooled client should reconnect failed connections', async () => {
|
|
const pooledClient = createPooledSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
maxConnections: 3,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
// Send emails to establish pool connections
|
|
const promises = [];
|
|
for (let i = 0; i < 3; i++) {
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: `recipient${i}@example.com`,
|
|
subject: `Pool Test ${i}`,
|
|
text: 'Testing connection pool'
|
|
});
|
|
promises.push(
|
|
pooledClient.sendMail(email).catch(error => {
|
|
console.error(`Failed to send initial email ${i}:`, error.message);
|
|
return { success: false, error: error.message };
|
|
})
|
|
);
|
|
}
|
|
|
|
await Promise.all(promises);
|
|
|
|
const poolStatus1 = pooledClient.getPoolStatus();
|
|
console.log('📊 Pool status before disruption:', poolStatus1);
|
|
|
|
// Send more emails - pool should handle any connection issues
|
|
const promises2 = [];
|
|
for (let i = 0; i < 5; i++) {
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: `recipient${i}@example.com`,
|
|
subject: `Pool Recovery ${i}`,
|
|
text: 'Testing pool recovery'
|
|
});
|
|
promises2.push(
|
|
pooledClient.sendMail(email).catch(error => {
|
|
console.error(`Failed to send email ${i}:`, error.message);
|
|
return { success: false, error: error.message };
|
|
})
|
|
);
|
|
}
|
|
|
|
const results = await Promise.all(promises2);
|
|
let successCount = 0;
|
|
results.forEach(result => {
|
|
if (result.success) {
|
|
successCount++;
|
|
}
|
|
});
|
|
|
|
// At least some emails should succeed
|
|
expect(successCount).toBeGreaterThan(0);
|
|
console.log(`✅ Pool recovery: ${successCount}/${results.length} emails succeeded`);
|
|
|
|
const poolStatus2 = pooledClient.getPoolStatus();
|
|
console.log('📊 Pool status after recovery:', poolStatus2);
|
|
|
|
await pooledClient.close();
|
|
console.log('✅ Connection pool handles reconnection automatically');
|
|
});
|
|
|
|
tap.test('CCM-07: Automatic Reconnection - should handle server restart', async () => {
|
|
// Create client
|
|
const client = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000
|
|
});
|
|
|
|
// Send first email
|
|
const email1 = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: 'Before Server Restart',
|
|
text: 'Email before server restart'
|
|
});
|
|
|
|
const result1 = await client.sendMail(email1);
|
|
expect(result1.success).toBeTrue();
|
|
|
|
// Simulate server restart
|
|
console.log('🔄 Simulating server restart...');
|
|
await stopTestServer(testServer);
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
// Restart server on same port
|
|
testServer = await startTestServer({
|
|
port: 2533,
|
|
tlsEnabled: false,
|
|
authRequired: false
|
|
});
|
|
|
|
// Try to send another email
|
|
const email2 = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: 'After Server Restart',
|
|
text: 'Email after server restart'
|
|
});
|
|
|
|
const result2 = await client.sendMail(email2);
|
|
expect(result2.success).toBeTrue();
|
|
|
|
await client.close();
|
|
console.log('✅ Client recovered from server restart');
|
|
});
|
|
|
|
tap.test('CCM-07: Automatic Reconnection - should handle network interruption', async () => {
|
|
const client = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
socketTimeout: 10000
|
|
});
|
|
|
|
// Establish connection
|
|
await client.verify();
|
|
|
|
// Send emails with simulated network issues
|
|
for (let i = 0; i < 3; i++) {
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: `Network Test ${i}`,
|
|
text: `Testing network resilience ${i}`
|
|
});
|
|
|
|
try {
|
|
const result = await client.sendMail(email);
|
|
expect(result.success).toBeTrue();
|
|
console.log(`✅ Email ${i + 1} sent successfully`);
|
|
} catch (error) {
|
|
console.log(`⚠️ Email ${i + 1} failed, will retry`);
|
|
// Client should recover on next attempt
|
|
}
|
|
|
|
// Add small delay between sends
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
}
|
|
|
|
await client.close();
|
|
});
|
|
|
|
tap.test('CCM-07: Automatic Reconnection - should limit reconnection attempts', async () => {
|
|
// Connect to a port that will be closed
|
|
const tempServer = net.createServer();
|
|
await new Promise<void>((resolve) => {
|
|
tempServer.listen(2534, () => resolve());
|
|
});
|
|
|
|
const client = createSmtpClient({
|
|
host: 'localhost',
|
|
port: 2534,
|
|
secure: false,
|
|
connectionTimeout: 2000
|
|
});
|
|
|
|
// Close the server to simulate failure
|
|
tempServer.close();
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
let failureCount = 0;
|
|
const maxAttempts = 3;
|
|
|
|
// Try multiple times
|
|
for (let i = 0; i < maxAttempts; i++) {
|
|
const verified = await client.verify();
|
|
if (!verified) {
|
|
failureCount++;
|
|
}
|
|
}
|
|
|
|
expect(failureCount).toEqual(maxAttempts);
|
|
console.log('✅ Reconnection attempts are limited to prevent infinite loops');
|
|
});
|
|
|
|
tap.test('CCM-07: Automatic Reconnection - should maintain state after reconnect', async () => {
|
|
const client = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000
|
|
});
|
|
|
|
// Send email with specific settings
|
|
const email1 = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: 'State Test 1',
|
|
text: 'Testing state persistence',
|
|
priority: 'high',
|
|
headers: {
|
|
'X-Test-ID': 'test-123'
|
|
}
|
|
});
|
|
|
|
const result1 = await client.sendMail(email1);
|
|
expect(result1.success).toBeTrue();
|
|
|
|
// Force reconnection
|
|
await client.close();
|
|
|
|
// Send another email - client state should be maintained
|
|
const email2 = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: 'State Test 2',
|
|
text: 'After reconnection',
|
|
priority: 'high',
|
|
headers: {
|
|
'X-Test-ID': 'test-456'
|
|
}
|
|
});
|
|
|
|
const result2 = await client.sendMail(email2);
|
|
expect(result2.success).toBeTrue();
|
|
|
|
await client.close();
|
|
console.log('✅ Client state maintained after reconnection');
|
|
});
|
|
|
|
tap.test('CCM-07: Automatic Reconnection - should handle rapid reconnections', async () => {
|
|
const client = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000
|
|
});
|
|
|
|
// Rapid connect/disconnect cycles
|
|
for (let i = 0; i < 5; i++) {
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: `Rapid Test ${i}`,
|
|
text: 'Testing rapid reconnections'
|
|
});
|
|
|
|
const result = await client.sendMail(email);
|
|
expect(result.success).toBeTrue();
|
|
|
|
// Force disconnect
|
|
await client.close();
|
|
|
|
// No delay - immediate next attempt
|
|
}
|
|
|
|
console.log('✅ Rapid reconnections handled successfully');
|
|
});
|
|
|
|
tap.test('cleanup - stop SMTP server', async () => {
|
|
await stopTestServer(testServer);
|
|
});
|
|
|
|
export default tap.start(); |