315 lines
9.7 KiB
TypeScript
315 lines
9.7 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
|
import { createBulkSmtpClient, createPooledSmtpClient } 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 bulkClient: SmtpClient;
|
|
|
|
tap.test('setup - start SMTP server for bulk sending tests', async () => {
|
|
testServer = await startTestServer({
|
|
port: 2580,
|
|
tlsEnabled: false,
|
|
authRequired: false,
|
|
maxConnections: 20,
|
|
size: 5 * 1024 * 1024 // 5MB per message
|
|
});
|
|
|
|
expect(testServer.port).toEqual(2580);
|
|
});
|
|
|
|
tap.test('CPERF-01: Bulk Sending - should send 100 emails efficiently', async (tools) => {
|
|
tools.timeout(60000); // 60 second timeout for bulk test
|
|
|
|
bulkClient = createBulkSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
debug: false // Disable debug for performance
|
|
});
|
|
|
|
const emailCount = 100;
|
|
const startTime = Date.now();
|
|
|
|
// Create batch of emails
|
|
const emails = [];
|
|
for (let i = 0; i < emailCount; i++) {
|
|
emails.push(new Email({
|
|
from: 'bulk-sender@example.com',
|
|
to: `recipient-${i}@example.com`,
|
|
subject: `Bulk Email ${i + 1}`,
|
|
text: `This is bulk email number ${i + 1} of ${emailCount}`,
|
|
html: `<p>This is <strong>bulk email</strong> number ${i + 1} of ${emailCount}</p>`
|
|
}));
|
|
}
|
|
|
|
// Send all emails
|
|
const results = await Promise.all(
|
|
emails.map(email => bulkClient.sendMail(email))
|
|
);
|
|
|
|
const duration = Date.now() - startTime;
|
|
const successCount = results.filter(r => r.success).length;
|
|
|
|
expect(successCount).toEqual(emailCount);
|
|
|
|
const rate = (emailCount / (duration / 1000)).toFixed(2);
|
|
console.log(`✅ Sent ${emailCount} emails in ${duration}ms (${rate} emails/sec)`);
|
|
|
|
// Performance expectations
|
|
expect(duration).toBeLessThan(30000); // Should complete within 30 seconds
|
|
expect(parseFloat(rate)).toBeGreaterThan(3); // At least 3 emails/sec
|
|
});
|
|
|
|
tap.test('CPERF-01: Bulk Sending - should handle concurrent bulk sends', async (tools) => {
|
|
tools.timeout(30000);
|
|
|
|
const concurrentBatches = 5;
|
|
const emailsPerBatch = 20;
|
|
const startTime = Date.now();
|
|
|
|
// Create multiple batches
|
|
const batches = [];
|
|
for (let batch = 0; batch < concurrentBatches; batch++) {
|
|
const batchEmails = [];
|
|
for (let i = 0; i < emailsPerBatch; i++) {
|
|
batchEmails.push(new Email({
|
|
from: 'batch-sender@example.com',
|
|
to: `batch${batch}-recipient${i}@example.com`,
|
|
subject: `Batch ${batch} Email ${i}`,
|
|
text: `Concurrent batch ${batch}, email ${i}`
|
|
}));
|
|
}
|
|
batches.push(batchEmails);
|
|
}
|
|
|
|
// Send all batches concurrently
|
|
const batchResults = await Promise.all(
|
|
batches.map(batchEmails =>
|
|
Promise.all(batchEmails.map(email => bulkClient.sendMail(email)))
|
|
)
|
|
);
|
|
|
|
const duration = Date.now() - startTime;
|
|
const totalEmails = concurrentBatches * emailsPerBatch;
|
|
const successCount = batchResults.flat().filter(r => r.success).length;
|
|
|
|
expect(successCount).toEqual(totalEmails);
|
|
|
|
const rate = (totalEmails / (duration / 1000)).toFixed(2);
|
|
console.log(`✅ Sent ${totalEmails} emails in ${concurrentBatches} concurrent batches`);
|
|
console.log(` Duration: ${duration}ms (${rate} emails/sec)`);
|
|
|
|
// Check pool utilization
|
|
const poolStatus = bulkClient.getPoolStatus();
|
|
console.log('📊 Pool status during bulk send:', poolStatus);
|
|
});
|
|
|
|
tap.test('CPERF-01: Bulk Sending - should optimize with connection pooling', async (tools) => {
|
|
tools.timeout(30000);
|
|
|
|
// Compare pooled vs non-pooled performance
|
|
const testEmails = 50;
|
|
|
|
// Test 1: With pooling
|
|
const pooledClient = createPooledSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
maxConnections: 5,
|
|
debug: false
|
|
});
|
|
|
|
const pooledStart = Date.now();
|
|
const pooledPromises = [];
|
|
|
|
for (let i = 0; i < testEmails; i++) {
|
|
const email = new Email({
|
|
from: 'pooled@example.com',
|
|
to: `recipient${i}@example.com`,
|
|
subject: `Pooled Email ${i}`,
|
|
text: 'Testing pooled performance'
|
|
});
|
|
pooledPromises.push(pooledClient.sendMail(email));
|
|
}
|
|
|
|
await Promise.all(pooledPromises);
|
|
const pooledDuration = Date.now() - pooledStart;
|
|
const pooledRate = (testEmails / (pooledDuration / 1000)).toFixed(2);
|
|
|
|
await pooledClient.close();
|
|
|
|
console.log(`✅ Pooled client: ${testEmails} emails in ${pooledDuration}ms (${pooledRate} emails/sec)`);
|
|
|
|
// Pooled should be significantly faster
|
|
expect(parseFloat(pooledRate)).toBeGreaterThan(2);
|
|
});
|
|
|
|
tap.test('CPERF-01: Bulk Sending - should handle large bulk emails', async (tools) => {
|
|
tools.timeout(60000);
|
|
|
|
// Create emails with attachments
|
|
const largeEmailCount = 20;
|
|
const attachmentSize = 100 * 1024; // 100KB attachment
|
|
const attachmentData = Buffer.alloc(attachmentSize);
|
|
|
|
// Fill with random data
|
|
for (let i = 0; i < attachmentSize; i++) {
|
|
attachmentData[i] = Math.floor(Math.random() * 256);
|
|
}
|
|
|
|
const startTime = Date.now();
|
|
const promises = [];
|
|
|
|
for (let i = 0; i < largeEmailCount; i++) {
|
|
const email = new Email({
|
|
from: 'bulk-sender@example.com',
|
|
to: `recipient${i}@example.com`,
|
|
subject: `Large Bulk Email ${i}`,
|
|
text: 'This email contains an attachment',
|
|
attachments: [{
|
|
filename: `attachment-${i}.dat`,
|
|
content: attachmentData,
|
|
contentType: 'application/octet-stream'
|
|
}]
|
|
});
|
|
promises.push(bulkClient.sendMail(email));
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const duration = Date.now() - startTime;
|
|
const successCount = results.filter(r => r.success).length;
|
|
|
|
expect(successCount).toEqual(largeEmailCount);
|
|
|
|
const totalSize = largeEmailCount * attachmentSize;
|
|
const throughput = (totalSize / 1024 / 1024 / (duration / 1000)).toFixed(2);
|
|
|
|
console.log(`✅ Sent ${largeEmailCount} emails with attachments in ${duration}ms`);
|
|
console.log(` Total data: ${(totalSize / 1024 / 1024).toFixed(2)}MB`);
|
|
console.log(` Throughput: ${throughput} MB/s`);
|
|
});
|
|
|
|
tap.test('CPERF-01: Bulk Sending - should maintain performance under sustained load', async (tools) => {
|
|
tools.timeout(120000); // 2 minutes
|
|
|
|
const sustainedDuration = 30000; // 30 seconds
|
|
const startTime = Date.now();
|
|
let emailsSent = 0;
|
|
let errors = 0;
|
|
|
|
console.log('📊 Starting sustained load test...');
|
|
|
|
// Send emails continuously for duration
|
|
while (Date.now() - startTime < sustainedDuration) {
|
|
const email = new Email({
|
|
from: 'sustained@example.com',
|
|
to: 'recipient@example.com',
|
|
subject: `Sustained Load Email ${emailsSent + 1}`,
|
|
text: `Email sent at ${new Date().toISOString()}`
|
|
});
|
|
|
|
try {
|
|
const result = await bulkClient.sendMail(email);
|
|
if (result.success) {
|
|
emailsSent++;
|
|
} else {
|
|
errors++;
|
|
}
|
|
} catch (error) {
|
|
errors++;
|
|
}
|
|
|
|
// Log progress every 10 emails
|
|
if (emailsSent % 10 === 0 && emailsSent > 0) {
|
|
const elapsed = Date.now() - startTime;
|
|
const rate = (emailsSent / (elapsed / 1000)).toFixed(2);
|
|
console.log(` Progress: ${emailsSent} emails, ${rate} emails/sec`);
|
|
}
|
|
}
|
|
|
|
const totalDuration = Date.now() - startTime;
|
|
const avgRate = (emailsSent / (totalDuration / 1000)).toFixed(2);
|
|
|
|
console.log(`✅ Sustained load test completed:`);
|
|
console.log(` Duration: ${totalDuration}ms`);
|
|
console.log(` Emails sent: ${emailsSent}`);
|
|
console.log(` Errors: ${errors}`);
|
|
console.log(` Average rate: ${avgRate} emails/sec`);
|
|
|
|
expect(emailsSent).toBeGreaterThan(50); // Should send many emails
|
|
expect(errors).toBeLessThan(emailsSent * 0.05); // Less than 5% error rate
|
|
});
|
|
|
|
tap.test('CPERF-01: Bulk Sending - should track performance metrics', async () => {
|
|
const metricsClient = createBulkSmtpClient({
|
|
host: testServer.hostname,
|
|
port: testServer.port,
|
|
secure: false,
|
|
debug: false
|
|
});
|
|
|
|
const metrics = {
|
|
sent: 0,
|
|
failed: 0,
|
|
totalTime: 0,
|
|
minTime: Infinity,
|
|
maxTime: 0
|
|
};
|
|
|
|
// Send emails and collect metrics
|
|
for (let i = 0; i < 20; i++) {
|
|
const email = new Email({
|
|
from: 'metrics@example.com',
|
|
to: `recipient${i}@example.com`,
|
|
subject: `Metrics Test ${i}`,
|
|
text: 'Collecting performance metrics'
|
|
});
|
|
|
|
const sendStart = Date.now();
|
|
try {
|
|
const result = await metricsClient.sendMail(email);
|
|
const sendTime = Date.now() - sendStart;
|
|
|
|
if (result.success) {
|
|
metrics.sent++;
|
|
metrics.totalTime += sendTime;
|
|
metrics.minTime = Math.min(metrics.minTime, sendTime);
|
|
metrics.maxTime = Math.max(metrics.maxTime, sendTime);
|
|
} else {
|
|
metrics.failed++;
|
|
}
|
|
} catch (error) {
|
|
metrics.failed++;
|
|
}
|
|
}
|
|
|
|
const avgTime = metrics.totalTime / metrics.sent;
|
|
|
|
console.log('📊 Performance metrics:');
|
|
console.log(` Sent: ${metrics.sent}`);
|
|
console.log(` Failed: ${metrics.failed}`);
|
|
console.log(` Avg time: ${avgTime.toFixed(2)}ms`);
|
|
console.log(` Min time: ${metrics.minTime}ms`);
|
|
console.log(` Max time: ${metrics.maxTime}ms`);
|
|
|
|
await metricsClient.close();
|
|
|
|
expect(metrics.sent).toBeGreaterThan(0);
|
|
expect(avgTime).toBeLessThan(5000); // Average should be under 5 seconds
|
|
});
|
|
|
|
tap.test('cleanup - close bulk client', async () => {
|
|
if (bulkClient && bulkClient.isConnected()) {
|
|
const finalStatus = bulkClient.getPoolStatus();
|
|
console.log('📊 Final pool status:', finalStatus);
|
|
await bulkClient.close();
|
|
}
|
|
});
|
|
|
|
tap.test('cleanup - stop SMTP server', async () => {
|
|
await stopTestServer(testServer);
|
|
});
|
|
|
|
export default tap.start(); |