Skip to main content
Glama

CTS MCP Server

by EricA1019
file_hash_cache.test.tsโ€ข8.89 kB
/** * File Hash Cache Tests * * Tests for incremental analysis caching system */ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; import { FileHashCache, getFileHashCache } from '../cache/file_hash_cache.js'; import { writeFileSync, unlinkSync, mkdirSync, existsSync, rmdirSync, readdirSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; describe('FileHashCache', () => { let testDir: string; let cache: FileHashCache; let testFile1: string; let testFile2: string; beforeEach(() => { // Create temp directory testDir = join(tmpdir(), `file-cache-test-${Date.now()}`); mkdirSync(testDir, { recursive: true }); // Create test files testFile1 = join(testDir, 'test1.gd'); testFile2 = join(testDir, 'test2.gd'); writeFileSync(testFile1, 'extends Node\nfunc test(): pass'); writeFileSync(testFile2, 'extends Node2D\nfunc test2(): pass'); // Create cache instance cache = new FileHashCache(testDir); }); afterEach(() => { // Clean up if (existsSync(testDir)) { try { const cacheDir = join(testDir, '.cts_cache'); if (existsSync(cacheDir)) { const files = readdirSync(cacheDir); for (const file of files) { unlinkSync(join(cacheDir, file)); } rmdirSync(cacheDir); } unlinkSync(testFile1); unlinkSync(testFile2); rmdirSync(testDir); } catch (err) { // Ignore cleanup errors } } }); describe('Basic Operations', () => { it('should return null for cache miss', () => { const result = cache.get(testFile1, 'CTS_FILE_SIZE'); expect(result).toBeNull(); }); it('should store and retrieve cached results', () => { const ruleResult = { passed: true, violations: [], score: 100 }; cache.set(testFile1, 'CTS_FILE_SIZE', ruleResult); const retrieved = cache.get(testFile1, 'CTS_FILE_SIZE'); expect(retrieved).toEqual(ruleResult); }); it('should handle multiple files', () => { cache.set(testFile1, 'CTS_FILE_SIZE', { passed: true }); cache.set(testFile2, 'CTS_FILE_SIZE', { passed: false }); expect(cache.get(testFile1, 'CTS_FILE_SIZE')).toEqual({ passed: true }); expect(cache.get(testFile2, 'CTS_FILE_SIZE')).toEqual({ passed: false }); }); it('should handle multiple rules per file', () => { cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); cache.set(testFile1, 'CTS_TYPE_HINTS', { score: 80 }); expect(cache.get(testFile1, 'CTS_FILE_SIZE')).toEqual({ score: 100 }); expect(cache.get(testFile1, 'CTS_TYPE_HINTS')).toEqual({ score: 80 }); }); }); describe('Cache Invalidation', () => { beforeEach(() => { // Set up initial cache cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); cache.set(testFile1, 'CTS_TYPE_HINTS', { score: 90 }); cache.set(testFile2, 'CTS_FILE_SIZE', { score: 80 }); }); it('should invalidate cache when file changes', () => { // Verify cache hit expect(cache.get(testFile1, 'CTS_FILE_SIZE')).toEqual({ score: 100 }); // Modify file writeFileSync(testFile1, 'extends Node\nfunc test(): pass\n# New line'); // Cache should miss (different hash) const result = cache.get(testFile1, 'CTS_FILE_SIZE'); expect(result).toBeNull(); }); it('should invalidate all entries for a file', () => { const invalidated = cache.invalidateFile(testFile1); expect(invalidated).toBe(2); // Two rules cached for testFile1 expect(cache.get(testFile1, 'CTS_FILE_SIZE')).toBeNull(); expect(cache.get(testFile1, 'CTS_TYPE_HINTS')).toBeNull(); // testFile2 should still be cached expect(cache.get(testFile2, 'CTS_FILE_SIZE')).toEqual({ score: 80 }); }); it('should invalidate all entries for a rule', () => { const invalidated = cache.invalidateRule('CTS_FILE_SIZE'); expect(invalidated).toBe(2); // testFile1 and testFile2 expect(cache.get(testFile1, 'CTS_FILE_SIZE')).toBeNull(); expect(cache.get(testFile2, 'CTS_FILE_SIZE')).toBeNull(); // CTS_TYPE_HINTS should still be cached expect(cache.get(testFile1, 'CTS_TYPE_HINTS')).toEqual({ score: 90 }); }); it('should clear all cache', () => { cache.clear(); expect(cache.get(testFile1, 'CTS_FILE_SIZE')).toBeNull(); expect(cache.get(testFile1, 'CTS_TYPE_HINTS')).toBeNull(); expect(cache.get(testFile2, 'CTS_FILE_SIZE')).toBeNull(); expect(cache.size()).toBe(0); }); }); describe('Statistics', () => { it('should track cache hits and misses', () => { cache.resetStats(); // Miss cache.get(testFile1, 'CTS_FILE_SIZE'); // Set cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); // Hit cache.get(testFile1, 'CTS_FILE_SIZE'); cache.get(testFile1, 'CTS_FILE_SIZE'); // Miss cache.get(testFile2, 'CTS_FILE_SIZE'); const stats = cache.getStats(); expect(stats.hits).toBe(2); expect(stats.misses).toBe(2); expect(stats.hitRate).toBe(0.5); // 2/4 }); it('should track unchanged files', () => { cache.resetStats(); // First access - sets hash cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); // Second access - same hash (files checked: 1, unchanged: 1) cache.get(testFile1, 'CTS_FILE_SIZE'); // Third access - same hash again (files checked: 2, unchanged: 2) cache.get(testFile1, 'CTS_FILE_SIZE'); const stats = cache.getStats(); expect(stats.filesUnchanged).toBe(2); // Both gets saw unchanged hash expect(stats.unchangedRate).toBeGreaterThan(0); }); it('should calculate performance metrics', () => { cache.resetStats(); // Simulate 90% cache hit rate for (let i = 0; i < 9; i++) { cache.set(testFile1, `RULE_${i}`, { score: 100 }); cache.get(testFile1, `RULE_${i}`); } cache.get(testFile1, 'MISS_RULE'); // 1 miss const perf = cache.getPerformanceMetrics(); expect(perf.hitRate).toBeCloseTo(0.9, 1); expect(perf.estimatedSpeedup).toBeGreaterThan(5); // ~10x with 90% hit rate }); }); describe('Persistence', () => { it('should save cache to disk', () => { cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); cache.set(testFile2, 'CTS_TYPE_HINTS', { score: 90 }); cache.saveCache(); const cacheFile = join(testDir, '.cts_cache', 'file_results.json'); expect(existsSync(cacheFile)).toBe(true); }); it('should load cache from disk', () => { // Save cache cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); cache.saveCache(); // Create new cache instance (should load from disk) const cache2 = new FileHashCache(testDir); const result = cache2.get(testFile1, 'CTS_FILE_SIZE'); expect(result).toEqual({ score: 100 }); }); it('should persist across sessions', () => { // Session 1 cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); cache.saveCache(); // Session 2 const cache2 = getFileHashCache(testDir); const result = cache2.get(testFile1, 'CTS_FILE_SIZE'); expect(result).toEqual({ score: 100 }); }); }); describe('Pruning', () => { it('should prune old entries', () => { // Add entries cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); // Manually set old timestamp on cache entry const cacheMap = (cache as any).cache as Map<string, any>; const entries = Array.from(cacheMap.values()); if (entries.length > 0) { entries[0].timestamp = Date.now() - 8 * 24 * 60 * 60 * 1000; // 8 days old } const pruned = cache.prune(7 * 24 * 60 * 60 * 1000); // 7 day max age expect(pruned).toBe(1); expect(cache.size()).toBe(0); }); }); describe('Enable/Disable', () => { it('should disable cache', () => { cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); cache.disable(); expect(cache.isEnabled()).toBe(false); expect(cache.get(testFile1, 'CTS_FILE_SIZE')).toBeNull(); expect(cache.size()).toBe(0); }); it('should re-enable cache', () => { cache.set(testFile1, 'CTS_FILE_SIZE', { score: 100 }); cache.saveCache(); cache.disable(); cache.enable(); expect(cache.isEnabled()).toBe(true); // Should reload from disk expect(cache.size()).toBeGreaterThan(0); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/EricA1019/CTS_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server