dcrouter/test/suite/smtpclient_commands/test.ccmd-09.noop-command.ts

340 lines
9.5 KiB
TypeScript
Raw Normal View History

2025-05-24 16:19:19 +00:00
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();
expect(testServer).toBeTruthy();
expect(testServer.port).toBeGreaterThan(0);
});
tap.test('CCMD-09: Basic NOOP command', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Send NOOP command
const noopResponse = await smtpClient.sendCommand('NOOP');
// Verify response
expect(noopResponse).toInclude('250');
console.log(`NOOP response: ${noopResponse.trim()}`);
await smtpClient.close();
});
tap.test('CCMD-09: NOOP during transaction', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Start a transaction
await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
await smtpClient.sendCommand('RCPT TO:<recipient@example.com>');
// Send NOOP - should not affect transaction
const noopResponse = await smtpClient.sendCommand('NOOP');
expect(noopResponse).toInclude('250');
// Continue transaction - should still work
const dataResponse = await smtpClient.sendCommand('DATA');
expect(dataResponse).toInclude('354');
// Send message
const messageResponse = await smtpClient.sendCommand('Subject: Test\r\n\r\nTest message\r\n.');
expect(messageResponse).toInclude('250');
console.log('Transaction completed successfully after NOOP');
await smtpClient.close();
});
tap.test('CCMD-09: Multiple NOOP commands', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Send multiple NOOPs rapidly
const noopCount = 10;
const responses: string[] = [];
console.log(`Sending ${noopCount} NOOP commands...`);
for (let i = 0; i < noopCount; i++) {
const response = await smtpClient.sendCommand('NOOP');
responses.push(response);
}
// All should succeed
responses.forEach((response, index) => {
expect(response).toInclude('250');
});
console.log(`All ${noopCount} NOOP commands succeeded`);
await smtpClient.close();
});
tap.test('CCMD-09: NOOP for keep-alive', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 10000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
console.log('Using NOOP for keep-alive over 10 seconds...');
// Send NOOP every 2 seconds for 10 seconds
const keepAliveInterval = 2000;
const duration = 10000;
const iterations = duration / keepAliveInterval;
for (let i = 0; i < iterations; i++) {
await new Promise(resolve => setTimeout(resolve, keepAliveInterval));
const startTime = Date.now();
const response = await smtpClient.sendCommand('NOOP');
const elapsed = Date.now() - startTime;
expect(response).toInclude('250');
console.log(`Keep-alive NOOP ${i + 1}: ${elapsed}ms`);
}
// Connection should still be active
expect(smtpClient.isConnected()).toBeTruthy();
await smtpClient.close();
});
tap.test('CCMD-09: NOOP with parameters', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// RFC 5321 allows NOOP to have parameters (which are ignored)
const noopVariants = [
'NOOP',
'NOOP test',
'NOOP hello world',
'NOOP 12345',
'NOOP check connection'
];
for (const command of noopVariants) {
const response = await smtpClient.sendCommand(command);
expect(response).toInclude('250');
console.log(`"${command}" -> ${response.trim()}`);
}
await smtpClient.close();
});
tap.test('CCMD-09: NOOP timing analysis', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: false // Quiet for timing
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Measure NOOP response times
const measurements = 20;
const times: number[] = [];
for (let i = 0; i < measurements; i++) {
const startTime = Date.now();
await smtpClient.sendCommand('NOOP');
const elapsed = Date.now() - startTime;
times.push(elapsed);
}
// Calculate statistics
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
const minTime = Math.min(...times);
const maxTime = Math.max(...times);
// Calculate standard deviation
const variance = times.reduce((sum, time) => sum + Math.pow(time - avgTime, 2), 0) / times.length;
const stdDev = Math.sqrt(variance);
console.log(`NOOP timing analysis (${measurements} samples):`);
console.log(` Average: ${avgTime.toFixed(2)}ms`);
console.log(` Min: ${minTime}ms`);
console.log(` Max: ${maxTime}ms`);
console.log(` Std Dev: ${stdDev.toFixed(2)}ms`);
// NOOP should be very fast
expect(avgTime).toBeLessThan(50);
// Check for consistency (low standard deviation)
expect(stdDev).toBeLessThan(avgTime * 0.5); // Less than 50% of average
await smtpClient.close();
});
tap.test('CCMD-09: NOOP during DATA phase', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
await smtpClient.sendCommand('EHLO testclient.example.com');
// Setup transaction
await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
await smtpClient.sendCommand('RCPT TO:<recipient@example.com>');
// Enter DATA phase
const dataResponse = await smtpClient.sendCommand('DATA');
expect(dataResponse).toInclude('354');
// During DATA phase, NOOP will be treated as message content
await smtpClient.sendCommand('Subject: Test with NOOP');
await smtpClient.sendCommand('');
await smtpClient.sendCommand('This message contains the word NOOP');
await smtpClient.sendCommand('NOOP'); // This is message content, not a command
await smtpClient.sendCommand('End of message');
// End DATA phase
const endResponse = await smtpClient.sendCommand('.');
expect(endResponse).toInclude('250');
// Now NOOP should work as a command again
const noopResponse = await smtpClient.sendCommand('NOOP');
expect(noopResponse).toInclude('250');
console.log('NOOP works correctly after DATA phase');
await smtpClient.close();
});
tap.test('CCMD-09: NOOP in pipelined commands', 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 NOOP with other commands
console.log('Pipelining NOOP with other commands...');
const pipelinedCommands = [
smtpClient.sendCommand('NOOP'),
smtpClient.sendCommand('MAIL FROM:<sender@example.com>'),
smtpClient.sendCommand('NOOP'),
smtpClient.sendCommand('RCPT TO:<recipient@example.com>'),
smtpClient.sendCommand('NOOP'),
smtpClient.sendCommand('RSET'),
smtpClient.sendCommand('NOOP')
];
const responses = await Promise.all(pipelinedCommands);
// All commands should succeed
responses.forEach((response, index) => {
expect(response).toInclude('250');
});
console.log('All pipelined commands including NOOPs succeeded');
await smtpClient.close();
});
tap.test('CCMD-09: NOOP error scenarios', async () => {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: false,
connectionTimeout: 5000,
debug: true
});
await smtpClient.connect();
// Try NOOP before EHLO/HELO (some servers might reject)
const earlyNoop = await smtpClient.sendCommand('NOOP');
console.log(`NOOP before EHLO: ${earlyNoop.trim()}`);
// Most servers allow it, but check response
expect(earlyNoop).toMatch(/[25]\d\d/);
// Now do proper handshake
await smtpClient.sendCommand('EHLO testclient.example.com');
// Test malformed NOOP (though it should be accepted)
const malformedTests = [
'NOOP\t\ttabs',
'NOOP multiple spaces',
'noop lowercase',
'NoOp MixedCase'
];
for (const command of malformedTests) {
try {
const response = await smtpClient.sendCommand(command);
console.log(`"${command}" -> ${response.trim()}`);
// Most servers are lenient
} catch (error) {
console.log(`"${command}" -> Error: ${error.message}`);
}
}
await smtpClient.close();
});
tap.test('cleanup test SMTP server', async () => {
if (testServer) {
await testServer.stop();
}
});
export default tap.start();