185 lines
4.7 KiB
TypeScript
185 lines
4.7 KiB
TypeScript
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<void>[] = [];
|
|
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<void>[] = [];
|
|
|
|
// 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(); |