update
This commit is contained in:
352
test/suite/smtpclient_commands/test.ccmd-07.response-parsing.ts
Normal file
352
test/suite/smtpclient_commands/test.ccmd-07.response-parsing.ts
Normal file
@ -0,0 +1,352 @@
|
||||
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-07: Parse single-line responses', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Test various single-line responses
|
||||
const testCases = [
|
||||
{ command: 'NOOP', expectedCode: '250', expectedText: /OK/ },
|
||||
{ command: 'RSET', expectedCode: '250', expectedText: /Reset/ },
|
||||
{ command: 'HELP', expectedCode: '214', expectedText: /Help/ }
|
||||
];
|
||||
|
||||
for (const test of testCases) {
|
||||
const response = await smtpClient.sendCommand(test.command);
|
||||
|
||||
// Parse response code and text
|
||||
const codeMatch = response.match(/^(\d{3})\s+(.*)$/m);
|
||||
expect(codeMatch).toBeTruthy();
|
||||
|
||||
if (codeMatch) {
|
||||
const [, code, text] = codeMatch;
|
||||
expect(code).toEqual(test.expectedCode);
|
||||
expect(text).toMatch(test.expectedText);
|
||||
console.log(`${test.command}: ${code} ${text}`);
|
||||
}
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CCMD-07: Parse multi-line responses', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// EHLO typically returns multi-line response
|
||||
const ehloResponse = await smtpClient.sendCommand('EHLO testclient.example.com');
|
||||
|
||||
// Parse multi-line response
|
||||
const lines = ehloResponse.split('\r\n').filter(line => line.length > 0);
|
||||
|
||||
let capabilities: string[] = [];
|
||||
let finalCode = '';
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
const multiLineMatch = line.match(/^(\d{3})-(.*)$/); // 250-CAPABILITY
|
||||
const finalLineMatch = line.match(/^(\d{3})\s+(.*)$/); // 250 CAPABILITY
|
||||
|
||||
if (multiLineMatch) {
|
||||
const [, code, capability] = multiLineMatch;
|
||||
expect(code).toEqual('250');
|
||||
capabilities.push(capability);
|
||||
} else if (finalLineMatch) {
|
||||
const [, code, capability] = finalLineMatch;
|
||||
expect(code).toEqual('250');
|
||||
finalCode = code;
|
||||
capabilities.push(capability);
|
||||
}
|
||||
});
|
||||
|
||||
expect(finalCode).toEqual('250');
|
||||
expect(capabilities.length).toBeGreaterThan(0);
|
||||
|
||||
console.log('Parsed capabilities:', capabilities);
|
||||
|
||||
// Common capabilities to check for
|
||||
const commonCapabilities = ['PIPELINING', 'SIZE', '8BITMIME'];
|
||||
const foundCapabilities = commonCapabilities.filter(cap =>
|
||||
capabilities.some(c => c.includes(cap))
|
||||
);
|
||||
|
||||
console.log('Found common capabilities:', foundCapabilities);
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CCMD-07: Parse error response codes', 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');
|
||||
|
||||
// Test various error conditions
|
||||
const errorTests = [
|
||||
{
|
||||
command: 'RCPT TO:<recipient@example.com>', // Without MAIL FROM
|
||||
expectedCodeRange: [500, 599],
|
||||
description: 'RCPT without MAIL FROM'
|
||||
},
|
||||
{
|
||||
command: 'INVALID_COMMAND',
|
||||
expectedCodeRange: [500, 502],
|
||||
description: 'Invalid command'
|
||||
},
|
||||
{
|
||||
command: 'MAIL FROM:<invalid email format>',
|
||||
expectedCodeRange: [501, 553],
|
||||
description: 'Invalid email format'
|
||||
}
|
||||
];
|
||||
|
||||
for (const test of errorTests) {
|
||||
try {
|
||||
const response = await smtpClient.sendCommand(test.command);
|
||||
const codeMatch = response.match(/^(\d{3})/);
|
||||
|
||||
if (codeMatch) {
|
||||
const code = parseInt(codeMatch[1]);
|
||||
console.log(`${test.description}: ${code} ${response.trim()}`);
|
||||
|
||||
expect(code).toBeGreaterThanOrEqual(test.expectedCodeRange[0]);
|
||||
expect(code).toBeLessThanOrEqual(test.expectedCodeRange[1]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`${test.description}: Error caught - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
await smtpClient.sendCommand('RSET');
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CCMD-07: Parse enhanced status codes', 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 commands that might return enhanced status codes
|
||||
await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
|
||||
|
||||
// Try to send to a potentially problematic address
|
||||
const response = await smtpClient.sendCommand('RCPT TO:<postmaster@[127.0.0.1]>');
|
||||
|
||||
// Parse for enhanced status codes (X.Y.Z format)
|
||||
const enhancedMatch = response.match(/\b(\d\.\d+\.\d+)\b/);
|
||||
|
||||
if (enhancedMatch) {
|
||||
const [, enhancedCode] = enhancedMatch;
|
||||
console.log(`Found enhanced status code: ${enhancedCode}`);
|
||||
|
||||
// Parse enhanced code components
|
||||
const [classCode, subjectCode, detailCode] = enhancedCode.split('.').map(Number);
|
||||
|
||||
expect(classCode).toBeGreaterThanOrEqual(2);
|
||||
expect(classCode).toBeLessThanOrEqual(5);
|
||||
expect(subjectCode).toBeGreaterThanOrEqual(0);
|
||||
expect(detailCode).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Interpret the enhanced code
|
||||
const classDescriptions = {
|
||||
2: 'Success',
|
||||
3: 'Temporary Failure',
|
||||
4: 'Persistent Transient Failure',
|
||||
5: 'Permanent Failure'
|
||||
};
|
||||
|
||||
console.log(`Enhanced code ${enhancedCode} means: ${classDescriptions[classCode] || 'Unknown'}`);
|
||||
} else {
|
||||
console.log('No enhanced status code found in response');
|
||||
}
|
||||
|
||||
await smtpClient.sendCommand('RSET');
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CCMD-07: Parse response timing and delays', 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');
|
||||
|
||||
// Measure response times for different commands
|
||||
const timingTests = [
|
||||
'NOOP',
|
||||
'HELP',
|
||||
'MAIL FROM:<sender@example.com>',
|
||||
'RSET'
|
||||
];
|
||||
|
||||
const timings: { command: string; time: number; code: string }[] = [];
|
||||
|
||||
for (const command of timingTests) {
|
||||
const startTime = Date.now();
|
||||
const response = await smtpClient.sendCommand(command);
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
const codeMatch = response.match(/^(\d{3})/);
|
||||
const code = codeMatch ? codeMatch[1] : 'unknown';
|
||||
|
||||
timings.push({ command, time: elapsed, code });
|
||||
}
|
||||
|
||||
// Analyze timings
|
||||
console.log('\nCommand response times:');
|
||||
timings.forEach(t => {
|
||||
console.log(` ${t.command}: ${t.time}ms (${t.code})`);
|
||||
});
|
||||
|
||||
const avgTime = timings.reduce((sum, t) => sum + t.time, 0) / timings.length;
|
||||
console.log(`Average response time: ${avgTime.toFixed(2)}ms`);
|
||||
|
||||
// All commands should respond quickly (under 1 second)
|
||||
timings.forEach(t => {
|
||||
expect(t.time).toBeLessThan(1000);
|
||||
});
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CCMD-07: Parse continuation responses', 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');
|
||||
await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
|
||||
await smtpClient.sendCommand('RCPT TO:<recipient@example.com>');
|
||||
|
||||
// DATA command returns a continuation response (354)
|
||||
const dataResponse = await smtpClient.sendCommand('DATA');
|
||||
|
||||
// Parse continuation response
|
||||
const contMatch = dataResponse.match(/^(\d{3})[\s-](.*)$/);
|
||||
expect(contMatch).toBeTruthy();
|
||||
|
||||
if (contMatch) {
|
||||
const [, code, text] = contMatch;
|
||||
expect(code).toEqual('354');
|
||||
expect(text).toMatch(/mail input|end with/i);
|
||||
|
||||
console.log(`Continuation response: ${code} ${text}`);
|
||||
}
|
||||
|
||||
// Send message data
|
||||
const messageData = 'Subject: Test\r\n\r\nTest message\r\n.';
|
||||
const finalResponse = await smtpClient.sendCommand(messageData);
|
||||
|
||||
// Parse final response
|
||||
const finalMatch = finalResponse.match(/^(\d{3})/);
|
||||
expect(finalMatch).toBeTruthy();
|
||||
expect(finalMatch![1]).toEqual('250');
|
||||
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
tap.test('CCMD-07: Parse response text variations', async () => {
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await smtpClient.connect();
|
||||
|
||||
// Different servers may have different response text
|
||||
const response = await smtpClient.sendCommand('EHLO testclient.example.com');
|
||||
|
||||
// Extract server identification from first line
|
||||
const firstLineMatch = response.match(/^250[\s-](.+?)(?:\r?\n|$)/);
|
||||
|
||||
if (firstLineMatch) {
|
||||
const serverIdent = firstLineMatch[1];
|
||||
console.log(`Server identification: ${serverIdent}`);
|
||||
|
||||
// Check for common patterns
|
||||
const patterns = [
|
||||
{ pattern: /ESMTP/, description: 'Extended SMTP' },
|
||||
{ pattern: /ready|ok|hello/i, description: 'Greeting' },
|
||||
{ pattern: /\d+\.\d+/, description: 'Version number' },
|
||||
{ pattern: /[a-zA-Z0-9.-]+/, description: 'Hostname' }
|
||||
];
|
||||
|
||||
patterns.forEach(p => {
|
||||
if (p.pattern.test(serverIdent)) {
|
||||
console.log(` Found: ${p.description}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test QUIT response variations
|
||||
const quitResponse = await smtpClient.sendCommand('QUIT');
|
||||
const quitMatch = quitResponse.match(/^(\d{3})\s+(.*)$/);
|
||||
|
||||
if (quitMatch) {
|
||||
const [, code, text] = quitMatch;
|
||||
expect(code).toEqual('221');
|
||||
|
||||
// Common QUIT response patterns
|
||||
const quitPatterns = ['bye', 'closing', 'goodbye', 'terminating'];
|
||||
const foundPattern = quitPatterns.some(p => text.toLowerCase().includes(p));
|
||||
|
||||
console.log(`QUIT response: ${text} (matches pattern: ${foundPattern})`);
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await testServer.stop();
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
Reference in New Issue
Block a user