import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../ts/plugins.js'; import * as path from 'path'; import * as fs from 'fs'; import { DcRouter, type IDcRouterOptions, type IEmailConfig, type EmailProcessingMode, type IDomainRule } from '../ts/classes.dcrouter.js'; // Mock platform service for testing const mockPlatformService = { mtaService: { saveToLocalMailbox: async (email: any) => { // Mock implementation console.log('Mock: Saving email to local mailbox'); return Promise.resolve(); }, isBounceNotification: (email: any) => false, processBounceNotification: async (email: any) => { // Mock implementation return Promise.resolve(); } } }; tap.test('DcRouter class - Custom email port configuration', async () => { // Define custom port mapping const customPortMapping = { 25: 11025, // Custom SMTP port mapping 587: 11587, // Custom submission port mapping 465: 11465, // Custom SMTPS port mapping 2525: 12525 // Additional custom port }; // Create a custom email configuration const emailConfig: IEmailConfig = { ports: [25, 587, 465, 2525], // Added a non-standard port hostname: 'mail.example.com', maxMessageSize: 50 * 1024 * 1024, // 50MB defaultMode: 'forward' as EmailProcessingMode, defaultServer: 'fallback-mail.example.com', defaultPort: 25, defaultTls: true, domainRules: [ { pattern: '*@example.com', mode: 'forward' as EmailProcessingMode, target: { server: 'mail1.example.com', port: 25, useTls: true } }, { pattern: '*@example.org', mode: 'mta' as EmailProcessingMode, mtaOptions: { domain: 'example.org', allowLocalDelivery: true } } ] }; // Create custom email storage path const customEmailsPath = path.join(process.cwd(), 'email'); // Ensure directory exists and is empty if (fs.existsSync(customEmailsPath)) { try { fs.rmdirSync(customEmailsPath, { recursive: true }); } catch (e) { console.warn('Could not remove test directory:', e); } } fs.mkdirSync(customEmailsPath, { recursive: true }); // Create DcRouter options with custom email port configuration const options: IDcRouterOptions = { emailConfig, emailPortConfig: { portMapping: customPortMapping, portSettings: { 2525: { terminateTls: false, routeName: 'custom-smtp-route' } }, receivedEmailsPath: customEmailsPath }, tls: { contactEmail: 'test@example.com' } }; // Create DcRouter instance with mock platform service const router = new DcRouter(options, mockPlatformService); // Verify the options are correctly set expect(router.options.emailPortConfig).toBeTruthy(); expect(router.options.emailPortConfig.portMapping).toEqual(customPortMapping); expect(router.options.emailPortConfig.receivedEmailsPath).toEqual(customEmailsPath); // Test the generateEmailRoutes method if (typeof router['generateEmailRoutes'] === 'function') { const routes = router['generateEmailRoutes'](emailConfig); // Verify that all ports are configured expect(routes.length).toBeGreaterThan(0); // At least some routes are configured // Check the custom port configuration const customPortRoute = routes.find(r => r.match.ports?.includes(2525)); expect(customPortRoute).toBeTruthy(); expect(customPortRoute?.name).toEqual('custom-smtp-route'); expect(customPortRoute?.action.target.port).toEqual(12525); // Check standard port mappings const smtpRoute = routes.find(r => r.match.ports?.includes(25)); expect(smtpRoute?.action.target.port).toEqual(11025); const submissionRoute = routes.find(r => r.match.ports?.includes(587)); expect(submissionRoute?.action.target.port).toEqual(11587); } // Clean up try { fs.rmdirSync(customEmailsPath, { recursive: true }); } catch (e) { console.warn('Could not remove test directory in cleanup:', e); } }); tap.test('DcRouter class - Custom email storage path', async () => { // Create custom email storage path const customEmailsPath = path.join(process.cwd(), 'email'); // Ensure directory exists and is empty if (fs.existsSync(customEmailsPath)) { try { fs.rmdirSync(customEmailsPath, { recursive: true }); } catch (e) { console.warn('Could not remove test directory:', e); } } fs.mkdirSync(customEmailsPath, { recursive: true }); // Create a basic email configuration const emailConfig: IEmailConfig = { ports: [25], hostname: 'mail.example.com', defaultMode: 'mta' as EmailProcessingMode, domainRules: [] }; // Create DcRouter options with custom email storage path const options: IDcRouterOptions = { emailConfig, emailPortConfig: { receivedEmailsPath: customEmailsPath }, tls: { contactEmail: 'test@example.com' } }; // Create a mock MTA service with a trackable saveToLocalMailbox method let savedToCustomPath = false; const mockEmail = { to: ['test@example.com'], toRFC822String: () => 'From: sender@example.com\nTo: test@example.com\nSubject: Test\n\nTest email' }; const mockMtaService = { saveToLocalMailbox: async (email: any) => { console.log('Original saveToLocalMailbox called'); return Promise.resolve(); }, isBounceNotification: (email: any) => false, processBounceNotification: async (email: any) => { return Promise.resolve(); } }; const mockPlatformServiceWithMta = { mtaService: mockMtaService }; // Create DcRouter instance with mock platform service const router = new DcRouter(options, mockPlatformServiceWithMta); // Import the patch function and apply it directly for testing const { configureEmailStorage } = await import('../ts/mail/delivery/classes.mta.patch.js'); configureEmailStorage(mockMtaService, { receivedEmailsPath: customEmailsPath }); // Call the patched method await mockMtaService.saveToLocalMailbox(mockEmail); // Check if a file was created in the custom path const files = fs.readdirSync(customEmailsPath); expect(files.length).toBeGreaterThan(0); expect(files[0].endsWith('.eml')).toEqual(true); // Verify file contents const fileContent = fs.readFileSync(path.join(customEmailsPath, files[0]), 'utf8'); expect(fileContent).toContain('From: sender@example.com'); // Clean up try { fs.rmdirSync(customEmailsPath, { recursive: true }); } catch (e) { console.warn('Could not remove test directory in cleanup:', e); } }); // Final clean-up test tap.test('clean up after tests', async () => { // No-op - just to make sure everything is cleaned up properly }); tap.test('stop', async () => { await tap.stopForcefully(); }); // Export a function to run all tests export default tap.start();