import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as net from 'net'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; const TEST_PORT = 2525; let testServer: ITestServer; const TEST_TIMEOUT = 30000; tap.test('Plain Connection - should establish basic TCP connection', async (tools) => { const done = tools.defer(); // Start test server testServer = await startTestServer({ port: TEST_PORT }); await new Promise(resolve => setTimeout(resolve, 1000)); try { const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); const connected = await new Promise((resolve) => { socket.once('connect', () => resolve(true)); socket.once('error', () => resolve(false)); setTimeout(() => resolve(false), 5000); }); expect(connected).toEqual(true); 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 { await stopTestServer(testServer); done.resolve(); } }); tap.test('Plain Connection - should receive SMTP banner on plain connection', async (tools) => { const done = tools.defer(); // Start test server testServer = await startTestServer({ port: TEST_PORT }); await new Promise(resolve => setTimeout(resolve, 1000)); try { const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); await new Promise((resolve, reject) => { socket.once('connect', () => resolve()); socket.once('error', reject); }); // Get banner const banner = await new Promise((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 { await stopTestServer(testServer); done.resolve(); } }); tap.test('Plain Connection - should complete full SMTP transaction on plain connection', async (tools) => { const done = tools.defer(); // Start test server testServer = await startTestServer({ port: TEST_PORT }); await new Promise(resolve => setTimeout(resolve, 1000)); try { const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: TEST_TIMEOUT }); await new Promise((resolve, reject) => { socket.once('connect', () => resolve()); socket.once('error', reject); }); // Get banner await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); // Send EHLO socket.write('EHLO testhost\r\n'); const ehloResponse = await new Promise((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:\r\n'); const mailResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(mailResponse).toInclude('250'); // Send RCPT TO socket.write('RCPT TO:\r\n'); const rcptResponse = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(rcptResponse).toInclude('250'); // Send DATA socket.write('DATA\r\n'); const dataResponse = await new Promise((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((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((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(quitResponse).toInclude('221'); socket.end(); } finally { await stopTestServer(testServer); done.resolve(); } }); tap.test('Plain Connection - should handle multiple plain connections', async (tools) => { const done = tools.defer(); // Start test server testServer = await startTestServer({ port: TEST_PORT }); await new Promise(resolve => setTimeout(resolve, 1000)); try { 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((resolve, reject) => { socket.once('connect', () => { connections.push(socket); resolve(); }); socket.once('error', reject); }); // Get banner const banner = await new Promise((resolve) => { socket.once('data', (chunk) => resolve(chunk.toString())); }); expect(banner).toInclude('220'); console.log(`Connection ${i + 1} established`); } expect(connections.length).toEqual(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 { await stopTestServer(testServer); 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).toEqual(true); done.resolve(); }); export default tap.start();