2025-05-25 19:02:18 +00:00
|
|
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
2025-05-26 14:50:55 +00:00
|
|
|
import { createTestServer } from '../../helpers/server.loader.js';
|
|
|
|
import { createTestSmtpClient } from '../../helpers/smtp.client.js';
|
2025-05-24 18:12:08 +00:00
|
|
|
import { Email } from '../../../ts/mail/core/classes.email.js';
|
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
// Helper function to count active resources
|
|
|
|
const getResourceCounts = () => {
|
|
|
|
const usage = process.memoryUsage();
|
|
|
|
return {
|
|
|
|
memory: Math.round(usage.heapUsed / 1024 / 1024 * 100) / 100, // MB
|
|
|
|
handles: (process as any)._getActiveHandles ? (process as any)._getActiveHandles().length : 0,
|
|
|
|
requests: (process as any)._getActiveRequests ? (process as any)._getActiveRequests().length : 0
|
2025-05-24 18:12:08 +00:00
|
|
|
};
|
2025-05-26 14:50:55 +00:00
|
|
|
};
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
// Scenario 1: Basic Resource Cleanup
|
|
|
|
tap.test('CREL-07: Basic Resource Cleanup', async () => {
|
|
|
|
console.log('\n🧹 Testing SMTP Client Resource Cleanup');
|
|
|
|
console.log('=' .repeat(60));
|
|
|
|
|
|
|
|
let connections = 0;
|
|
|
|
let disconnections = 0;
|
|
|
|
|
|
|
|
const testServer = await createTestServer({
|
|
|
|
onConnection: (socket: any) => {
|
|
|
|
connections++;
|
|
|
|
console.log(` [Server] Connection opened (total: ${connections})`);
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
socket.on('close', () => {
|
|
|
|
disconnections++;
|
|
|
|
console.log(` [Server] Connection closed (total closed: ${disconnections})`);
|
|
|
|
});
|
2025-05-24 18:12:08 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
try {
|
|
|
|
const initialResources = getResourceCounts();
|
|
|
|
console.log(` Initial resources: ${initialResources.memory}MB memory, ${initialResources.handles} handles`);
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
console.log(' Creating SMTP clients and sending emails...');
|
|
|
|
const clients = [];
|
|
|
|
|
|
|
|
// Create multiple clients
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
const smtpClient = createTestSmtpClient({
|
2025-05-24 18:12:08 +00:00
|
|
|
host: testServer.hostname,
|
2025-05-26 14:50:55 +00:00
|
|
|
port: testServer.port
|
2025-05-24 18:12:08 +00:00
|
|
|
});
|
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
clients.push(smtpClient);
|
|
|
|
|
|
|
|
// Send a test email
|
|
|
|
const email = new Email({
|
|
|
|
from: `sender${i}@cleanup.test`,
|
|
|
|
to: [`recipient${i}@cleanup.test`],
|
|
|
|
subject: `Cleanup Test ${i + 1}`,
|
|
|
|
text: `Testing connection cleanup ${i + 1}`
|
2025-05-24 18:12:08 +00:00
|
|
|
});
|
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
try {
|
|
|
|
await smtpClient.sendMail(email);
|
|
|
|
console.log(` ✓ Client ${i + 1} email sent`);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(` ✗ Client ${i + 1} failed: ${error.message}`);
|
|
|
|
}
|
2025-05-24 18:12:08 +00:00
|
|
|
}
|
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
const afterSending = getResourceCounts();
|
|
|
|
console.log(` After sending: ${afterSending.memory}MB memory, ${afterSending.handles} handles`);
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
console.log(' Closing all clients...');
|
|
|
|
for (let i = 0; i < clients.length; i++) {
|
|
|
|
console.log(` Closing client ${i + 1}...`);
|
|
|
|
clients[i].close();
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
}
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
// Wait for cleanup to complete
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
const finalResources = getResourceCounts();
|
|
|
|
console.log(`\n Resource cleanup assessment:`);
|
|
|
|
console.log(` Initial: ${initialResources.memory}MB memory, ${initialResources.handles} handles`);
|
|
|
|
console.log(` Final: ${finalResources.memory}MB memory, ${finalResources.handles} handles`);
|
|
|
|
console.log(` Connections opened: ${connections}`);
|
|
|
|
console.log(` Connections closed: ${disconnections}`);
|
|
|
|
console.log(` Cleanup: ${disconnections >= connections - 1 ? 'Complete' : 'Incomplete'}`);
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
expect(disconnections).toBeGreaterThanOrEqual(connections - 1);
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
} finally {
|
|
|
|
testServer.server.close();
|
|
|
|
}
|
|
|
|
});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
// Scenario 2: Multiple Close Safety
|
|
|
|
tap.test('CREL-07: Multiple Close Safety', async () => {
|
|
|
|
console.log('\n🔁 Testing multiple close calls safety...');
|
|
|
|
|
|
|
|
const testServer = await createTestServer({});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
try {
|
|
|
|
const smtpClient = createTestSmtpClient({
|
|
|
|
host: testServer.hostname,
|
|
|
|
port: testServer.port
|
|
|
|
});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
// Send a test email
|
|
|
|
const email = new Email({
|
|
|
|
from: 'sender@multiclose.test',
|
|
|
|
to: ['recipient@multiclose.test'],
|
|
|
|
subject: 'Multiple Close Test',
|
|
|
|
text: 'Testing multiple close calls'
|
|
|
|
});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
console.log(' Sending test email...');
|
|
|
|
await smtpClient.sendMail(email);
|
|
|
|
console.log(' ✓ Email sent successfully');
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
console.log(' Attempting multiple close calls...');
|
|
|
|
let closeErrors = 0;
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
|
try {
|
|
|
|
smtpClient.close();
|
|
|
|
console.log(` ✓ Close call ${i + 1} completed`);
|
|
|
|
} catch (error) {
|
|
|
|
closeErrors++;
|
|
|
|
console.log(` ✗ Close call ${i + 1} error: ${error.message}`);
|
2025-05-24 18:12:08 +00:00
|
|
|
}
|
2025-05-26 14:50:55 +00:00
|
|
|
}
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
console.log(` Close errors: ${closeErrors}`);
|
|
|
|
console.log(` Safety: ${closeErrors === 0 ? 'Safe' : 'Issues detected'}`);
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
expect(closeErrors).toEqual(0);
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
} finally {
|
|
|
|
testServer.server.close();
|
|
|
|
}
|
|
|
|
});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
// Scenario 3: Error Recovery and Cleanup
|
|
|
|
tap.test('CREL-07: Error Recovery and Cleanup', async () => {
|
|
|
|
console.log('\n❌ Testing error recovery and cleanup...');
|
|
|
|
|
|
|
|
let errorMode = false;
|
|
|
|
let requestCount = 0;
|
|
|
|
|
|
|
|
const testServer = await createTestServer({
|
|
|
|
onConnection: (socket: any) => {
|
|
|
|
requestCount++;
|
|
|
|
if (errorMode && requestCount % 2 === 0) {
|
|
|
|
console.log(` [Server] Simulating connection error`);
|
|
|
|
setTimeout(() => socket.destroy(), 50);
|
2025-05-24 18:12:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
try {
|
|
|
|
const smtpClient = createTestSmtpClient({
|
|
|
|
host: testServer.hostname,
|
|
|
|
port: testServer.port,
|
|
|
|
connectionTimeout: 2000
|
2025-05-24 18:12:08 +00:00
|
|
|
});
|
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
console.log(' Phase 1: Normal operation...');
|
|
|
|
const normalEmail = new Email({
|
|
|
|
from: 'sender@test.com',
|
|
|
|
to: ['recipient@test.com'],
|
|
|
|
subject: 'Normal Test',
|
|
|
|
text: 'Testing normal operation'
|
|
|
|
});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
let normalResult = false;
|
|
|
|
try {
|
|
|
|
await smtpClient.sendMail(normalEmail);
|
|
|
|
normalResult = true;
|
|
|
|
console.log(' ✓ Normal operation successful');
|
|
|
|
} catch (error) {
|
|
|
|
console.log(' ✗ Normal operation failed');
|
|
|
|
}
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
console.log(' Phase 2: Error injection...');
|
|
|
|
errorMode = true;
|
|
|
|
|
|
|
|
let errorCount = 0;
|
|
|
|
for (let i = 0; i < 3; i++) {
|
2025-05-24 18:12:08 +00:00
|
|
|
try {
|
2025-05-26 14:50:55 +00:00
|
|
|
const errorEmail = new Email({
|
|
|
|
from: 'sender@error.test',
|
|
|
|
to: ['recipient@error.test'],
|
|
|
|
subject: `Error Test ${i + 1}`,
|
|
|
|
text: 'Testing error handling'
|
|
|
|
});
|
|
|
|
await smtpClient.sendMail(errorEmail);
|
|
|
|
console.log(` ✓ Email ${i + 1} sent (recovered)`);
|
2025-05-24 18:12:08 +00:00
|
|
|
} catch (error) {
|
2025-05-26 14:50:55 +00:00
|
|
|
errorCount++;
|
|
|
|
console.log(` ✗ Email ${i + 1} failed as expected`);
|
2025-05-24 18:12:08 +00:00
|
|
|
}
|
2025-05-26 14:50:55 +00:00
|
|
|
}
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
console.log(' Phase 3: Recovery...');
|
|
|
|
errorMode = false;
|
|
|
|
|
|
|
|
const recoveryEmail = new Email({
|
|
|
|
from: 'sender@recovery.test',
|
|
|
|
to: ['recipient@recovery.test'],
|
|
|
|
subject: 'Recovery Test',
|
|
|
|
text: 'Testing recovery'
|
|
|
|
});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
let recovered = false;
|
|
|
|
try {
|
|
|
|
await smtpClient.sendMail(recoveryEmail);
|
|
|
|
recovered = true;
|
|
|
|
console.log(' ✓ Recovery successful');
|
|
|
|
} catch (error) {
|
|
|
|
console.log(' ✗ Recovery failed');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close and cleanup
|
|
|
|
smtpClient.close();
|
|
|
|
|
|
|
|
console.log(`\n Error recovery assessment:`);
|
|
|
|
console.log(` Normal operation: ${normalResult ? 'Success' : 'Failed'}`);
|
|
|
|
console.log(` Errors encountered: ${errorCount}`);
|
|
|
|
console.log(` Recovery: ${recovered ? 'Successful' : 'Failed'}`);
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
expect(normalResult).toEqual(true);
|
|
|
|
expect(errorCount).toBeGreaterThan(0);
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
testServer.server.close();
|
|
|
|
}
|
|
|
|
});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
// Scenario 4: Rapid Connect/Disconnect
|
|
|
|
tap.test('CREL-07: Rapid Connect/Disconnect Cycles', async () => {
|
|
|
|
console.log('\n⚡ Testing rapid connect/disconnect cycles...');
|
|
|
|
|
|
|
|
const testServer = await createTestServer({});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
try {
|
|
|
|
console.log(' Performing rapid connect/disconnect cycles...');
|
|
|
|
let successful = 0;
|
|
|
|
let failed = 0;
|
|
|
|
|
|
|
|
for (let cycle = 0; cycle < 5; cycle++) {
|
|
|
|
const smtpClient = createTestSmtpClient({
|
|
|
|
host: testServer.hostname,
|
|
|
|
port: testServer.port,
|
|
|
|
connectionTimeout: 1000
|
2025-05-24 18:12:08 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
2025-05-26 14:50:55 +00:00
|
|
|
// Quick verify to establish connection
|
|
|
|
await smtpClient.verify();
|
|
|
|
successful++;
|
|
|
|
console.log(` ✓ Cycle ${cycle + 1}: Connected`);
|
2025-05-24 18:12:08 +00:00
|
|
|
} catch (error) {
|
2025-05-26 14:50:55 +00:00
|
|
|
failed++;
|
|
|
|
console.log(` ✗ Cycle ${cycle + 1}: Failed`);
|
2025-05-24 18:12:08 +00:00
|
|
|
}
|
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
// Immediately close
|
|
|
|
smtpClient.close();
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
// Brief pause between cycles
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
2025-05-24 18:12:08 +00:00
|
|
|
}
|
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
console.log(`\n Rapid cycle results:`);
|
|
|
|
console.log(` Successful connections: ${successful}`);
|
|
|
|
console.log(` Failed connections: ${failed}`);
|
|
|
|
console.log(` Success rate: ${(successful / (successful + failed) * 100).toFixed(1)}%`);
|
|
|
|
|
|
|
|
expect(successful).toBeGreaterThan(0);
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
testServer.server.close();
|
2025-05-24 18:12:08 +00:00
|
|
|
}
|
2025-05-26 14:50:55 +00:00
|
|
|
});
|
2025-05-24 18:12:08 +00:00
|
|
|
|
2025-05-26 14:50:55 +00:00
|
|
|
tap.test('CREL-07: Test Summary', async () => {
|
2025-05-24 18:12:08 +00:00
|
|
|
console.log('\n✅ CREL-07: Resource Cleanup Reliability Tests completed');
|
|
|
|
console.log('🧹 All resource cleanup scenarios tested successfully');
|
2025-05-26 14:50:55 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
tap.start();
|