193 lines
6.2 KiB
TypeScript
193 lines
6.2 KiB
TypeScript
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
|
|
|
||
|
|
import type { ISmartProxyOptions } from '../ts/proxies/smart-proxy/models/interfaces.js';
|
||
|
|
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
||
|
|
import { RoutePreprocessor } from '../ts/proxies/smart-proxy/route-preprocessor.js';
|
||
|
|
import { buildRustProxyOptions } from '../ts/proxies/smart-proxy/utils/rust-config.js';
|
||
|
|
|
||
|
|
tap.test('Rust contract - preprocessor serializes regex headers for Rust', async () => {
|
||
|
|
const route: IRouteConfig = {
|
||
|
|
name: 'contract-route',
|
||
|
|
match: {
|
||
|
|
ports: [443, { from: 8443, to: 8444 }],
|
||
|
|
domains: ['api.example.com', '*.example.com'],
|
||
|
|
transport: 'udp',
|
||
|
|
protocol: 'http3',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': /^application\/json$/i,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
action: {
|
||
|
|
type: 'forward',
|
||
|
|
targets: [{
|
||
|
|
match: {
|
||
|
|
ports: [443],
|
||
|
|
path: '/api/*',
|
||
|
|
method: ['GET'],
|
||
|
|
headers: {
|
||
|
|
'X-Env': /^(prod|stage)$/,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
host: ['backend-a', 'backend-b'],
|
||
|
|
port: 'preserve',
|
||
|
|
sendProxyProtocol: true,
|
||
|
|
backendTransport: 'tcp',
|
||
|
|
}],
|
||
|
|
tls: {
|
||
|
|
mode: 'terminate',
|
||
|
|
certificate: 'auto',
|
||
|
|
},
|
||
|
|
sendProxyProtocol: true,
|
||
|
|
udp: {
|
||
|
|
maxSessionsPerIP: 321,
|
||
|
|
quic: {
|
||
|
|
enableHttp3: true,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
security: {
|
||
|
|
ipAllowList: [{
|
||
|
|
ip: '10.0.0.0/8',
|
||
|
|
domains: ['api.example.com'],
|
||
|
|
}],
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const preprocessor = new RoutePreprocessor();
|
||
|
|
const [rustRoute] = preprocessor.preprocessForRust([route]);
|
||
|
|
|
||
|
|
expect(rustRoute.match.headers?.['Content-Type']).toEqual('/^application\\/json$/i');
|
||
|
|
expect(rustRoute.match.transport).toEqual('udp');
|
||
|
|
expect(rustRoute.match.protocol).toEqual('http3');
|
||
|
|
expect(rustRoute.action.targets?.[0].match?.headers?.['X-Env']).toEqual('/^(prod|stage)$/');
|
||
|
|
expect(rustRoute.action.targets?.[0].port).toEqual('preserve');
|
||
|
|
expect(rustRoute.action.targets?.[0].backendTransport).toEqual('tcp');
|
||
|
|
expect(rustRoute.action.sendProxyProtocol).toBeTrue();
|
||
|
|
expect(rustRoute.action.udp?.maxSessionsPerIp).toEqual(321);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('Rust contract - preprocessor converts dynamic targets to relay-safe payloads', async () => {
|
||
|
|
const route: IRouteConfig = {
|
||
|
|
name: 'dynamic-contract-route',
|
||
|
|
match: {
|
||
|
|
ports: 8080,
|
||
|
|
},
|
||
|
|
action: {
|
||
|
|
type: 'forward',
|
||
|
|
targets: [{
|
||
|
|
host: () => 'dynamic-backend.internal',
|
||
|
|
port: () => 9443,
|
||
|
|
}],
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const preprocessor = new RoutePreprocessor();
|
||
|
|
const [rustRoute] = preprocessor.preprocessForRust([route]);
|
||
|
|
|
||
|
|
expect(rustRoute.action.type).toEqual('socket-handler');
|
||
|
|
expect(rustRoute.action.targets?.[0].host).toEqual('localhost');
|
||
|
|
expect(rustRoute.action.targets?.[0].port).toEqual(0);
|
||
|
|
expect(preprocessor.getOriginalRoute('dynamic-contract-route')).toEqual(route);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('Rust contract - top-level config keeps shared SmartProxy settings', async () => {
|
||
|
|
const settings: ISmartProxyOptions = {
|
||
|
|
routes: [{
|
||
|
|
name: 'top-level-contract-route',
|
||
|
|
match: {
|
||
|
|
ports: 443,
|
||
|
|
domains: 'api.example.com',
|
||
|
|
},
|
||
|
|
action: {
|
||
|
|
type: 'forward',
|
||
|
|
targets: [{
|
||
|
|
host: 'backend.internal',
|
||
|
|
port: 8443,
|
||
|
|
}],
|
||
|
|
tls: {
|
||
|
|
mode: 'terminate',
|
||
|
|
certificate: 'auto',
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}],
|
||
|
|
preserveSourceIP: true,
|
||
|
|
proxyIPs: ['10.0.0.1'],
|
||
|
|
acceptProxyProtocol: true,
|
||
|
|
sendProxyProtocol: true,
|
||
|
|
noDelay: true,
|
||
|
|
keepAlive: true,
|
||
|
|
keepAliveInitialDelay: 1500,
|
||
|
|
maxPendingDataSize: 4096,
|
||
|
|
disableInactivityCheck: true,
|
||
|
|
enableKeepAliveProbes: true,
|
||
|
|
enableDetailedLogging: true,
|
||
|
|
enableTlsDebugLogging: true,
|
||
|
|
enableRandomizedTimeouts: true,
|
||
|
|
connectionTimeout: 5000,
|
||
|
|
initialDataTimeout: 7000,
|
||
|
|
socketTimeout: 9000,
|
||
|
|
inactivityCheckInterval: 1100,
|
||
|
|
maxConnectionLifetime: 13000,
|
||
|
|
inactivityTimeout: 15000,
|
||
|
|
gracefulShutdownTimeout: 17000,
|
||
|
|
maxConnectionsPerIP: 20,
|
||
|
|
connectionRateLimitPerMinute: 30,
|
||
|
|
keepAliveTreatment: 'extended',
|
||
|
|
keepAliveInactivityMultiplier: 2,
|
||
|
|
extendedKeepAliveLifetime: 19000,
|
||
|
|
metrics: {
|
||
|
|
enabled: true,
|
||
|
|
sampleIntervalMs: 250,
|
||
|
|
retentionSeconds: 60,
|
||
|
|
},
|
||
|
|
acme: {
|
||
|
|
enabled: true,
|
||
|
|
email: 'ops@example.com',
|
||
|
|
environment: 'staging',
|
||
|
|
useProduction: false,
|
||
|
|
skipConfiguredCerts: true,
|
||
|
|
renewThresholdDays: 14,
|
||
|
|
renewCheckIntervalHours: 12,
|
||
|
|
autoRenew: true,
|
||
|
|
port: 80,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const preprocessor = new RoutePreprocessor();
|
||
|
|
const routes = preprocessor.preprocessForRust(settings.routes);
|
||
|
|
const config = buildRustProxyOptions(settings, routes);
|
||
|
|
|
||
|
|
expect(config.preserveSourceIp).toBeTrue();
|
||
|
|
expect(config.proxyIps).toEqual(['10.0.0.1']);
|
||
|
|
expect(config.acceptProxyProtocol).toBeTrue();
|
||
|
|
expect(config.sendProxyProtocol).toBeTrue();
|
||
|
|
expect(config.noDelay).toBeTrue();
|
||
|
|
expect(config.keepAlive).toBeTrue();
|
||
|
|
expect(config.keepAliveInitialDelay).toEqual(1500);
|
||
|
|
expect(config.maxPendingDataSize).toEqual(4096);
|
||
|
|
expect(config.disableInactivityCheck).toBeTrue();
|
||
|
|
expect(config.enableKeepAliveProbes).toBeTrue();
|
||
|
|
expect(config.enableDetailedLogging).toBeTrue();
|
||
|
|
expect(config.enableTlsDebugLogging).toBeTrue();
|
||
|
|
expect(config.enableRandomizedTimeouts).toBeTrue();
|
||
|
|
expect(config.connectionTimeout).toEqual(5000);
|
||
|
|
expect(config.initialDataTimeout).toEqual(7000);
|
||
|
|
expect(config.socketTimeout).toEqual(9000);
|
||
|
|
expect(config.inactivityCheckInterval).toEqual(1100);
|
||
|
|
expect(config.maxConnectionLifetime).toEqual(13000);
|
||
|
|
expect(config.inactivityTimeout).toEqual(15000);
|
||
|
|
expect(config.gracefulShutdownTimeout).toEqual(17000);
|
||
|
|
expect(config.maxConnectionsPerIp).toEqual(20);
|
||
|
|
expect(config.connectionRateLimitPerMinute).toEqual(30);
|
||
|
|
expect(config.keepAliveTreatment).toEqual('extended');
|
||
|
|
expect(config.keepAliveInactivityMultiplier).toEqual(2);
|
||
|
|
expect(config.extendedKeepAliveLifetime).toEqual(19000);
|
||
|
|
expect(config.metrics?.sampleIntervalMs).toEqual(250);
|
||
|
|
expect(config.acme?.email).toEqual('ops@example.com');
|
||
|
|
expect(config.acme?.environment).toEqual('staging');
|
||
|
|
expect(config.acme?.skipConfiguredCerts).toBeTrue();
|
||
|
|
expect(config.acme?.renewThresholdDays).toEqual(14);
|
||
|
|
});
|
||
|
|
|
||
|
|
export default tap.start();
|