smartproxy/test/test.router.ts

392 lines
12 KiB
TypeScript

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();