/* * This file is part of a proprietary work. * * Copyright (c) 2025 Fossorial, Inc. * All rights reserved. * * This file is licensed under the Fossorial Commercial License. * You may not use this file except in compliance with the License. * Unauthorized use, copying, modification, or distribution is strictly prohibited. * * This file is not licensed under the AGPLv3. */ // Simple test file for the rate limit service with Redis // Run with: npx ts-node rateLimitService.test.ts import { RateLimitService } from './rateLimit'; function generateClientId() { return 'client-' + Math.random().toString(36).substring(2, 15); } async function runTests() { console.log('Starting Rate Limit Service Tests...\n'); const rateLimitService = new RateLimitService(); let testsPassed = 0; let testsTotal = 0; // Helper function to run a test async function test(name: string, testFn: () => Promise) { testsTotal++; try { await testFn(); console.log(`āœ… ${name}`); testsPassed++; } catch (error) { console.log(`āŒ ${name}: ${error}`); } } // Helper function for assertions function assert(condition: boolean, message: string) { if (!condition) { throw new Error(message); } } // Test 1: Basic rate limiting await test('Should allow requests under the limit', async () => { const clientId = generateClientId(); const maxRequests = 5; for (let i = 0; i < maxRequests - 1; i++) { const result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); assert(!result.isLimited, `Request ${i + 1} should be allowed`); assert(result.totalHits === i + 1, `Expected ${i + 1} hits, got ${result.totalHits}`); } }); // Test 2: Rate limit blocking await test('Should block requests over the limit', async () => { const clientId = generateClientId(); const maxRequests = 30; // Use up all allowed requests for (let i = 0; i < maxRequests - 1; i++) { const result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); assert(!result.isLimited, `Request ${i + 1} should be allowed`); } // Next request should be blocked const blockedResult = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); assert(blockedResult.isLimited, 'Request should be blocked'); assert(blockedResult.reason === 'global', 'Should be blocked for global reason'); }); // Test 3: Message type limits await test('Should handle message type limits', async () => { const clientId = generateClientId(); const globalMax = 10; const messageTypeMax = 2; // Send messages of type 'ping' up to the limit for (let i = 0; i < messageTypeMax - 1; i++) { const result = await rateLimitService.checkRateLimit( clientId, 'ping', globalMax, messageTypeMax ); assert(!result.isLimited, `Ping message ${i + 1} should be allowed`); } // Next 'ping' should be blocked const blockedResult = await rateLimitService.checkRateLimit( clientId, 'ping', globalMax, messageTypeMax ); assert(blockedResult.isLimited, 'Ping message should be blocked'); assert(blockedResult.reason === 'message_type:ping', 'Should be blocked for message type'); // Other message types should still work const otherResult = await rateLimitService.checkRateLimit( clientId, 'pong', globalMax, messageTypeMax ); assert(!otherResult.isLimited, 'Pong message should be allowed'); }); // Test 4: Reset functionality await test('Should reset client correctly', async () => { const clientId = generateClientId(); const maxRequests = 3; // Use up some requests await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); await rateLimitService.checkRateLimit(clientId, 'test', maxRequests); // Reset the client await rateLimitService.resetKey(clientId); // Should be able to make fresh requests const result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); assert(!result.isLimited, 'Request after reset should be allowed'); assert(result.totalHits === 1, 'Should have 1 hit after reset'); }); // Test 5: Different clients are independent await test('Should handle different clients independently', async () => { const client1 = generateClientId(); const client2 = generateClientId(); const maxRequests = 2; // Client 1 uses up their limit await rateLimitService.checkRateLimit(client1, undefined, maxRequests); await rateLimitService.checkRateLimit(client1, undefined, maxRequests); const client1Blocked = await rateLimitService.checkRateLimit(client1, undefined, maxRequests); assert(client1Blocked.isLimited, 'Client 1 should be blocked'); // Client 2 should still be able to make requests const client2Result = await rateLimitService.checkRateLimit(client2, undefined, maxRequests); assert(!client2Result.isLimited, 'Client 2 should not be blocked'); assert(client2Result.totalHits === 1, 'Client 2 should have 1 hit'); }); // Test 6: Decrement functionality await test('Should decrement correctly', async () => { const clientId = generateClientId(); const maxRequests = 5; // Make some requests await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); let result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); assert(result.totalHits === 3, 'Should have 3 hits before decrement'); // Decrement await rateLimitService.decrementRateLimit(clientId); // Next request should reflect the decrement result = await rateLimitService.checkRateLimit(clientId, undefined, maxRequests); assert(result.totalHits === 3, 'Should have 3 hits after decrement + increment'); }); // Wait a moment for any pending Redis operations console.log('\nWaiting for Redis sync...'); await new Promise(resolve => setTimeout(resolve, 1000)); // Force sync to test Redis integration await test('Should sync to Redis', async () => { await rateLimitService.forceSyncAllPendingData(); // If this doesn't throw, Redis sync is working assert(true, 'Redis sync completed'); }); // Cleanup await rateLimitService.cleanup(); // Results console.log(`\n--- Test Results ---`); console.log(`āœ… Passed: ${testsPassed}/${testsTotal}`); console.log(`āŒ Failed: ${testsTotal - testsPassed}/${testsTotal}`); if (testsPassed === testsTotal) { console.log('\nšŸŽ‰ All tests passed!'); process.exit(0); } else { console.log('\nšŸ’„ Some tests failed!'); process.exit(1); } } // Run the tests runTests().catch(error => { console.error('Test runner error:', error); process.exit(1); });