import * as plugins from '../ts/plugins.js'; import { SmartProxy } from '../ts/index.js'; import { tap, expect } from '@git.zone/tstest/tapbundle'; import { logger } from '../ts/core/utils/logger.js'; // Store the original logger reference let originalLogger: any = logger; let mockLogger: any; // Create test routes using high ports to avoid permission issues const createRoute = (id: number, domain: string, port: number = 8443) => ({ name: `test-route-${id}`, match: { ports: [port], domains: [domain] }, action: { type: 'forward' as const, target: { host: 'localhost', port: 3000 + id }, tls: { mode: 'terminate' as const, certificate: 'auto' as const, acme: { email: 'test@testdomain.test', useProduction: false } } } }); let testProxy: SmartProxy; tap.test('should setup test proxy for logger error handling tests', async () => { // Create a proxy for testing testProxy = new SmartProxy({ routes: [createRoute(1, 'test1.error-handling.test', 8443)], acme: { email: 'test@testdomain.test', useProduction: false, port: 8080 } }); // Mock the certificate manager to avoid actual ACME initialization const originalCreateCertManager = (testProxy as any).createCertificateManager; (testProxy as any).createCertificateManager = async function(routes: any[], certDir: string, acmeOptions: any, initialState?: any) { const mockCertManager = { setUpdateRoutesCallback: function(callback: any) { this.updateRoutesCallback = callback; }, updateRoutesCallback: null as any, setHttpProxy: function() {}, setGlobalAcmeDefaults: function() {}, setAcmeStateManager: function() {}, initialize: async function() {}, provisionAllCertificates: async function() {}, stop: async function() {}, getAcmeOptions: function() { return acmeOptions || { email: 'test@testdomain.test', useProduction: false }; }, getState: function() { return initialState || { challengeRouteActive: false }; } }; // Always set up the route update callback for ACME challenges mockCertManager.setUpdateRoutesCallback(async (routes) => { await this.updateRoutes(routes); }); return mockCertManager; }; // Mock initializeCertificateManager as well (testProxy as any).initializeCertificateManager = async function() { // Create mock cert manager using the method above this.certManager = await this.createCertificateManager( this.settings.routes, './certs', { email: 'test@testdomain.test', useProduction: false } ); }; // Start the proxy with mocked components await testProxy.start(); expect(testProxy).toBeTruthy(); }); tap.test('should handle logger errors in updateRoutes without failing', async () => { // Temporarily inject the mock logger that throws errors const origConsoleLog = console.log; let consoleLogCalled = false; // Spy on console.log to verify it's used as fallback console.log = (...args: any[]) => { consoleLogCalled = true; // Call original implementation but mute the output for tests // origConsoleLog(...args); }; try { // Create mock logger that throws mockLogger = { log: () => { throw new Error('Simulated logger error'); } }; // Override the logger in the imported module // This is a hack but necessary for testing (global as any).logger = mockLogger; // Access the internal logger used by SmartProxy const smartProxyImport = await import('../ts/proxies/smart-proxy/smart-proxy.js'); // @ts-ignore smartProxyImport.logger = mockLogger; // Update routes - this should not fail even with logger errors const newRoutes = [ createRoute(1, 'test1.error-handling.test', 8443), createRoute(2, 'test2.error-handling.test', 8444) ]; await testProxy.updateRoutes(newRoutes); // Verify that the update was successful expect((testProxy as any).settings.routes.length).toEqual(2); expect(consoleLogCalled).toEqual(true); } finally { // Always restore console.log and logger console.log = origConsoleLog; (global as any).logger = originalLogger; } }); tap.test('should handle logger errors in certificate manager callbacks', async () => { // Temporarily inject the mock logger that throws errors const origConsoleLog = console.log; let consoleLogCalled = false; // Spy on console.log to verify it's used as fallback console.log = (...args: any[]) => { consoleLogCalled = true; // Call original implementation but mute the output for tests // origConsoleLog(...args); }; try { // Create mock logger that throws mockLogger = { log: () => { throw new Error('Simulated logger error'); } }; // Override the logger in the imported module // This is a hack but necessary for testing (global as any).logger = mockLogger; // Access the cert manager and trigger the updateRoutesCallback const certManager = (testProxy as any).certManager; expect(certManager).toBeTruthy(); expect(certManager.updateRoutesCallback).toBeTruthy(); // Call the certificate manager's updateRoutesCallback directly const challengeRoute = { name: 'acme-challenge', match: { ports: [8080], path: '/.well-known/acme-challenge/*' }, action: { type: 'static' as const, content: 'mock-challenge-content' } }; // This should not throw, despite logger errors await certManager.updateRoutesCallback([...testProxy.settings.routes, challengeRoute]); // Verify console.log was used as fallback expect(consoleLogCalled).toEqual(true); } finally { // Always restore console.log and logger console.log = origConsoleLog; (global as any).logger = originalLogger; } }); tap.test('should clean up properly', async () => { await testProxy.stop(); }); tap.start();