import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as http from 'http'; import { HttpRouter, type RouterResult } from '../ts/routing/router/http-router.js'; import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js'; // Test proxies and configurations let router: HttpRouter; // Sample hostname for testing const TEST_DOMAIN = 'example.com'; const TEST_SUBDOMAIN = 'api.example.com'; const TEST_WILDCARD = '*.example.com'; // Helper: Creates a mock HTTP request for testing function createMockRequest(host: string, url: string = '/'): http.IncomingMessage { const req = { headers: { host }, url, socket: { remoteAddress: '127.0.0.1' } } as any; return req; } // Helper: Creates a test route configuration function createRouteConfig( hostname: string, destinationIp: string = '10.0.0.1', destinationPort: number = 8080 ): IRouteConfig { return { name: `route-${hostname}`, match: { domains: [hostname], ports: 443 }, action: { type: 'forward', target: { host: destinationIp, port: destinationPort } } }; } // SETUP: Create an HttpRouter instance tap.test('setup http router test environment', async () => { router = new HttpRouter(); // Initialize with empty config router.updateRoutes([]); }); // Test basic routing by hostname tap.test('should route requests by hostname', async () => { const config = createRouteConfig(TEST_DOMAIN); router.updateRoutes([config]); const req = createMockRequest(TEST_DOMAIN); const result = router.routeReq(req); expect(result).toBeTruthy(); expect(result).toEqual(config); }); // Test handling of hostname with port number tap.test('should handle hostname with port number', async () => { const config = createRouteConfig(TEST_DOMAIN); router.updateRoutes([config]); const req = createMockRequest(`${TEST_DOMAIN}:443`); const result = router.routeReq(req); expect(result).toBeTruthy(); expect(result).toEqual(config); }); // Test case-insensitive hostname matching tap.test('should perform case-insensitive hostname matching', async () => { const config = createRouteConfig(TEST_DOMAIN.toLowerCase()); router.updateRoutes([config]); const req = createMockRequest(TEST_DOMAIN.toUpperCase()); const result = router.routeReq(req); expect(result).toBeTruthy(); expect(result).toEqual(config); }); // Test handling of unmatched hostnames tap.test('should return undefined for unmatched hostnames', async () => { const config = createRouteConfig(TEST_DOMAIN); router.updateRoutes([config]); const req = createMockRequest('unknown.domain.com'); const result = router.routeReq(req); expect(result).toBeUndefined(); }); // Test adding path patterns tap.test('should match requests using path patterns', async () => { const config = createRouteConfig(TEST_DOMAIN); config.match.path = '/api/users'; router.updateRoutes([config]); // Test that path matches const req1 = createMockRequest(TEST_DOMAIN, '/api/users'); const result1 = router.routeReqWithDetails(req1); expect(result1).toBeTruthy(); expect(result1.route).toEqual(config); expect(result1.pathMatch).toEqual('/api/users'); // Test that non-matching path doesn't match const req2 = createMockRequest(TEST_DOMAIN, '/web/users'); const result2 = router.routeReqWithDetails(req2); expect(result2).toBeUndefined(); }); // Test handling wildcard patterns tap.test('should support wildcard path patterns', async () => { const config = createRouteConfig(TEST_DOMAIN); config.match.path = '/api/*'; router.updateRoutes([config]); // Test with path that matches the wildcard pattern const req = createMockRequest(TEST_DOMAIN, '/api/users/123'); const result = router.routeReqWithDetails(req); expect(result).toBeTruthy(); expect(result.route).toEqual(config); expect(result.pathMatch).toEqual('/api'); // Print the actual value to diagnose issues console.log('Path remainder value:', result.pathRemainder); expect(result.pathRemainder).toBeTruthy(); expect(result.pathRemainder).toEqual('/users/123'); }); // Test extracting path parameters tap.test('should extract path parameters from URL', async () => { const config = createRouteConfig(TEST_DOMAIN); config.match.path = '/users/:id/profile'; router.updateRoutes([config]); const req = createMockRequest(TEST_DOMAIN, '/users/123/profile'); const result = router.routeReqWithDetails(req); expect(result).toBeTruthy(); expect(result.route).toEqual(config); expect(result.pathParams).toBeTruthy(); expect(result.pathParams.id).toEqual('123'); }); // Test multiple configs for same hostname with different paths tap.test('should support multiple configs for same hostname with different paths', async () => { const apiConfig = createRouteConfig(TEST_DOMAIN, '10.0.0.1', 8001); apiConfig.match.path = '/api'; apiConfig.name = 'api-route'; const webConfig = createRouteConfig(TEST_DOMAIN, '10.0.0.2', 8002); webConfig.match.path = '/web'; webConfig.name = 'web-route'; // Add both configs router.updateRoutes([apiConfig, webConfig]); // Test API path routes to API config const apiReq = createMockRequest(TEST_DOMAIN, '/api/users'); const apiResult = router.routeReq(apiReq); expect(apiResult).toEqual(apiConfig); // Test web path routes to web config const webReq = createMockRequest(TEST_DOMAIN, '/web/dashboard'); const webResult = router.routeReq(webReq); expect(webResult).toEqual(webConfig); // Test unknown path returns undefined const unknownReq = createMockRequest(TEST_DOMAIN, '/unknown'); const unknownResult = router.routeReq(unknownReq); expect(unknownResult).toBeUndefined(); }); // Test wildcard subdomains tap.test('should match wildcard subdomains', async () => { const wildcardConfig = createRouteConfig(TEST_WILDCARD); router.updateRoutes([wildcardConfig]); // Test that subdomain.example.com matches *.example.com const req = createMockRequest('subdomain.example.com'); const result = router.routeReq(req); expect(result).toBeTruthy(); expect(result).toEqual(wildcardConfig); }); // Test TLD wildcards (example.*) tap.test('should match TLD wildcards', async () => { const tldWildcardConfig = createRouteConfig('example.*'); router.updateRoutes([tldWildcardConfig]); // Test that example.com matches example.* const req1 = createMockRequest('example.com'); const result1 = router.routeReq(req1); expect(result1).toBeTruthy(); expect(result1).toEqual(tldWildcardConfig); // Test that example.org matches example.* const req2 = createMockRequest('example.org'); const result2 = router.routeReq(req2); expect(result2).toBeTruthy(); expect(result2).toEqual(tldWildcardConfig); // Test that subdomain.example.com doesn't match example.* const req3 = createMockRequest('subdomain.example.com'); const result3 = router.routeReq(req3); expect(result3).toBeUndefined(); }); // Test complex pattern matching (*.lossless*) tap.test('should match complex wildcard patterns', async () => { const complexWildcardConfig = createRouteConfig('*.lossless*'); router.updateRoutes([complexWildcardConfig]); // Test that sub.lossless.com matches *.lossless* const req1 = createMockRequest('sub.lossless.com'); const result1 = router.routeReq(req1); expect(result1).toBeTruthy(); expect(result1).toEqual(complexWildcardConfig); // Test that api.lossless.org matches *.lossless* const req2 = createMockRequest('api.lossless.org'); const result2 = router.routeReq(req2); expect(result2).toBeTruthy(); expect(result2).toEqual(complexWildcardConfig); // Test that losslessapi.com matches *.lossless* const req3 = createMockRequest('losslessapi.com'); const result3 = router.routeReq(req3); expect(result3).toBeUndefined(); // Should not match as it doesn't have a subdomain }); // Test default configuration fallback tap.test('should fall back to default configuration', async () => { const defaultConfig = createRouteConfig('*'); const specificConfig = createRouteConfig(TEST_DOMAIN); router.updateRoutes([defaultConfig, specificConfig]); // Test specific domain routes to specific config const specificReq = createMockRequest(TEST_DOMAIN); const specificResult = router.routeReq(specificReq); expect(specificResult).toEqual(specificConfig); // Test unknown domain falls back to default config const unknownReq = createMockRequest('unknown.com'); const unknownResult = router.routeReq(unknownReq); expect(unknownResult).toEqual(defaultConfig); }); // Test priority between exact and wildcard matches tap.test('should prioritize exact hostname over wildcard', async () => { const wildcardConfig = createRouteConfig(TEST_WILDCARD); const exactConfig = createRouteConfig(TEST_SUBDOMAIN); router.updateRoutes([wildcardConfig, exactConfig]); // Test that exact match takes priority const req = createMockRequest(TEST_SUBDOMAIN); const result = router.routeReq(req); expect(result).toEqual(exactConfig); }); // Test adding and removing configurations tap.test('should manage configurations correctly', async () => { router.updateRoutes([]); // Add a config const config = createRouteConfig(TEST_DOMAIN); router.updateRoutes([config]); // Verify routing works const req = createMockRequest(TEST_DOMAIN); let result = router.routeReq(req); expect(result).toEqual(config); // Remove the config and verify it no longer routes router.updateRoutes([]); result = router.routeReq(req); expect(result).toBeUndefined(); }); // Test path pattern specificity tap.test('should prioritize more specific path patterns', async () => { const genericConfig = createRouteConfig(TEST_DOMAIN, '10.0.0.1', 8001); genericConfig.match.path = '/api/*'; genericConfig.name = 'generic-api'; const specificConfig = createRouteConfig(TEST_DOMAIN, '10.0.0.2', 8002); specificConfig.match.path = '/api/users'; specificConfig.name = 'specific-api'; specificConfig.priority = 10; // Higher priority router.updateRoutes([genericConfig, specificConfig]); // The more specific '/api/users' should match before the '/api/*' wildcard const req = createMockRequest(TEST_DOMAIN, '/api/users'); const result = router.routeReq(req); expect(result).toEqual(specificConfig); }); // Test multiple hostnames tap.test('should handle multiple configured hostnames', async () => { const routes = [ createRouteConfig(TEST_DOMAIN), createRouteConfig(TEST_SUBDOMAIN) ]; router.updateRoutes(routes); // Test first domain routes correctly const req1 = createMockRequest(TEST_DOMAIN); const result1 = router.routeReq(req1); expect(result1).toEqual(routes[0]); // Test second domain routes correctly const req2 = createMockRequest(TEST_SUBDOMAIN); const result2 = router.routeReq(req2); expect(result2).toEqual(routes[1]); }); // Test handling missing host header tap.test('should handle missing host header', async () => { const defaultConfig = createRouteConfig('*'); router.updateRoutes([defaultConfig]); const req = createMockRequest(''); req.headers.host = undefined; const result = router.routeReq(req); expect(result).toEqual(defaultConfig); }); // Test complex path parameters tap.test('should handle complex path parameters', async () => { const config = createRouteConfig(TEST_DOMAIN); config.match.path = '/api/:version/users/:userId/posts/:postId'; router.updateRoutes([config]); const req = createMockRequest(TEST_DOMAIN, '/api/v1/users/123/posts/456'); const result = router.routeReqWithDetails(req); expect(result).toBeTruthy(); expect(result.route).toEqual(config); expect(result.pathParams).toBeTruthy(); expect(result.pathParams.version).toEqual('v1'); expect(result.pathParams.userId).toEqual('123'); expect(result.pathParams.postId).toEqual('456'); }); // Performance test tap.test('should handle many configurations efficiently', async () => { const configs = []; // Create many configs with different hostnames for (let i = 0; i < 100; i++) { configs.push(createRouteConfig(`host-${i}.example.com`)); } router.updateRoutes(configs); // Test middle of the list to avoid best/worst case const req = createMockRequest('host-50.example.com'); const result = router.routeReq(req); expect(result).toEqual(configs[50]); }); // Test cleanup tap.test('cleanup proxy router test environment', async () => { // Clear all configurations router.updateRoutes([]); // Verify empty state by testing that no routes match const req = createMockRequest(TEST_DOMAIN); const result = router.routeReq(req); expect(result).toBeUndefined(); }); export default tap.start();