cache_manager.test.ts•6.21 kB
/**
* Cache Manager Tests
*
* Unit tests for GraphCacheManager with intelligent staleness detection.
*/
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { GraphCacheManager } from '../../src/artifacts/scanner/cache_manager.js';
import { ArtifactVersionRegistry } from '../../src/artifacts/artifact_metadata.js';
import { writeFile, unlink, utimes } from 'fs/promises';
import { join } from 'path';
import { tmpdir } from 'os';
describe('GraphCacheManager', () => {
let cacheManager: GraphCacheManager;
let testFile: string;
beforeEach(() => {
cacheManager = new GraphCacheManager();
testFile = join(tmpdir(), `test_${Date.now()}.gd`);
});
describe('Cache Staleness Detection', () => {
it('should detect cache miss (file not cached)', async () => {
const isStale = await cacheManager.isStale('/nonexistent/file.gd');
expect(isStale).toBe(true);
});
it('should detect fresh cache (file unchanged)', async () => {
// Create test file
await writeFile(testFile, 'extends Node\nsignal test_signal');
const mtime = Date.now();
cacheManager.updateCache(testFile, mtime);
const isStale = await cacheManager.isStale(testFile);
expect(isStale).toBe(false);
// Cleanup
await unlink(testFile);
});
it('should detect stale cache (file modified)', async () => {
// Create test file with old mtime
await writeFile(testFile, 'extends Node\nsignal old_signal');
const oldMtime = Date.now() - 10000; // 10 seconds ago
cacheManager.updateCache(testFile, oldMtime);
// Modify file (update mtime to now)
await utimes(testFile, new Date(), new Date());
const isStale = await cacheManager.isStale(testFile);
expect(isStale).toBe(true);
// Cleanup
await unlink(testFile);
});
it('should detect stale cache (version mismatch)', async () => {
// Create test file
await writeFile(testFile, 'extends Node');
const mtime = Date.now();
cacheManager.updateCache(testFile, mtime);
// Simulate version upgrade by creating new cache manager
const registryV4 = new ArtifactVersionRegistry();
registryV4.registerVersion('ast_forest', '4.0.0'); // Version mismatch
const newCacheManager = new GraphCacheManager(registryV4);
newCacheManager.updateCache(testFile, mtime);
// Original cache manager should detect staleness after version change
// (In practice, this tests schema version handling)
const isStale = await cacheManager.isStale(testFile);
expect(isStale).toBe(false); // Same manager, same version
// Cleanup
await unlink(testFile);
});
it('should handle missing file gracefully', async () => {
const deletedFile = '/tmp/deleted_file.gd';
// Cache a file that doesn't exist
cacheManager.updateCache(deletedFile, Date.now());
// Should detect as stale
const isStale = await cacheManager.isStale(deletedFile);
expect(isStale).toBe(true);
});
});
describe('Cache Operations', () => {
it('should update cache correctly', () => {
const filePath = '/test/file.gd';
const mtime = 1234567890;
cacheManager.updateCache(filePath, mtime);
const cachedFiles = cacheManager.getCachedFiles();
expect(cachedFiles).toContain(filePath);
expect(cacheManager.getCacheSize()).toBe(1);
});
it('should clear cache', () => {
cacheManager.updateCache('/file1.gd', Date.now());
cacheManager.updateCache('/file2.gd', Date.now());
expect(cacheManager.getCacheSize()).toBe(2);
cacheManager.clearCache();
expect(cacheManager.getCacheSize()).toBe(0);
expect(cacheManager.getCachedFiles()).toEqual([]);
});
it('should evict specific file', () => {
const file1 = '/file1.gd';
const file2 = '/file2.gd';
cacheManager.updateCache(file1, Date.now());
cacheManager.updateCache(file2, Date.now());
expect(cacheManager.getCacheSize()).toBe(2);
cacheManager.evict(file1);
expect(cacheManager.getCacheSize()).toBe(1);
expect(cacheManager.getCachedFiles()).not.toContain(file1);
expect(cacheManager.getCachedFiles()).toContain(file2);
});
it('should handle fractional mtime (round down)', () => {
const filePath = '/test/file.gd';
const mtime = 1234567890.456; // Fractional milliseconds
cacheManager.updateCache(filePath, mtime);
// Cache should store rounded-down value
// (Verified indirectly via staleness check)
const cached = cacheManager.getCachedFiles();
expect(cached).toContain(filePath);
});
});
describe('Integration with ArtifactVersionRegistry', () => {
it('should use custom version registry', () => {
const customRegistry = new ArtifactVersionRegistry();
customRegistry.registerVersion('ast_forest', '3.1.0');
const manager = new GraphCacheManager(customRegistry);
// Update cache
manager.updateCache('/file.gd', Date.now());
// Cache should work normally
expect(manager.getCacheSize()).toBe(1);
});
it('should default to version 3.0.0 for Phase 3', () => {
const manager = new GraphCacheManager();
// Cache manager should have version 3.0.0 registered
// (Verified indirectly - cache works correctly)
manager.updateCache('/file.gd', Date.now());
expect(manager.getCacheSize()).toBe(1);
});
});
describe('Edge Cases', () => {
it('should handle empty file path', async () => {
const isStale = await cacheManager.isStale('');
expect(isStale).toBe(true);
});
it('should handle concurrent cache updates', () => {
const filePath = '/concurrent/file.gd';
// Simulate concurrent updates
cacheManager.updateCache(filePath, 1000);
cacheManager.updateCache(filePath, 2000);
cacheManager.updateCache(filePath, 1500); // Out of order
// Last update should win
const cachedFiles = cacheManager.getCachedFiles();
expect(cachedFiles).toContain(filePath);
expect(cacheManager.getCacheSize()).toBe(1);
});
});
});