Add comprehensive admin features and multi-system integration
- 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
This commit is contained in:
parent
e8462ef301
commit
6eee14f8af
171 changed files with 32671 additions and 621 deletions
246
dev/test-rate-limiter.ts
Normal file
246
dev/test-rate-limiter.ts
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/**
|
||||
* 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);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue