290 lines
8.8 KiB
TypeScript
290 lines
8.8 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();
|
||
|
expect(testServer).toBeTruthy();
|
||
|
expect(testServer.port).toBeGreaterThan(0);
|
||
|
});
|
||
|
|
||
|
tap.test('CCMD-08: Basic RSET 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 RSET command
|
||
|
const rsetResponse = await smtpClient.sendCommand('RSET');
|
||
|
|
||
|
// Verify response
|
||
|
expect(rsetResponse).toInclude('250');
|
||
|
expect(rsetResponse).toMatch(/reset|ok/i);
|
||
|
|
||
|
console.log(`RSET response: ${rsetResponse.trim()}`);
|
||
|
|
||
|
await smtpClient.close();
|
||
|
});
|
||
|
|
||
|
tap.test('CCMD-08: RSET after MAIL FROM', 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 transaction
|
||
|
const mailResponse = await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
|
||
|
expect(mailResponse).toInclude('250');
|
||
|
|
||
|
// Reset transaction
|
||
|
const rsetResponse = await smtpClient.sendCommand('RSET');
|
||
|
expect(rsetResponse).toInclude('250');
|
||
|
|
||
|
// Verify transaction was reset by trying RCPT TO without MAIL FROM
|
||
|
const rcptResponse = await smtpClient.sendCommand('RCPT TO:<recipient@example.com>');
|
||
|
expect(rcptResponse).toMatch(/[45]\d\d/); // Should fail
|
||
|
|
||
|
await smtpClient.close();
|
||
|
});
|
||
|
|
||
|
tap.test('CCMD-08: RSET after multiple recipients', 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');
|
||
|
|
||
|
// Build up a transaction with multiple recipients
|
||
|
await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
|
||
|
await smtpClient.sendCommand('RCPT TO:<recipient1@example.com>');
|
||
|
await smtpClient.sendCommand('RCPT TO:<recipient2@example.com>');
|
||
|
await smtpClient.sendCommand('RCPT TO:<recipient3@example.com>');
|
||
|
|
||
|
console.log('Transaction built with 3 recipients');
|
||
|
|
||
|
// Reset the transaction
|
||
|
const rsetResponse = await smtpClient.sendCommand('RSET');
|
||
|
expect(rsetResponse).toInclude('250');
|
||
|
|
||
|
// Start a new transaction to verify reset
|
||
|
const newMailResponse = await smtpClient.sendCommand('MAIL FROM:<newsender@example.com>');
|
||
|
expect(newMailResponse).toInclude('250');
|
||
|
|
||
|
const newRcptResponse = await smtpClient.sendCommand('RCPT TO:<newrecipient@example.com>');
|
||
|
expect(newRcptResponse).toInclude('250');
|
||
|
|
||
|
console.log('Successfully started new transaction after RSET');
|
||
|
|
||
|
await smtpClient.sendCommand('RSET');
|
||
|
await smtpClient.close();
|
||
|
});
|
||
|
|
||
|
tap.test('CCMD-08: RSET 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');
|
||
|
|
||
|
// Start 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');
|
||
|
|
||
|
// Try RSET during DATA (should fail or be queued)
|
||
|
// Most servers will interpret this as message content
|
||
|
await smtpClient.sendCommand('RSET');
|
||
|
|
||
|
// Complete the DATA phase
|
||
|
const endDataResponse = await smtpClient.sendCommand('.');
|
||
|
expect(endDataResponse).toInclude('250');
|
||
|
|
||
|
// Now RSET should work
|
||
|
const rsetResponse = await smtpClient.sendCommand('RSET');
|
||
|
expect(rsetResponse).toInclude('250');
|
||
|
|
||
|
await smtpClient.close();
|
||
|
});
|
||
|
|
||
|
tap.test('CCMD-08: Multiple RSET 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 RSET commands
|
||
|
for (let i = 0; i < 5; i++) {
|
||
|
const rsetResponse = await smtpClient.sendCommand('RSET');
|
||
|
expect(rsetResponse).toInclude('250');
|
||
|
console.log(`RSET ${i + 1}: ${rsetResponse.trim()}`);
|
||
|
}
|
||
|
|
||
|
// Should still be able to start a transaction
|
||
|
const mailResponse = await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
|
||
|
expect(mailResponse).toInclude('250');
|
||
|
|
||
|
await smtpClient.sendCommand('RSET');
|
||
|
await smtpClient.close();
|
||
|
});
|
||
|
|
||
|
tap.test('CCMD-08: RSET with pipelining', 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 including RSET
|
||
|
const pipelinedCommands = [
|
||
|
smtpClient.sendCommand('MAIL FROM:<sender@example.com>'),
|
||
|
smtpClient.sendCommand('RCPT TO:<recipient@example.com>'),
|
||
|
smtpClient.sendCommand('RSET'),
|
||
|
smtpClient.sendCommand('MAIL FROM:<newsender@example.com>'),
|
||
|
smtpClient.sendCommand('RCPT TO:<newrecipient@example.com>')
|
||
|
];
|
||
|
|
||
|
const responses = await Promise.all(pipelinedCommands);
|
||
|
|
||
|
// Check responses
|
||
|
expect(responses[0]).toInclude('250'); // MAIL FROM
|
||
|
expect(responses[1]).toInclude('250'); // RCPT TO
|
||
|
expect(responses[2]).toInclude('250'); // RSET
|
||
|
expect(responses[3]).toInclude('250'); // New MAIL FROM
|
||
|
expect(responses[4]).toInclude('250'); // New RCPT TO
|
||
|
|
||
|
console.log('Successfully pipelined commands with RSET');
|
||
|
|
||
|
await smtpClient.sendCommand('RSET');
|
||
|
await smtpClient.close();
|
||
|
});
|
||
|
|
||
|
tap.test('CCMD-08: RSET state verification', 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');
|
||
|
|
||
|
// Build complex state
|
||
|
await smtpClient.sendCommand('MAIL FROM:<sender@example.com> SIZE=1000');
|
||
|
await smtpClient.sendCommand('RCPT TO:<recipient1@example.com>');
|
||
|
await smtpClient.sendCommand('RCPT TO:<recipient2@example.com>');
|
||
|
|
||
|
console.log('Built transaction state with SIZE parameter and 2 recipients');
|
||
|
|
||
|
// Reset
|
||
|
const rsetResponse = await smtpClient.sendCommand('RSET');
|
||
|
expect(rsetResponse).toInclude('250');
|
||
|
|
||
|
// Verify all state is cleared
|
||
|
// 1. Can't add recipients without MAIL FROM
|
||
|
const rcptResponse = await smtpClient.sendCommand('RCPT TO:<test@example.com>');
|
||
|
expect(rcptResponse).toMatch(/[45]\d\d/);
|
||
|
|
||
|
// 2. Can start fresh transaction
|
||
|
const newMailResponse = await smtpClient.sendCommand('MAIL FROM:<different@example.com>');
|
||
|
expect(newMailResponse).toInclude('250');
|
||
|
|
||
|
// 3. Previous recipients are not remembered
|
||
|
const dataResponse = await smtpClient.sendCommand('DATA');
|
||
|
expect(dataResponse).toMatch(/[45]\d\d/); // Should fail - no recipients
|
||
|
|
||
|
await smtpClient.sendCommand('RSET');
|
||
|
await smtpClient.close();
|
||
|
});
|
||
|
|
||
|
tap.test('CCMD-08: RSET performance impact', async () => {
|
||
|
const smtpClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false,
|
||
|
connectionTimeout: 5000,
|
||
|
debug: false // Quiet for performance test
|
||
|
});
|
||
|
|
||
|
await smtpClient.connect();
|
||
|
await smtpClient.sendCommand('EHLO testclient.example.com');
|
||
|
|
||
|
const iterations = 20;
|
||
|
const times: number[] = [];
|
||
|
|
||
|
for (let i = 0; i < iterations; i++) {
|
||
|
// Build transaction
|
||
|
await smtpClient.sendCommand('MAIL FROM:<sender@example.com>');
|
||
|
await smtpClient.sendCommand('RCPT TO:<recipient@example.com>');
|
||
|
|
||
|
// Measure RSET time
|
||
|
const startTime = Date.now();
|
||
|
await smtpClient.sendCommand('RSET');
|
||
|
const elapsed = Date.now() - startTime;
|
||
|
|
||
|
times.push(elapsed);
|
||
|
}
|
||
|
|
||
|
// Analyze RSET performance
|
||
|
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
|
||
|
const minTime = Math.min(...times);
|
||
|
const maxTime = Math.max(...times);
|
||
|
|
||
|
console.log(`RSET performance over ${iterations} iterations:`);
|
||
|
console.log(` Average: ${avgTime.toFixed(2)}ms`);
|
||
|
console.log(` Min: ${minTime}ms`);
|
||
|
console.log(` Max: ${maxTime}ms`);
|
||
|
|
||
|
// RSET should be fast
|
||
|
expect(avgTime).toBeLessThan(100);
|
||
|
|
||
|
await smtpClient.close();
|
||
|
});
|
||
|
|
||
|
tap.test('cleanup test SMTP server', async () => {
|
||
|
if (testServer) {
|
||
|
await testServer.stop();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
export default tap.start();
|