import { SmartProxy } from '../ts/proxies/smart-proxy/index.js'; import { expect, tap } from '@git.zone/tstest/tapbundle'; const testProxy = new SmartProxy({ routes: [{ name: 'test-route', match: { ports: 9443, domains: 'test.local' }, action: { type: 'forward', target: { host: 'localhost', port: 8080 }, tls: { mode: 'terminate', certificate: 'auto', acme: { email: 'test@test.local', useProduction: false } } } }], acme: { port: 9080 // Use high port for ACME challenges } }); tap.test('should provision certificate automatically', async () => { // Mock certificate manager to avoid real ACME initialization const mockCertStatus = { domain: 'test-route', status: 'valid' as const, source: 'acme' as const, expiryDate: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), issueDate: new Date() }; (testProxy as any).createCertificateManager = async function() { return { setUpdateRoutesCallback: () => {}, setHttpProxy: () => {}, setGlobalAcmeDefaults: () => {}, setAcmeStateManager: () => {}, initialize: async () => {}, provisionAllCertificates: async () => {}, stop: async () => {}, getAcmeOptions: () => ({ email: 'test@test.local', useProduction: false }), getState: () => ({ challengeRouteActive: false }), getCertificateStatus: () => mockCertStatus }; }; (testProxy as any).getCertificateStatus = () => mockCertStatus; await testProxy.start(); const status = testProxy.getCertificateStatus('test-route'); expect(status).toBeDefined(); expect(status.status).toEqual('valid'); expect(status.source).toEqual('acme'); await testProxy.stop(); }); tap.test('should handle static certificates', async () => { const proxy = new SmartProxy({ routes: [{ name: 'static-route', match: { ports: 9444, domains: 'static.example.com' }, action: { type: 'forward', target: { host: 'localhost', port: 8080 }, tls: { mode: 'terminate', certificate: { cert: '-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----', key: '-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----' } } } }] }); await proxy.start(); const status = proxy.getCertificateStatus('static-route'); expect(status).toBeDefined(); expect(status.status).toEqual('valid'); expect(status.source).toEqual('static'); await proxy.stop(); }); tap.test('should handle ACME challenge routes', async () => { const proxy = new SmartProxy({ routes: [{ name: 'auto-cert-route', match: { ports: 9445, domains: 'acme.local' }, action: { type: 'forward', target: { host: 'localhost', port: 8080 }, tls: { mode: 'terminate', certificate: 'auto', acme: { email: 'acme@test.local', useProduction: false, challengePort: 9081 } } } }, { name: 'port-9081-route', match: { ports: 9081, domains: 'acme.local' }, action: { type: 'forward', target: { host: 'localhost', port: 8080 } } }], acme: { port: 9081 // Use high port for ACME challenges } }); // Mock certificate manager to avoid real ACME initialization (proxy as any).createCertificateManager = async function() { return { setUpdateRoutesCallback: () => {}, setHttpProxy: () => {}, setGlobalAcmeDefaults: () => {}, setAcmeStateManager: () => {}, initialize: async () => {}, provisionAllCertificates: async () => {}, stop: async () => {}, getAcmeOptions: () => ({ email: 'acme@test.local', useProduction: false }), getState: () => ({ challengeRouteActive: false }) }; }; await proxy.start(); // Verify the proxy is configured with routes including the necessary port const routes = proxy.settings.routes; // Check that we have a route listening on the ACME challenge port const acmeChallengePort = 9081; const routesOnChallengePort = routes.filter((r: any) => { const ports = Array.isArray(r.match.ports) ? r.match.ports : [r.match.ports]; return ports.includes(acmeChallengePort); }); expect(routesOnChallengePort.length).toBeGreaterThan(0); expect(routesOnChallengePort[0].name).toEqual('port-9081-route'); // Verify the main route has ACME configuration const mainRoute = routes.find((r: any) => r.name === 'auto-cert-route'); expect(mainRoute).toBeDefined(); expect(mainRoute?.action.tls?.certificate).toEqual('auto'); expect(mainRoute?.action.tls?.acme?.email).toEqual('acme@test.local'); expect(mainRoute?.action.tls?.acme?.challengePort).toEqual(9081); await proxy.stop(); }); tap.test('should renew certificates', async () => { const proxy = new SmartProxy({ routes: [{ name: 'renew-route', match: { ports: 9446, domains: 'renew.local' }, action: { type: 'forward', target: { host: 'localhost', port: 8080 }, tls: { mode: 'terminate', certificate: 'auto', acme: { email: 'renew@test.local', useProduction: false, renewBeforeDays: 30 } } } }], acme: { port: 9082 // Use high port for ACME challenges } }); // Mock certificate manager with renewal capability let renewCalled = false; const mockCertStatus = { domain: 'renew-route', status: 'valid' as const, source: 'acme' as const, expiryDate: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), issueDate: new Date() }; (proxy as any).certManager = { renewCertificate: async (routeName: string) => { renewCalled = true; expect(routeName).toEqual('renew-route'); }, getCertificateStatus: () => mockCertStatus, setUpdateRoutesCallback: () => {}, setHttpProxy: () => {}, setGlobalAcmeDefaults: () => {}, setAcmeStateManager: () => {}, initialize: async () => {}, provisionAllCertificates: async () => {}, stop: async () => {}, getAcmeOptions: () => ({ email: 'renew@test.local', useProduction: false }), getState: () => ({ challengeRouteActive: false }) }; (proxy as any).createCertificateManager = async function() { return this.certManager; }; (proxy as any).getCertificateStatus = function(routeName: string) { return this.certManager.getCertificateStatus(routeName); }; (proxy as any).renewCertificate = async function(routeName: string) { if (this.certManager) { await this.certManager.renewCertificate(routeName); } }; await proxy.start(); // Force renewal await proxy.renewCertificate('renew-route'); expect(renewCalled).toBeTrue(); const status = proxy.getCertificateStatus('renew-route'); expect(status).toBeDefined(); expect(status.status).toEqual('valid'); await proxy.stop(); }); tap.start();