import { tap, expect } from '@git.zone/tstest/tapbundle'; import { UnifiedEmailServer } from '../ts/mail/routing/classes.unified.email.server.js'; import { RustSecurityBridge } from '../ts/security/classes.rustsecuritybridge.js'; import { Email } from '../ts/mail/core/classes.email.js'; import { SmtpState } from '../ts/mail/delivery/interfaces.js'; 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; /** * Build a minimal SMTP session object for processEmailByMode(). */ function buildSession(email: Email): any { return { id: `test-${Date.now()}-${Math.random().toString(36).substring(2)}`, state: SmtpState.FINISHED, mailFrom: email.from, rcptTo: email.to, emailData: '', useTLS: false, connectionEnded: false, remoteAddress: '127.0.0.1', clientHostname: 'test-client.local', secure: false, authenticated: false, envelope: { mailFrom: { address: email.from, args: {} }, rcptTo: email.to.map((addr: string) => ({ address: addr, args: {} })), }, }; } tap.test('setup - start server with routing rules on port 10225', async () => { RustSecurityBridge.resetInstance(); bridge = RustSecurityBridge.getInstance(); server = new UnifiedEmailServer(mockDcRouter, { ports: [10225], hostname: 'test.routing.local', domains: [ { domain: 'process.com', dnsMode: 'forward' }, { domain: 'local.com', dnsMode: 'forward' }, { domain: 'external.com', dnsMode: 'forward' }, ], routes: [ { name: 'reject-route', priority: 40, match: { senders: '*@spammer.com', }, action: { type: 'reject', reject: { code: 550, message: 'Spam rejected', }, }, }, { name: 'process-route', priority: 30, match: { recipients: '*@process.com', }, action: { type: 'process', process: { scan: true, }, }, }, { name: 'deliver-route', priority: 20, match: { recipients: '*@local.com', }, action: { type: 'deliver', }, }, { name: 'forward-route', priority: 10, match: { recipients: '*@external.com', }, action: { type: 'forward', forward: { host: '127.0.0.1', port: 59999, // No server listening — expected failure }, }, }, ], }); 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'); } }); tap.test('process action - queues email for processing', async () => { if (!bridgeAvailable) { console.log('SKIP: bridge not running'); return; } const email = new Email({ from: 'sender@example.com', to: 'user@process.com', subject: 'Process test', text: 'This email should be queued for processing.', }); const session = buildSession(email); const result = await server.processEmailByMode(email, session); expect(result).toBeTruthy(); const stats = server.deliveryQueue.getStats(); expect(stats.modes.process).toBeGreaterThan(0); }); tap.test('deliver action - queues email for MTA delivery', async () => { if (!bridgeAvailable) { console.log('SKIP: bridge not running'); return; } const email = new Email({ from: 'sender@example.com', to: 'user@local.com', subject: 'Deliver test', text: 'This email should be queued for local delivery.', }); const session = buildSession(email); const result = await server.processEmailByMode(email, session); expect(result).toBeTruthy(); const stats = server.deliveryQueue.getStats(); expect(stats.modes.mta).toBeGreaterThan(0); }); tap.test('reject action - throws with correct code', async () => { if (!bridgeAvailable) { console.log('SKIP: bridge not running'); return; } const email = new Email({ from: 'bad@spammer.com', to: 'user@process.com', subject: 'Spam attempt', text: 'This should be rejected.', }); const session = buildSession(email); try { await server.processEmailByMode(email, session); throw new Error('Expected processEmailByMode to throw for rejected email'); } catch (err: any) { expect(err.responseCode).toEqual(550); expect(err.message).toInclude('Spam rejected'); } }); tap.test('forward action - fails to unreachable host', async () => { if (!bridgeAvailable) { console.log('SKIP: bridge not running'); return; } const email = new Email({ from: 'sender@example.com', to: 'user@external.com', subject: 'Forward test', text: 'This forward should fail — no server at port 59999.', }); const session = buildSession(email); try { await server.processEmailByMode(email, session); throw new Error('Expected processEmailByMode to throw for unreachable forward host'); } catch (err: any) { // We expect an error from the failed SMTP connection expect(err).toBeTruthy(); expect(err.message).toBeTruthy(); } }); tap.test('no matching route - throws error', async () => { if (!bridgeAvailable) { console.log('SKIP: bridge not running'); return; } const email = new Email({ from: 'sender@example.com', to: 'nobody@unmatched.com', subject: 'Unmatched route test', text: 'No route matches this recipient.', }); const session = buildSession(email); try { await server.processEmailByMode(email, session); throw new Error('Expected processEmailByMode to throw for no matching route'); } catch (err: any) { expect(err.message).toInclude('No matching route'); } }); tap.test('cleanup - stop server', async () => { if (bridgeAvailable) { await server.stop(); } await tap.stopForcefully(); }); export default tap.start();