dcrouter/test/suite/connection/test.plain-connection.ts

287 lines
7.7 KiB
TypeScript
Raw Normal View History

2025-05-23 19:49:25 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
2025-05-23 19:03:44 +00:00
import * as net from 'net';
2025-05-23 19:49:25 +00:00
import { startTestServer, stopTestServer, type ITestServer } from '../server.loader.js';
2025-05-23 19:03:44 +00:00
2025-05-23 19:49:25 +00:00
const TEST_PORT = 2525;
2025-05-23 19:03:44 +00:00
const TEST_TIMEOUT = 30000;
tap.test('Plain Connection - should establish basic TCP connection', async (tools) => {
const done = tools.defer();
// Start test server
2025-05-23 19:49:25 +00:00
const testServer = await startTestServer();
2025-05-23 19:03:44 +00:00
2025-05-23 19:49:25 +00:00
await new Promise(resolve => setTimeout(resolve, 1000));try {
2025-05-23 19:03:44 +00:00
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: TEST_TIMEOUT
});
const connected = await new Promise<boolean>((resolve) => {
socket.once('connect', () => resolve(true));
socket.once('error', () => resolve(false));
setTimeout(() => resolve(false), 5000);
});
expect(connected).toBeTrue();
if (connected) {
console.log('Plain connection established:');
console.log('- Local:', `${socket.localAddress}:${socket.localPort}`);
console.log('- Remote:', `${socket.remoteAddress}:${socket.remotePort}`);
// Close connection
socket.destroy();
}
} finally {
2025-05-23 19:49:25 +00:00
await stopTestServer();
2025-05-23 19:03:44 +00:00
done.resolve();
}
});
tap.test('Plain Connection - should receive SMTP banner on plain connection', async (tools) => {
const done = tools.defer();
// Start test server
2025-05-23 19:49:25 +00:00
const testServer = await startTestServer();
2025-05-23 19:03:44 +00:00
2025-05-23 19:49:25 +00:00
await new Promise(resolve => setTimeout(resolve, 1000));try {
2025-05-23 19:03:44 +00:00
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: TEST_TIMEOUT
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', () => resolve());
socket.once('error', reject);
});
// Get banner
const banner = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
console.log('Received banner:', banner.trim());
expect(banner).toInclude('220');
expect(banner).toInclude('ESMTP');
// Clean up
socket.write('QUIT\r\n');
socket.end();
} finally {
2025-05-23 19:49:25 +00:00
await stopTestServer();
2025-05-23 19:03:44 +00:00
done.resolve();
}
});
tap.test('Plain Connection - should complete full SMTP transaction on plain connection', async (tools) => {
const done = tools.defer();
// Start test server
2025-05-23 19:49:25 +00:00
const testServer = await startTestServer();
2025-05-23 19:03:44 +00:00
2025-05-23 19:49:25 +00:00
await new Promise(resolve => setTimeout(resolve, 1000));try {
2025-05-23 19:03:44 +00:00
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: TEST_TIMEOUT
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', () => resolve());
socket.once('error', reject);
});
// Get banner
await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
// Send EHLO
socket.write('EHLO testhost\r\n');
const ehloResponse = await new Promise<string>((resolve) => {
let data = '';
const handler = (chunk: Buffer) => {
data += chunk.toString();
if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
socket.removeListener('data', handler);
resolve(data);
}
};
socket.on('data', handler);
});
expect(ehloResponse).toInclude('250');
console.log('EHLO successful on plain connection');
// Send MAIL FROM
socket.write('MAIL FROM:<sender@example.com>\r\n');
const mailResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
expect(mailResponse).toInclude('250');
// Send RCPT TO
socket.write('RCPT TO:<recipient@example.com>\r\n');
const rcptResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
expect(rcptResponse).toInclude('250');
// Send DATA
socket.write('DATA\r\n');
const dataResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
expect(dataResponse).toInclude('354');
// Send email content
const emailContent =
'From: sender@example.com\r\n' +
'To: recipient@example.com\r\n' +
'Subject: Plain Connection Test\r\n' +
'\r\n' +
'This email was sent over a plain connection.\r\n' +
'.\r\n';
socket.write(emailContent);
const finalResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
expect(finalResponse).toInclude('250');
console.log('Email sent successfully over plain connection');
// Clean up
socket.write('QUIT\r\n');
const quitResponse = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
expect(quitResponse).toInclude('221');
socket.end();
} finally {
2025-05-23 19:49:25 +00:00
await stopTestServer();
2025-05-23 19:03:44 +00:00
done.resolve();
}
});
tap.test('Plain Connection - should handle multiple plain connections', async (tools) => {
const done = tools.defer();
// Start test server
2025-05-23 19:49:25 +00:00
const testServer = await startTestServer();
2025-05-23 19:03:44 +00:00
2025-05-23 19:49:25 +00:00
await new Promise(resolve => setTimeout(resolve, 1000));try {
2025-05-23 19:03:44 +00:00
const connectionCount = 3;
const connections: net.Socket[] = [];
// Create multiple connections
for (let i = 0; i < connectionCount; i++) {
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: TEST_TIMEOUT
});
await new Promise<void>((resolve, reject) => {
socket.once('connect', () => {
connections.push(socket);
resolve();
});
socket.once('error', reject);
});
// Get banner
const banner = await new Promise<string>((resolve) => {
socket.once('data', (chunk) => resolve(chunk.toString()));
});
expect(banner).toInclude('220');
console.log(`Connection ${i + 1} established`);
}
expect(connections.length).toBe(connectionCount);
console.log(`All ${connectionCount} plain connections established successfully`);
// Clean up all connections
for (const socket of connections) {
socket.write('QUIT\r\n');
socket.end();
}
} finally {
2025-05-23 19:49:25 +00:00
await stopTestServer();
2025-05-23 19:03:44 +00:00
done.resolve();
}
});
tap.test('Plain Connection - should work on standard SMTP port 25', async (tools) => {
const done = tools.defer();
// Test port 25 (standard SMTP port)
const SMTP_PORT = 25;
// Note: Port 25 might require special permissions or might be blocked
// We'll test the connection but handle failures gracefully
const socket = net.createConnection({
host: 'localhost',
port: SMTP_PORT,
timeout: 5000
});
const result = await new Promise<{connected: boolean, error?: string}>((resolve) => {
socket.once('connect', () => {
socket.destroy();
resolve({ connected: true });
});
socket.once('error', (err) => {
resolve({
connected: false,
error: err.message
});
});
setTimeout(() => {
socket.destroy();
resolve({
connected: false,
error: 'Connection timeout'
});
}, 5000);
});
if (result.connected) {
console.log('Successfully connected to port 25 (standard SMTP)');
} else {
console.log(`Could not connect to port 25: ${result.error}`);
console.log('This is expected if port 25 is blocked or requires privileges');
}
// Test passes regardless - port 25 connectivity is environment-dependent
expect(true).toBeTrue();
done.resolve();
});
tap.start();