UNPKG

npmplus-mcp-server

Version:

Production-ready MCP server for intelligent JavaScript package management. Works with Claude, Windsurf, Cursor, VS Code, and any MCP-compatible AI editor.

250 lines (192 loc) 7.75 kB
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; import { CacheService } from '../../services/CacheService.js'; import { TEST_CONSTANTS } from '../setup.js'; describe('CacheService', () => { let cacheService: CacheService; beforeEach(() => { cacheService = new CacheService({ stdTTL: TEST_CONSTANTS.CACHE_TTL, checkperiod: 10, maxKeys: 100, }); }); afterEach(async () => { await cacheService.clear(); }); describe('Basic Operations', () => { it('should set and get values', async () => { const key = 'test-key'; const value = { data: 'test-value', number: 42 }; await cacheService.set(key, value); const retrieved = await cacheService.get(key); expect(retrieved).toEqual(value); }); it('should return undefined for non-existent keys', async () => { const result = await cacheService.get('non-existent'); expect(result).toBeUndefined(); }); it('should delete values', async () => { const key = 'delete-test'; const value = 'to-be-deleted'; await cacheService.set(key, value); expect(await cacheService.get(key)).toBe(value); await cacheService.delete(key); expect(await cacheService.get(key)).toBeUndefined(); }); it('should check if key exists', async () => { const key = 'exists-test'; expect(await cacheService.has(key)).toBe(false); await cacheService.set(key, 'value'); expect(await cacheService.has(key)).toBe(true); await cacheService.delete(key); expect(await cacheService.has(key)).toBe(false); }); it('should clear all values', async () => { await cacheService.set('key1', 'value1'); await cacheService.set('key2', 'value2'); expect(await cacheService.has('key1')).toBe(true); expect(await cacheService.has('key2')).toBe(true); await cacheService.clear(); expect(await cacheService.has('key1')).toBe(false); expect(await cacheService.has('key2')).toBe(false); }); }); describe('TTL (Time To Live)', () => { it('should respect custom TTL', async () => { const key = 'ttl-test'; const value = 'expires-soon'; const shortTTL = 0.05; // 50ms await cacheService.set(key, value, shortTTL); // Should exist immediately expect(await cacheService.get(key)).toBe(value); // Wait for expiration await new Promise(resolve => setTimeout(resolve, 100)); // Should be expired expect(await cacheService.get(key)).toBeUndefined(); }); it('should set and get TTL for keys', async () => { const key = 'ttl-update-test'; const value = 'test-value'; await cacheService.set(key, value); // Set TTL to 1 second const success = await cacheService.setTTL(key, 1); expect(success).toBe(true); // Get TTL (should be 1 second, might be slightly less due to execution time) const ttl = await cacheService.getTTL(key); expect(ttl).toBeGreaterThanOrEqual(0); expect(ttl).toBeLessThanOrEqual(1); }); }); describe('Metrics and Statistics', () => { it('should track cache hits and misses', async () => { const key = 'metrics-test'; const value = 'test-value'; // Miss await cacheService.get(key); // Set and hit await cacheService.set(key, value); await cacheService.get(key); await cacheService.get(key); // Another hit const metrics = cacheService.getMetrics(); expect(metrics.hits).toBe(2); expect(metrics.misses).toBe(1); expect(metrics.totalRequests).toBe(3); expect(metrics.hitRate).toBe(2/3); expect(metrics.keyCount).toBe(1); }); it('should track cache operations', async () => { const key1 = 'op-test-1'; const key2 = 'op-test-2'; await cacheService.set(key1, 'value1'); await cacheService.set(key2, 'value2'); await cacheService.delete(key1); const stats = cacheService.getStats(); expect(stats.keys).toBe(1); // Only key2 remains }); it('should return zero hit rate with no requests', () => { const metrics = cacheService.getMetrics(); expect(metrics.hitRate).toBe(0); expect(metrics.totalRequests).toBe(0); }); }); describe('Key Management', () => { it('should list all keys', async () => { const keys = ['key1', 'key2', 'key3']; for (const key of keys) { await cacheService.set(key, `value-${key}`); } const retrievedKeys = cacheService.getKeys(); expect(retrievedKeys.sort()).toEqual(keys.sort()); }); it('should create valid cache keys from parts', () => { const key1 = CacheService.createKey('search', 'react', 25, 0); expect(key1).toBe('search:react:25:0'); const key2 = CacheService.createKey('package', '@types/node', '18.0.0'); expect(key2).toBe('package:@types_node:18.0.0'); const key3 = CacheService.createKey('user', 'test@example.com', true); expect(key3).toBe('user:test@example.com:true'); }); it('should sanitize special characters in keys', () => { const key = CacheService.createKey('test', 'with/special:chars', '@version'); expect(key).toBe('test:with_special_chars:@version'); }); }); describe('Edge Cases', () => { it('should handle null and undefined values', async () => { await cacheService.set('null-test', null); await cacheService.set('undefined-test', undefined); expect(await cacheService.get('null-test')).toBeNull(); // node-cache converts undefined to null, which is expected behavior expect(await cacheService.get('undefined-test')).toBeNull(); }); it('should handle complex objects', async () => { const complexObject = { string: 'test', number: 42, boolean: true, array: [1, 2, 3], nested: { deep: { value: 'nested-value' } }, date: new Date('2023-01-01'), }; await cacheService.set('complex', complexObject); const retrieved = await cacheService.get('complex'); expect(retrieved).toEqual(complexObject); }); it('should handle very long keys', async () => { const longKey = 'a'.repeat(1000); const value = 'long-key-value'; await cacheService.set(longKey, value); expect(await cacheService.get(longKey)).toBe(value); }); it('should handle concurrent operations', async () => { const promises = []; // Set multiple keys concurrently for (let i = 0; i < 10; i++) { promises.push(cacheService.set(`concurrent-${i}`, `value-${i}`)); } await Promise.all(promises); // Verify all keys were set for (let i = 0; i < 10; i++) { expect(await cacheService.get(`concurrent-${i}`)).toBe(`value-${i}`); } }); }); describe('Memory Management', () => { it('should respect maxKeys limit', async () => { const limitedCache = new CacheService({ maxKeys: 3 }); // Add more keys than the limit await limitedCache.set('key1', 'value1'); await limitedCache.set('key2', 'value2'); await limitedCache.set('key3', 'value3'); // Adding one more key should throw an error (node-cache behavior) await expect(limitedCache.set('key4', 'value4')).rejects.toThrow('Cache max keys amount exceeded'); const keys = limitedCache.getKeys(); expect(keys.length).toBe(3); await limitedCache.clear(); }); }); });