update
This commit is contained in:
193
test/suite/smtpserver_commands/test.cmd-01.ehlo-command.ts
Normal file
193
test/suite/smtpserver_commands/test.cmd-01.ehlo-command.ts
Normal file
@ -0,0 +1,193 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as net from 'net';
|
||||
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
|
||||
|
||||
const TEST_PORT = 2525;
|
||||
|
||||
let testServer;
|
||||
const TEST_TIMEOUT = 10000;
|
||||
|
||||
tap.test('prepare server', async () => {
|
||||
testServer = await startTestServer({ port: TEST_PORT });
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
});
|
||||
|
||||
tap.test('CMD-01: EHLO Command - server responds with proper capabilities', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
let receivedData = '';
|
||||
let currentStep = 'connecting';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
receivedData += data.toString();
|
||||
|
||||
if (currentStep === 'connecting' && receivedData.includes('220')) {
|
||||
currentStep = 'ehlo';
|
||||
receivedData = ''; // Clear buffer
|
||||
socket.write('EHLO test.example.com\r\n');
|
||||
} else if (currentStep === 'ehlo' && receivedData.includes('250 ')) {
|
||||
// Parse response - only lines that start with 250
|
||||
const lines = receivedData.split('\r\n')
|
||||
.filter(line => line.startsWith('250'))
|
||||
.filter(line => line.length > 0);
|
||||
|
||||
// Check for required ESMTP extensions
|
||||
const capabilities = lines.map(line => line.substring(4).trim());
|
||||
console.log('📋 Server capabilities:', capabilities);
|
||||
|
||||
// Verify essential capabilities
|
||||
expect(capabilities.some(cap => cap.includes('SIZE'))).toBeTruthy();
|
||||
expect(capabilities.some(cap => cap.includes('8BITMIME'))).toBeTruthy();
|
||||
|
||||
// The last line should be "250 " (without hyphen)
|
||||
const lastLine = lines[lines.length - 1];
|
||||
expect(lastLine.startsWith('250 ')).toBeTruthy();
|
||||
|
||||
currentStep = 'quit';
|
||||
receivedData = ''; // Clear buffer
|
||||
socket.write('QUIT\r\n');
|
||||
} else if (currentStep === 'quit' && receivedData.includes('221')) {
|
||||
socket.destroy();
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (error) => {
|
||||
done.reject(error);
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
socket.destroy();
|
||||
done.reject(new Error(`Connection timeout at step: ${currentStep}`));
|
||||
});
|
||||
|
||||
await done.promise;
|
||||
});
|
||||
|
||||
tap.test('CMD-01: EHLO with invalid hostname - server handles gracefully', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
let receivedData = '';
|
||||
let currentStep = 'connecting';
|
||||
let testIndex = 0;
|
||||
|
||||
const invalidHostnames = [
|
||||
'', // Empty hostname
|
||||
' ', // Whitespace only
|
||||
'invalid..hostname', // Double dots
|
||||
'.invalid', // Leading dot
|
||||
'invalid.', // Trailing dot
|
||||
'very-long-hostname-that-exceeds-reasonable-limits-' + 'x'.repeat(200)
|
||||
];
|
||||
|
||||
socket.on('data', (data) => {
|
||||
receivedData += data.toString();
|
||||
|
||||
if (currentStep === 'connecting' && receivedData.includes('220')) {
|
||||
currentStep = 'testing';
|
||||
receivedData = ''; // Clear buffer
|
||||
console.log(`Testing invalid hostname: "${invalidHostnames[testIndex]}"`);
|
||||
socket.write(`EHLO ${invalidHostnames[testIndex]}\r\n`);
|
||||
} else if (currentStep === 'testing' && (receivedData.includes('250') || receivedData.includes('5'))) {
|
||||
// Server should either accept with warning or reject with 5xx
|
||||
expect(receivedData).toMatch(/^(250|5\d\d)/);
|
||||
|
||||
testIndex++;
|
||||
if (testIndex < invalidHostnames.length) {
|
||||
currentStep = 'reset';
|
||||
receivedData = ''; // Clear buffer
|
||||
socket.write('RSET\r\n');
|
||||
} else {
|
||||
socket.write('QUIT\r\n');
|
||||
setTimeout(() => {
|
||||
socket.destroy();
|
||||
done.resolve();
|
||||
}, 100);
|
||||
}
|
||||
} else if (currentStep === 'reset' && receivedData.includes('250')) {
|
||||
currentStep = 'testing';
|
||||
receivedData = ''; // Clear buffer
|
||||
console.log(`Testing invalid hostname: "${invalidHostnames[testIndex]}"`);
|
||||
socket.write(`EHLO ${invalidHostnames[testIndex]}\r\n`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (error) => {
|
||||
done.reject(error);
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
socket.destroy();
|
||||
done.reject(new Error(`Connection timeout at step: ${currentStep}`));
|
||||
});
|
||||
|
||||
await done.promise;
|
||||
});
|
||||
|
||||
tap.test('CMD-01: EHLO command pipelining - multiple EHLO commands', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
let receivedData = '';
|
||||
let currentStep = 'connecting';
|
||||
|
||||
socket.on('data', (data) => {
|
||||
receivedData += data.toString();
|
||||
|
||||
if (currentStep === 'connecting' && receivedData.includes('220')) {
|
||||
currentStep = 'first_ehlo';
|
||||
receivedData = ''; // Clear buffer
|
||||
socket.write('EHLO first.example.com\r\n');
|
||||
} else if (currentStep === 'first_ehlo' && receivedData.includes('250 ')) {
|
||||
currentStep = 'second_ehlo';
|
||||
receivedData = ''; // Clear buffer
|
||||
// Second EHLO (should reset session)
|
||||
socket.write('EHLO second.example.com\r\n');
|
||||
} else if (currentStep === 'second_ehlo' && receivedData.includes('250 ')) {
|
||||
currentStep = 'mail_from';
|
||||
receivedData = ''; // Clear buffer
|
||||
// Verify session was reset by trying MAIL FROM
|
||||
socket.write('MAIL FROM:<test@example.com>\r\n');
|
||||
} else if (currentStep === 'mail_from' && receivedData.includes('250')) {
|
||||
socket.write('QUIT\r\n');
|
||||
setTimeout(() => {
|
||||
socket.destroy();
|
||||
done.resolve();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (error) => {
|
||||
done.reject(error);
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
socket.destroy();
|
||||
done.reject(new Error(`Connection timeout at step: ${currentStep}`));
|
||||
});
|
||||
|
||||
await done.promise;
|
||||
});
|
||||
|
||||
tap.test('cleanup server', async () => {
|
||||
await stopTestServer(testServer);
|
||||
});
|
||||
|
||||
tap.start();
|
Reference in New Issue
Block a user