339 lines
10 KiB
TypeScript
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(); |