import { expect, tap } from '@push.rocks/tapbundle'; import * as tsclass from '@tsclass/tsclass'; import * as http from 'http'; import { ProxyRouter, type IRouterResult } from '../ts/classes.router.js'; // Test proxies and configurations let router: ProxyRouter; // 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 proxy configuration function createProxyConfig( hostname: string, destinationIp: string = '10.0.0.1', destinationPort: number = 8080 ): tsclass.network.IReverseProxyConfig { return { hostName: hostname, destinationIp, destinationPort: destinationPort.toString(), // Convert to string for IReverseProxyConfig publicKey: 'mock-cert', privateKey: 'mock-key' } as tsclass.network.IReverseProxyConfig; } // SETUP: Create a ProxyRouter instance tap.test('setup proxy router test environment', async () => { router = new ProxyRouter(); // Initialize with empty config router.setNewProxyConfigs([]); }); // Test basic routing by hostname tap.test('should route requests by hostname', async () => { const config = createProxyConfig(TEST_DOMAIN); router.setNewProxyConfigs([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 = createProxyConfig(TEST_DOMAIN); router.setNewProxyConfigs([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 = createProxyConfig(TEST_DOMAIN.toLowerCase()); router.setNewProxyConfigs([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 = createProxyConfig(TEST_DOMAIN); router.setNewProxyConfigs([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 = createProxyConfig(TEST_DOMAIN); router.setNewProxyConfigs([config]); // Add a path pattern to the config router.setPathPattern(config, '/api/users'); // Test that path matches const req1 = createMockRequest(TEST_DOMAIN, '/api/users'); const result1 = router.routeReqWithDetails(req1); expect(result1).toBeTruthy(); expect(result1.config).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 = createProxyConfig(TEST_DOMAIN); router.setNewProxyConfigs([config]); router.setPathPattern(config, '/api/*'); // 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.config).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 = createProxyConfig(TEST_DOMAIN); router.setNewProxyConfigs([config]); router.setPathPattern(config, '/users/:id/profile'); const req = createMockRequest(TEST_DOMAIN, '/users/123/profile'); const result = router.routeReqWithDetails(req); expect(result).toBeTruthy(); expect(result.config).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 = createProxyConfig(TEST_DOMAIN, '10.0.0.1', 8001); const webConfig = createProxyConfig(TEST_DOMAIN, '10.0.0.2', 8002); // Add both configs router.setNewProxyConfigs([apiConfig, webConfig]); // Set different path patterns router.setPathPattern(apiConfig, '/api'); router.setPathPattern(webConfig, '/web'); // 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 = createProxyConfig(TEST_WILDCARD); router.setNewProxyConfigs([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 = createProxyConfig('example.*'); router.setNewProxyConfigs([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 = createProxyConfig('*.lossless*'); router.setNewProxyConfigs([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 = createProxyConfig('*'); const specificConfig = createProxyConfig(TEST_DOMAIN); router.setNewProxyConfigs([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 = createProxyConfig(TEST_WILDCARD); const exactConfig = createProxyConfig(TEST_SUBDOMAIN); router.setNewProxyConfigs([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.setNewProxyConfigs([]); // Add a config const config = createProxyConfig(TEST_DOMAIN); router.addProxyConfig(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 const removed = router.removeProxyConfig(TEST_DOMAIN); expect(removed).toBeTrue(); result = router.routeReq(req); expect(result).toBeUndefined(); }); // Test path pattern specificity tap.test('should prioritize more specific path patterns', async () => { const genericConfig = createProxyConfig(TEST_DOMAIN, '10.0.0.1', 8001); const specificConfig = createProxyConfig(TEST_DOMAIN, '10.0.0.2', 8002); router.setNewProxyConfigs([genericConfig, specificConfig]); router.setPathPattern(genericConfig, '/api/*'); router.setPathPattern(specificConfig, '/api/users'); // 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 getHostnames method tap.test('should retrieve all configured hostnames', async () => { router.setNewProxyConfigs([ createProxyConfig(TEST_DOMAIN), createProxyConfig(TEST_SUBDOMAIN) ]); const hostnames = router.getHostnames(); expect(hostnames.length).toEqual(2); expect(hostnames).toContain(TEST_DOMAIN.toLowerCase()); expect(hostnames).toContain(TEST_SUBDOMAIN.toLowerCase()); }); // Test handling missing host header tap.test('should handle missing host header', async () => { const defaultConfig = createProxyConfig('*'); router.setNewProxyConfigs([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 = createProxyConfig(TEST_DOMAIN); router.setNewProxyConfigs([config]); router.setPathPattern(config, '/api/:version/users/:userId/posts/:postId'); const req = createMockRequest(TEST_DOMAIN, '/api/v1/users/123/posts/456'); const result = router.routeReqWithDetails(req); expect(result).toBeTruthy(); expect(result.config).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(createProxyConfig(`host-${i}.example.com`)); } router.setNewProxyConfigs(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.setNewProxyConfigs([]); // Verify empty state expect(router.getHostnames().length).toEqual(0); expect(router.getProxyConfigs().length).toEqual(0); }); export default tap.start();