change to route based approach

This commit is contained in:
2025-05-10 00:01:02 +00:00
parent 552f4c246b
commit 28022ebe87
13 changed files with 3594 additions and 1167 deletions

181
test/test.route-config.ts Normal file
View File

@ -0,0 +1,181 @@
/**
* Tests for the new route-based configuration system
*/
import { expect, tap } from '@push.rocks/tapbundle';
// Import from core modules
import {
SmartProxy,
createHttpRoute,
createHttpsRoute,
createPassthroughRoute,
createRedirectRoute,
createHttpToHttpsRedirect,
createHttpsServer,
createLoadBalancerRoute
} from '../ts/proxies/smart-proxy/index.js';
// Import test helpers
import { getCertificate } from './helpers/certificates.js';
tap.test('Routes: Should create basic HTTP route', async () => {
// Create a simple HTTP route
const httpRoute = createHttpRoute({
ports: 8080,
domains: 'example.com',
target: {
host: 'localhost',
port: 3000
},
name: 'Basic HTTP Route'
});
// Validate the route configuration
expect(httpRoute.match.ports).to.equal(8080);
expect(httpRoute.match.domains).to.equal('example.com');
expect(httpRoute.action.type).to.equal('forward');
expect(httpRoute.action.target?.host).to.equal('localhost');
expect(httpRoute.action.target?.port).to.equal(3000);
expect(httpRoute.name).to.equal('Basic HTTP Route');
});
tap.test('Routes: Should create HTTPS route with TLS termination', async () => {
// Create an HTTPS route with TLS termination
const httpsRoute = createHttpsRoute({
domains: 'secure.example.com',
target: {
host: 'localhost',
port: 8080
},
certificate: 'auto',
name: 'HTTPS Route'
});
// Validate the route configuration
expect(httpsRoute.match.ports).to.equal(443); // Default HTTPS port
expect(httpsRoute.match.domains).to.equal('secure.example.com');
expect(httpsRoute.action.type).to.equal('forward');
expect(httpsRoute.action.tls?.mode).to.equal('terminate');
expect(httpsRoute.action.tls?.certificate).to.equal('auto');
expect(httpsRoute.action.target?.host).to.equal('localhost');
expect(httpsRoute.action.target?.port).to.equal(8080);
expect(httpsRoute.name).to.equal('HTTPS Route');
});
tap.test('Routes: Should create HTTP to HTTPS redirect', async () => {
// Create an HTTP to HTTPS redirect
const redirectRoute = createHttpToHttpsRedirect({
domains: 'example.com',
statusCode: 301
});
// Validate the route configuration
expect(redirectRoute.match.ports).to.equal(80);
expect(redirectRoute.match.domains).to.equal('example.com');
expect(redirectRoute.action.type).to.equal('redirect');
expect(redirectRoute.action.redirect?.to).to.equal('https://{domain}{path}');
expect(redirectRoute.action.redirect?.status).to.equal(301);
});
tap.test('Routes: Should create complete HTTPS server with redirects', async () => {
// Create a complete HTTPS server setup
const routes = createHttpsServer({
domains: 'example.com',
target: {
host: 'localhost',
port: 8080
},
certificate: 'auto',
addHttpRedirect: true
});
// Validate that we got two routes (HTTPS route and HTTP redirect)
expect(routes.length).to.equal(2);
// Validate HTTPS route
const httpsRoute = routes[0];
expect(httpsRoute.match.ports).to.equal(443);
expect(httpsRoute.match.domains).to.equal('example.com');
expect(httpsRoute.action.type).to.equal('forward');
expect(httpsRoute.action.tls?.mode).to.equal('terminate');
// Validate HTTP redirect route
const redirectRoute = routes[1];
expect(redirectRoute.match.ports).to.equal(80);
expect(redirectRoute.action.type).to.equal('redirect');
expect(redirectRoute.action.redirect?.to).to.equal('https://{domain}{path}');
});
tap.test('Routes: Should create load balancer route', async () => {
// Create a load balancer route
const lbRoute = createLoadBalancerRoute({
domains: 'app.example.com',
targets: ['10.0.0.1', '10.0.0.2', '10.0.0.3'],
targetPort: 8080,
tlsMode: 'terminate',
certificate: 'auto',
name: 'Load Balanced Route'
});
// Validate the route configuration
expect(lbRoute.match.domains).to.equal('app.example.com');
expect(lbRoute.action.type).to.equal('forward');
expect(Array.isArray(lbRoute.action.target?.host)).to.equal(true);
expect((lbRoute.action.target?.host as string[]).length).to.equal(3);
expect((lbRoute.action.target?.host as string[])[0]).to.equal('10.0.0.1');
expect(lbRoute.action.target?.port).to.equal(8080);
expect(lbRoute.action.tls?.mode).to.equal('terminate');
});
tap.test('SmartProxy: Should create instance with route-based config', async () => {
// Create TLS certificates for testing
const cert = await getCertificate();
// Create a SmartProxy instance with route-based configuration
const proxy = new SmartProxy({
routes: [
createHttpRoute({
ports: 8080,
domains: 'example.com',
target: {
host: 'localhost',
port: 3000
},
name: 'HTTP Route'
}),
createHttpsRoute({
domains: 'secure.example.com',
target: {
host: 'localhost',
port: 8443
},
certificate: {
key: cert.key,
cert: cert.cert
},
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(proxy).to.be.an('object');
expect(proxy.start).to.be.a('function');
expect(proxy.stop).to.be.a('function');
});
export default tap.start();

View File

@ -66,13 +66,25 @@ function createTestClient(port: number, data: string): Promise<string> {
tap.test('setup port proxy test environment', async () => {
testServer = await createTestServer(TEST_SERVER_PORT);
smartProxy = new SmartProxy({
fromPort: PROXY_PORT,
toPort: TEST_SERVER_PORT,
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'],
globalPortRanges: []
routes: [
{
match: {
ports: PROXY_PORT
},
action: {
type: 'forward',
target: {
host: 'localhost',
port: TEST_SERVER_PORT
}
}
}
],
defaults: {
security: {
allowedIPs: ['127.0.0.1']
}
}
});
allProxies.push(smartProxy); // Track this proxy
});
@ -92,13 +104,25 @@ tap.test('should forward TCP connections and data to localhost', async () => {
// Test proxy with a custom target host.
tap.test('should forward TCP connections to custom host', async () => {
const customHostProxy = new SmartProxy({
fromPort: PROXY_PORT + 1,
toPort: TEST_SERVER_PORT,
targetIP: '127.0.0.1',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'],
globalPortRanges: []
routes: [
{
match: {
ports: PROXY_PORT + 1
},
action: {
type: 'forward',
target: {
host: '127.0.0.1',
port: TEST_SERVER_PORT
}
}
}
],
defaults: {
security: {
allowedIPs: ['127.0.0.1']
}
}
});
allProxies.push(customHostProxy); // Track this proxy
@ -125,14 +149,25 @@ tap.test('should forward connections to custom IP', async () => {
// We're simulating routing to a different IP by using a different port
// This tests the core functionality without requiring multiple IPs
const domainProxy = new SmartProxy({
fromPort: forcedProxyPort, // 4003 - Listen on this port
toPort: targetServerPort, // 4200 - Forward to this port
targetIP: '127.0.0.1', // Always use localhost (works in Docker)
domainConfigs: [], // No domain configs to confuse things
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'], // Allow localhost
// We'll test the functionality WITHOUT port ranges this time
globalPortRanges: []
routes: [
{
match: {
ports: forcedProxyPort
},
action: {
type: 'forward',
target: {
host: '127.0.0.1',
port: targetServerPort
}
}
}
],
defaults: {
security: {
allowedIPs: ['127.0.0.1', '::ffff:127.0.0.1']
}
}
});
allProxies.push(domainProxy); // Track this proxy
@ -208,22 +243,46 @@ tap.test('should stop port proxy', async () => {
tap.test('should support optional source IP preservation in chained proxies', async () => {
// Chained proxies without IP preservation.
const firstProxyDefault = new SmartProxy({
fromPort: PROXY_PORT + 4,
toPort: PROXY_PORT + 5,
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'],
globalPortRanges: []
routes: [
{
match: {
ports: PROXY_PORT + 4
},
action: {
type: 'forward',
target: {
host: 'localhost',
port: PROXY_PORT + 5
}
}
}
],
defaults: {
security: {
allowedIPs: ['127.0.0.1', '::ffff:127.0.0.1']
}
}
});
const secondProxyDefault = new SmartProxy({
fromPort: PROXY_PORT + 5,
toPort: TEST_SERVER_PORT,
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'],
globalPortRanges: []
routes: [
{
match: {
ports: PROXY_PORT + 5
},
action: {
type: 'forward',
target: {
host: 'localhost',
port: TEST_SERVER_PORT
}
}
}
],
defaults: {
security: {
allowedIPs: ['127.0.0.1', '::ffff:127.0.0.1']
}
}
});
allProxies.push(firstProxyDefault, secondProxyDefault); // Track these proxies
@ -243,24 +302,50 @@ tap.test('should support optional source IP preservation in chained proxies', as
// Chained proxies with IP preservation.
const firstProxyPreserved = new SmartProxy({
fromPort: PROXY_PORT + 6,
toPort: PROXY_PORT + 7,
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'],
preserveSourceIP: true,
globalPortRanges: []
routes: [
{
match: {
ports: PROXY_PORT + 6
},
action: {
type: 'forward',
target: {
host: 'localhost',
port: PROXY_PORT + 7
}
}
}
],
defaults: {
security: {
allowedIPs: ['127.0.0.1']
},
preserveSourceIP: true
},
preserveSourceIP: true
});
const secondProxyPreserved = new SmartProxy({
fromPort: PROXY_PORT + 7,
toPort: TEST_SERVER_PORT,
targetIP: 'localhost',
domainConfigs: [],
sniEnabled: false,
defaultAllowedIPs: ['127.0.0.1'],
preserveSourceIP: true,
globalPortRanges: []
routes: [
{
match: {
ports: PROXY_PORT + 7
},
action: {
type: 'forward',
target: {
host: 'localhost',
port: TEST_SERVER_PORT
}
}
}
],
defaults: {
security: {
allowedIPs: ['127.0.0.1']
},
preserveSourceIP: true
},
preserveSourceIP: true
});
allProxies.push(firstProxyPreserved, secondProxyPreserved); // Track these proxies