import { tap, expect } from '@git.zone/tstest/tapbundle'; import { type IEmailRoute } from '../ts/mail/routing/interfaces.js'; import { EmailRouter } from '../ts/mail/routing/classes.email.router.js'; import { Email } from '../ts/mail/core/classes.email.js'; tap.test('Email Integration - Route-based forwarding scenario', async () => { // Define routes with match/action pattern const routes: IEmailRoute[] = [ { name: 'office-relay', priority: 100, match: { clientIp: '192.168.0.0/16' }, action: { type: 'forward', forward: { host: 'internal.mail.example.com', port: 25 } } }, { name: 'company-mail', priority: 50, match: { recipients: '*@mycompany.com' }, action: { type: 'process', process: { scan: true, dkim: true, queue: 'normal' } } }, { name: 'admin-priority', priority: 90, match: { recipients: 'admin@mycompany.com' }, action: { type: 'process', process: { scan: true, dkim: true, queue: 'priority' } } }, { name: 'spam-reject', priority: 80, match: { senders: '*@spammer.com' }, action: { type: 'reject', reject: { code: 550, message: 'Sender blocked' } } }, { name: 'default-reject', priority: 1, match: { recipients: '*' }, action: { type: 'reject', reject: { code: 550, message: 'Relay denied' } } } ]; // Create email router with routes const emailRouter = new EmailRouter(routes); // Test route priority sorting const sortedRoutes = emailRouter.getRoutes(); expect(sortedRoutes[0].name).toEqual('office-relay'); // Highest priority (100) expect(sortedRoutes[1].name).toEqual('admin-priority'); // Priority 90 expect(sortedRoutes[2].name).toEqual('spam-reject'); // Priority 80 expect(sortedRoutes[sortedRoutes.length - 1].name).toEqual('default-reject'); // Lowest priority (1) // Test route evaluation with different scenarios const testCases = [ { description: 'Office relay scenario (IP-based)', email: new Email({ from: 'user@external.com', to: 'anyone@anywhere.com', subject: 'Test from office', text: 'Test message' }), session: { id: 'test-1', remoteAddress: '192.168.1.100' }, expectedRoute: 'office-relay' }, { description: 'Admin priority mail', email: new Email({ from: 'user@external.com', to: 'admin@mycompany.com', subject: 'Important admin message', text: 'Admin message content' }), session: { id: 'test-2', remoteAddress: '10.0.0.1' }, expectedRoute: 'admin-priority' }, { description: 'Company mail processing', email: new Email({ from: 'partner@partner.com', to: 'sales@mycompany.com', subject: 'Business proposal', text: 'Business content' }), session: { id: 'test-3', remoteAddress: '203.0.113.1' }, expectedRoute: 'company-mail' }, { description: 'Spam rejection', email: new Email({ from: 'bad@spammer.com', to: 'victim@mycompany.com', subject: 'Spam message', text: 'Spam content' }), session: { id: 'test-4', remoteAddress: '203.0.113.2' }, expectedRoute: 'spam-reject' }, { description: 'Default rejection', email: new Email({ from: 'unknown@unknown.com', to: 'random@random.com', subject: 'Random message', text: 'Random content' }), session: { id: 'test-5', remoteAddress: '203.0.113.3' }, expectedRoute: 'default-reject' } ]; for (const testCase of testCases) { const context = { email: testCase.email, session: testCase.session as any }; const matchedRoute = await emailRouter.evaluateRoutes(context); expect(matchedRoute).not.toEqual(null); expect(matchedRoute?.name).toEqual(testCase.expectedRoute); console.log(`✓ ${testCase.description}: Matched route '${matchedRoute?.name}'`); } }); tap.test('Email Integration - CIDR IP matching', async () => { const routes: IEmailRoute[] = [ { name: 'internal-network', match: { clientIp: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] }, action: { type: 'deliver' } }, { name: 'specific-subnet', priority: 10, match: { clientIp: '192.168.1.0/24' }, action: { type: 'forward', forward: { host: 'subnet-mail.com', port: 25 } } } ]; const emailRouter = new EmailRouter(routes); const testIps = [ { ip: '192.168.1.100', expectedRoute: 'specific-subnet' }, // More specific match { ip: '192.168.2.100', expectedRoute: 'internal-network' }, // General internal { ip: '10.5.10.20', expectedRoute: 'internal-network' }, { ip: '172.16.5.10', expectedRoute: 'internal-network' } ]; for (const testCase of testIps) { const context = { email: new Email({ from: 'test@test.com', to: 'user@test.com', subject: 'Test', text: 'Test' }), session: { id: 'test', remoteAddress: testCase.ip } as any }; const route = await emailRouter.evaluateRoutes(context); expect(route?.name).toEqual(testCase.expectedRoute); console.log(`✓ IP ${testCase.ip}: Matched route '${route?.name}'`); } }); tap.test('Email Integration - Authentication-based routing', async () => { const routes: IEmailRoute[] = [ { name: 'authenticated-relay', priority: 100, match: { authenticated: true }, action: { type: 'forward', forward: { host: 'relay.example.com', port: 587 } } }, { name: 'unauthenticated-local', match: { authenticated: false, recipients: '*@localserver.com' }, action: { type: 'deliver' } }, { name: 'unauthenticated-reject', match: { authenticated: false }, action: { type: 'reject', reject: { code: 550, message: 'Authentication required' } } } ]; const emailRouter = new EmailRouter(routes); // Test authenticated user const authContext = { email: new Email({ from: 'user@anywhere.com', to: 'dest@anywhere.com', subject: 'Test', text: 'Test' }), session: { id: 'auth-test', remoteAddress: '203.0.113.1', authenticated: true, authenticatedUser: 'user@anywhere.com' } as any }; const authRoute = await emailRouter.evaluateRoutes(authContext); expect(authRoute?.name).toEqual('authenticated-relay'); // Test unauthenticated local delivery const localContext = { email: new Email({ from: 'external@external.com', to: 'user@localserver.com', subject: 'Test', text: 'Test' }), session: { id: 'local-test', remoteAddress: '203.0.113.2', authenticated: false } as any }; const localRoute = await emailRouter.evaluateRoutes(localContext); expect(localRoute?.name).toEqual('unauthenticated-local'); // Test unauthenticated rejection const rejectContext = { email: new Email({ from: 'external@external.com', to: 'user@external.com', subject: 'Test', text: 'Test' }), session: { id: 'reject-test', remoteAddress: '203.0.113.3', authenticated: false } as any }; const rejectRoute = await emailRouter.evaluateRoutes(rejectContext); expect(rejectRoute?.name).toEqual('unauthenticated-reject'); console.log('✓ Authentication-based routing works correctly'); }); tap.test('Email Integration - Pattern caching performance', async () => { const routes: IEmailRoute[] = [ { name: 'complex-pattern', match: { recipients: ['*@domain1.com', '*@domain2.com', 'admin@*.domain3.com'], senders: 'partner-*@*.partner.net' }, action: { type: 'forward', forward: { host: 'partner-relay.com', port: 25 } } } ]; const emailRouter = new EmailRouter(routes); const email = new Email({ from: 'partner-sales@us.partner.net', to: 'admin@sales.domain3.com', subject: 'Test', text: 'Test' }); const context = { email, session: { id: 'perf-test', remoteAddress: '10.0.0.1' } as any }; // First evaluation - should populate cache const start1 = Date.now(); const route1 = await emailRouter.evaluateRoutes(context); const time1 = Date.now() - start1; // Second evaluation - should use cache const start2 = Date.now(); const route2 = await emailRouter.evaluateRoutes(context); const time2 = Date.now() - start2; expect(route1?.name).toEqual('complex-pattern'); expect(route2?.name).toEqual('complex-pattern'); // Cache should make second evaluation faster (though this is timing-dependent) console.log(`✓ Pattern caching: First evaluation: ${time1}ms, Second: ${time2}ms`); }); tap.test('Email Integration - Route update functionality', async () => { const initialRoutes: IEmailRoute[] = [ { name: 'test-route', match: { recipients: '*@test.com' }, action: { type: 'deliver' } } ]; const emailRouter = new EmailRouter(initialRoutes); // Test initial configuration expect(emailRouter.getRoutes().length).toEqual(1); expect(emailRouter.getRoutes()[0].name).toEqual('test-route'); // Update routes const newRoutes: IEmailRoute[] = [ { name: 'updated-route', match: { recipients: '*@updated.com' }, action: { type: 'forward', forward: { host: 'new-server.com', port: 25 } } }, { name: 'additional-route', match: { recipients: '*@additional.com' }, action: { type: 'reject', reject: { code: 550, message: 'Blocked' } } } ]; emailRouter.updateRoutes(newRoutes); // Verify routes were updated expect(emailRouter.getRoutes().length).toEqual(2); expect(emailRouter.getRoutes()[0].name).toEqual('updated-route'); expect(emailRouter.getRoutes()[1].name).toEqual('additional-route'); console.log('✓ Route update functionality works correctly'); }); tap.test('stop', async () => { await tap.stopForcefully(); }); export default tap.start();