dcrouter/test/suite/smtpclient_commands/test.ccmd-09.noop-command.ts
2025-05-25 19:02:18 +00:00

339 lines
10 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
import { createSmtpClient } 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 smtpClient: SmtpClient;
tap.test('setup test SMTP server', async () => {
testServer = await startTestServer({
port: 2549,
tlsEnabled: false,
authRequired: false
});
expect(testServer).toBeTruthy();
expect(testServer.port).toBeGreaterThan(0);
});
tap.test('CCMD-09: Connection keepalive test', async () => {
// NOOP is used internally for keepalive - test that connections remain active
smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 10000,
greetingTimeout: 5000,
socketTimeout: 10000
});
// Send an initial email to establish connection
const email1 = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Initial connection test',
text: 'Testing connection establishment'
});
await smtpClient.sendMail(email1);
console.log('First email sent successfully');
// Wait 5 seconds (connection should stay alive with internal NOOP)
await new Promise(resolve => setTimeout(resolve, 5000));
// Send another email on the same connection
const email2 = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Keepalive test',
text: 'Testing connection after delay'
});
await smtpClient.sendMail(email2);
console.log('Second email sent successfully after 5 second delay');
});
tap.test('CCMD-09: Multiple emails in sequence', async () => {
// Test that client can handle multiple emails without issues
// Internal NOOP commands may be used between transactions
const emails = [];
for (let i = 0; i < 5; i++) {
emails.push(new Email({
from: 'sender@example.com',
to: [`recipient${i}@example.com`],
subject: `Sequential email ${i + 1}`,
text: `This is email number ${i + 1}`
}));
}
console.log('Sending 5 emails in sequence...');
for (let i = 0; i < emails.length; i++) {
await smtpClient.sendMail(emails[i]);
console.log(`Email ${i + 1} sent successfully`);
// Small delay between emails
await new Promise(resolve => setTimeout(resolve, 500));
}
console.log('All emails sent successfully');
});
tap.test('CCMD-09: Rapid email sending', async () => {
// Test rapid email sending without delays
// Internal connection management should handle this properly
const emailCount = 10;
const emails = [];
for (let i = 0; i < emailCount; i++) {
emails.push(new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Rapid email ${i + 1}`,
text: `Rapid fire email number ${i + 1}`
}));
}
console.log(`Sending ${emailCount} emails rapidly...`);
const startTime = Date.now();
// Send all emails as fast as possible
for (const email of emails) {
await smtpClient.sendMail(email);
}
const elapsed = Date.now() - startTime;
console.log(`All ${emailCount} emails sent in ${elapsed}ms`);
console.log(`Average: ${(elapsed / emailCount).toFixed(2)}ms per email`);
});
tap.test('CCMD-09: Long-lived connection test', async () => {
// Test that connection stays alive over extended period
// SmtpClient should use internal keepalive mechanisms
console.log('Testing connection over 10 seconds with periodic emails...');
const testDuration = 10000;
const emailInterval = 2500;
const iterations = Math.floor(testDuration / emailInterval);
for (let i = 0; i < iterations; i++) {
const email = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Keepalive test ${i + 1}`,
text: `Testing connection keepalive - email ${i + 1}`
});
const startTime = Date.now();
await smtpClient.sendMail(email);
const elapsed = Date.now() - startTime;
console.log(`Email ${i + 1} sent in ${elapsed}ms`);
if (i < iterations - 1) {
await new Promise(resolve => setTimeout(resolve, emailInterval));
}
}
console.log('Connection remained stable over 10 seconds');
});
tap.test('CCMD-09: Connection pooling behavior', async () => {
// Test connection pooling with different email patterns
// Internal NOOP may be used to maintain pool connections
const testPatterns = [
{ count: 3, delay: 0, desc: 'Burst of 3 emails' },
{ count: 2, delay: 1000, desc: '2 emails with 1s delay' },
{ count: 1, delay: 3000, desc: '1 email after 3s delay' }
];
for (const pattern of testPatterns) {
console.log(`\nTesting: ${pattern.desc}`);
if (pattern.delay > 0) {
await new Promise(resolve => setTimeout(resolve, pattern.delay));
}
for (let i = 0; i < pattern.count; i++) {
const email = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `${pattern.desc} - Email ${i + 1}`,
text: 'Testing connection pooling behavior'
});
await smtpClient.sendMail(email);
}
console.log(`Completed: ${pattern.desc}`);
}
});
tap.test('CCMD-09: Email sending performance', async () => {
// Measure email sending performance
// Connection management (including internal NOOP) affects timing
const measurements = 20;
const times: number[] = [];
console.log(`Measuring performance over ${measurements} emails...`);
for (let i = 0; i < measurements; i++) {
const email = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Performance test ${i + 1}`,
text: 'Measuring email sending performance'
});
const startTime = Date.now();
await smtpClient.sendMail(email);
const elapsed = Date.now() - startTime;
times.push(elapsed);
}
// Calculate statistics
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
const minTime = Math.min(...times);
const maxTime = Math.max(...times);
// Calculate standard deviation
const variance = times.reduce((sum, time) => sum + Math.pow(time - avgTime, 2), 0) / times.length;
const stdDev = Math.sqrt(variance);
console.log(`\nPerformance analysis (${measurements} emails):`);
console.log(` Average: ${avgTime.toFixed(2)}ms`);
console.log(` Min: ${minTime}ms`);
console.log(` Max: ${maxTime}ms`);
console.log(` Std Dev: ${stdDev.toFixed(2)}ms`);
// First email might be slower due to connection establishment
const avgWithoutFirst = times.slice(1).reduce((a, b) => a + b, 0) / (times.length - 1);
console.log(` Average (excl. first): ${avgWithoutFirst.toFixed(2)}ms`);
// Performance should be reasonable
expect(avgTime).toBeLessThan(200);
});
tap.test('CCMD-09: Email with NOOP in content', async () => {
// Test that NOOP as email content doesn't affect delivery
const email = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Email containing NOOP',
text: `This email contains SMTP commands as content:
NOOP
HELO test
MAIL FROM:<test@example.com>
These should be treated as plain text, not commands.
The word NOOP appears multiple times in this email.
NOOP is used internally by SMTP for keepalive.`
});
await smtpClient.sendMail(email);
console.log('Email with NOOP content sent successfully');
// Send another email to verify connection still works
const email2 = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Follow-up email',
text: 'Verifying connection still works after NOOP content'
});
await smtpClient.sendMail(email2);
console.log('Follow-up email sent successfully');
});
tap.test('CCMD-09: Concurrent email sending', async () => {
// Test concurrent email sending
// Connection pooling and internal management should handle this
const concurrentCount = 5;
const emails = [];
for (let i = 0; i < concurrentCount; i++) {
emails.push(new Email({
from: 'sender@example.com',
to: [`recipient${i}@example.com`],
subject: `Concurrent email ${i + 1}`,
text: `Testing concurrent email sending - message ${i + 1}`
}));
}
console.log(`Sending ${concurrentCount} emails concurrently...`);
const startTime = Date.now();
// Send all emails concurrently
try {
await Promise.all(emails.map(email => smtpClient.sendMail(email)));
const elapsed = Date.now() - startTime;
console.log(`All ${concurrentCount} emails sent concurrently in ${elapsed}ms`);
} catch (error) {
// Concurrent sending might not be supported - that's OK
console.log('Concurrent sending not supported, falling back to sequential');
for (const email of emails) {
await smtpClient.sendMail(email);
}
}
});
tap.test('CCMD-09: Connection recovery test', async () => {
// Test connection recovery and error handling
// SmtpClient should handle connection issues gracefully
// Create a new client with shorter timeouts for testing
const testClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 3000,
socketTimeout: 3000
});
// Send initial email
const email1 = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Connection test 1',
text: 'Testing initial connection'
});
await testClient.sendMail(email1);
console.log('Initial email sent');
// Simulate long delay that might timeout connection
console.log('Waiting 5 seconds to test connection recovery...');
await new Promise(resolve => setTimeout(resolve, 5000));
// Try to send another email - client should recover if needed
const email2 = new Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Connection test 2',
text: 'Testing connection recovery'
});
try {
await testClient.sendMail(email2);
console.log('Email sent successfully after delay - connection recovered');
} catch (error) {
console.log('Connection recovery failed (this might be expected):', error.message);
}
});
tap.test('cleanup test SMTP server', async () => {
if (testServer) {
await stopTestServer(testServer);
}
});
export default tap.start();