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 { 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();