update
This commit is contained in:
@ -0,0 +1,328 @@
|
||||
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();
|
Reference in New Issue
Block a user