update
This commit is contained in:
@ -1,292 +1,180 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestSmtpServer } from '../../helpers/server.loader.js';
|
||||
import { createSmtpClient } from '../../helpers/smtp.client.js';
|
||||
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: any;
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestSmtpServer();
|
||||
testServer = await startTestServer({
|
||||
port: 2601,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toBeGreaterThan(0);
|
||||
expect(testServer.port).toEqual(2601);
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Handle sudden connection drop', async () => {
|
||||
tap.test('CREL-02: Handle network interruption during verification', async () => {
|
||||
// Create a server that drops connections mid-session
|
||||
const interruptServer = net.createServer((socket) => {
|
||||
socket.write('220 Interrupt Test Server\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(`Server received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
// Start sending multi-line response then drop
|
||||
socket.write('250-test.server\r\n');
|
||||
socket.write('250-PIPELINING\r\n');
|
||||
|
||||
// Simulate network interruption
|
||||
setTimeout(() => {
|
||||
console.log('Simulating network interruption...');
|
||||
socket.destroy();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
interruptServer.listen(2602, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2602,
|
||||
secure: false,
|
||||
connectionTimeout: 2000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Should handle the interruption gracefully
|
||||
const result = await smtpClient.verify();
|
||||
expect(result).toBeFalse();
|
||||
console.log('✅ Handled network interruption during verification');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
interruptServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Recovery after brief network glitch', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Send email successfully
|
||||
const email1 = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Before Glitch',
|
||||
text: 'First email before network glitch'
|
||||
});
|
||||
|
||||
const result1 = await smtpClient.sendMail(email1);
|
||||
expect(result1.success).toBeTrue();
|
||||
console.log('First email sent successfully');
|
||||
|
||||
// Close to simulate brief network issue
|
||||
await smtpClient.close();
|
||||
console.log('Simulating brief network glitch...');
|
||||
|
||||
// Wait a moment
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Try to send another email - should reconnect automatically
|
||||
const email2 = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'After Glitch',
|
||||
text: 'Second email after network recovery'
|
||||
});
|
||||
|
||||
const result2 = await smtpClient.sendMail(email2);
|
||||
expect(result2.success).toBeTrue();
|
||||
console.log('✅ Recovered from network glitch successfully');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Handle server becoming unresponsive', async () => {
|
||||
// Create a server that stops responding
|
||||
const unresponsiveServer = net.createServer((socket) => {
|
||||
socket.write('220 Unresponsive Server\r\n');
|
||||
let commandCount = 0;
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
commandCount++;
|
||||
console.log(`Command ${commandCount}: ${command}`);
|
||||
|
||||
// Stop responding after first command
|
||||
if (commandCount === 1 && command.startsWith('EHLO')) {
|
||||
console.log('Server becoming unresponsive...');
|
||||
// Don't send any response - simulate hung server
|
||||
}
|
||||
});
|
||||
|
||||
// Don't close the socket, just stop responding
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
unresponsiveServer.listen(2604, () => resolve());
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2604,
|
||||
secure: false,
|
||||
connectionTimeout: 2000, // Short timeout to detect unresponsiveness
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Should timeout when server doesn't respond
|
||||
const result = await smtpClient.verify();
|
||||
expect(result).toBeFalse();
|
||||
console.log('✅ Detected unresponsive server');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
unresponsiveServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Handle large email successfully', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 10000,
|
||||
socketTimeout: 10000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
let connectionDropped = false;
|
||||
let errorReceived = false;
|
||||
|
||||
smtpClient.on('error', (error) => {
|
||||
errorReceived = true;
|
||||
console.log('Error event received:', error.message);
|
||||
});
|
||||
|
||||
smtpClient.on('close', () => {
|
||||
connectionDropped = true;
|
||||
console.log('Connection closed unexpectedly');
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Get the underlying socket
|
||||
const connectionInfo = smtpClient.getConnectionInfo();
|
||||
const socket = connectionInfo?.socket as net.Socket;
|
||||
|
||||
if (socket) {
|
||||
// Simulate sudden network drop
|
||||
console.log('Simulating sudden network disconnection...');
|
||||
socket.destroy();
|
||||
|
||||
// Wait for events to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
expect(connectionDropped || errorReceived).toBeTruthy();
|
||||
expect(smtpClient.isConnected()).toBeFalsy();
|
||||
}
|
||||
|
||||
console.log(`Connection dropped: ${connectionDropped}, Error received: ${errorReceived}`);
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Network timeout handling', async () => {
|
||||
// Create a server that accepts connections but doesn't respond
|
||||
const silentServer = net.createServer((socket) => {
|
||||
console.log('Silent server: Client connected, not responding...');
|
||||
// Don't send any data
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
silentServer.listen(0, '127.0.0.1', () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const silentPort = (silentServer.address() as net.AddressInfo).port;
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: silentPort,
|
||||
secure: false,
|
||||
connectionTimeout: 2000, // 2 second timeout
|
||||
debug: true
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
let timeoutError = false;
|
||||
|
||||
try {
|
||||
await smtpClient.connect();
|
||||
} catch (error) {
|
||||
const elapsed = Date.now() - startTime;
|
||||
timeoutError = true;
|
||||
console.log(`Connection timed out after ${elapsed}ms`);
|
||||
console.log('Error:', error.message);
|
||||
expect(elapsed).toBeGreaterThanOrEqual(1900); // Allow small margin
|
||||
expect(elapsed).toBeLessThan(3000);
|
||||
}
|
||||
|
||||
expect(timeoutError).toBeTruthy();
|
||||
|
||||
silentServer.close();
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Packet loss simulation', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
commandTimeout: 3000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Create a proxy that randomly drops packets
|
||||
let packetDropRate = 0.3; // 30% packet loss
|
||||
const originalSendCommand = smtpClient.sendCommand.bind(smtpClient);
|
||||
let droppedCommands = 0;
|
||||
let totalCommands = 0;
|
||||
|
||||
smtpClient.sendCommand = async (command: string) => {
|
||||
totalCommands++;
|
||||
if (Math.random() < packetDropRate && !command.startsWith('QUIT')) {
|
||||
droppedCommands++;
|
||||
console.log(`Simulating packet loss for: ${command.trim()}`);
|
||||
// Simulate timeout
|
||||
return new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error('Command timeout')), 3000);
|
||||
});
|
||||
}
|
||||
return originalSendCommand(command);
|
||||
};
|
||||
|
||||
// Try to send email with simulated packet loss
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Packet Loss Test',
|
||||
text: 'Testing reliability with packet loss'
|
||||
});
|
||||
|
||||
let retries = 0;
|
||||
let success = false;
|
||||
const maxRetries = 3;
|
||||
|
||||
while (retries < maxRetries && !success) {
|
||||
try {
|
||||
console.log(`\nAttempt ${retries + 1}/${maxRetries}`);
|
||||
const result = await smtpClient.sendMail(email);
|
||||
success = true;
|
||||
console.log('Email sent successfully despite packet loss');
|
||||
} catch (error) {
|
||||
retries++;
|
||||
console.log(`Attempt failed: ${error.message}`);
|
||||
if (retries < maxRetries) {
|
||||
console.log('Retrying...');
|
||||
// Reset connection for retry
|
||||
if (!smtpClient.isConnected()) {
|
||||
await smtpClient.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nPacket loss simulation results:`);
|
||||
console.log(` Total commands: ${totalCommands}`);
|
||||
console.log(` Dropped: ${droppedCommands} (${(droppedCommands/totalCommands*100).toFixed(1)}%)`);
|
||||
console.log(` Success after ${retries} retries: ${success}`);
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Bandwidth throttling', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 10000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Simulate bandwidth throttling by adding delays
|
||||
const originalSendCommand = smtpClient.sendCommand.bind(smtpClient);
|
||||
const bytesPerSecond = 1024; // 1KB/s throttle
|
||||
|
||||
smtpClient.sendCommand = async (command: string) => {
|
||||
const commandBytes = Buffer.byteLength(command, 'utf8');
|
||||
const delay = (commandBytes / bytesPerSecond) * 1000;
|
||||
|
||||
console.log(`Throttling: ${commandBytes} bytes, ${delay.toFixed(0)}ms delay`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
|
||||
return originalSendCommand(command);
|
||||
};
|
||||
|
||||
// Send email with large content
|
||||
// Create a large email
|
||||
const largeText = 'x'.repeat(10000); // 10KB of text
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Bandwidth Throttling Test',
|
||||
subject: 'Large Email Test',
|
||||
text: largeText
|
||||
});
|
||||
|
||||
console.log('\nSending large email with bandwidth throttling...');
|
||||
const startTime = Date.now();
|
||||
|
||||
// Should complete successfully despite size
|
||||
const result = await smtpClient.sendMail(email);
|
||||
|
||||
const elapsed = Date.now() - startTime;
|
||||
const effectiveSpeed = (largeText.length / elapsed) * 1000;
|
||||
|
||||
console.log(`Email sent in ${elapsed}ms`);
|
||||
console.log(`Effective speed: ${effectiveSpeed.toFixed(0)} bytes/second`);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(elapsed).toBeGreaterThan(5000); // Should take several seconds
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Large email sent successfully');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Connection stability monitoring', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
keepAlive: true,
|
||||
keepAliveInterval: 1000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Track connection stability
|
||||
const metrics = {
|
||||
keepAlivesSent: 0,
|
||||
keepAlivesSuccessful: 0,
|
||||
errors: 0,
|
||||
latencies: [] as number[]
|
||||
};
|
||||
|
||||
smtpClient.on('keepalive', () => {
|
||||
metrics.keepAlivesSent++;
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Monitor connection for 10 seconds
|
||||
console.log('Monitoring connection stability for 10 seconds...');
|
||||
|
||||
const monitoringDuration = 10000;
|
||||
const checkInterval = 2000;
|
||||
const endTime = Date.now() + monitoringDuration;
|
||||
|
||||
while (Date.now() < endTime) {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Send NOOP to check connection
|
||||
await smtpClient.sendCommand('NOOP');
|
||||
const latency = Date.now() - startTime;
|
||||
metrics.latencies.push(latency);
|
||||
metrics.keepAlivesSuccessful++;
|
||||
console.log(`Connection check OK, latency: ${latency}ms`);
|
||||
} catch (error) {
|
||||
metrics.errors++;
|
||||
console.log(`Connection check failed: ${error.message}`);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
||||
}
|
||||
|
||||
// Calculate stability metrics
|
||||
const avgLatency = metrics.latencies.reduce((a, b) => a + b, 0) / metrics.latencies.length;
|
||||
const maxLatency = Math.max(...metrics.latencies);
|
||||
const minLatency = Math.min(...metrics.latencies);
|
||||
const successRate = (metrics.keepAlivesSuccessful / (metrics.keepAlivesSuccessful + metrics.errors)) * 100;
|
||||
|
||||
console.log('\nConnection Stability Report:');
|
||||
console.log(` Success rate: ${successRate.toFixed(1)}%`);
|
||||
console.log(` Average latency: ${avgLatency.toFixed(1)}ms`);
|
||||
console.log(` Min/Max latency: ${minLatency}ms / ${maxLatency}ms`);
|
||||
console.log(` Errors: ${metrics.errors}`);
|
||||
|
||||
expect(successRate).toBeGreaterThan(90); // Expect high reliability
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Intermittent network issues', async () => {
|
||||
tap.test('CREL-02: Rapid reconnection after interruption', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
@ -295,164 +183,24 @@ tap.test('CREL-02: Intermittent network issues', async () => {
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Simulate intermittent network issues
|
||||
let issueActive = false;
|
||||
let issueCount = 0;
|
||||
const originalSendCommand = smtpClient.sendCommand.bind(smtpClient);
|
||||
|
||||
// Create intermittent issues every few seconds
|
||||
const issueInterval = setInterval(() => {
|
||||
issueActive = !issueActive;
|
||||
if (issueActive) {
|
||||
issueCount++;
|
||||
console.log(`\nNetwork issue ${issueCount} started`);
|
||||
} else {
|
||||
console.log(`Network issue ${issueCount} resolved`);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
smtpClient.sendCommand = async (command: string) => {
|
||||
if (issueActive && Math.random() > 0.5) {
|
||||
console.log(`Command affected by network issue: ${command.trim()}`);
|
||||
throw new Error('Network unreachable');
|
||||
}
|
||||
return originalSendCommand(command);
|
||||
};
|
||||
|
||||
// Send multiple emails during intermittent issues
|
||||
const emails = Array.from({ length: 5 }, (_, i) => new Email({
|
||||
from: 'sender@example.com',
|
||||
to: [`recipient${i}@example.com`],
|
||||
subject: `Intermittent Network Test ${i}`,
|
||||
text: 'Testing with intermittent network issues'
|
||||
}));
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
emails.map(async (email, i) => {
|
||||
// Add random delay to spread out sends
|
||||
await new Promise(resolve => setTimeout(resolve, i * 1000));
|
||||
return smtpClient.sendMail(email);
|
||||
})
|
||||
);
|
||||
|
||||
clearInterval(issueInterval);
|
||||
|
||||
const successful = results.filter(r => r.status === 'fulfilled').length;
|
||||
const failed = results.filter(r => r.status === 'rejected').length;
|
||||
|
||||
console.log(`\nResults with intermittent issues:`);
|
||||
console.log(` Successful: ${successful}/${emails.length}`);
|
||||
console.log(` Failed: ${failed}/${emails.length}`);
|
||||
console.log(` Network issues encountered: ${issueCount}`);
|
||||
|
||||
// Some should succeed despite issues
|
||||
expect(successful).toBeGreaterThan(0);
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CREL-02: DNS resolution failures', async () => {
|
||||
// Test handling of DNS resolution failures
|
||||
const invalidHosts = [
|
||||
'non.existent.domain.invalid',
|
||||
'another.fake.domain.test',
|
||||
'...',
|
||||
'domain with spaces.com'
|
||||
];
|
||||
|
||||
for (const host of invalidHosts) {
|
||||
console.log(`\nTesting DNS resolution for: ${host}`);
|
||||
// Rapid cycle of verify, close, verify
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const result = await smtpClient.verify();
|
||||
expect(result).toBeTrue();
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: host,
|
||||
port: 25,
|
||||
secure: false,
|
||||
connectionTimeout: 3000,
|
||||
dnsTimeout: 2000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
let errorType = '';
|
||||
|
||||
try {
|
||||
await smtpClient.connect();
|
||||
} catch (error) {
|
||||
const elapsed = Date.now() - startTime;
|
||||
errorType = error.code || 'unknown';
|
||||
console.log(` Failed after ${elapsed}ms`);
|
||||
console.log(` Error type: ${errorType}`);
|
||||
console.log(` Error message: ${error.message}`);
|
||||
}
|
||||
|
||||
expect(errorType).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('CREL-02: Network latency spikes', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 10000,
|
||||
commandTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Simulate latency spikes
|
||||
const originalSendCommand = smtpClient.sendCommand.bind(smtpClient);
|
||||
let spikeCount = 0;
|
||||
|
||||
smtpClient.sendCommand = async (command: string) => {
|
||||
// Random latency spikes
|
||||
if (Math.random() < 0.2) { // 20% chance of spike
|
||||
spikeCount++;
|
||||
const spikeDelay = 1000 + Math.random() * 3000; // 1-4 second spike
|
||||
console.log(`Latency spike ${spikeCount}: ${spikeDelay.toFixed(0)}ms delay`);
|
||||
await new Promise(resolve => setTimeout(resolve, spikeDelay));
|
||||
}
|
||||
await smtpClient.close();
|
||||
console.log(`Rapid cycle ${i + 1} completed`);
|
||||
|
||||
return originalSendCommand(command);
|
||||
};
|
||||
|
||||
// Send email with potential latency spikes
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Latency Spike Test',
|
||||
text: 'Testing behavior during network latency spikes'
|
||||
});
|
||||
|
||||
console.log('\nSending email with potential latency spikes...');
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const result = await smtpClient.sendMail(email);
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
console.log(`\nEmail sent successfully in ${elapsed}ms`);
|
||||
console.log(`Latency spikes encountered: ${spikeCount}`);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
|
||||
if (spikeCount > 0) {
|
||||
expect(elapsed).toBeGreaterThan(1000); // Should show impact of spikes
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Send failed due to timeout:', error.message);
|
||||
// This is acceptable if spike exceeded timeout
|
||||
// Very short delay
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
console.log('✅ Rapid reconnection handled successfully');
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await testServer.stop();
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user