305 lines
11 KiB
TypeScript
305 lines
11 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import {
|
|
routeQualifiesForHttp3,
|
|
augmentRouteWithHttp3,
|
|
augmentRoutesWithHttp3,
|
|
type IHttp3Config,
|
|
} from '../ts/http3/index.js';
|
|
import type * as plugins from '../ts/plugins.js';
|
|
|
|
// Helper to create a basic HTTPS forward route on port 443
|
|
function makeRoute(
|
|
overrides: Partial<plugins.smartproxy.IRouteConfig> = {},
|
|
): plugins.smartproxy.IRouteConfig {
|
|
return {
|
|
match: { ports: 443, ...overrides.match },
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: 'localhost', port: 8080 }],
|
|
tls: { mode: 'terminate', certificate: 'auto' },
|
|
...overrides.action,
|
|
},
|
|
name: overrides.name ?? 'test-https-route',
|
|
...Object.fromEntries(
|
|
Object.entries(overrides).filter(([k]) => !['match', 'action', 'name'].includes(k)),
|
|
),
|
|
} as plugins.smartproxy.IRouteConfig;
|
|
}
|
|
|
|
const defaultConfig: IHttp3Config = { enabled: true };
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
// Qualification tests
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
tap.test('should augment qualifying HTTPS route on port 443', async () => {
|
|
const route = makeRoute();
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toEqual('all');
|
|
expect(result.action.udp).toBeTruthy();
|
|
expect(result.action.udp!.quic).toBeTruthy();
|
|
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
|
|
expect(result.action.udp!.quic!.altSvcMaxAge).toEqual(86400);
|
|
});
|
|
|
|
tap.test('should NOT augment route on non-443 port', async () => {
|
|
const route = makeRoute({ match: { ports: 8080 } });
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toBeUndefined();
|
|
expect(result.action.udp).toBeUndefined();
|
|
});
|
|
|
|
tap.test('should NOT augment socket-handler type route', async () => {
|
|
const route = makeRoute({
|
|
action: {
|
|
type: 'socket-handler' as any,
|
|
socketHandler: (() => {}) as any,
|
|
},
|
|
});
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toBeUndefined();
|
|
});
|
|
|
|
tap.test('should NOT augment route without TLS', async () => {
|
|
const route: plugins.smartproxy.IRouteConfig = {
|
|
match: { ports: 443 },
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: 'localhost', port: 8080 }],
|
|
},
|
|
name: 'no-tls-route',
|
|
};
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toBeUndefined();
|
|
});
|
|
|
|
tap.test('should NOT augment email routes', async () => {
|
|
const emailNames = ['smtp-route', 'submission-route', 'smtps-route', 'email-port-2525-route'];
|
|
for (const name of emailNames) {
|
|
const route = makeRoute({ name });
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
expect(result.match.transport).toBeUndefined();
|
|
}
|
|
});
|
|
|
|
tap.test('should respect per-route opt-out (options.http3 = false)', async () => {
|
|
const route = makeRoute({
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: 'localhost', port: 8080 }],
|
|
tls: { mode: 'terminate', certificate: 'auto' },
|
|
options: { http3: false },
|
|
},
|
|
});
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toBeUndefined();
|
|
expect(result.action.udp).toBeUndefined();
|
|
});
|
|
|
|
tap.test('should respect per-route opt-in when global is disabled', async () => {
|
|
const route = makeRoute({
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: 'localhost', port: 8080 }],
|
|
tls: { mode: 'terminate', certificate: 'auto' },
|
|
options: { http3: true },
|
|
},
|
|
});
|
|
const result = augmentRouteWithHttp3(route, { enabled: false });
|
|
|
|
expect(result.match.transport).toEqual('all');
|
|
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
|
|
});
|
|
|
|
tap.test('should NOT double-augment routes with transport: all', async () => {
|
|
const route = makeRoute({
|
|
match: { ports: 443, transport: 'all' as any },
|
|
});
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
// Should be the exact same object (no augmentation)
|
|
expect(result).toEqual(route);
|
|
});
|
|
|
|
tap.test('should NOT double-augment routes with existing udp.quic', async () => {
|
|
const route = makeRoute({
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: 'localhost', port: 8080 }],
|
|
tls: { mode: 'terminate', certificate: 'auto' },
|
|
udp: { quic: { enableHttp3: true } },
|
|
},
|
|
});
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result).toEqual(route);
|
|
});
|
|
|
|
tap.test('should augment route with port range including 443', async () => {
|
|
const route = makeRoute({
|
|
match: { ports: [{ from: 400, to: 500 }] },
|
|
});
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toEqual('all');
|
|
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
|
|
});
|
|
|
|
tap.test('should augment route with port array including 443', async () => {
|
|
const route = makeRoute({
|
|
match: { ports: [80, 443] },
|
|
});
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toEqual('all');
|
|
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
|
|
});
|
|
|
|
tap.test('should NOT augment route with port range NOT including 443', async () => {
|
|
const route = makeRoute({
|
|
match: { ports: [{ from: 8000, to: 9000 }] },
|
|
});
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toBeUndefined();
|
|
});
|
|
|
|
tap.test('should augment TLS passthrough routes', async () => {
|
|
const route = makeRoute({
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: 'localhost', port: 8080 }],
|
|
tls: { mode: 'passthrough' },
|
|
},
|
|
});
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toEqual('all');
|
|
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
|
|
});
|
|
|
|
tap.test('should augment terminate-and-reencrypt routes', async () => {
|
|
const route = makeRoute({
|
|
action: {
|
|
type: 'forward',
|
|
targets: [{ host: 'localhost', port: 8080 }],
|
|
tls: { mode: 'terminate-and-reencrypt', certificate: 'auto' },
|
|
},
|
|
});
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.match.transport).toEqual('all');
|
|
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
|
|
});
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
// Configuration tests
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
tap.test('should apply default QUIC settings when none provided', async () => {
|
|
const route = makeRoute();
|
|
const result = augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(result.action.udp!.quic!.altSvcMaxAge).toEqual(86400);
|
|
// Undefined means SmartProxy will use its own defaults
|
|
expect(result.action.udp!.quic!.maxIdleTimeout).toBeUndefined();
|
|
expect(result.action.udp!.quic!.altSvcPort).toBeUndefined();
|
|
});
|
|
|
|
tap.test('should apply custom QUIC settings', async () => {
|
|
const route = makeRoute();
|
|
const config: IHttp3Config = {
|
|
enabled: true,
|
|
quicSettings: {
|
|
maxIdleTimeout: 60000,
|
|
maxConcurrentBidiStreams: 200,
|
|
maxConcurrentUniStreams: 50,
|
|
initialCongestionWindow: 65536,
|
|
},
|
|
altSvc: {
|
|
port: 8443,
|
|
maxAge: 3600,
|
|
},
|
|
udpSettings: {
|
|
sessionTimeout: 120000,
|
|
maxSessionsPerIP: 500,
|
|
maxDatagramSize: 32768,
|
|
},
|
|
};
|
|
const result = augmentRouteWithHttp3(route, config);
|
|
|
|
expect(result.action.udp!.quic!.maxIdleTimeout).toEqual(60000);
|
|
expect(result.action.udp!.quic!.maxConcurrentBidiStreams).toEqual(200);
|
|
expect(result.action.udp!.quic!.maxConcurrentUniStreams).toEqual(50);
|
|
expect(result.action.udp!.quic!.initialCongestionWindow).toEqual(65536);
|
|
expect(result.action.udp!.quic!.altSvcPort).toEqual(8443);
|
|
expect(result.action.udp!.quic!.altSvcMaxAge).toEqual(3600);
|
|
expect(result.action.udp!.sessionTimeout).toEqual(120000);
|
|
expect(result.action.udp!.maxSessionsPerIP).toEqual(500);
|
|
expect(result.action.udp!.maxDatagramSize).toEqual(32768);
|
|
});
|
|
|
|
tap.test('should not mutate the original route', async () => {
|
|
const route = makeRoute();
|
|
const originalTransport = route.match.transport;
|
|
const originalUdp = route.action.udp;
|
|
|
|
augmentRouteWithHttp3(route, defaultConfig);
|
|
|
|
expect(route.match.transport).toEqual(originalTransport);
|
|
expect(route.action.udp).toEqual(originalUdp);
|
|
});
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
// Batch augmentation
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
tap.test('should augment multiple routes in a batch', async () => {
|
|
const routes = [
|
|
makeRoute({ name: 'web-app' }),
|
|
makeRoute({ name: 'smtp-route', match: { ports: 25 } }),
|
|
makeRoute({ name: 'api-gateway' }),
|
|
makeRoute({
|
|
name: 'dns-query',
|
|
action: { type: 'socket-handler' as any, socketHandler: (() => {}) as any },
|
|
}),
|
|
];
|
|
|
|
const results = augmentRoutesWithHttp3(routes, defaultConfig);
|
|
|
|
// web-app and api-gateway should be augmented
|
|
expect(results[0].match.transport).toEqual('all');
|
|
expect(results[2].match.transport).toEqual('all');
|
|
|
|
// smtp and dns should NOT be augmented
|
|
expect(results[1].match.transport).toBeUndefined();
|
|
expect(results[3].match.transport).toBeUndefined();
|
|
});
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
// Default enabled behavior
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
tap.test('should treat undefined enabled as true (default on)', async () => {
|
|
const route = makeRoute();
|
|
const result = augmentRouteWithHttp3(route, {}); // no enabled field at all
|
|
|
|
expect(result.match.transport).toEqual('all');
|
|
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
|
|
});
|
|
|
|
tap.test('should disable when enabled is explicitly false', async () => {
|
|
const route = makeRoute();
|
|
const result = augmentRouteWithHttp3(route, { enabled: false });
|
|
|
|
expect(result.match.transport).toBeUndefined();
|
|
expect(result.action.udp).toBeUndefined();
|
|
});
|
|
|
|
export default tap.start();
|