dcrouter/test/suite/smtpclient_commands/test.ccmd-06.command-pipelining.ts
2025-05-24 16:19:19 +00:00

328 lines
9.4 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { startTestSmtpServer } from '../../helpers/server.loader.js';
import { createSmtpClient } from '../../helpers/smtp.client.js';
let testServer: any;
tap.test('setup test SMTP server', async () => {
testServer = await startTestSmtpServer({
features: ['PIPELINING'] // Ensure server advertises PIPELINING
});
expect(testServer).toBeTruthy();
expect(testServer.port).toBeGreaterThan(0);
});
tap.test('CCMD-06: Check PIPELINING capability', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
// Send EHLO to get capabilities
const ehloResponse = await smtpClient.sendCommand('EHLO testclient.example.com');
expect(ehloResponse).toInclude('250');
// Check if PIPELINING is advertised
const supportsPipelining = ehloResponse.includes('PIPELINING');
console.log(`Server supports PIPELINING: ${supportsPipelining}`);
if (supportsPipelining) {
expect(ehloResponse).toInclude('PIPELINING');
}
await smtpClient.close();
});
tap.test('CCMD-06: Basic command pipelining', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
enablePipelining: true,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
// Send EHLO first
await smtpClient.sendCommand('EHLO testclient.example.com');
// Pipeline multiple commands
console.log('Sending pipelined commands...');
const startTime = Date.now();
// Send commands without waiting for responses
const promises = [
smtpClient.sendCommand('MAIL FROM:<sender@example.com>'),
smtpClient.sendCommand('RCPT TO:<recipient1@example.com>'),
smtpClient.sendCommand('RCPT TO:<recipient2@example.com>')
];
// Wait for all responses
const responses = await Promise.all(promises);
const elapsed = Date.now() - startTime;
console.log(`Pipelined commands completed in ${elapsed}ms`);
// Verify all responses are successful
responses.forEach((response, index) => {
expect(response).toInclude('250');
console.log(`Response ${index + 1}: ${response.trim()}`);
});
// Reset for cleanup
await smtpClient.sendCommand('RSET');
await smtpClient.close();
});
tap.test('CCMD-06: Pipelining with DATA command', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
enablePipelining: true,
connectionTimeout: 10000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Pipeline commands up to DATA
console.log('Pipelining commands before DATA...');
const setupPromises = [
smtpClient.sendCommand('MAIL FROM:<sender@example.com>'),
smtpClient.sendCommand('RCPT TO:<recipient@example.com>')
];
const setupResponses = await Promise.all(setupPromises);
setupResponses.forEach(response => {
expect(response).toInclude('250');
});
// DATA command should not be pipelined
const dataResponse = await smtpClient.sendCommand('DATA');
expect(dataResponse).toInclude('354');
// Send message data
const messageData = [
'Subject: Test Pipelining',
'From: sender@example.com',
'To: recipient@example.com',
'',
'This is a test message sent with pipelining.',
'.'
].join('\r\n');
const messageResponse = await smtpClient.sendCommand(messageData);
expect(messageResponse).toInclude('250');
await smtpClient.close();
});
tap.test('CCMD-06: Pipelining error handling', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
enablePipelining: true,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Pipeline commands with an invalid one
console.log('Testing pipelining with invalid command...');
const mixedPromises = [
smtpClient.sendCommand('MAIL FROM:<sender@example.com>'),
smtpClient.sendCommand('RCPT TO:<invalid-email>'), // Invalid format
smtpClient.sendCommand('RCPT TO:<valid@example.com>')
];
const responses = await Promise.allSettled(mixedPromises);
// Check responses
responses.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Command ${index + 1} response: ${result.value.trim()}`);
if (index === 1) {
// Invalid email might get rejected
expect(result.value).toMatch(/[45]\d\d/);
}
} else {
console.log(`Command ${index + 1} failed: ${result.reason}`);
}
});
// Reset
await smtpClient.sendCommand('RSET');
await smtpClient.close();
});
tap.test('CCMD-06: Pipelining performance comparison', async () => {
// Test without pipelining
const clientNoPipeline = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
enablePipelining: false,
connectionTimeout: 10000,
debug: false
});
await clientNoPipeline.connect();
await clientNoPipeline.sendCommand('EHLO testclient.example.com');
const startNoPipeline = Date.now();
// Send commands sequentially
await clientNoPipeline.sendCommand('MAIL FROM:<sender@example.com>');
await clientNoPipeline.sendCommand('RCPT TO:<recipient1@example.com>');
await clientNoPipeline.sendCommand('RCPT TO:<recipient2@example.com>');
await clientNoPipeline.sendCommand('RCPT TO:<recipient3@example.com>');
await clientNoPipeline.sendCommand('RSET');
const timeNoPipeline = Date.now() - startNoPipeline;
await clientNoPipeline.close();
// Test with pipelining
const clientPipeline = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
enablePipelining: true,
connectionTimeout: 10000,
debug: false
});
await clientPipeline.connect();
await clientPipeline.sendCommand('EHLO testclient.example.com');
const startPipeline = Date.now();
// Send commands pipelined
await Promise.all([
clientPipeline.sendCommand('MAIL FROM:<sender@example.com>'),
clientPipeline.sendCommand('RCPT TO:<recipient1@example.com>'),
clientPipeline.sendCommand('RCPT TO:<recipient2@example.com>'),
clientPipeline.sendCommand('RCPT TO:<recipient3@example.com>'),
clientPipeline.sendCommand('RSET')
]);
const timePipeline = Date.now() - startPipeline;
await clientPipeline.close();
console.log(`Sequential: ${timeNoPipeline}ms, Pipelined: ${timePipeline}ms`);
console.log(`Speedup: ${(timeNoPipeline / timePipeline).toFixed(2)}x`);
// Pipelining should be faster (but might not be in local testing)
expect(timePipeline).toBeLessThanOrEqual(timeNoPipeline * 1.1); // Allow 10% margin
});
tap.test('CCMD-06: Pipelining with multiple recipients', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
enablePipelining: true,
connectionTimeout: 10000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Create many recipients
const recipientCount = 10;
const recipients = Array.from({ length: recipientCount },
(_, i) => `recipient${i + 1}@example.com`
);
console.log(`Pipelining ${recipientCount} recipients...`);
// Pipeline MAIL FROM and all RCPT TO commands
const commands = [
smtpClient.sendCommand('MAIL FROM:<sender@example.com>'),
...recipients.map(rcpt => smtpClient.sendCommand(`RCPT TO:<${rcpt}>`))
];
const startTime = Date.now();
const responses = await Promise.all(commands);
const elapsed = Date.now() - startTime;
console.log(`Sent ${commands.length} pipelined commands in ${elapsed}ms`);
// Verify all succeeded
responses.forEach((response, index) => {
expect(response).toInclude('250');
});
// Calculate average time per command
const avgTime = elapsed / commands.length;
console.log(`Average time per command: ${avgTime.toFixed(2)}ms`);
await smtpClient.sendCommand('RSET');
await smtpClient.close();
});
tap.test('CCMD-06: Pipelining limits and buffering', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
enablePipelining: true,
pipelineMaxCommands: 5, // Limit pipeline size
connectionTimeout: 10000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Try to pipeline more than the limit
const commandCount = 8;
const commands = [
smtpClient.sendCommand('MAIL FROM:<sender@example.com>'),
...Array.from({ length: commandCount - 1 }, (_, i) =>
smtpClient.sendCommand(`RCPT TO:<recipient${i + 1}@example.com>`)
)
];
console.log(`Attempting to pipeline ${commandCount} commands with limit of 5...`);
const responses = await Promise.all(commands);
// All should still succeed, even if sent in batches
responses.forEach(response => {
expect(response).toInclude('250');
});
console.log('All commands processed successfully despite pipeline limit');
await smtpClient.sendCommand('RSET');
await smtpClient.close();
});
tap.test('cleanup test SMTP server', async () => {
if (testServer) {
await testServer.stop();
}
});
export default tap.start();