Files
smartproxy/test/test.route-utils.ts

724 lines
23 KiB
TypeScript
Raw Normal View History

2025-05-19 12:04:26 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
2025-05-10 13:59:34 +00:00
import * as plugins from '../ts/plugins.js';
import {
validateRouteConfig,
validateRoutes,
isValidDomain,
2025-05-10 15:09:58 +00:00
isValidPort,
validateRouteMatch,
validateRouteAction,
hasRequiredPropertiesForAction,
assertValidRoute
} from '../ts/proxies/smart-proxy/utils/route-validator.js';
2025-05-10 13:59:34 +00:00
import {
mergeRouteConfigs,
findMatchingRoutes,
2025-05-10 15:09:58 +00:00
findBestMatchingRoute,
2025-05-10 13:59:34 +00:00
routeMatchesDomain,
2025-05-10 15:09:58 +00:00
routeMatchesPort,
routeMatchesPath,
routeMatchesHeaders,
generateRouteId,
cloneRoute
2025-05-10 13:59:34 +00:00
} from '../ts/proxies/smart-proxy/utils/route-utils.js';
2025-05-10 15:09:58 +00:00
import type {
IRouteConfig,
IRouteMatch,
IRouteAction,
IRouteTarget,
IRouteTls,
TRouteActionType
} from '../ts/proxies/smart-proxy/models/route-types.js';
// --------------------------------- Route Validation Tests ---------------------------------
2025-05-10 13:59:34 +00:00
tap.test('Route Validation - isValidDomain', async () => {
// Valid domains
expect(isValidDomain('example.com')).toBeTrue();
expect(isValidDomain('sub.example.com')).toBeTrue();
expect(isValidDomain('*.example.com')).toBeTrue();
expect(isValidDomain('localhost')).toBeTrue();
expect(isValidDomain('*')).toBeTrue();
expect(isValidDomain('192.168.1.1')).toBeTrue();
// Single-word hostnames are valid (for internal network use)
expect(isValidDomain('example')).toBeTrue();
2025-05-10 13:59:34 +00:00
// Invalid domains
expect(isValidDomain('example.')).toBeFalse();
expect(isValidDomain('example..com')).toBeFalse();
expect(isValidDomain('-example.com')).toBeFalse();
expect(isValidDomain('')).toBeFalse();
2025-05-10 13:59:34 +00:00
});
tap.test('Route Validation - isValidPort', async () => {
// Valid ports
expect(isValidPort(80)).toBeTrue();
expect(isValidPort(443)).toBeTrue();
expect(isValidPort(8080)).toBeTrue();
expect(isValidPort([80, 443])).toBeTrue();
2025-05-10 13:59:34 +00:00
// Invalid ports
expect(isValidPort(0)).toBeFalse();
expect(isValidPort(65536)).toBeFalse();
expect(isValidPort(-1)).toBeFalse();
expect(isValidPort([0, 80])).toBeFalse();
});
2025-05-10 15:09:58 +00:00
tap.test('Route Validation - validateRouteMatch', async () => {
// Valid match configuration
const validMatch: IRouteMatch = {
ports: 80,
domains: 'example.com'
};
const validResult = validateRouteMatch(validMatch);
expect(validResult.valid).toBeTrue();
expect(validResult.errors.length).toEqual(0);
2025-05-10 15:09:58 +00:00
// Invalid match configuration (invalid domain)
const invalidMatch: IRouteMatch = {
ports: 80,
domains: 'invalid..domain'
};
const invalidResult = validateRouteMatch(invalidMatch);
expect(invalidResult.valid).toBeFalse();
expect(invalidResult.errors.length).toBeGreaterThan(0);
expect(invalidResult.errors[0]).toInclude('Invalid domain');
2025-05-10 15:09:58 +00:00
// Invalid match configuration (invalid port)
const invalidPortMatch: IRouteMatch = {
ports: 0,
domains: 'example.com'
};
const invalidPortResult = validateRouteMatch(invalidPortMatch);
expect(invalidPortResult.valid).toBeFalse();
expect(invalidPortResult.errors.length).toBeGreaterThan(0);
expect(invalidPortResult.errors[0]).toInclude('Invalid port');
2025-05-10 15:09:58 +00:00
// Test path validation
const invalidPathMatch: IRouteMatch = {
ports: 80,
domains: 'example.com',
path: 'invalid-path-without-slash'
};
const invalidPathResult = validateRouteMatch(invalidPathMatch);
expect(invalidPathResult.valid).toBeFalse();
expect(invalidPathResult.errors.length).toBeGreaterThan(0);
expect(invalidPathResult.errors[0]).toInclude('starting with /');
});
tap.test('Route Validation - validateRouteAction', async () => {
// Valid forward action
const validForwardAction: IRouteAction = {
type: 'forward',
2025-07-17 20:51:50 +00:00
targets: [{
2025-05-10 15:09:58 +00:00
host: 'localhost',
port: 3000
2025-07-17 20:51:50 +00:00
}]
2025-05-10 15:09:58 +00:00
};
const validForwardResult = validateRouteAction(validForwardAction);
expect(validForwardResult.valid).toBeTrue();
expect(validForwardResult.errors.length).toEqual(0);
2025-05-29 10:13:41 +00:00
// Valid socket-handler action
const validSocketAction: IRouteAction = {
type: 'socket-handler',
socketHandler: (socket, context) => {
socket.end();
2025-05-10 15:09:58 +00:00
}
};
2025-05-29 10:13:41 +00:00
const validSocketResult = validateRouteAction(validSocketAction);
expect(validSocketResult.valid).toBeTrue();
expect(validSocketResult.errors.length).toEqual(0);
2025-07-17 20:51:50 +00:00
// Invalid action (missing targets)
2025-05-10 15:09:58 +00:00
const invalidAction: IRouteAction = {
type: 'forward'
};
const invalidResult = validateRouteAction(invalidAction);
expect(invalidResult.valid).toBeFalse();
expect(invalidResult.errors.length).toBeGreaterThan(0);
2025-07-17 20:51:50 +00:00
expect(invalidResult.errors[0]).toInclude('Targets array is required');
2025-05-29 10:13:41 +00:00
// Invalid action (missing socket handler)
const invalidSocketAction: IRouteAction = {
type: 'socket-handler'
2025-05-10 15:09:58 +00:00
};
2025-05-29 10:13:41 +00:00
const invalidSocketResult = validateRouteAction(invalidSocketAction);
expect(invalidSocketResult.valid).toBeFalse();
expect(invalidSocketResult.errors.length).toBeGreaterThan(0);
expect(invalidSocketResult.errors[0]).toInclude('handler function is required');
2025-05-10 15:09:58 +00:00
});
2025-05-10 13:59:34 +00:00
tap.test('Route Validation - validateRouteConfig', async () => {
// Valid route config
const validRoute: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 13:59:34 +00:00
const validResult = validateRouteConfig(validRoute);
expect(validResult.valid).toBeTrue();
expect(validResult.errors.length).toEqual(0);
2025-07-17 20:51:50 +00:00
// Invalid route config (missing targets)
2025-05-10 13:59:34 +00:00
const invalidRoute: IRouteConfig = {
match: {
domains: 'example.com',
ports: 80
},
action: {
type: 'forward'
},
name: 'Invalid Route'
};
const invalidResult = validateRouteConfig(invalidRoute);
expect(invalidResult.valid).toBeFalse();
expect(invalidResult.errors.length).toBeGreaterThan(0);
});
2025-05-10 15:09:58 +00:00
tap.test('Route Validation - validateRoutes', async () => {
// Create valid and invalid routes
const routes = [
{
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
} as IRouteConfig,
2025-05-10 15:09:58 +00:00
{
match: {
domains: 'invalid..domain',
ports: 80
},
action: {
type: 'forward',
target: {
host: 'localhost',
port: 3000
}
}
} as IRouteConfig,
{
match: { ports: 443, domains: 'secure.example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3001 }], tls: { mode: 'terminate', certificate: 'auto' } },
name: 'HTTPS Terminate Route for secure.example.com',
} as IRouteConfig
2025-05-10 15:09:58 +00:00
];
2025-05-10 15:09:58 +00:00
const result = validateRoutes(routes);
expect(result.valid).toBeFalse();
expect(result.errors.length).toEqual(1);
expect(result.errors[0].index).toEqual(1); // The second route is invalid
expect(result.errors[0].errors.length).toBeGreaterThan(0);
expect(result.errors[0].errors[0]).toInclude('Invalid domain');
});
tap.test('Route Validation - hasRequiredPropertiesForAction', async () => {
// Forward action
const forwardRoute: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
expect(hasRequiredPropertiesForAction(forwardRoute, 'forward')).toBeTrue();
2025-05-29 10:13:41 +00:00
// Socket handler action
const socketRoute: IRouteConfig = {
2025-05-10 15:09:58 +00:00
match: {
2025-05-29 10:13:41 +00:00
domains: 'socket.example.com',
2025-05-10 15:09:58 +00:00
ports: 80
},
action: {
2025-05-29 10:13:41 +00:00
type: 'socket-handler',
socketHandler: (socket, context) => {
socket.end();
}
2025-05-10 15:09:58 +00:00
},
2025-05-29 10:13:41 +00:00
name: 'Socket Handler Route'
2025-05-10 15:09:58 +00:00
};
2025-05-29 10:13:41 +00:00
expect(hasRequiredPropertiesForAction(socketRoute, 'socket-handler')).toBeTrue();
2025-05-10 15:09:58 +00:00
// Missing required properties
const invalidForwardRoute: IRouteConfig = {
match: {
domains: 'example.com',
ports: 80
},
action: {
type: 'forward'
},
name: 'Invalid Forward Route'
};
expect(hasRequiredPropertiesForAction(invalidForwardRoute, 'forward')).toBeFalse();
});
tap.test('Route Validation - assertValidRoute', async () => {
// Valid route
const validRoute: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
expect(() => assertValidRoute(validRoute)).not.toThrow();
2025-05-10 15:09:58 +00:00
// Invalid route
const invalidRoute: IRouteConfig = {
match: {
domains: 'example.com',
ports: 80
},
action: {
type: 'forward'
},
name: 'Invalid Route'
};
expect(() => assertValidRoute(invalidRoute)).toThrow();
});
// --------------------------------- Route Utilities Tests ---------------------------------
2025-05-10 13:59:34 +00:00
tap.test('Route Utilities - mergeRouteConfigs', async () => {
// Base route
const baseRoute: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 13:59:34 +00:00
// Override with different name and port
const overrideRoute: Partial<IRouteConfig> = {
name: 'Merged Route',
match: {
ports: 8080
}
};
2025-05-10 13:59:34 +00:00
// Merge configs
const mergedRoute = mergeRouteConfigs(baseRoute, overrideRoute);
2025-05-10 13:59:34 +00:00
// Check merged properties
expect(mergedRoute.name).toEqual('Merged Route');
expect(mergedRoute.match.ports).toEqual(8080);
expect(mergedRoute.match.domains).toEqual('example.com');
expect(mergedRoute.action.type).toEqual('forward');
2025-05-10 15:09:58 +00:00
// Test merging action properties
const actionOverride: Partial<IRouteConfig> = {
action: {
type: 'forward',
2025-07-17 20:51:50 +00:00
targets: [{
2025-05-10 15:09:58 +00:00
host: 'new-host.local',
port: 5000
2025-07-17 20:51:50 +00:00
}]
2025-05-10 15:09:58 +00:00
}
};
2025-05-10 15:09:58 +00:00
const actionMergedRoute = mergeRouteConfigs(baseRoute, actionOverride);
2025-07-17 20:51:50 +00:00
expect(actionMergedRoute.action.targets?.[0]?.host).toEqual('new-host.local');
expect(actionMergedRoute.action.targets?.[0]?.port).toEqual(5000);
2025-05-29 10:13:41 +00:00
// Test replacing action with socket handler
2025-05-10 15:09:58 +00:00
const typeChangeOverride: Partial<IRouteConfig> = {
action: {
2025-05-29 10:13:41 +00:00
type: 'socket-handler',
socketHandler: (socket, context) => {
socket.write('HTTP/1.1 301 Moved Permanently\r\n');
socket.write('Location: https://example.com\r\n');
socket.write('\r\n');
socket.end();
2025-05-10 15:09:58 +00:00
}
}
};
2025-05-10 15:09:58 +00:00
const typeChangedRoute = mergeRouteConfigs(baseRoute, typeChangeOverride);
2025-05-29 10:13:41 +00:00
expect(typeChangedRoute.action.type).toEqual('socket-handler');
expect(typeChangedRoute.action.socketHandler).toBeDefined();
2025-07-17 20:51:50 +00:00
expect(typeChangedRoute.action.targets).toBeUndefined();
2025-05-10 13:59:34 +00:00
});
tap.test('Route Matching - routeMatchesDomain', async () => {
// Create route with wildcard domain
const wildcardRoute: IRouteConfig = {
match: { ports: 80, domains: '*.example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for *.example.com',
};
2025-05-10 13:59:34 +00:00
// Create route with exact domain
const exactRoute: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
// Create route with multiple domains
const multiDomainRoute: IRouteConfig = {
match: { ports: 80, domains: ['example.com', 'example.org'] },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com,example.org',
};
2025-05-10 13:59:34 +00:00
// Test wildcard domain matching
expect(routeMatchesDomain(wildcardRoute, 'sub.example.com')).toBeTrue();
expect(routeMatchesDomain(wildcardRoute, 'another.example.com')).toBeTrue();
expect(routeMatchesDomain(wildcardRoute, 'example.com')).toBeFalse();
expect(routeMatchesDomain(wildcardRoute, 'example.org')).toBeFalse();
2025-05-10 13:59:34 +00:00
// Test exact domain matching
expect(routeMatchesDomain(exactRoute, 'example.com')).toBeTrue();
expect(routeMatchesDomain(exactRoute, 'sub.example.com')).toBeFalse();
2025-05-10 15:09:58 +00:00
// Test multiple domains matching
expect(routeMatchesDomain(multiDomainRoute, 'example.com')).toBeTrue();
expect(routeMatchesDomain(multiDomainRoute, 'example.org')).toBeTrue();
expect(routeMatchesDomain(multiDomainRoute, 'example.net')).toBeFalse();
2025-05-10 15:09:58 +00:00
// Test case insensitivity
expect(routeMatchesDomain(exactRoute, 'Example.Com')).toBeTrue();
});
tap.test('Route Matching - routeMatchesPort', async () => {
// Create routes with different port configurations
const singlePortRoute: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
const multiPortRoute: IRouteConfig = {
match: {
domains: 'example.com',
ports: [80, 8080]
},
action: {
type: 'forward',
2025-07-17 20:51:50 +00:00
targets: [{
2025-05-10 15:09:58 +00:00
host: 'localhost',
port: 3000
2025-07-17 20:51:50 +00:00
}]
2025-05-10 15:09:58 +00:00
}
};
2025-05-10 15:09:58 +00:00
const portRangeRoute: IRouteConfig = {
match: {
domains: 'example.com',
ports: [{ from: 8000, to: 9000 }]
},
action: {
type: 'forward',
2025-07-17 20:51:50 +00:00
targets: [{
2025-05-10 15:09:58 +00:00
host: 'localhost',
port: 3000
2025-07-17 20:51:50 +00:00
}]
2025-05-10 15:09:58 +00:00
}
};
2025-05-10 15:09:58 +00:00
// Test single port matching
expect(routeMatchesPort(singlePortRoute, 80)).toBeTrue();
expect(routeMatchesPort(singlePortRoute, 443)).toBeFalse();
2025-05-10 15:09:58 +00:00
// Test multi-port matching
expect(routeMatchesPort(multiPortRoute, 80)).toBeTrue();
expect(routeMatchesPort(multiPortRoute, 8080)).toBeTrue();
expect(routeMatchesPort(multiPortRoute, 3000)).toBeFalse();
2025-05-10 15:09:58 +00:00
// Test port range matching
expect(routeMatchesPort(portRangeRoute, 8000)).toBeTrue();
expect(routeMatchesPort(portRangeRoute, 8500)).toBeTrue();
expect(routeMatchesPort(portRangeRoute, 9000)).toBeTrue();
expect(routeMatchesPort(portRangeRoute, 7999)).toBeFalse();
expect(routeMatchesPort(portRangeRoute, 9001)).toBeFalse();
});
tap.test('Route Matching - routeMatchesPath', async () => {
// Create route with path configuration
const exactPathRoute: IRouteConfig = {
match: {
domains: 'example.com',
ports: 80,
path: '/api'
},
action: {
type: 'forward',
2025-07-17 20:51:50 +00:00
targets: [{
2025-05-10 15:09:58 +00:00
host: 'localhost',
port: 3000
2025-07-17 20:51:50 +00:00
}]
2025-05-10 15:09:58 +00:00
}
};
// Test prefix matching with wildcard (not trailing slash)
const prefixPathRoute: IRouteConfig = {
2025-05-10 15:09:58 +00:00
match: {
domains: 'example.com',
2025-05-10 15:09:58 +00:00
ports: 80,
path: '/api/*'
2025-05-10 15:09:58 +00:00
},
action: {
type: 'forward',
2025-07-17 20:51:50 +00:00
targets: [{
2025-05-10 15:09:58 +00:00
host: 'localhost',
port: 3000
2025-07-17 20:51:50 +00:00
}]
2025-05-10 15:09:58 +00:00
}
};
2025-05-10 15:09:58 +00:00
const wildcardPathRoute: IRouteConfig = {
match: {
domains: 'example.com',
ports: 80,
path: '/api/*'
},
action: {
type: 'forward',
2025-07-17 20:51:50 +00:00
targets: [{
2025-05-10 15:09:58 +00:00
host: 'localhost',
port: 3000
2025-07-17 20:51:50 +00:00
}]
2025-05-10 15:09:58 +00:00
}
};
2025-05-10 15:09:58 +00:00
// Test exact path matching
expect(routeMatchesPath(exactPathRoute, '/api')).toBeTrue();
expect(routeMatchesPath(exactPathRoute, '/api/users')).toBeFalse();
expect(routeMatchesPath(exactPathRoute, '/app')).toBeFalse();
// Test prefix path matching with wildcard
expect(routeMatchesPath(prefixPathRoute, '/api/')).toBeFalse(); // Wildcard requires content after /api/
expect(routeMatchesPath(prefixPathRoute, '/api/users')).toBeTrue();
expect(routeMatchesPath(prefixPathRoute, '/app/')).toBeFalse();
2025-05-10 15:09:58 +00:00
// Test wildcard path matching
expect(routeMatchesPath(wildcardPathRoute, '/api/users')).toBeTrue();
expect(routeMatchesPath(wildcardPathRoute, '/api/products')).toBeTrue();
expect(routeMatchesPath(wildcardPathRoute, '/app/api')).toBeFalse();
});
tap.test('Route Matching - routeMatchesHeaders', async () => {
// Create route with header matching
const headerRoute: IRouteConfig = {
match: {
domains: 'example.com',
ports: 80,
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
}
},
action: {
type: 'forward',
2025-07-17 20:51:50 +00:00
targets: [{
2025-05-10 15:09:58 +00:00
host: 'localhost',
port: 3000
2025-07-17 20:51:50 +00:00
}]
2025-05-10 15:09:58 +00:00
}
};
2025-05-10 15:09:58 +00:00
// Test header matching
expect(routeMatchesHeaders(headerRoute, {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
})).toBeTrue();
2025-05-10 15:09:58 +00:00
expect(routeMatchesHeaders(headerRoute, {
'Content-Type': 'application/json',
'X-Custom-Header': 'value',
'Extra-Header': 'something'
})).toBeTrue();
2025-05-10 15:09:58 +00:00
expect(routeMatchesHeaders(headerRoute, {
'Content-Type': 'application/json'
})).toBeFalse();
2025-05-10 15:09:58 +00:00
expect(routeMatchesHeaders(headerRoute, {
'Content-Type': 'text/html',
'X-Custom-Header': 'value'
})).toBeFalse();
2025-05-10 15:09:58 +00:00
// Route without header matching should match any headers
const noHeaderRoute: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
expect(routeMatchesHeaders(noHeaderRoute, {
'Content-Type': 'application/json'
})).toBeTrue();
2025-05-10 13:59:34 +00:00
});
tap.test('Route Finding - findMatchingRoutes', async () => {
// Create multiple routes
const routes: IRouteConfig[] = [
{
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
},
{
match: { ports: 443, domains: 'secure.example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3001 }], tls: { mode: 'terminate', certificate: 'auto' } },
name: 'HTTPS Route for secure.example.com',
},
{
match: { ports: 443, domains: 'api.example.com', path: '/v1/*' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3002 }], tls: { mode: 'terminate', certificate: 'auto' } },
name: 'API Route for api.example.com',
},
{
match: { ports: 443, domains: 'ws.example.com', path: '/socket' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3003 }], tls: { mode: 'terminate', certificate: 'auto' }, websocket: { enabled: true } },
name: 'WebSocket Route for ws.example.com',
},
2025-05-10 13:59:34 +00:00
];
2025-05-10 13:59:34 +00:00
// Set priorities
routes[0].priority = 10;
routes[1].priority = 20;
routes[2].priority = 30;
routes[3].priority = 40;
2025-05-10 13:59:34 +00:00
// Find routes for different criteria
const httpMatches = findMatchingRoutes(routes, { domain: 'example.com', port: 80 });
expect(httpMatches.length).toEqual(1);
expect(httpMatches[0].name).toInclude('HTTP Route');
2025-05-10 13:59:34 +00:00
const httpsMatches = findMatchingRoutes(routes, { domain: 'secure.example.com', port: 443 });
expect(httpsMatches.length).toEqual(1);
expect(httpsMatches[0].name).toInclude('HTTPS Route');
2025-05-10 13:59:34 +00:00
const apiMatches = findMatchingRoutes(routes, { domain: 'api.example.com', path: '/v1/users' });
expect(apiMatches.length).toEqual(1);
expect(apiMatches[0].name).toInclude('API Route');
2025-05-10 13:59:34 +00:00
const wsMatches = findMatchingRoutes(routes, { domain: 'ws.example.com', path: '/socket' });
expect(wsMatches.length).toEqual(1);
expect(wsMatches[0].name).toInclude('WebSocket Route');
2025-05-10 15:09:58 +00:00
// Test finding multiple routes that match same criteria
const route1: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
route1.priority = 10;
const route2: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3001 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
route2.priority = 20;
route2.match.path = '/api';
2025-05-10 15:09:58 +00:00
const multiMatchRoutes = [route1, route2];
2025-05-10 15:09:58 +00:00
const multiMatches = findMatchingRoutes(multiMatchRoutes, { domain: 'example.com', port: 80 });
expect(multiMatches.length).toEqual(2);
expect(multiMatches[0].priority).toEqual(20); // Higher priority should be first
expect(multiMatches[1].priority).toEqual(10);
2025-05-10 15:09:58 +00:00
// Test disabled routes
const disabledRoute: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
disabledRoute.enabled = false;
2025-05-10 15:09:58 +00:00
const enabledRoutes = findMatchingRoutes([disabledRoute], { domain: 'example.com', port: 80 });
expect(enabledRoutes.length).toEqual(0);
});
tap.test('Route Finding - findBestMatchingRoute', async () => {
// Create multiple routes with different priorities
const route1: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
route1.priority = 10;
const route2: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3001 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
route2.priority = 20;
route2.match.path = '/api';
const route3: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3002 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
route3.priority = 30;
route3.match.path = '/api/users';
2025-05-10 15:09:58 +00:00
const routes = [route1, route2, route3];
2025-05-10 15:09:58 +00:00
// Find best route for different criteria
const bestGeneral = findBestMatchingRoute(routes, { domain: 'example.com', port: 80 });
expect(bestGeneral).not.toBeUndefined();
expect(bestGeneral?.priority).toEqual(30);
2025-05-10 15:09:58 +00:00
// Test when no routes match
const noMatch = findBestMatchingRoute(routes, { domain: 'unknown.com', port: 80 });
expect(noMatch).toBeUndefined();
});
tap.test('Route Utilities - generateRouteId', async () => {
// Test ID generation for different route types
const httpRoute: IRouteConfig = {
match: { ports: 80, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com',
};
2025-05-10 15:09:58 +00:00
const httpId = generateRouteId(httpRoute);
expect(httpId).toInclude('example-com');
expect(httpId).toInclude('80');
expect(httpId).toInclude('forward');
const httpsRoute: IRouteConfig = {
match: { ports: 443, domains: 'secure.example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3001 }], tls: { mode: 'terminate', certificate: 'auto' } },
name: 'HTTPS Terminate Route for secure.example.com',
};
2025-05-10 15:09:58 +00:00
const httpsId = generateRouteId(httpsRoute);
expect(httpsId).toInclude('secure-example-com');
expect(httpsId).toInclude('443');
expect(httpsId).toInclude('forward');
const multiDomainRoute: IRouteConfig = {
match: { ports: 80, domains: ['example.com', 'example.org'] },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }] },
name: 'HTTP Route for example.com,example.org',
};
2025-05-10 15:09:58 +00:00
const multiDomainId = generateRouteId(multiDomainRoute);
expect(multiDomainId).toInclude('example-com-example-org');
});
tap.test('Route Utilities - cloneRoute', async () => {
// Create a route and clone it
const originalRoute: IRouteConfig = {
match: { ports: 443, domains: 'example.com' },
action: { type: 'forward', targets: [{ host: 'localhost', port: 3000 }], tls: { mode: 'terminate', certificate: 'auto' } },
name: 'Original Route',
};
2025-05-10 15:09:58 +00:00
const clonedRoute = cloneRoute(originalRoute);
2025-05-10 15:09:58 +00:00
// Check that the values are identical
expect(clonedRoute.name).toEqual(originalRoute.name);
expect(clonedRoute.match.domains).toEqual(originalRoute.match.domains);
expect(clonedRoute.action.type).toEqual(originalRoute.action.type);
2025-07-17 20:51:50 +00:00
expect(clonedRoute.action.targets?.[0]?.port).toEqual(originalRoute.action.targets?.[0]?.port);
2025-05-10 15:09:58 +00:00
// Modify the clone and check that the original is unchanged
clonedRoute.name = 'Modified Clone';
expect(originalRoute.name).toEqual('Original Route');
});
export default tap.start();