- Add admin dashboard with sync controls and data browser - Implement RMM, Auvik, and Addigy organization mappings - Add chunked ticket sync with progress tracking - Implement entity sync service with rate limiting - Add analytics engine and performance optimizer - Create data browser for all PSA entities - Add navigation components and UI improvements - Implement background processing and sync services - Add comprehensive documentation and migration scripts - Update configuration items with multi-system support - Enhance contact management and purchase history - Add issue type assignment and LLM analyzer - Improve error handling and logging utilities
246 lines
8.8 KiB
TypeScript
246 lines
8.8 KiB
TypeScript
/**
|
|
* Test script for Rate Limiter functionality
|
|
* Run with: npx tsx dev/test-rate-limiter.ts
|
|
*/
|
|
|
|
import { RateLimiter } from '../lib/services/rate-limiter';
|
|
|
|
// Mock API call function
|
|
async function mockApiCall(id: number, delay: number = 10): Promise<{ id: number; timestamp: number }> {
|
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
return { id, timestamp: Date.now() };
|
|
}
|
|
|
|
async function testRateLimiter() {
|
|
console.log('🔍 Testing Rate Limiter Functionality\n');
|
|
|
|
try {
|
|
// Test 1: Basic throttling with 5 requests/second
|
|
console.log('Test 1: Basic throttling (5 requests/second)');
|
|
const limiter1 = new RateLimiter(5);
|
|
const startTime1 = Date.now();
|
|
const results1: any[] = [];
|
|
|
|
// Queue 10 requests (should take ~2 seconds with 5 req/sec limit)
|
|
const promises1 = Array.from({ length: 10 }, (_, i) =>
|
|
limiter1.throttle(() => mockApiCall(i + 1))
|
|
);
|
|
|
|
for (const promise of promises1) {
|
|
const result = await promise;
|
|
results1.push(result);
|
|
}
|
|
|
|
const duration1 = Date.now() - startTime1;
|
|
console.log(`✅ Completed 10 requests in ${duration1}ms`);
|
|
console.log(` Expected: ~2000ms (10 requests ÷ 5 req/sec)`);
|
|
console.log(` Within acceptable range: ${duration1 >= 1800 && duration1 <= 2500 ? 'Yes' : 'No'}\n`);
|
|
|
|
// Test 2: High-volume throttling with 10 requests/second
|
|
console.log('Test 2: High-volume throttling (10 requests/second)');
|
|
const limiter2 = new RateLimiter(10);
|
|
const startTime2 = Date.now();
|
|
const results2: any[] = [];
|
|
|
|
// Queue 25 requests (should take ~2.5 seconds with 10 req/sec limit)
|
|
const promises2 = Array.from({ length: 25 }, (_, i) =>
|
|
limiter2.throttle(() => mockApiCall(i + 1))
|
|
);
|
|
|
|
for (const promise of promises2) {
|
|
const result = await promise;
|
|
results2.push(result);
|
|
}
|
|
|
|
const duration2 = Date.now() - startTime2;
|
|
console.log(`✅ Completed 25 requests in ${duration2}ms`);
|
|
console.log(` Expected: ~2500ms (25 requests ÷ 10 req/sec)`);
|
|
console.log(` Within acceptable range: ${duration2 >= 2300 && duration2 <= 3000 ? 'Yes' : 'No'}\n`);
|
|
|
|
// Test 3: Verify rate limit enforcement
|
|
console.log('Test 3: Verify rate limit enforcement (10 requests/second)');
|
|
const limiter3 = new RateLimiter(10);
|
|
const timestamps: number[] = [];
|
|
|
|
// Execute 15 requests and track timestamps
|
|
const promises3 = Array.from({ length: 15 }, (_, i) =>
|
|
limiter3.throttle(async () => {
|
|
const now = Date.now();
|
|
timestamps.push(now);
|
|
return mockApiCall(i + 1, 1);
|
|
})
|
|
);
|
|
|
|
await Promise.all(promises3);
|
|
|
|
// Check that no more than 10 requests happened in any 1-second window
|
|
let maxRequestsInWindow = 0;
|
|
for (let i = 0; i < timestamps.length; i++) {
|
|
const windowStart = timestamps[i];
|
|
const windowEnd = windowStart + 1000;
|
|
const requestsInWindow = timestamps.filter(t => t >= windowStart && t < windowEnd).length;
|
|
maxRequestsInWindow = Math.max(maxRequestsInWindow, requestsInWindow);
|
|
}
|
|
|
|
console.log(`✅ Maximum requests in any 1-second window: ${maxRequestsInWindow}`);
|
|
console.log(` Rate limit respected: ${maxRequestsInWindow <= 10 ? 'Yes' : 'No'}\n`);
|
|
|
|
// Test 4: Queue length tracking
|
|
console.log('Test 4: Queue length tracking');
|
|
const limiter4 = new RateLimiter(5);
|
|
|
|
// Queue multiple requests without awaiting
|
|
const promises4 = Array.from({ length: 20 }, (_, i) =>
|
|
limiter4.throttle(() => mockApiCall(i + 1, 50))
|
|
);
|
|
|
|
// Check queue length immediately after queuing
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
const queueLength = limiter4.getQueueLength();
|
|
console.log(`✅ Queue length after queuing 20 requests: ${queueLength}`);
|
|
console.log(` Queue has pending requests: ${queueLength > 0 ? 'Yes' : 'No'}`);
|
|
|
|
// Wait for all to complete
|
|
await Promise.all(promises4);
|
|
const finalQueueLength = limiter4.getQueueLength();
|
|
console.log(`✅ Queue length after completion: ${finalQueueLength}`);
|
|
console.log(` Queue is empty: ${finalQueueLength === 0 ? 'Yes' : 'No'}\n`);
|
|
|
|
// Test 5: Current request count tracking
|
|
console.log('Test 5: Current request count tracking');
|
|
const limiter5 = new RateLimiter(10);
|
|
const requestCounts: number[] = [];
|
|
|
|
// Execute requests and track current count
|
|
const promises5 = Array.from({ length: 15 }, (_, i) =>
|
|
limiter5.throttle(async () => {
|
|
const count = limiter5.getCurrentRequestCount();
|
|
requestCounts.push(count);
|
|
return mockApiCall(i + 1, 1);
|
|
})
|
|
);
|
|
|
|
await Promise.all(promises5);
|
|
|
|
const maxCount = Math.max(...requestCounts);
|
|
console.log(`✅ Maximum concurrent request count: ${maxCount}`);
|
|
console.log(` Never exceeded limit: ${maxCount <= 10 ? 'Yes' : 'No'}\n`);
|
|
|
|
// Test 6: Reset functionality
|
|
console.log('Test 6: Reset functionality');
|
|
const limiter6 = new RateLimiter(5);
|
|
|
|
// Queue some requests
|
|
const promises6 = Array.from({ length: 10 }, (_, i) =>
|
|
limiter6.throttle(() => mockApiCall(i + 1, 100))
|
|
);
|
|
|
|
// Wait a bit then reset
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
const queueBeforeReset = limiter6.getQueueLength();
|
|
limiter6.reset();
|
|
const queueAfterReset = limiter6.getQueueLength();
|
|
|
|
console.log(`✅ Queue length before reset: ${queueBeforeReset}`);
|
|
console.log(`✅ Queue length after reset: ${queueAfterReset}`);
|
|
console.log(` Reset cleared queue: ${queueAfterReset === 0 ? 'Yes' : 'No'}\n`);
|
|
|
|
// Test 7: Error handling
|
|
console.log('Test 7: Error handling');
|
|
const limiter7 = new RateLimiter(10);
|
|
let errorCaught = false;
|
|
|
|
try {
|
|
await limiter7.throttle(async () => {
|
|
throw new Error('Mock API error');
|
|
});
|
|
} catch (error) {
|
|
errorCaught = true;
|
|
}
|
|
|
|
console.log(`✅ Error properly propagated: ${errorCaught ? 'Yes' : 'No'}`);
|
|
|
|
// Verify limiter still works after error
|
|
const resultAfterError = await limiter7.throttle(() => mockApiCall(1));
|
|
console.log(`✅ Limiter functional after error: ${resultAfterError.id === 1 ? 'Yes' : 'No'}\n`);
|
|
|
|
// Test 8: Parallel execution within limit
|
|
console.log('Test 8: Parallel execution within limit');
|
|
const limiter8 = new RateLimiter(10);
|
|
const startTime8 = Date.now();
|
|
|
|
// Queue 10 requests that each take 100ms
|
|
// With 10 req/sec limit, they should execute in parallel (not sequentially)
|
|
const promises8 = Array.from({ length: 10 }, (_, i) =>
|
|
limiter8.throttle(() => mockApiCall(i + 1, 100))
|
|
);
|
|
|
|
await Promise.all(promises8);
|
|
const duration8 = Date.now() - startTime8;
|
|
|
|
console.log(`✅ Completed 10 requests (100ms each) in ${duration8}ms`);
|
|
console.log(` Executed in parallel: ${duration8 < 500 ? 'Yes' : 'No'}`);
|
|
console.log(` (Sequential would take ~1000ms, parallel ~100ms)\n`);
|
|
|
|
// Test 9: Stress test with many requests
|
|
console.log('Test 9: Stress test (100 requests at 10 req/sec)');
|
|
const limiter9 = new RateLimiter(10);
|
|
const startTime9 = Date.now();
|
|
|
|
const promises9 = Array.from({ length: 100 }, (_, i) =>
|
|
limiter9.throttle(() => mockApiCall(i + 1, 1))
|
|
);
|
|
|
|
await Promise.all(promises9);
|
|
const duration9 = Date.now() - startTime9;
|
|
|
|
console.log(`✅ Completed 100 requests in ${duration9}ms`);
|
|
console.log(` Expected: ~10000ms (100 requests ÷ 10 req/sec)`);
|
|
console.log(` Within acceptable range: ${duration9 >= 9500 && duration9 <= 11000 ? 'Yes' : 'No'}\n`);
|
|
|
|
// Test 10: Different rate limits
|
|
console.log('Test 10: Custom rate limits');
|
|
const limiter10a = new RateLimiter(2); // 2 req/sec
|
|
const limiter10b = new RateLimiter(20); // 20 req/sec
|
|
|
|
const startTime10a = Date.now();
|
|
await Promise.all(
|
|
Array.from({ length: 6 }, (_, i) =>
|
|
limiter10a.throttle(() => mockApiCall(i + 1, 1))
|
|
)
|
|
);
|
|
const duration10a = Date.now() - startTime10a;
|
|
|
|
const startTime10b = Date.now();
|
|
await Promise.all(
|
|
Array.from({ length: 40 }, (_, i) =>
|
|
limiter10b.throttle(() => mockApiCall(i + 1, 1))
|
|
)
|
|
);
|
|
const duration10b = Date.now() - startTime10b;
|
|
|
|
console.log(`✅ 6 requests at 2 req/sec: ${duration10a}ms (expected ~3000ms)`);
|
|
console.log(`✅ 40 requests at 20 req/sec: ${duration10b}ms (expected ~2000ms)`);
|
|
console.log(` Both within acceptable ranges: ${
|
|
(duration10a >= 2700 && duration10a <= 3500) &&
|
|
(duration10b >= 1800 && duration10b <= 2500) ? 'Yes' : 'No'
|
|
}\n`);
|
|
|
|
console.log('🎉 All rate limiter tests completed successfully!');
|
|
|
|
} catch (error) {
|
|
console.error('❌ Test failed with error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Run the tests
|
|
testRateLimiter()
|
|
.then(() => {
|
|
console.log('\n✅ Test script completed successfully');
|
|
process.exit(0);
|
|
})
|
|
.catch((error) => {
|
|
console.error('\n❌ Test script failed:', error);
|
|
process.exit(1);
|
|
});
|