/** * Tests for the unified route-based configuration system */ import { expect, tap } from '@push.rocks/tapbundle'; // Import from core modules import { SmartProxy } from '../ts/proxies/smart-proxy/index.js'; // Import route utilities and helpers import { findMatchingRoutes, findBestMatchingRoute, routeMatchesDomain, routeMatchesPort, routeMatchesPath, routeMatchesHeaders, mergeRouteConfigs, generateRouteId, cloneRoute } from '../ts/proxies/smart-proxy/utils/route-utils.js'; import { validateRouteConfig, validateRoutes, isValidDomain, isValidPort, hasRequiredPropertiesForAction, assertValidRoute } from '../ts/proxies/smart-proxy/utils/route-validators.js'; import { createHttpRoute, createHttpsTerminateRoute, createHttpsPassthroughRoute, createHttpToHttpsRedirect, createCompleteHttpsServer, createLoadBalancerRoute, createStaticFileRoute, createApiRoute, createWebSocketRoute } from '../ts/proxies/smart-proxy/utils/route-helpers.js'; // Import test helpers import { loadTestCertificates } from './helpers/certificates.js'; import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js'; // --------------------------------- Route Creation Tests --------------------------------- tap.test('Routes: Should create basic HTTP route', async () => { // Create a simple HTTP route const httpRoute = createHttpRoute('example.com', { host: 'localhost', port: 3000 }, { name: 'Basic HTTP Route' }); // Validate the route configuration expect(httpRoute.match.ports).toEqual(80); expect(httpRoute.match.domains).toEqual('example.com'); expect(httpRoute.action.type).toEqual('forward'); expect(httpRoute.action.target?.host).toEqual('localhost'); expect(httpRoute.action.target?.port).toEqual(3000); expect(httpRoute.name).toEqual('Basic HTTP Route'); }); tap.test('Routes: Should create HTTPS route with TLS termination', async () => { // Create an HTTPS route with TLS termination const httpsRoute = createHttpsTerminateRoute('secure.example.com', { host: 'localhost', port: 8080 }, { certificate: 'auto', name: 'HTTPS Route' }); // Validate the route configuration expect(httpsRoute.match.ports).toEqual(443); // Default HTTPS port expect(httpsRoute.match.domains).toEqual('secure.example.com'); expect(httpsRoute.action.type).toEqual('forward'); expect(httpsRoute.action.tls?.mode).toEqual('terminate'); expect(httpsRoute.action.tls?.certificate).toEqual('auto'); expect(httpsRoute.action.target?.host).toEqual('localhost'); expect(httpsRoute.action.target?.port).toEqual(8080); expect(httpsRoute.name).toEqual('HTTPS Route'); }); tap.test('Routes: Should create HTTP to HTTPS redirect', async () => { // Create an HTTP to HTTPS redirect const redirectRoute = createHttpToHttpsRedirect('example.com', 443, { status: 301 }); // Validate the route configuration expect(redirectRoute.match.ports).toEqual(80); expect(redirectRoute.match.domains).toEqual('example.com'); expect(redirectRoute.action.type).toEqual('redirect'); expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}:443{path}'); expect(redirectRoute.action.redirect?.status).toEqual(301); }); tap.test('Routes: Should create complete HTTPS server with redirects', async () => { // Create a complete HTTPS server setup const routes = createCompleteHttpsServer('example.com', { host: 'localhost', port: 8080 }, { certificate: 'auto' }); // Validate that we got two routes (HTTPS route and HTTP redirect) expect(routes.length).toEqual(2); // Validate HTTPS route const httpsRoute = routes[0]; expect(httpsRoute.match.ports).toEqual(443); expect(httpsRoute.match.domains).toEqual('example.com'); expect(httpsRoute.action.type).toEqual('forward'); expect(httpsRoute.action.tls?.mode).toEqual('terminate'); // Validate HTTP redirect route const redirectRoute = routes[1]; expect(redirectRoute.match.ports).toEqual(80); expect(redirectRoute.action.type).toEqual('redirect'); expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}:443{path}'); }); tap.test('Routes: Should create load balancer route', async () => { // Create a load balancer route const lbRoute = createLoadBalancerRoute( 'app.example.com', ['10.0.0.1', '10.0.0.2', '10.0.0.3'], 8080, { tls: { mode: 'terminate', certificate: 'auto' }, name: 'Load Balanced Route' } ); // Validate the route configuration expect(lbRoute.match.domains).toEqual('app.example.com'); expect(lbRoute.action.type).toEqual('forward'); expect(Array.isArray(lbRoute.action.target?.host)).toBeTrue(); expect((lbRoute.action.target?.host as string[]).length).toEqual(3); expect((lbRoute.action.target?.host as string[])[0]).toEqual('10.0.0.1'); expect(lbRoute.action.target?.port).toEqual(8080); expect(lbRoute.action.tls?.mode).toEqual('terminate'); }); tap.test('Routes: Should create API route with CORS', async () => { // Create an API route with CORS headers const apiRoute = createApiRoute('api.example.com', '/v1', { host: 'localhost', port: 3000 }, { useTls: true, certificate: 'auto', addCorsHeaders: true, name: 'API Route' }); // Validate the route configuration expect(apiRoute.match.domains).toEqual('api.example.com'); expect(apiRoute.match.path).toEqual('/v1/*'); expect(apiRoute.action.type).toEqual('forward'); expect(apiRoute.action.tls?.mode).toEqual('terminate'); expect(apiRoute.action.target?.host).toEqual('localhost'); expect(apiRoute.action.target?.port).toEqual(3000); // Check CORS headers expect(apiRoute.headers).toBeDefined(); if (apiRoute.headers?.response) { expect(apiRoute.headers.response['Access-Control-Allow-Origin']).toEqual('*'); expect(apiRoute.headers.response['Access-Control-Allow-Methods']).toInclude('GET'); } }); tap.test('Routes: Should create WebSocket route', async () => { // Create a WebSocket route const wsRoute = createWebSocketRoute('ws.example.com', '/socket', { host: 'localhost', port: 5000 }, { useTls: true, certificate: 'auto', pingInterval: 15000, name: 'WebSocket Route' }); // Validate the route configuration expect(wsRoute.match.domains).toEqual('ws.example.com'); expect(wsRoute.match.path).toEqual('/socket'); expect(wsRoute.action.type).toEqual('forward'); expect(wsRoute.action.tls?.mode).toEqual('terminate'); expect(wsRoute.action.target?.host).toEqual('localhost'); expect(wsRoute.action.target?.port).toEqual(5000); // Check WebSocket configuration expect(wsRoute.action.websocket).toBeDefined(); if (wsRoute.action.websocket) { expect(wsRoute.action.websocket.enabled).toBeTrue(); expect(wsRoute.action.websocket.pingInterval).toEqual(15000); } }); tap.test('Routes: Should create static file route', async () => { // Create a static file route const staticRoute = createStaticFileRoute('static.example.com', '/var/www/html', { serveOnHttps: true, certificate: 'auto', indexFiles: ['index.html', 'index.htm', 'default.html'], name: 'Static File Route' }); // Validate the route configuration expect(staticRoute.match.domains).toEqual('static.example.com'); expect(staticRoute.action.type).toEqual('static'); expect(staticRoute.action.static?.root).toEqual('/var/www/html'); expect(staticRoute.action.static?.index).toBeInstanceOf(Array); expect(staticRoute.action.static?.index).toInclude('index.html'); expect(staticRoute.action.static?.index).toInclude('default.html'); expect(staticRoute.action.tls?.mode).toEqual('terminate'); }); tap.test('SmartProxy: Should create instance with route-based config', async () => { // Create TLS certificates for testing const certs = loadTestCertificates(); // Create a SmartProxy instance with route-based configuration const proxy = new SmartProxy({ routes: [ createHttpRoute('example.com', { host: 'localhost', port: 3000 }, { name: 'HTTP Route' }), createHttpsTerminateRoute('secure.example.com', { host: 'localhost', port: 8443 }, { certificate: { key: certs.privateKey, cert: certs.publicKey }, name: 'HTTPS Route' }) ], defaults: { target: { host: 'localhost', port: 8080 }, security: { allowedIps: ['127.0.0.1', '192.168.0.*'], maxConnections: 100 } }, // Additional settings initialDataTimeout: 10000, inactivityTimeout: 300000, enableDetailedLogging: true }); // Simply verify the instance was created successfully expect(typeof proxy).toEqual('object'); expect(typeof proxy.start).toEqual('function'); expect(typeof proxy.stop).toEqual('function'); }); // --------------------------------- Edge Case Tests --------------------------------- tap.test('Edge Case - Empty Routes Array', async () => { // Attempting to find routes in an empty array const emptyRoutes: IRouteConfig[] = []; const matches = findMatchingRoutes(emptyRoutes, { domain: 'example.com', port: 80 }); expect(matches).toBeInstanceOf(Array); expect(matches.length).toEqual(0); const bestMatch = findBestMatchingRoute(emptyRoutes, { domain: 'example.com', port: 80 }); expect(bestMatch).toBeUndefined(); }); tap.test('Edge Case - Multiple Matching Routes with Same Priority', async () => { // Create multiple routes with identical priority but different targets const route1 = createHttpRoute('example.com', { host: 'server1', port: 3000 }); const route2 = createHttpRoute('example.com', { host: 'server2', port: 3000 }); const route3 = createHttpRoute('example.com', { host: 'server3', port: 3000 }); // Set all to the same priority route1.priority = 100; route2.priority = 100; route3.priority = 100; const routes = [route1, route2, route3]; // Find matching routes const matches = findMatchingRoutes(routes, { domain: 'example.com', port: 80 }); // Should find all three routes expect(matches.length).toEqual(3); // First match could be any of the routes since they have the same priority // But the implementation should be consistent (likely keep the original order) const bestMatch = findBestMatchingRoute(routes, { domain: 'example.com', port: 80 }); expect(bestMatch).not.toBeUndefined(); }); tap.test('Edge Case - Wildcard Domains and Path Matching', async () => { // Create routes with wildcard domains and path patterns const wildcardApiRoute = createApiRoute('*.example.com', '/api', { host: 'api-server', port: 3000 }, { useTls: true, certificate: 'auto' }); const exactApiRoute = createApiRoute('api.example.com', '/api', { host: 'specific-api-server', port: 3001 }, { useTls: true, certificate: 'auto', priority: 200 // Higher priority }); const routes = [wildcardApiRoute, exactApiRoute]; // Test with a specific subdomain that matches both routes const matches = findMatchingRoutes(routes, { domain: 'api.example.com', path: '/api/users', port: 443 }); // Should match both routes expect(matches.length).toEqual(2); // The exact domain match should have higher priority const bestMatch = findBestMatchingRoute(routes, { domain: 'api.example.com', path: '/api/users', port: 443 }); expect(bestMatch).not.toBeUndefined(); if (bestMatch) { expect(bestMatch.action.target.port).toEqual(3001); // Should match the exact domain route } // Test with a different subdomain - should only match the wildcard route const otherMatches = findMatchingRoutes(routes, { domain: 'other.example.com', path: '/api/products', port: 443 }); expect(otherMatches.length).toEqual(1); expect(otherMatches[0].action.target.port).toEqual(3000); // Should match the wildcard domain route }); tap.test('Edge Case - Disabled Routes', async () => { // Create enabled and disabled routes const enabledRoute = createHttpRoute('example.com', { host: 'server1', port: 3000 }); const disabledRoute = createHttpRoute('example.com', { host: 'server2', port: 3001 }); disabledRoute.enabled = false; const routes = [enabledRoute, disabledRoute]; // Find matching routes const matches = findMatchingRoutes(routes, { domain: 'example.com', port: 80 }); // Should only find the enabled route expect(matches.length).toEqual(1); expect(matches[0].action.target.port).toEqual(3000); }); tap.test('Edge Case - Complex Path and Headers Matching', async () => { // Create route with complex path and headers matching const complexRoute: IRouteConfig = { match: { domains: 'api.example.com', ports: 443, path: '/api/v2/*', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'valid-key' } }, action: { type: 'forward', target: { host: 'internal-api', port: 8080 }, tls: { mode: 'terminate', certificate: 'auto' } }, name: 'Complex API Route' }; // Test with matching criteria const matchingPath = routeMatchesPath(complexRoute, '/api/v2/users'); expect(matchingPath).toBeTrue(); const matchingHeaders = routeMatchesHeaders(complexRoute, { 'Content-Type': 'application/json', 'X-API-Key': 'valid-key', 'Accept': 'application/json' }); expect(matchingHeaders).toBeTrue(); // Test with non-matching criteria const nonMatchingPath = routeMatchesPath(complexRoute, '/api/v1/users'); expect(nonMatchingPath).toBeFalse(); const nonMatchingHeaders = routeMatchesHeaders(complexRoute, { 'Content-Type': 'application/json', 'X-API-Key': 'invalid-key' }); expect(nonMatchingHeaders).toBeFalse(); }); tap.test('Edge Case - Port Range Matching', async () => { // Create route with port range matching const portRangeRoute: IRouteConfig = { match: { domains: 'example.com', ports: [{ from: 8000, to: 9000 }] }, action: { type: 'forward', target: { host: 'backend', port: 3000 } }, name: 'Port Range Route' }; // Test with ports in the range expect(routeMatchesPort(portRangeRoute, 8000)).toBeTrue(); // Lower bound expect(routeMatchesPort(portRangeRoute, 8500)).toBeTrue(); // Middle expect(routeMatchesPort(portRangeRoute, 9000)).toBeTrue(); // Upper bound // Test with ports outside the range expect(routeMatchesPort(portRangeRoute, 7999)).toBeFalse(); // Just below expect(routeMatchesPort(portRangeRoute, 9001)).toBeFalse(); // Just above // Test with multiple port ranges const multiRangeRoute: IRouteConfig = { match: { domains: 'example.com', ports: [ { from: 80, to: 90 }, { from: 8000, to: 9000 } ] }, action: { type: 'forward', target: { host: 'backend', port: 3000 } }, name: 'Multi Range Route' }; expect(routeMatchesPort(multiRangeRoute, 85)).toBeTrue(); expect(routeMatchesPort(multiRangeRoute, 8500)).toBeTrue(); expect(routeMatchesPort(multiRangeRoute, 100)).toBeFalse(); }); // --------------------------------- Wildcard Domain Tests --------------------------------- tap.test('Wildcard Domain Handling', async () => { // Create routes with different wildcard patterns const simpleDomainRoute = createHttpRoute('example.com', { host: 'server1', port: 3000 }); const wildcardSubdomainRoute = createHttpRoute('*.example.com', { host: 'server2', port: 3001 }); const specificSubdomainRoute = createHttpRoute('api.example.com', { host: 'server3', port: 3002 }); // Set explicit priorities to ensure deterministic matching specificSubdomainRoute.priority = 200; // Highest priority for specific domain wildcardSubdomainRoute.priority = 100; // Medium priority for wildcard simpleDomainRoute.priority = 50; // Lowest priority for generic domain const routes = [simpleDomainRoute, wildcardSubdomainRoute, specificSubdomainRoute]; // Test exact domain match expect(routeMatchesDomain(simpleDomainRoute, 'example.com')).toBeTrue(); expect(routeMatchesDomain(simpleDomainRoute, 'sub.example.com')).toBeFalse(); // Test wildcard subdomain match expect(routeMatchesDomain(wildcardSubdomainRoute, 'any.example.com')).toBeTrue(); expect(routeMatchesDomain(wildcardSubdomainRoute, 'nested.sub.example.com')).toBeTrue(); expect(routeMatchesDomain(wildcardSubdomainRoute, 'example.com')).toBeFalse(); // Test specific subdomain match expect(routeMatchesDomain(specificSubdomainRoute, 'api.example.com')).toBeTrue(); expect(routeMatchesDomain(specificSubdomainRoute, 'other.example.com')).toBeFalse(); expect(routeMatchesDomain(specificSubdomainRoute, 'sub.api.example.com')).toBeFalse(); // Test finding best match when multiple domains match const specificSubdomainRequest = { domain: 'api.example.com', port: 80 }; const bestSpecificMatch = findBestMatchingRoute(routes, specificSubdomainRequest); expect(bestSpecificMatch).not.toBeUndefined(); if (bestSpecificMatch) { // Find which route was matched const matchedPort = bestSpecificMatch.action.target.port; console.log(`Matched route with port: ${matchedPort}`); // Verify it's the specific subdomain route (with highest priority) expect(bestSpecificMatch.priority).toEqual(200); } // Test with a subdomain that matches wildcard but not specific const otherSubdomainRequest = { domain: 'other.example.com', port: 80 }; const bestWildcardMatch = findBestMatchingRoute(routes, otherSubdomainRequest); expect(bestWildcardMatch).not.toBeUndefined(); if (bestWildcardMatch) { // Find which route was matched const matchedPort = bestWildcardMatch.action.target.port; console.log(`Matched route with port: ${matchedPort}`); // Verify it's the wildcard subdomain route (with medium priority) expect(bestWildcardMatch.priority).toEqual(100); } }); // --------------------------------- Integration Tests --------------------------------- tap.test('Route Integration - Combining Multiple Route Types', async () => { // Create a comprehensive set of routes for a full application const routes: IRouteConfig[] = [ // Main website with HTTPS and HTTP redirect ...createCompleteHttpsServer('example.com', { host: 'web-server', port: 8080 }, { certificate: 'auto' }), // API endpoints createApiRoute('api.example.com', '/v1', { host: 'api-server', port: 3000 }, { useTls: true, certificate: 'auto', addCorsHeaders: true }), // WebSocket for real-time updates createWebSocketRoute('ws.example.com', '/live', { host: 'websocket-server', port: 5000 }, { useTls: true, certificate: 'auto' }), // Static assets createStaticFileRoute('static.example.com', '/var/www/assets', { serveOnHttps: true, certificate: 'auto' }), // Legacy system with passthrough createHttpsPassthroughRoute('legacy.example.com', { host: 'legacy-server', port: 443 }) ]; // Validate all routes const validationResult = validateRoutes(routes); expect(validationResult.valid).toBeTrue(); expect(validationResult.errors.length).toEqual(0); // Test route matching for different endpoints // Web server (HTTPS) const webServerMatch = findBestMatchingRoute(routes, { domain: 'example.com', port: 443 }); expect(webServerMatch).not.toBeUndefined(); if (webServerMatch) { expect(webServerMatch.action.type).toEqual('forward'); expect(webServerMatch.action.target.host).toEqual('web-server'); } // Web server (HTTP redirect) const webRedirectMatch = findBestMatchingRoute(routes, { domain: 'example.com', port: 80 }); expect(webRedirectMatch).not.toBeUndefined(); if (webRedirectMatch) { expect(webRedirectMatch.action.type).toEqual('redirect'); } // API server const apiMatch = findBestMatchingRoute(routes, { domain: 'api.example.com', port: 443, path: '/v1/users' }); expect(apiMatch).not.toBeUndefined(); if (apiMatch) { expect(apiMatch.action.type).toEqual('forward'); expect(apiMatch.action.target.host).toEqual('api-server'); } // WebSocket server const wsMatch = findBestMatchingRoute(routes, { domain: 'ws.example.com', port: 443, path: '/live' }); expect(wsMatch).not.toBeUndefined(); if (wsMatch) { expect(wsMatch.action.type).toEqual('forward'); expect(wsMatch.action.target.host).toEqual('websocket-server'); expect(wsMatch.action.websocket?.enabled).toBeTrue(); } // Static assets const staticMatch = findBestMatchingRoute(routes, { domain: 'static.example.com', port: 443 }); expect(staticMatch).not.toBeUndefined(); if (staticMatch) { expect(staticMatch.action.type).toEqual('static'); expect(staticMatch.action.static.root).toEqual('/var/www/assets'); } // Legacy system const legacyMatch = findBestMatchingRoute(routes, { domain: 'legacy.example.com', port: 443 }); expect(legacyMatch).not.toBeUndefined(); if (legacyMatch) { expect(legacyMatch.action.type).toEqual('forward'); expect(legacyMatch.action.tls?.mode).toEqual('passthrough'); } }); export default tap.start();