769 lines
27 KiB
TypeScript
769 lines
27 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import * as plugins from './plugins.js';
|
|
import { createTestServer } from '../../helpers/server.loader.js';
|
|
import { createSmtpClient } from '../../helpers/smtp.client.js';
|
|
|
|
tap.test('CPERF-06: should implement efficient caching strategies', async (tools) => {
|
|
const testId = 'CPERF-06-caching-strategies';
|
|
console.log(`\n${testId}: Testing caching strategies performance...`);
|
|
|
|
let scenarioCount = 0;
|
|
|
|
// Scenario 1: DNS resolution caching
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing DNS resolution caching`);
|
|
|
|
let dnsLookupCount = 0;
|
|
const dnsCache = new Map<string, { address: string; timestamp: number }>();
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
console.log(' [Server] Client connected');
|
|
socket.write('220 dns-cache.example.com ESMTP\r\n');
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-dns-cache.example.com\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Simulate DNS lookup caching
|
|
const mockDnsLookup = (hostname: string) => {
|
|
const cached = dnsCache.get(hostname);
|
|
const now = Date.now();
|
|
|
|
if (cached && (now - cached.timestamp) < 300000) { // 5 minute cache
|
|
console.log(` [DNS] Cache hit for ${hostname}`);
|
|
return cached.address;
|
|
}
|
|
|
|
dnsLookupCount++;
|
|
console.log(` [DNS] Cache miss for ${hostname} (lookup #${dnsLookupCount})`);
|
|
|
|
const address = testServer.hostname; // Mock resolution
|
|
dnsCache.set(hostname, { address, timestamp: now });
|
|
return address;
|
|
};
|
|
|
|
// Test multiple connections to same host
|
|
const connectionCount = 10;
|
|
console.log(` Creating ${connectionCount} connections to test DNS caching...`);
|
|
|
|
const clients: any[] = [];
|
|
const startTime = Date.now();
|
|
|
|
for (let i = 0; i < connectionCount; i++) {
|
|
// Simulate DNS lookup
|
|
const resolvedHost = mockDnsLookup(testServer.hostname);
|
|
|
|
const client = createSmtpClient({
|
|
host: resolvedHost,
|
|
port: testServer.port,
|
|
secure: false
|
|
});
|
|
clients.push(client);
|
|
}
|
|
|
|
// Send emails through cached connections
|
|
const emails = clients.map((client, i) =>
|
|
new plugins.smartmail.Email({
|
|
from: 'sender@example.com',
|
|
to: [`recipient${i + 1}@example.com`],
|
|
subject: `DNS cache test ${i + 1}`,
|
|
text: `Testing DNS resolution caching - connection ${i + 1}`
|
|
})
|
|
);
|
|
|
|
await Promise.all(emails.map((email, i) => clients[i].sendMail(email)));
|
|
|
|
const totalTime = Date.now() - startTime;
|
|
const cacheHitRate = ((connectionCount - dnsLookupCount) / connectionCount) * 100;
|
|
|
|
console.log(` DNS lookups performed: ${dnsLookupCount}/${connectionCount}`);
|
|
console.log(` Cache hit rate: ${cacheHitRate.toFixed(1)}%`);
|
|
console.log(` Total time: ${totalTime}ms`);
|
|
console.log(` Time per connection: ${(totalTime / connectionCount).toFixed(1)}ms`);
|
|
|
|
// Close clients
|
|
await Promise.all(clients.map(client => {
|
|
if (client.close) {
|
|
return client.close();
|
|
}
|
|
return Promise.resolve();
|
|
}));
|
|
|
|
// DNS caching should reduce lookups
|
|
expect(dnsLookupCount).toBeLessThan(connectionCount);
|
|
expect(cacheHitRate).toBeGreaterThan(50); // At least 50% cache hit rate
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 2: Connection pool caching
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing connection pool caching`);
|
|
|
|
let connectionCount = 0;
|
|
let connectionReuse = 0;
|
|
const connectionPool = new Map<string, { connection: any; lastUsed: number; messageCount: number }>();
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
connectionCount++;
|
|
const connId = connectionCount;
|
|
console.log(` [Server] New connection ${connId} created`);
|
|
socket.write('220 pool-cache.example.com ESMTP\r\n');
|
|
|
|
let messageCount = 0;
|
|
|
|
socket.on('close', () => {
|
|
console.log(` [Server] Connection ${connId} closed after ${messageCount} messages`);
|
|
});
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-pool-cache.example.com\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
messageCount++;
|
|
socket.write(`250 OK: Message ${messageCount} on connection ${connId}\r\n`);
|
|
} else if (command === 'RSET') {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Mock connection pool management
|
|
const getPooledConnection = (key: string) => {
|
|
const cached = connectionPool.get(key);
|
|
const now = Date.now();
|
|
|
|
if (cached && (now - cached.lastUsed) < 60000) { // 1 minute idle timeout
|
|
connectionReuse++;
|
|
cached.lastUsed = now;
|
|
cached.messageCount++;
|
|
console.log(` [Pool] Reusing connection for ${key} (reuse #${connectionReuse})`);
|
|
return cached.connection;
|
|
}
|
|
|
|
console.log(` [Pool] Creating new connection for ${key}`);
|
|
const newConnection = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
pool: true,
|
|
maxConnections: 5,
|
|
maxMessages: 10
|
|
});
|
|
|
|
connectionPool.set(key, {
|
|
connection: newConnection,
|
|
lastUsed: now,
|
|
messageCount: 0
|
|
});
|
|
|
|
return newConnection;
|
|
};
|
|
|
|
// Test connection reuse with same destination
|
|
const destinations = [
|
|
'example.com',
|
|
'example.com', // Same as first (should reuse)
|
|
'example.com', // Same as first (should reuse)
|
|
'another.com',
|
|
'example.com', // Back to first (should reuse)
|
|
'another.com' // Same as fourth (should reuse)
|
|
];
|
|
|
|
console.log(` Sending emails to test connection pool caching...`);
|
|
|
|
for (let i = 0; i < destinations.length; i++) {
|
|
const destination = destinations[i];
|
|
const poolKey = destination;
|
|
|
|
const client = getPooledConnection(poolKey);
|
|
|
|
const email = new plugins.smartmail.Email({
|
|
from: 'sender@example.com',
|
|
to: [`recipient${i + 1}@${destination}`],
|
|
subject: `Pool cache test ${i + 1}`,
|
|
text: `Testing connection pool caching - destination ${destination}`
|
|
});
|
|
|
|
await client.sendMail(email);
|
|
}
|
|
|
|
// Close all pooled connections
|
|
for (const [key, pooled] of connectionPool) {
|
|
if (pooled.connection.close) {
|
|
await pooled.connection.close();
|
|
}
|
|
}
|
|
|
|
const uniqueDestinations = new Set(destinations).size;
|
|
const poolEfficiency = (connectionReuse / destinations.length) * 100;
|
|
|
|
console.log(` Total emails sent: ${destinations.length}`);
|
|
console.log(` Unique destinations: ${uniqueDestinations}`);
|
|
console.log(` New connections: ${connectionCount}`);
|
|
console.log(` Connection reuses: ${connectionReuse}`);
|
|
console.log(` Pool efficiency: ${poolEfficiency.toFixed(1)}%`);
|
|
|
|
// Connection pool should reuse connections efficiently
|
|
expect(connectionCount).toBeLessThanOrEqual(uniqueDestinations + 1);
|
|
expect(connectionReuse).toBeGreaterThan(0);
|
|
expect(poolEfficiency).toBeGreaterThan(30); // At least 30% reuse
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 3: Template and content caching
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing template and content caching`);
|
|
|
|
let templateCompilations = 0;
|
|
let cacheHits = 0;
|
|
const templateCache = new Map<string, { compiled: string; timestamp: number; uses: number }>();
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
socket.write('220 template-cache.example.com ESMTP\r\n');
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-template-cache.example.com\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Mock template compilation and caching
|
|
const compileTemplate = (template: string, data: any) => {
|
|
const cacheKey = template;
|
|
const cached = templateCache.get(cacheKey);
|
|
const now = Date.now();
|
|
|
|
if (cached && (now - cached.timestamp) < 3600000) { // 1 hour cache
|
|
cacheHits++;
|
|
cached.uses++;
|
|
console.log(` [Template] Cache hit for template (use #${cached.uses})`);
|
|
return cached.compiled.replace(/\{\{(\w+)\}\}/g, (match, key) => data[key] || match);
|
|
}
|
|
|
|
templateCompilations++;
|
|
console.log(` [Template] Compiling template (compilation #${templateCompilations})`);
|
|
|
|
// Simulate template compilation overhead
|
|
const compiled = template.replace(/\{\{(\w+)\}\}/g, (match, key) => data[key] || match);
|
|
|
|
templateCache.set(cacheKey, {
|
|
compiled: template, // Store template for reuse
|
|
timestamp: now,
|
|
uses: 1
|
|
});
|
|
|
|
return compiled;
|
|
};
|
|
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false
|
|
});
|
|
|
|
// Test template caching with repeated templates
|
|
const templates = [
|
|
{
|
|
id: 'welcome',
|
|
subject: 'Welcome {{name}}!',
|
|
text: 'Hello {{name}}, welcome to our service!'
|
|
},
|
|
{
|
|
id: 'notification',
|
|
subject: 'Notification for {{name}}',
|
|
text: 'Dear {{name}}, you have a new notification.'
|
|
},
|
|
{
|
|
id: 'welcome', // Repeat of first template
|
|
subject: 'Welcome {{name}}!',
|
|
text: 'Hello {{name}}, welcome to our service!'
|
|
}
|
|
];
|
|
|
|
const users = [
|
|
{ name: 'Alice', email: 'alice@example.com' },
|
|
{ name: 'Bob', email: 'bob@example.com' },
|
|
{ name: 'Charlie', email: 'charlie@example.com' },
|
|
{ name: 'Diana', email: 'diana@example.com' }
|
|
];
|
|
|
|
console.log(' Sending templated emails to test content caching...');
|
|
const startTime = Date.now();
|
|
|
|
for (const user of users) {
|
|
for (const template of templates) {
|
|
const compiledSubject = compileTemplate(template.subject, user);
|
|
const compiledText = compileTemplate(template.text, user);
|
|
|
|
const email = new plugins.smartmail.Email({
|
|
from: 'sender@example.com',
|
|
to: [user.email],
|
|
subject: compiledSubject,
|
|
text: compiledText
|
|
});
|
|
|
|
await smtpClient.sendMail(email);
|
|
}
|
|
}
|
|
|
|
const totalTime = Date.now() - startTime;
|
|
const totalTemplateUses = users.length * templates.length;
|
|
const uniqueTemplates = new Set(templates.map(t => t.id)).size;
|
|
const cacheEfficiency = (cacheHits / (templateCompilations + cacheHits)) * 100;
|
|
|
|
console.log(` Total template uses: ${totalTemplateUses}`);
|
|
console.log(` Unique templates: ${uniqueTemplates}`);
|
|
console.log(` Template compilations: ${templateCompilations}`);
|
|
console.log(` Cache hits: ${cacheHits}`);
|
|
console.log(` Cache efficiency: ${cacheEfficiency.toFixed(1)}%`);
|
|
console.log(` Average time per email: ${(totalTime / totalTemplateUses).toFixed(1)}ms`);
|
|
|
|
// Template caching should reduce compilation overhead
|
|
expect(templateCompilations).toBeLessThan(totalTemplateUses);
|
|
expect(cacheHits).toBeGreaterThan(0);
|
|
expect(cacheEfficiency).toBeGreaterThan(50); // At least 50% cache efficiency
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 4: Message header caching
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing message header caching`);
|
|
|
|
let headerGenerations = 0;
|
|
let headerCacheHits = 0;
|
|
const headerCache = new Map<string, { headers: any; timestamp: number }>();
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
socket.write('220 header-cache.example.com ESMTP\r\n');
|
|
|
|
let messageCount = 0;
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-header-cache.example.com\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
messageCount++;
|
|
socket.write(`250 OK: Message ${messageCount} with cached headers\r\n`);
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Mock header generation and caching
|
|
const generateHeaders = (from: string, subject: string, messageType: string) => {
|
|
const cacheKey = `${from}-${messageType}`;
|
|
const cached = headerCache.get(cacheKey);
|
|
const now = Date.now();
|
|
|
|
if (cached && (now - cached.timestamp) < 1800000) { // 30 minute cache
|
|
headerCacheHits++;
|
|
console.log(` [Headers] Cache hit for ${messageType} headers`);
|
|
return {
|
|
...cached.headers,
|
|
Subject: subject, // Subject is dynamic
|
|
Date: new Date().toISOString(),
|
|
'Message-ID': `<${Date.now()}-${Math.random()}@example.com>`
|
|
};
|
|
}
|
|
|
|
headerGenerations++;
|
|
console.log(` [Headers] Generating ${messageType} headers (generation #${headerGenerations})`);
|
|
|
|
// Simulate header generation overhead
|
|
const headers = {
|
|
From: from,
|
|
Subject: subject,
|
|
Date: new Date().toISOString(),
|
|
'Message-ID': `<${Date.now()}-${Math.random()}@example.com>`,
|
|
'X-Mailer': 'Test Mailer 1.0',
|
|
'MIME-Version': '1.0',
|
|
'Content-Type': messageType === 'html' ? 'text/html; charset=UTF-8' : 'text/plain; charset=UTF-8'
|
|
};
|
|
|
|
// Cache the static parts
|
|
const cacheableHeaders = {
|
|
From: from,
|
|
'X-Mailer': 'Test Mailer 1.0',
|
|
'MIME-Version': '1.0',
|
|
'Content-Type': headers['Content-Type']
|
|
};
|
|
|
|
headerCache.set(cacheKey, {
|
|
headers: cacheableHeaders,
|
|
timestamp: now
|
|
});
|
|
|
|
return headers;
|
|
};
|
|
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false
|
|
});
|
|
|
|
// Test header caching with similar message types
|
|
const messageTypes = ['text', 'html', 'text', 'html', 'text']; // Repeated types
|
|
const sender = 'sender@example.com';
|
|
|
|
console.log(' Sending emails to test header caching...');
|
|
|
|
for (let i = 0; i < messageTypes.length; i++) {
|
|
const messageType = messageTypes[i];
|
|
const headers = generateHeaders(sender, `Test ${i + 1}`, messageType);
|
|
|
|
const email = new plugins.smartmail.Email({
|
|
from: headers.From,
|
|
to: [`recipient${i + 1}@example.com`],
|
|
subject: headers.Subject,
|
|
text: messageType === 'text' ? 'Plain text message' : undefined,
|
|
html: messageType === 'html' ? '<p>HTML message</p>' : undefined,
|
|
headers: {
|
|
'X-Mailer': headers['X-Mailer'],
|
|
'Message-ID': headers['Message-ID']
|
|
}
|
|
});
|
|
|
|
await smtpClient.sendMail(email);
|
|
}
|
|
|
|
const uniqueMessageTypes = new Set(messageTypes).size;
|
|
const headerCacheEfficiency = (headerCacheHits / (headerGenerations + headerCacheHits)) * 100;
|
|
|
|
console.log(` Messages sent: ${messageTypes.length}`);
|
|
console.log(` Unique message types: ${uniqueMessageTypes}`);
|
|
console.log(` Header generations: ${headerGenerations}`);
|
|
console.log(` Header cache hits: ${headerCacheHits}`);
|
|
console.log(` Header cache efficiency: ${headerCacheEfficiency.toFixed(1)}%`);
|
|
|
|
// Header caching should reduce generation overhead
|
|
expect(headerGenerations).toBeLessThan(messageTypes.length);
|
|
expect(headerCacheHits).toBeGreaterThan(0);
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 5: Attachment processing caching
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing attachment processing caching`);
|
|
|
|
let attachmentProcessing = 0;
|
|
let attachmentCacheHits = 0;
|
|
const attachmentCache = new Map<string, { processed: string; timestamp: number; size: number }>();
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
socket.write('220 attachment-cache.example.com ESMTP\r\n');
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-attachment-cache.example.com\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Mock attachment processing with caching
|
|
const processAttachment = (filename: string, content: Buffer) => {
|
|
const contentHash = require('crypto').createHash('md5').update(content).digest('hex');
|
|
const cacheKey = `${filename}-${contentHash}`;
|
|
const cached = attachmentCache.get(cacheKey);
|
|
const now = Date.now();
|
|
|
|
if (cached && (now - cached.timestamp) < 7200000) { // 2 hour cache
|
|
attachmentCacheHits++;
|
|
console.log(` [Attachment] Cache hit for ${filename}`);
|
|
return cached.processed;
|
|
}
|
|
|
|
attachmentProcessing++;
|
|
console.log(` [Attachment] Processing ${filename} (processing #${attachmentProcessing})`);
|
|
|
|
// Simulate attachment processing (base64 encoding)
|
|
const processed = content.toString('base64');
|
|
|
|
attachmentCache.set(cacheKey, {
|
|
processed,
|
|
timestamp: now,
|
|
size: content.length
|
|
});
|
|
|
|
return processed;
|
|
};
|
|
|
|
const smtpClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false
|
|
});
|
|
|
|
// Create reusable attachment content
|
|
const commonAttachment = Buffer.from('This is a common attachment used in multiple emails.');
|
|
const uniqueAttachment = Buffer.from('This is a unique attachment.');
|
|
|
|
const emails = [
|
|
{
|
|
subject: 'Email 1 with common attachment',
|
|
attachments: [{ filename: 'common.txt', content: commonAttachment }]
|
|
},
|
|
{
|
|
subject: 'Email 2 with unique attachment',
|
|
attachments: [{ filename: 'unique.txt', content: uniqueAttachment }]
|
|
},
|
|
{
|
|
subject: 'Email 3 with common attachment again',
|
|
attachments: [{ filename: 'common.txt', content: commonAttachment }] // Same as first
|
|
},
|
|
{
|
|
subject: 'Email 4 with both attachments',
|
|
attachments: [
|
|
{ filename: 'common.txt', content: commonAttachment },
|
|
{ filename: 'unique.txt', content: uniqueAttachment }
|
|
]
|
|
}
|
|
];
|
|
|
|
console.log(' Sending emails with attachments to test caching...');
|
|
|
|
for (let i = 0; i < emails.length; i++) {
|
|
const emailData = emails[i];
|
|
|
|
// Process attachments (with caching)
|
|
const processedAttachments = emailData.attachments.map(att => ({
|
|
filename: att.filename,
|
|
content: processAttachment(att.filename, att.content)
|
|
}));
|
|
|
|
const email = new plugins.smartmail.Email({
|
|
from: 'sender@example.com',
|
|
to: [`recipient${i + 1}@example.com`],
|
|
subject: emailData.subject,
|
|
text: 'Email with attachments for caching test',
|
|
attachments: processedAttachments.map(att => ({
|
|
filename: att.filename,
|
|
content: att.content,
|
|
encoding: 'base64' as const
|
|
}))
|
|
});
|
|
|
|
await smtpClient.sendMail(email);
|
|
}
|
|
|
|
const totalAttachments = emails.reduce((sum, email) => sum + email.attachments.length, 0);
|
|
const attachmentCacheEfficiency = (attachmentCacheHits / (attachmentProcessing + attachmentCacheHits)) * 100;
|
|
|
|
console.log(` Total attachments sent: ${totalAttachments}`);
|
|
console.log(` Attachment processing operations: ${attachmentProcessing}`);
|
|
console.log(` Attachment cache hits: ${attachmentCacheHits}`);
|
|
console.log(` Attachment cache efficiency: ${attachmentCacheEfficiency.toFixed(1)}%`);
|
|
|
|
// Attachment caching should reduce processing overhead
|
|
expect(attachmentProcessing).toBeLessThan(totalAttachments);
|
|
expect(attachmentCacheHits).toBeGreaterThan(0);
|
|
expect(attachmentCacheEfficiency).toBeGreaterThan(30); // At least 30% cache efficiency
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
// Scenario 6: Overall caching performance impact
|
|
await (async () => {
|
|
scenarioCount++;
|
|
console.log(`\nScenario ${scenarioCount}: Testing overall caching performance impact`);
|
|
|
|
const testServer = await createTestServer({
|
|
onConnection: async (socket) => {
|
|
socket.write('220 performance-cache.example.com ESMTP\r\n');
|
|
|
|
socket.on('data', (data) => {
|
|
const command = data.toString().trim();
|
|
|
|
if (command.startsWith('EHLO')) {
|
|
socket.write('250-performance-cache.example.com\r\n');
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('MAIL FROM:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command.startsWith('RCPT TO:')) {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'DATA') {
|
|
socket.write('354 Start mail input\r\n');
|
|
} else if (command === '.') {
|
|
socket.write('250 OK\r\n');
|
|
} else if (command === 'QUIT') {
|
|
socket.write('221 Bye\r\n');
|
|
socket.end();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Test performance with caching enabled vs disabled
|
|
const emailCount = 20;
|
|
|
|
// Simulate no caching (always process)
|
|
console.log(' Testing performance without caching...');
|
|
const noCacheStart = Date.now();
|
|
let noCacheOperations = 0;
|
|
|
|
const noCacheClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false
|
|
});
|
|
|
|
for (let i = 0; i < emailCount; i++) {
|
|
// Simulate processing overhead for each email
|
|
noCacheOperations += 3; // DNS lookup, header generation, template processing
|
|
|
|
const email = new plugins.smartmail.Email({
|
|
from: 'sender@example.com',
|
|
to: [`nocache${i + 1}@example.com`],
|
|
subject: `No cache test ${i + 1}`,
|
|
text: `Testing performance without caching - email ${i + 1}`
|
|
});
|
|
|
|
await noCacheClient.sendMail(email);
|
|
}
|
|
|
|
const noCacheTime = Date.now() - noCacheStart;
|
|
|
|
// Simulate with caching (reduced processing)
|
|
console.log(' Testing performance with caching...');
|
|
const cacheStart = Date.now();
|
|
let cacheOperations = 5; // Initial setup, then reuse
|
|
|
|
const cacheClient = createSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
pool: true,
|
|
maxConnections: 2
|
|
});
|
|
|
|
for (let i = 0; i < emailCount; i++) {
|
|
// Simulate reduced operations due to caching
|
|
if (i < 5) {
|
|
cacheOperations += 1; // Some cache misses initially
|
|
}
|
|
// Most operations are cache hits (no additional operations)
|
|
|
|
const email = new plugins.smartmail.Email({
|
|
from: 'sender@example.com',
|
|
to: [`cache${i + 1}@example.com`],
|
|
subject: `Cache test ${i + 1}`,
|
|
text: `Testing performance with caching - email ${i + 1}`
|
|
});
|
|
|
|
await cacheClient.sendMail(email);
|
|
}
|
|
|
|
await cacheClient.close();
|
|
const cacheTime = Date.now() - cacheStart;
|
|
|
|
// Calculate performance improvements
|
|
const timeImprovement = ((noCacheTime - cacheTime) / noCacheTime) * 100;
|
|
const operationReduction = ((noCacheOperations - cacheOperations) / noCacheOperations) * 100;
|
|
const throughputImprovement = (emailCount / cacheTime) / (emailCount / noCacheTime);
|
|
|
|
console.log(` Performance comparison (${emailCount} emails):`);
|
|
console.log(` Without caching: ${noCacheTime}ms, ${noCacheOperations} operations`);
|
|
console.log(` With caching: ${cacheTime}ms, ${cacheOperations} operations`);
|
|
console.log(` Time improvement: ${timeImprovement.toFixed(1)}%`);
|
|
console.log(` Operation reduction: ${operationReduction.toFixed(1)}%`);
|
|
console.log(` Throughput improvement: ${throughputImprovement.toFixed(2)}x`);
|
|
|
|
// Caching should improve performance
|
|
expect(cacheTime).toBeLessThan(noCacheTime);
|
|
expect(cacheOperations).toBeLessThan(noCacheOperations);
|
|
expect(timeImprovement).toBeGreaterThan(10); // At least 10% improvement
|
|
expect(throughputImprovement).toBeGreaterThan(1.1); // At least 10% better throughput
|
|
|
|
await testServer.server.close();
|
|
})();
|
|
|
|
console.log(`\n${testId}: All ${scenarioCount} caching strategy scenarios tested ✓`);
|
|
}); |