import { tap, expect } from '@git.zone/tstest/tapbundle'; import { SmartProxy } from '../ts/index.js'; import * as plugins from '../ts/plugins.js'; /** * Test that verifies ACME challenge routes are properly created */ tap.test('should create ACME challenge route with high ports', async (tools) => { tools.timeout(5000); const capturedRoutes: any[] = []; const settings = { routes: [ { name: 'secure-route', match: { ports: [18443], // High port to avoid permission issues domains: 'test.local' }, action: { type: 'forward' as const, target: { host: 'localhost', port: 8080 }, tls: { mode: 'terminate' as const, certificate: 'auto' as const } } } ], acme: { email: 'test@example.com', port: 18080, // High port for ACME challenges useProduction: false // Use staging environment } }; const proxy = new SmartProxy(settings); // Capture route updates const originalUpdateRoutes = (proxy as any).updateRoutes.bind(proxy); (proxy as any).updateRoutes = async function(routes: any[]) { capturedRoutes.push([...routes]); return originalUpdateRoutes(routes); }; await proxy.start(); // Check that ACME challenge route was added const finalRoutes = capturedRoutes[capturedRoutes.length - 1]; const challengeRoute = finalRoutes.find((r: any) => r.name === 'acme-challenge'); expect(challengeRoute).toBeDefined(); expect(challengeRoute.match.path).toEqual('/.well-known/acme-challenge/*'); expect(challengeRoute.match.ports).toEqual(18080); expect(challengeRoute.action.type).toEqual('socket-handler'); expect(challengeRoute.priority).toEqual(1000); await proxy.stop(); }); tap.test('should handle HTTP request parsing correctly', async (tools) => { tools.timeout(5000); let handlerCalled = false; let receivedContext: any; const settings = { routes: [ { name: 'test-static', match: { ports: [18090], path: '/test/*' }, action: { type: 'socket-handler' as const, socketHandler: (socket, context) => { handlerCalled = true; receivedContext = context; // Parse HTTP request from socket socket.once('data', (data) => { const request = data.toString(); const lines = request.split('\r\n'); const [method, path, protocol] = lines[0].split(' '); // Send HTTP response const response = [ 'HTTP/1.1 200 OK', 'Content-Type: text/plain', 'Content-Length: 2', 'Connection: close', '', 'OK' ].join('\r\n'); socket.write(response); socket.end(); }); } } } ] }; const proxy = new SmartProxy(settings); // Mock NFTables manager (proxy as any).nftablesManager = { ensureNFTablesSetup: async () => {}, stop: async () => {} }; await proxy.start(); // Create a simple HTTP request const client = new plugins.net.Socket(); await new Promise((resolve, reject) => { client.connect(18090, 'localhost', () => { // Send HTTP request const request = [ 'GET /test/example HTTP/1.1', 'Host: localhost:18090', 'User-Agent: test-client', '', '' ].join('\r\n'); client.write(request); // Wait for response client.on('data', (data) => { const response = data.toString(); expect(response).toContain('HTTP/1.1 200'); expect(response).toContain('OK'); client.end(); resolve(); }); }); client.on('error', reject); }); // Verify handler was called expect(handlerCalled).toBeTrue(); expect(receivedContext).toBeDefined(); expect(receivedContext.path).toEqual('/test/example'); expect(receivedContext.method).toEqual('GET'); expect(receivedContext.headers.host).toEqual('localhost:18090'); await proxy.stop(); }); tap.start();