177 lines
4.7 KiB
TypeScript
177 lines
4.7 KiB
TypeScript
|
|
/**
|
||
|
|
* CMD-13: QUIT Command Tests
|
||
|
|
* Tests SMTP QUIT command for graceful connection termination
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { assert, assertMatch } from '@std/assert';
|
||
|
|
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.ts';
|
||
|
|
import {
|
||
|
|
connectToSmtp,
|
||
|
|
waitForGreeting,
|
||
|
|
sendSmtpCommand,
|
||
|
|
closeSmtpConnection,
|
||
|
|
} from '../../helpers/utils.ts';
|
||
|
|
|
||
|
|
const TEST_PORT = 25255;
|
||
|
|
let testServer: ITestServer;
|
||
|
|
|
||
|
|
Deno.test({
|
||
|
|
name: 'CMD-13: Setup - Start SMTP server',
|
||
|
|
async fn() {
|
||
|
|
testServer = await startTestServer({ port: TEST_PORT });
|
||
|
|
assert(testServer, 'Test server should be created');
|
||
|
|
},
|
||
|
|
sanitizeResources: false,
|
||
|
|
sanitizeOps: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
Deno.test({
|
||
|
|
name: 'CMD-13: QUIT - gracefully closes connection',
|
||
|
|
async fn() {
|
||
|
|
const conn = await connectToSmtp('localhost', TEST_PORT);
|
||
|
|
|
||
|
|
try {
|
||
|
|
await waitForGreeting(conn);
|
||
|
|
await sendSmtpCommand(conn, 'EHLO test.example.com', '250');
|
||
|
|
|
||
|
|
// Send QUIT command
|
||
|
|
const response = await sendSmtpCommand(conn, 'QUIT', '221');
|
||
|
|
assertMatch(response, /^221/, 'Should respond with 221 Service closing');
|
||
|
|
assert(response.includes('Service closing'), 'Should indicate service is closing');
|
||
|
|
|
||
|
|
console.log('✓ QUIT command received 221 response');
|
||
|
|
} finally {
|
||
|
|
try {
|
||
|
|
conn.close();
|
||
|
|
} catch {
|
||
|
|
// Connection may already be closed by server
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
sanitizeResources: false,
|
||
|
|
sanitizeOps: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
Deno.test({
|
||
|
|
name: 'CMD-13: QUIT - works after MAIL FROM',
|
||
|
|
async fn() {
|
||
|
|
const conn = await connectToSmtp('localhost', TEST_PORT);
|
||
|
|
|
||
|
|
try {
|
||
|
|
await waitForGreeting(conn);
|
||
|
|
await sendSmtpCommand(conn, 'EHLO test.example.com', '250');
|
||
|
|
await sendSmtpCommand(conn, 'MAIL FROM:<sender@example.com>', '250');
|
||
|
|
|
||
|
|
// QUIT should work at any point
|
||
|
|
const response = await sendSmtpCommand(conn, 'QUIT', '221');
|
||
|
|
assertMatch(response, /^221/, 'Should respond with 221');
|
||
|
|
|
||
|
|
console.log('✓ QUIT works after MAIL FROM');
|
||
|
|
} finally {
|
||
|
|
try {
|
||
|
|
conn.close();
|
||
|
|
} catch {
|
||
|
|
// Ignore
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
sanitizeResources: false,
|
||
|
|
sanitizeOps: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
Deno.test({
|
||
|
|
name: 'CMD-13: QUIT - works after complete transaction',
|
||
|
|
async fn() {
|
||
|
|
const conn = await connectToSmtp('localhost', TEST_PORT);
|
||
|
|
|
||
|
|
try {
|
||
|
|
await waitForGreeting(conn);
|
||
|
|
await sendSmtpCommand(conn, 'EHLO test.example.com', '250');
|
||
|
|
await sendSmtpCommand(conn, 'MAIL FROM:<sender@example.com>', '250');
|
||
|
|
await sendSmtpCommand(conn, 'RCPT TO:<recipient@example.com>', '250');
|
||
|
|
|
||
|
|
// QUIT should work after a complete transaction setup
|
||
|
|
const response = await sendSmtpCommand(conn, 'QUIT', '221');
|
||
|
|
assertMatch(response, /^221/, 'Should respond with 221');
|
||
|
|
|
||
|
|
console.log('✓ QUIT works after complete transaction');
|
||
|
|
} finally {
|
||
|
|
try {
|
||
|
|
conn.close();
|
||
|
|
} catch {
|
||
|
|
// Ignore
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
sanitizeResources: false,
|
||
|
|
sanitizeOps: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
Deno.test({
|
||
|
|
name: 'CMD-13: QUIT - can be called multiple times (idempotent)',
|
||
|
|
async fn() {
|
||
|
|
const conn = await connectToSmtp('localhost', TEST_PORT);
|
||
|
|
|
||
|
|
try {
|
||
|
|
await waitForGreeting(conn);
|
||
|
|
await sendSmtpCommand(conn, 'EHLO test.example.com', '250');
|
||
|
|
|
||
|
|
// First QUIT
|
||
|
|
const response1 = await sendSmtpCommand(conn, 'QUIT', '221');
|
||
|
|
assertMatch(response1, /^221/, 'First QUIT should respond with 221');
|
||
|
|
|
||
|
|
// Try second QUIT (connection might be closed, so catch error)
|
||
|
|
try {
|
||
|
|
const response2 = await sendSmtpCommand(conn, 'QUIT');
|
||
|
|
// If we get here, server allowed second QUIT
|
||
|
|
console.log('⚠️ Server allows multiple QUIT commands');
|
||
|
|
} catch (error) {
|
||
|
|
// This is expected - connection should be closed after first QUIT
|
||
|
|
console.log('✓ Connection closed after first QUIT (expected)');
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
try {
|
||
|
|
conn.close();
|
||
|
|
} catch {
|
||
|
|
// Ignore
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
sanitizeResources: false,
|
||
|
|
sanitizeOps: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
Deno.test({
|
||
|
|
name: 'CMD-13: QUIT - works without EHLO (immediate quit)',
|
||
|
|
async fn() {
|
||
|
|
const conn = await connectToSmtp('localhost', TEST_PORT);
|
||
|
|
|
||
|
|
try {
|
||
|
|
await waitForGreeting(conn);
|
||
|
|
|
||
|
|
// QUIT immediately after greeting
|
||
|
|
const response = await sendSmtpCommand(conn, 'QUIT', '221');
|
||
|
|
assertMatch(response, /^221/, 'Should respond with 221 even without EHLO');
|
||
|
|
|
||
|
|
console.log('✓ QUIT works without EHLO');
|
||
|
|
} finally {
|
||
|
|
try {
|
||
|
|
conn.close();
|
||
|
|
} catch {
|
||
|
|
// Ignore
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
sanitizeResources: false,
|
||
|
|
sanitizeOps: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
Deno.test({
|
||
|
|
name: 'CMD-13: Cleanup - Stop SMTP server',
|
||
|
|
async fn() {
|
||
|
|
await stopTestServer(testServer);
|
||
|
|
},
|
||
|
|
sanitizeResources: false,
|
||
|
|
sanitizeOps: false,
|
||
|
|
});
|