import { tap, expect } from '@git.zone/tstest/tapbundle'; import { UnifiedEmailServer } from '../ts/mail/routing/classes.unified.email.server.js'; import { RustSecurityBridge, BridgeState } from '../ts/security/classes.rustsecuritybridge.js'; import type { ISmtpPoolStatus } from '../ts/security/classes.rustsecuritybridge.js'; import { Email } from '../ts/mail/core/classes.email.js'; import * as net from 'net'; const storageMap = new Map(); const mockDcRouter = { storageManager: { get: async (key: string) => storageMap.get(key) || null, set: async (key: string, value: string) => { storageMap.set(key, value); }, }, }; let server: UnifiedEmailServer; let bridge: RustSecurityBridge; let bridgeAvailable = false; let mockSmtpServer: net.Server; /** * Create a minimal mock SMTP server that accepts any email. * This avoids the IPC deadlock that occurs when the Rust SMTP client * sends to the same Rust process's SMTP server (the IPC stdin reader * blocks on the sendEmail command and can't process emailProcessingResult). */ function createMockSmtpServer(port: number): Promise { return new Promise((resolve, reject) => { const srv = net.createServer((socket) => { socket.write('220 mock-smtp.local ESMTP MockServer\r\n'); let inData = false; let dataBuffer = ''; socket.on('data', (chunk) => { const input = chunk.toString(); if (inData) { dataBuffer += input; if (dataBuffer.includes('\r\n.\r\n')) { inData = false; dataBuffer = ''; socket.write('250 2.0.0 Ok: queued\r\n'); } return; } // Process SMTP commands line by line const lines = input.split('\r\n').filter((l: string) => l.length > 0); for (const line of lines) { const cmd = line.toUpperCase(); if (cmd.startsWith('EHLO') || cmd.startsWith('HELO')) { socket.write(`250-mock-smtp.local\r\n250-SIZE 10485760\r\n250 OK\r\n`); } else if (cmd.startsWith('MAIL FROM')) { socket.write('250 2.1.0 Ok\r\n'); } else if (cmd.startsWith('RCPT TO')) { socket.write('250 2.1.5 Ok\r\n'); } else if (cmd === 'DATA') { inData = true; dataBuffer = ''; socket.write('354 End data with .\r\n'); } else if (cmd === 'QUIT') { socket.write('221 2.0.0 Bye\r\n'); socket.end(); } else if (cmd === 'RSET') { socket.write('250 2.0.0 Ok\r\n'); } } }); }); srv.listen(port, '127.0.0.1', () => { resolve(srv); }); srv.on('error', reject); }); } tap.test('setup - start bridge and mock SMTP server', async () => { RustSecurityBridge.resetInstance(); bridge = RustSecurityBridge.getInstance(); server = new UnifiedEmailServer(mockDcRouter, { ports: [10325], hostname: 'test.outbound.local', domains: [ { domain: 'outbound-test.com', dnsMode: 'forward', }, ], routes: [ { name: 'catch-all', priority: 0, match: { recipients: '*', }, action: { type: 'process', }, }, ], }); try { await server.start(); bridgeAvailable = true; } catch (err) { console.log(`SKIP: Server failed to start — ${(err as Error).message}`); console.log('Build the Rust binary with: cd rust && cargo build --release'); return; } // Start a mock SMTP server on a separate port for outbound delivery tests mockSmtpServer = await createMockSmtpServer(10326); }); tap.test('send email to mock SMTP receiver', async () => { if (!bridgeAvailable) { console.log('SKIP: bridge not running'); return; } const email = new Email({ from: 'sender@outbound-test.com', to: 'recipient@outbound-test.com', subject: 'Outbound E2E Test', text: 'Testing outbound delivery to the mock SMTP server.', }); // Send to the mock SMTP server (port 10326), not the Rust SMTP server (port 10325) const result = await server.sendOutboundEmail('127.0.0.1', 10326, email); expect(result).toBeTruthy(); expect(result.accepted).toBeTruthy(); expect(result.accepted.length).toBeGreaterThan(0); expect(result.response).toBeTruthy(); // Rust SMTP client returns enhanced status code without the 250 prefix expect(result.response).toInclude('2.0.0'); }); tap.test('send email - connection refused', async () => { if (!bridgeAvailable) { console.log('SKIP: bridge not running'); return; } const email = new Email({ from: 'sender@outbound-test.com', to: 'recipient@outbound-test.com', subject: 'Connection Refused Test', text: 'This should fail — no server at port 59888.', }); try { await server.sendOutboundEmail('127.0.0.1', 59888, email); throw new Error('Expected sendOutboundEmail to fail on connection refused'); } catch (err: any) { expect(err).toBeTruthy(); expect(err.message.length).toBeGreaterThan(0); } }); tap.test('SMTP pool status and cleanup', async () => { if (!bridgeAvailable) { console.log('SKIP: bridge not running'); return; } const status: ISmtpPoolStatus = await bridge.getSmtpPoolStatus(); expect(status).toBeTruthy(); expect(status.pools).toBeTruthy(); expect(typeof status.pools).toEqual('object'); // Close all pools await bridge.closeSmtpPool(); // Verify pools are empty const statusAfter = await bridge.getSmtpPoolStatus(); const poolKeys = Object.keys(statusAfter.pools); expect(poolKeys.length).toEqual(0); }); tap.test('cleanup - stop server and mock', async () => { if (mockSmtpServer) { await new Promise((resolve) => mockSmtpServer.close(() => resolve())); } if (bridgeAvailable) { await server.stop(); } await tap.stopForcefully(); }); export default tap.start();