import { describe, it } from 'node:test';
import assert from 'node:assert';
// Mock cache implementation for testing
class LRUCache<K, V> {
private cache: Map<K, { value: V; timestamp: number }>;
private maxSize: number;
private ttl: number;
constructor(maxSize: number = 1000, ttlMs: number = 3600000) {
this.cache = new Map();
this.maxSize = maxSize;
this.ttl = ttlMs;
}
set(key: K, value: V): void {
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
if (firstKey !== undefined) {
this.cache.delete(firstKey);
}
}
this.cache.set(key, { value, timestamp: Date.now() });
}
get(key: K): V | undefined {
const entry = this.cache.get(key);
if (!entry) return undefined;
if (Date.now() - entry.timestamp > this.ttl) {
this.cache.delete(key);
return undefined;
}
return entry.value;
}
has(key: K): boolean {
return this.get(key) !== undefined;
}
clear(): void {
this.cache.clear();
}
size(): number {
return this.cache.size;
}
}
describe('LRU Cache Tests', () => {
describe('Basic Operations', () => {
it('should store and retrieve values', () => {
const cache = new LRUCache<string, number>(5);
cache.set('key1', 100);
assert.equal(cache.get('key1'), 100);
});
it('should return undefined for missing keys', () => {
const cache = new LRUCache<string, number>(5);
assert.equal(cache.get('nonexistent'), undefined);
});
it('should update existing keys', () => {
const cache = new LRUCache<string, number>(5);
cache.set('key1', 100);
cache.set('key1', 200);
assert.equal(cache.get('key1'), 200);
});
});
describe('Size Limits', () => {
it('should respect max size', () => {
const cache = new LRUCache<string, number>(3);
cache.set('key1', 1);
cache.set('key2', 2);
cache.set('key3', 3);
cache.set('key4', 4); // Should evict key1
assert.equal(cache.size(), 3);
assert.equal(cache.get('key1'), undefined);
assert.equal(cache.get('key2'), 2);
assert.equal(cache.get('key3'), 3);
assert.equal(cache.get('key4'), 4);
});
it('should handle single item cache', () => {
const cache = new LRUCache<string, number>(1);
cache.set('key1', 1);
cache.set('key2', 2);
assert.equal(cache.size(), 1);
assert.equal(cache.get('key1'), undefined);
assert.equal(cache.get('key2'), 2);
});
});
describe('TTL (Time To Live)', () => {
it('should expire entries after TTL', async () => {
const cache = new LRUCache<string, number>(5, 100); // 100ms TTL
cache.set('key1', 100);
assert.equal(cache.get('key1'), 100);
await new Promise(resolve => setTimeout(resolve, 150));
assert.equal(cache.get('key1'), undefined);
});
it('should not expire entries before TTL', async () => {
const cache = new LRUCache<string, number>(5, 1000); // 1s TTL
cache.set('key1', 100);
await new Promise(resolve => setTimeout(resolve, 100));
assert.equal(cache.get('key1'), 100);
});
});
describe('Has Method', () => {
it('should return true for existing keys', () => {
const cache = new LRUCache<string, number>(5);
cache.set('key1', 100);
assert.equal(cache.has('key1'), true);
});
it('should return false for missing keys', () => {
const cache = new LRUCache<string, number>(5);
assert.equal(cache.has('nonexistent'), false);
});
it('should return false for expired keys', async () => {
const cache = new LRUCache<string, number>(5, 100);
cache.set('key1', 100);
await new Promise(resolve => setTimeout(resolve, 150));
assert.equal(cache.has('key1'), false);
});
});
describe('Clear Method', () => {
it('should remove all entries', () => {
const cache = new LRUCache<string, number>(5);
cache.set('key1', 1);
cache.set('key2', 2);
cache.set('key3', 3);
cache.clear();
assert.equal(cache.size(), 0);
assert.equal(cache.get('key1'), undefined);
assert.equal(cache.get('key2'), undefined);
assert.equal(cache.get('key3'), undefined);
});
});
describe('Complex Data Types', () => {
it('should cache objects', () => {
const cache = new LRUCache<string, { name: string; value: number }>(5);
const obj = { name: 'test', value: 42 };
cache.set('obj1', obj);
const retrieved = cache.get('obj1');
assert.deepEqual(retrieved, obj);
});
it('should cache arrays', () => {
const cache = new LRUCache<string, number[]>(5);
const arr = [1, 2, 3, 4, 5];
cache.set('arr1', arr);
const retrieved = cache.get('arr1');
assert.deepEqual(retrieved, arr);
});
});
});
describe('Cache Performance Tests', () => {
it('should handle many operations efficiently', () => {
const cache = new LRUCache<number, string>(1000);
const start = Date.now();
// Insert 1000 items
for (let i = 0; i < 1000; i++) {
cache.set(i, `value${i}`);
}
// Retrieve 1000 items
for (let i = 0; i < 1000; i++) {
cache.get(i);
}
const duration = Date.now() - start;
// Should complete in reasonable time (less than 100ms)
assert.ok(duration < 100, `Cache operations took ${duration}ms, expected < 100ms`);
});
it('should handle cache overflow efficiently', () => {
const cache = new LRUCache<number, string>(100);
const start = Date.now();
// Insert 1000 items (will overflow cache multiple times)
for (let i = 0; i < 1000; i++) {
cache.set(i, `value${i}`);
}
const duration = Date.now() - start;
assert.equal(cache.size(), 100);
assert.ok(duration < 100, `Cache overflow handling took ${duration}ms, expected < 100ms`);
});
});