update
This commit is contained in:
284
test/suite/smtpclient_connection/test.ccm-11.keepalive.ts
Normal file
284
test/suite/smtpclient_connection/test.ccm-11.keepalive.ts
Normal file
@ -0,0 +1,284 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestSmtpServer } from '../../helpers/server.loader.js';
|
||||
import { createSmtpClient } from '../../helpers/smtp.client.js';
|
||||
import * as net from 'net';
|
||||
|
||||
let testServer: any;
|
||||
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestSmtpServer({
|
||||
socketTimeout: 30000 // 30 second timeout for keep-alive tests
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('CCM-11: Basic keep-alive functionality', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
keepAlive: true,
|
||||
keepAliveInterval: 5000, // 5 seconds
|
||||
connectionTimeout: 10000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
// Connect to server
|
||||
const connected = await smtpClient.connect();
|
||||
expect(connected).toBeTruthy();
|
||||
expect(smtpClient.isConnected()).toBeTruthy();
|
||||
|
||||
// Track keep-alive activity
|
||||
let keepAliveCount = 0;
|
||||
let lastActivity = Date.now();
|
||||
|
||||
smtpClient.on('keepalive', () => {
|
||||
keepAliveCount++;
|
||||
const elapsed = Date.now() - lastActivity;
|
||||
console.log(`Keep-alive sent after ${elapsed}ms`);
|
||||
lastActivity = Date.now();
|
||||
});
|
||||
|
||||
// Wait for multiple keep-alive cycles
|
||||
await new Promise(resolve => setTimeout(resolve, 12000)); // Wait 12 seconds
|
||||
|
||||
// Should have sent at least 2 keep-alive messages
|
||||
expect(keepAliveCount).toBeGreaterThanOrEqual(2);
|
||||
|
||||
// Connection should still be alive
|
||||
expect(smtpClient.isConnected()).toBeTruthy();
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CCM-11: Keep-alive with NOOP command', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
keepAlive: true,
|
||||
keepAliveInterval: 3000,
|
||||
connectionTimeout: 10000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
let noopResponses = 0;
|
||||
|
||||
// Send NOOP commands manually to simulate keep-alive
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
try {
|
||||
const response = await smtpClient.sendCommand('NOOP');
|
||||
if (response && response.includes('250')) {
|
||||
noopResponses++;
|
||||
console.log(`NOOP response ${i + 1}: ${response.trim()}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('NOOP error:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
expect(noopResponses).toEqual(3);
|
||||
expect(smtpClient.isConnected()).toBeTruthy();
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CCM-11: Connection idle timeout without keep-alive', async () => {
|
||||
// Create a client without keep-alive
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
keepAlive: false, // Disabled
|
||||
connectionTimeout: 5000,
|
||||
socketTimeout: 5000, // 5 second socket timeout
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
expect(smtpClient.isConnected()).toBeTruthy();
|
||||
|
||||
let disconnected = false;
|
||||
let timeoutError = false;
|
||||
|
||||
smtpClient.on('timeout', () => {
|
||||
timeoutError = true;
|
||||
console.log('Socket timeout detected');
|
||||
});
|
||||
|
||||
smtpClient.on('close', () => {
|
||||
disconnected = true;
|
||||
console.log('Connection closed');
|
||||
});
|
||||
|
||||
smtpClient.on('error', (error: Error) => {
|
||||
console.log('Connection error:', error.message);
|
||||
if (error.message.includes('timeout')) {
|
||||
timeoutError = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for timeout (longer than socket timeout)
|
||||
await new Promise(resolve => setTimeout(resolve, 7000));
|
||||
|
||||
// Without keep-alive, connection might timeout
|
||||
// This depends on server configuration
|
||||
if (disconnected || timeoutError) {
|
||||
console.log('Connection timed out as expected without keep-alive');
|
||||
expect(disconnected || timeoutError).toBeTruthy();
|
||||
} else {
|
||||
// Some servers might not timeout quickly
|
||||
console.log('Server did not timeout connection (may have long timeout setting)');
|
||||
await smtpClient.close();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('CCM-11: Keep-alive during long operations', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
keepAlive: true,
|
||||
keepAliveInterval: 2000,
|
||||
connectionTimeout: 10000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Simulate a long operation
|
||||
console.log('Starting simulated long operation...');
|
||||
|
||||
// Send initial MAIL FROM
|
||||
await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
|
||||
|
||||
// Track keep-alives during operation
|
||||
let keepAliveDuringOperation = 0;
|
||||
|
||||
smtpClient.on('keepalive', () => {
|
||||
keepAliveDuringOperation++;
|
||||
});
|
||||
|
||||
// Simulate processing delay
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
// Continue with RCPT TO
|
||||
await smtpClient.sendCommand('RCPT TO:<recipient@example.com>');
|
||||
|
||||
// More delay
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// Should have sent keep-alives during delays
|
||||
expect(keepAliveDuringOperation).toBeGreaterThan(0);
|
||||
console.log(`Sent ${keepAliveDuringOperation} keep-alives during operation`);
|
||||
|
||||
// Reset the session
|
||||
await smtpClient.sendCommand('RSET');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CCM-11: Keep-alive interval adjustment', async () => {
|
||||
const intervals = [1000, 3000, 5000]; // Different intervals to test
|
||||
|
||||
for (const interval of intervals) {
|
||||
console.log(`\nTesting keep-alive with ${interval}ms interval`);
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
keepAlive: true,
|
||||
keepAliveInterval: interval,
|
||||
connectionTimeout: 10000,
|
||||
debug: false // Less verbose for this test
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
let keepAliveCount = 0;
|
||||
let keepAliveTimes: number[] = [];
|
||||
let lastTime = Date.now();
|
||||
|
||||
smtpClient.on('keepalive', () => {
|
||||
const now = Date.now();
|
||||
const elapsed = now - lastTime;
|
||||
keepAliveTimes.push(elapsed);
|
||||
lastTime = now;
|
||||
keepAliveCount++;
|
||||
});
|
||||
|
||||
// Wait for multiple intervals
|
||||
await new Promise(resolve => setTimeout(resolve, interval * 3.5));
|
||||
|
||||
// Should have sent approximately 3 keep-alives
|
||||
expect(keepAliveCount).toBeGreaterThanOrEqual(2);
|
||||
expect(keepAliveCount).toBeLessThanOrEqual(4);
|
||||
|
||||
// Check interval accuracy (allowing 20% variance)
|
||||
const avgInterval = keepAliveTimes.reduce((a, b) => a + b, 0) / keepAliveTimes.length;
|
||||
expect(avgInterval).toBeGreaterThan(interval * 0.8);
|
||||
expect(avgInterval).toBeLessThan(interval * 1.2);
|
||||
|
||||
console.log(`Sent ${keepAliveCount} keep-alives, avg interval: ${avgInterval.toFixed(0)}ms`);
|
||||
|
||||
await smtpClient.close();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('CCM-11: TCP keep-alive socket options', async () => {
|
||||
// Test low-level TCP keep-alive options
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
socketOptions: {
|
||||
keepAlive: true,
|
||||
keepAliveInitialDelay: 1000
|
||||
},
|
||||
connectionTimeout: 10000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
let socketConfigured = false;
|
||||
|
||||
smtpClient.on('connection', (info: any) => {
|
||||
if (info && info.socket && info.socket instanceof net.Socket) {
|
||||
// Check if keep-alive is enabled at socket level
|
||||
const socket = info.socket as net.Socket;
|
||||
|
||||
// These methods might not be available in all Node versions
|
||||
if (typeof socket.setKeepAlive === 'function') {
|
||||
socket.setKeepAlive(true, 1000);
|
||||
socketConfigured = true;
|
||||
console.log('TCP keep-alive configured at socket level');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Wait a bit to ensure socket options take effect
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
expect(smtpClient.isConnected()).toBeTruthy();
|
||||
|
||||
if (!socketConfigured) {
|
||||
console.log('Socket-level keep-alive configuration not available');
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await testServer.stop();
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
Reference in New Issue
Block a user