Creates diff-cache.ts module with generic DiffCache<T> class (TTL, prefix invalidation, env-var configuration) and exports phaseMetaCache / fileDiffCache singletons. Wires cache into getPhaseReviewDiff via getHeadCommitHash on BranchManager. Adds 6 unit tests for DiffCache and 5 integration tests verifying cache hit/miss behaviour, prefix invalidation, and NOT_FOUND guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
/**
|
|
* Unit tests for DiffCache class.
|
|
*
|
|
* Tests TTL expiry, prefix invalidation, and env-var TTL configuration.
|
|
*/
|
|
|
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import { DiffCache } from './diff-cache.js';
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('DiffCache', () => {
|
|
it('returns undefined for a key that was never set', () => {
|
|
const cache = new DiffCache<string>(5000);
|
|
expect(cache.get('nonexistent')).toBeUndefined();
|
|
});
|
|
|
|
it('returns value when entry has not expired', () => {
|
|
vi.spyOn(Date, 'now').mockReturnValue(1000);
|
|
const cache = new DiffCache<string>(5000);
|
|
cache.set('key', 'value');
|
|
vi.spyOn(Date, 'now').mockReturnValue(5999); // 4999ms elapsed, TTL=5000
|
|
expect(cache.get('key')).toBe('value');
|
|
});
|
|
|
|
it('returns undefined and deletes the entry when TTL has elapsed', () => {
|
|
vi.spyOn(Date, 'now').mockReturnValue(1000);
|
|
const cache = new DiffCache<string>(5000);
|
|
cache.set('key', 'value');
|
|
vi.spyOn(Date, 'now').mockReturnValue(6001); // 5001ms elapsed, TTL=5000
|
|
expect(cache.get('key')).toBeUndefined();
|
|
// Verify the key is no longer stored (second get also returns undefined)
|
|
vi.spyOn(Date, 'now').mockReturnValue(6001);
|
|
expect(cache.get('key')).toBeUndefined();
|
|
});
|
|
|
|
it('overwrites an existing entry and resets its TTL', () => {
|
|
vi.spyOn(Date, 'now').mockReturnValue(1000);
|
|
const cache = new DiffCache<string>(5000);
|
|
cache.set('key', 'first');
|
|
vi.spyOn(Date, 'now').mockReturnValue(4000); // overwrite before expiry
|
|
cache.set('key', 'second');
|
|
vi.spyOn(Date, 'now').mockReturnValue(8999); // 4999ms after overwrite, TTL=5000
|
|
expect(cache.get('key')).toBe('second');
|
|
vi.spyOn(Date, 'now').mockReturnValue(9001); // 5001ms after overwrite
|
|
expect(cache.get('key')).toBeUndefined();
|
|
});
|
|
|
|
it('invalidateByPrefix removes all matching keys and preserves others', () => {
|
|
const cache = new DiffCache<string>(60_000);
|
|
cache.set('phase-1:abc', 'a');
|
|
cache.set('phase-1:abc:file.ts', 'b');
|
|
cache.set('phase-2:xyz', 'c');
|
|
cache.invalidateByPrefix('phase-1:');
|
|
expect(cache.get('phase-1:abc')).toBeUndefined();
|
|
expect(cache.get('phase-1:abc:file.ts')).toBeUndefined();
|
|
expect(cache.get('phase-2:xyz')).toBe('c');
|
|
});
|
|
|
|
it('singleton instances use REVIEW_DIFF_CACHE_TTL_MS env var for TTL', async () => {
|
|
vi.resetModules();
|
|
vi.stubEnv('REVIEW_DIFF_CACHE_TTL_MS', '1000');
|
|
const { phaseMetaCache } = await import('./diff-cache.js');
|
|
|
|
vi.spyOn(Date, 'now').mockReturnValue(0);
|
|
phaseMetaCache.set('key', {} as any);
|
|
vi.spyOn(Date, 'now').mockReturnValue(999);
|
|
expect(phaseMetaCache.get('key')).toBeDefined();
|
|
vi.spyOn(Date, 'now').mockReturnValue(1001);
|
|
expect(phaseMetaCache.get('key')).toBeUndefined();
|
|
|
|
vi.unstubAllEnvs();
|
|
});
|
|
});
|