feat(storage): add comprehensive tests for StorageManager with memory, filesystem, and custom function backends
feat(email): implement EmailSendJob class for robust email delivery with retry logic and MX record resolution feat(mail): restructure mail module exports for simplified access to core and delivery functionalities
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.ts';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.ts';
|
||||
import * as net from 'net';
|
||||
|
||||
let testServer: ITestServer;
|
||||
|
||||
tap.test('setup - start SMTP server for connection pool tests', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2583,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
|
||||
expect(testServer.port).toEqual(2583);
|
||||
});
|
||||
|
||||
tap.test('CERR-09: Connection pool with concurrent sends', async () => {
|
||||
// Test basic connection pooling functionality
|
||||
const pooledClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
pool: true,
|
||||
maxConnections: 2,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
console.log('Testing connection pool with concurrent sends...');
|
||||
|
||||
// Send multiple messages concurrently
|
||||
const emails = [
|
||||
new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient1@example.com',
|
||||
subject: 'Pool test 1',
|
||||
text: 'Testing connection pool'
|
||||
}),
|
||||
new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient2@example.com',
|
||||
subject: 'Pool test 2',
|
||||
text: 'Testing connection pool'
|
||||
}),
|
||||
new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient3@example.com',
|
||||
subject: 'Pool test 3',
|
||||
text: 'Testing connection pool'
|
||||
})
|
||||
];
|
||||
|
||||
const results = await Promise.all(
|
||||
emails.map(email => pooledClient.sendMail(email))
|
||||
);
|
||||
|
||||
const successful = results.filter(r => r.success).length;
|
||||
|
||||
console.log(`✅ Sent ${successful} messages using connection pool`);
|
||||
expect(successful).toBeGreaterThan(0);
|
||||
|
||||
await pooledClient.close();
|
||||
});
|
||||
|
||||
tap.test('CERR-09: Connection pool with server limit', async () => {
|
||||
// Create server that limits concurrent connections
|
||||
let activeConnections = 0;
|
||||
const maxServerConnections = 1;
|
||||
|
||||
const limitedServer = net.createServer((socket) => {
|
||||
activeConnections++;
|
||||
|
||||
if (activeConnections > maxServerConnections) {
|
||||
socket.write('421 4.7.0 Too many connections\r\n');
|
||||
socket.end();
|
||||
activeConnections--;
|
||||
return;
|
||||
}
|
||||
|
||||
socket.write('220 Limited Server\r\n');
|
||||
|
||||
let buffer = '';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
buffer += data.toString();
|
||||
|
||||
let lines = buffer.split('\r\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
const command = line.trim();
|
||||
if (!command) continue;
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
} else {
|
||||
socket.write('250 OK\r\n');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
activeConnections--;
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
limitedServer.listen(2584, () => resolve());
|
||||
});
|
||||
|
||||
const pooledClient = await createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2584,
|
||||
secure: false,
|
||||
pool: true,
|
||||
maxConnections: 3, // Client wants 3 but server only allows 1
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Try concurrent connections
|
||||
const results = await Promise.all([
|
||||
pooledClient.verify(),
|
||||
pooledClient.verify(),
|
||||
pooledClient.verify()
|
||||
]);
|
||||
|
||||
const successful = results.filter(r => r === true).length;
|
||||
|
||||
console.log(`✅ ${successful} connections succeeded with server limit`);
|
||||
expect(successful).toBeGreaterThan(0);
|
||||
|
||||
await pooledClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
limitedServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-09: Connection pool recovery after error', async () => {
|
||||
// Create server that fails sometimes
|
||||
let requestCount = 0;
|
||||
|
||||
const flakyServer = net.createServer((socket) => {
|
||||
requestCount++;
|
||||
|
||||
// Fail every 3rd connection
|
||||
if (requestCount % 3 === 0) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
socket.write('220 Flaky Server\r\n');
|
||||
|
||||
let buffer = '';
|
||||
let inData = false;
|
||||
|
||||
socket.on('data', (data) => {
|
||||
buffer += data.toString();
|
||||
|
||||
let lines = buffer.split('\r\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
const command = line.trim();
|
||||
if (!command) continue;
|
||||
|
||||
if (inData) {
|
||||
if (command === '.') {
|
||||
inData = false;
|
||||
socket.write('250 OK\r\n');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
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 Send data\r\n');
|
||||
inData = true;
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
flakyServer.listen(2585, () => resolve());
|
||||
});
|
||||
|
||||
const pooledClient = await createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2585,
|
||||
secure: false,
|
||||
pool: true,
|
||||
maxConnections: 2,
|
||||
connectionTimeout: 5000
|
||||
});
|
||||
|
||||
// Send multiple messages to test recovery
|
||||
const results = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: `Recovery test ${i}`,
|
||||
text: 'Testing pool recovery'
|
||||
});
|
||||
|
||||
const result = await pooledClient.sendMail(email);
|
||||
results.push(result.success);
|
||||
console.log(`Message ${i}: ${result.success ? 'Success' : 'Failed'}`);
|
||||
}
|
||||
|
||||
const successful = results.filter(r => r === true).length;
|
||||
|
||||
console.log(`✅ Pool recovered from errors: ${successful}/5 succeeded`);
|
||||
expect(successful).toBeGreaterThan(2);
|
||||
|
||||
await pooledClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
flakyServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-09: Connection pool timeout handling', async () => {
|
||||
// Create very slow server
|
||||
const slowServer = net.createServer((socket) => {
|
||||
// Wait 2 seconds before sending greeting
|
||||
setTimeout(() => {
|
||||
socket.write('220 Very Slow Server\r\n');
|
||||
}, 2000);
|
||||
|
||||
socket.on('data', () => {
|
||||
// Don't respond to any commands
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
slowServer.listen(2586, () => resolve());
|
||||
});
|
||||
|
||||
const pooledClient = await createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: 2586,
|
||||
secure: false,
|
||||
pool: true,
|
||||
connectionTimeout: 1000 // 1 second timeout
|
||||
});
|
||||
|
||||
const result = await pooledClient.verify();
|
||||
|
||||
expect(result).toBeFalse();
|
||||
console.log('✅ Connection pool handled timeout correctly');
|
||||
|
||||
await pooledClient.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
slowServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('CERR-09: Normal pooled operation', async () => {
|
||||
// Test successful pooled operation
|
||||
const pooledClient = await createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
pool: true,
|
||||
maxConnections: 2
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Pool Test',
|
||||
text: 'Testing normal pooled operation'
|
||||
});
|
||||
|
||||
const result = await pooledClient.sendMail(email);
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Normal pooled email sent successfully');
|
||||
|
||||
await pooledClient.close();
|
||||
});
|
||||
|
||||
tap.test('cleanup - stop SMTP server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user