import { expect, tap } from '@git.zone/tstest/tapbundle'; import { SmartProxy, type IRouteConfig } from '../ts/index.js'; /** * Test that concurrent route updates complete successfully and maintain consistency * This replaces the previous implementation-specific mutex tests with behavior-based tests */ tap.test('should handle concurrent route updates correctly', async (tools) => { tools.timeout(15000); const initialRoute: IRouteConfig = { name: 'base-route', match: { ports: 8080 }, action: { type: 'forward', target: { host: 'localhost', port: 3000 } } }; const proxy = new SmartProxy({ routes: [initialRoute] }); await proxy.start(); // Create many concurrent updates to stress test the system const updatePromises: Promise[] = []; const routeNames: string[] = []; // Launch 20 concurrent updates for (let i = 0; i < 20; i++) { const routeName = `concurrent-route-${i}`; routeNames.push(routeName); const updatePromise = proxy.updateRoutes([ initialRoute, { name: routeName, match: { ports: 9000 + i }, action: { type: 'forward', target: { host: 'localhost', port: 4000 + i } } } ]); updatePromises.push(updatePromise); } // All updates should complete without errors await Promise.all(updatePromises); // Verify the final state is consistent const finalRoutes = proxy.routeManager.getAllRoutes(); // Should have base route plus one of the concurrent routes expect(finalRoutes.length).toEqual(2); expect(finalRoutes.some(r => r.name === 'base-route')).toBeTrue(); // One of the concurrent routes should have won const concurrentRoute = finalRoutes.find(r => r.name?.startsWith('concurrent-route-')); expect(concurrentRoute).toBeTruthy(); expect(routeNames).toContain(concurrentRoute!.name); await proxy.stop(); }); /** * Test rapid sequential route updates */ tap.test('should handle rapid sequential route updates', async (tools) => { tools.timeout(10000); const proxy = new SmartProxy({ routes: [{ name: 'initial', match: { ports: 8081 }, action: { type: 'forward', target: { host: 'localhost', port: 3000 } } }] }); await proxy.start(); // Perform rapid sequential updates for (let i = 0; i < 10; i++) { await proxy.updateRoutes([{ name: 'changing-route', match: { ports: 8081 }, action: { type: 'forward', target: { host: 'localhost', port: 3000 + i } } }]); } // Verify final state const finalRoutes = proxy.routeManager.getAllRoutes(); expect(finalRoutes.length).toEqual(1); expect(finalRoutes[0].name).toEqual('changing-route'); expect((finalRoutes[0].action as any).target.port).toEqual(3009); await proxy.stop(); }); /** * Test that port management remains consistent during concurrent updates */ tap.test('should maintain port consistency during concurrent updates', async (tools) => { tools.timeout(10000); const proxy = new SmartProxy({ routes: [{ name: 'port-test', match: { ports: 8082 }, action: { type: 'forward', target: { host: 'localhost', port: 3000 } } }] }); await proxy.start(); // Create updates that add and remove ports const updates: Promise[] = []; // Some updates add new ports for (let i = 0; i < 5; i++) { updates.push(proxy.updateRoutes([ { name: 'port-test', match: { ports: 8082 }, action: { type: 'forward', target: { host: 'localhost', port: 3000 } } }, { name: `new-port-${i}`, match: { ports: 9100 + i }, action: { type: 'forward', target: { host: 'localhost', port: 4000 + i } } } ])); } // Some updates remove ports for (let i = 0; i < 5; i++) { updates.push(proxy.updateRoutes([ { name: 'port-test', match: { ports: 8082 }, action: { type: 'forward', target: { host: 'localhost', port: 3000 } } } ])); } // Wait for all updates await Promise.all(updates); // Give time for port cleanup await new Promise(resolve => setTimeout(resolve, 100)); // Verify final state const finalRoutes = proxy.routeManager.getAllRoutes(); const listeningPorts = proxy['portManager'].getListeningPorts(); // Should only have the base port listening expect(listeningPorts).toContain(8082); // Routes should be consistent expect(finalRoutes.some(r => r.name === 'port-test')).toBeTrue(); await proxy.stop(); }); export default tap.start();