import { tap, expect } from '@git.zone/tstest/tapbundle'; import { DcRouter } from '../ts/classes.dcrouter.js'; import * as plugins from '../ts/plugins.js'; let dcRouter: DcRouter; tap.test('should use traditional port forwarding when useSocketHandler is false', async () => { dcRouter = new DcRouter({ emailConfig: { ports: [25, 587, 465], hostname: 'mail.test.local', domains: ['test.local'], routes: [], useSocketHandler: false // Traditional mode }, smartProxyConfig: { routes: [] } }); await dcRouter.start(); // Check that email server is created and listening on ports const emailServer = (dcRouter as any).emailServer; expect(emailServer).toBeDefined(); // Check SmartProxy routes are forward type const smartProxy = (dcRouter as any).smartProxy; const routes = smartProxy?.options?.routes || []; const emailRoutes = routes.filter((route: any) => route.name?.includes('-route') ); emailRoutes.forEach((route: any) => { expect(route.action.type).toEqual('forward'); expect(route.action.target).toBeDefined(); expect(route.action.target.host).toEqual('localhost'); }); await dcRouter.stop(); }); tap.test('should use socket-handler mode when useSocketHandler is true', async () => { dcRouter = new DcRouter({ emailConfig: { ports: [25, 587, 465], hostname: 'mail.test.local', domains: ['test.local'], routes: [], useSocketHandler: true // Socket-handler mode }, smartProxyConfig: { routes: [] } }); await dcRouter.start(); // Check that email server is created const emailServer = (dcRouter as any).emailServer; expect(emailServer).toBeDefined(); // Check SmartProxy routes are socket-handler type const smartProxy = (dcRouter as any).smartProxy; const routes = smartProxy?.options?.routes || []; const emailRoutes = routes.filter((route: any) => route.name?.includes('-route') ); emailRoutes.forEach((route: any) => { expect(route.action.type).toEqual('socket-handler'); expect(route.action.socketHandler).toBeDefined(); expect(typeof route.action.socketHandler).toEqual('function'); }); await dcRouter.stop(); }); tap.test('should generate correct email routes for each port', async () => { const emailConfig = { ports: [25, 587, 465], hostname: 'mail.test.local', domains: ['test.local'], routes: [], useSocketHandler: true }; dcRouter = new DcRouter({ emailConfig }); // Access the private method to generate routes const emailRoutes = (dcRouter as any).generateEmailRoutes(emailConfig); expect(emailRoutes.length).toEqual(3); // Check SMTP route (port 25) const smtpRoute = emailRoutes.find((r: any) => r.name === 'smtp-route'); expect(smtpRoute).toBeDefined(); expect(smtpRoute.match.ports).toContain(25); expect(smtpRoute.action.type).toEqual('socket-handler'); // Check Submission route (port 587) const submissionRoute = emailRoutes.find((r: any) => r.name === 'submission-route'); expect(submissionRoute).toBeDefined(); expect(submissionRoute.match.ports).toContain(587); expect(submissionRoute.action.type).toEqual('socket-handler'); // Check SMTPS route (port 465) const smtpsRoute = emailRoutes.find((r: any) => r.name === 'smtps-route'); expect(smtpsRoute).toBeDefined(); expect(smtpsRoute.match.ports).toContain(465); expect(smtpsRoute.action.type).toEqual('socket-handler'); }); tap.test('email socket handler should handle different ports correctly', async () => { dcRouter = new DcRouter({ emailConfig: { ports: [25, 587, 465], hostname: 'mail.test.local', domains: ['test.local'], routes: [], useSocketHandler: true } }); await dcRouter.start(); // Test port 25 handler (plain SMTP) const port25Handler = (dcRouter as any).createMailSocketHandler(25); expect(port25Handler).toBeDefined(); expect(typeof port25Handler).toEqual('function'); // Test port 465 handler (SMTPS - should wrap in TLS) const port465Handler = (dcRouter as any).createMailSocketHandler(465); expect(port465Handler).toBeDefined(); expect(typeof port465Handler).toEqual('function'); await dcRouter.stop(); }); tap.test('email server handleSocket method should work', async () => { dcRouter = new DcRouter({ emailConfig: { ports: [25], hostname: 'mail.test.local', domains: ['test.local'], routes: [], useSocketHandler: true } }); await dcRouter.start(); const emailServer = (dcRouter as any).emailServer; expect(emailServer).toBeDefined(); expect(emailServer.handleSocket).toBeDefined(); expect(typeof emailServer.handleSocket).toEqual('function'); // Create a mock socket const mockSocket = new plugins.net.Socket(); let socketDestroyed = false; mockSocket.destroy = () => { socketDestroyed = true; }; // Test handleSocket try { await emailServer.handleSocket(mockSocket, 25); // It will fail because we don't have a real socket, but it should handle it gracefully } catch (error) { // Expected to error with mock socket } await dcRouter.stop(); }); tap.test('should not create SMTP servers when useSocketHandler is true', async () => { dcRouter = new DcRouter({ emailConfig: { ports: [25, 587, 465], hostname: 'mail.test.local', domains: ['test.local'], routes: [], useSocketHandler: true } }); await dcRouter.start(); // The email server should not have any SMTP server instances const emailServer = (dcRouter as any).emailServer; expect(emailServer).toBeDefined(); // The servers array should be empty (no port binding) expect(emailServer.servers).toBeDefined(); expect(emailServer.servers.length).toEqual(0); await dcRouter.stop(); }); tap.test('TLS handling should differ between ports', async () => { const emailConfig = { ports: [25, 465], hostname: 'mail.test.local', domains: ['test.local'], routes: [], useSocketHandler: false // Use traditional mode to check TLS config }; dcRouter = new DcRouter({ emailConfig }); const emailRoutes = (dcRouter as any).generateEmailRoutes(emailConfig); // Port 25 should use passthrough const smtpRoute = emailRoutes.find((r: any) => r.match.ports[0] === 25); expect(smtpRoute.action.tls.mode).toEqual('passthrough'); // Port 465 should use terminate const smtpsRoute = emailRoutes.find((r: any) => r.match.ports[0] === 465); expect(smtpsRoute.action.tls.mode).toEqual('terminate'); expect(smtpsRoute.action.tls.certificate).toEqual('auto'); }); tap.test('stop', async () => { await tap.stopForcefully(); }); export default tap.start();