import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { cache } from './cache'; describe('Cache', () => { beforeEach(() => { vi.useFakeTimers(); cache.clear(); // Clear cache before each test }); afterEach(() => { vi.useRealTimers(); cache.clear(); // Clean up after each test }); describe('set and get', () => { it('should set and retrieve a value', () => { cache.set('key', 'value'); expect(cache.get('key')).toBe('value'); }); it('should set multiple values with different keys', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); cache.set('key3', 'value3'); expect(cache.get('key1')).toBe('value1'); expect(cache.get('key2')).toBe('value2'); expect(cache.get('key3')).toBe('value3'); }); it('should overwrite existing value for same key', () => { cache.set('key', 'value1'); expect(cache.get('key')).toBe('value1'); cache.set('key', 'value2'); expect(cache.get('key')).toBe('value2'); }); it('should return null for non-existing key', () => { expect(cache.get('nonexistent')).toBeNull(); }); it('should store different types of values', () => { cache.set('string', 'text'); cache.set('number', 42); cache.set('boolean', true); cache.set('object', { name: 'test' }); cache.set('array', [1, 2, 3]); cache.set('null', null); expect(cache.get('string')).toBe('text'); expect(cache.get('number')).toBe(42); expect(cache.get('boolean')).toBe(true); expect(cache.get('object')).toEqual({ name: 'test' }); expect(cache.get('array')).toEqual([1, 2, 3]); expect(cache.get('null')).toBeNull(); }); }); describe('TTL expiration', () => { it('should use default TTL of 5 minutes', () => { cache.set('test', 'value'); // Should be available before TTL expires expect(cache.get('test')).toBe('value'); // Advance time by 4 minutes 59 seconds vi.advanceTimersByTime(4 * 60 * 1000 + 59 * 1000); expect(cache.get('test')).toBe('value'); // Advance time by 2 more seconds (total 5 minutes 1 second) vi.advanceTimersByTime(2 * 1000); expect(cache.get('test')).toBeNull(); }); it('should accept custom TTL per item', () => { cache.set('short', 'value', 1000); // 1 second TTL cache.set('long', 'value', 10000); // 10 seconds TTL // Both should be available initially expect(cache.get('short')).toBe('value'); expect(cache.get('long')).toBe('value'); // After 1.5 seconds, short should expire but long should remain vi.advanceTimersByTime(1500); expect(cache.get('short')).toBeNull(); expect(cache.get('long')).toBe('value'); // After 9 more seconds (total 10.5), long should expire vi.advanceTimersByTime(9000); expect(cache.get('long')).toBeNull(); }); it('should return null for expired item', () => { cache.set('key', 'value', 1000); // Should be available initially expect(cache.get('key')).toBe('value'); // Should be null after expiration vi.advanceTimersByTime(1001); expect(cache.get('key')).toBeNull(); }); it('should delete expired item from storage', () => { cache.set('key', 'value', 1000); // Item should exist expect(cache.get('key')).toBe('value'); // After expiration, get should delete it vi.advanceTimersByTime(1001); expect(cache.get('key')).toBeNull(); // Subsequent get should also return null (item was deleted) expect(cache.get('key')).toBeNull(); }); it('should return value at exact TTL boundary', () => { cache.set('key', 'value', 1000); // At exactly TTL time, should still be valid vi.advanceTimersByTime(1000); expect(cache.get('key')).toBe('value'); // One millisecond later, should expire vi.advanceTimersByTime(1); expect(cache.get('key')).toBeNull(); }); it('should respect different TTLs for different items', () => { cache.set('item1', 'value1', 1000); cache.set('item2', 'value2', 2000); cache.set('item3', 'value3', 3000); // All items should be available initially expect(cache.get('item1')).toBe('value1'); expect(cache.get('item2')).toBe('value2'); expect(cache.get('item3')).toBe('value3'); // After 1.5 seconds, item1 should expire vi.advanceTimersByTime(1500); expect(cache.get('item1')).toBeNull(); expect(cache.get('item2')).toBe('value2'); expect(cache.get('item3')).toBe('value3'); // After 1 more second (total 2.5), item2 should expire vi.advanceTimersByTime(1000); expect(cache.get('item1')).toBeNull(); expect(cache.get('item2')).toBeNull(); expect(cache.get('item3')).toBe('value3'); // After 1 more second (total 3.5), item3 should expire vi.advanceTimersByTime(1000); expect(cache.get('item1')).toBeNull(); expect(cache.get('item2')).toBeNull(); expect(cache.get('item3')).toBeNull(); }); it('should reset TTL when item is overwritten', () => { cache.set('key', 'value1', 1000); // Advance time by 500ms vi.advanceTimersByTime(500); // Overwrite with new value and new TTL cache.set('key', 'value2', 2000); // After 1500ms more (total 2000ms from first set), should still be available // because TTL was reset vi.advanceTimersByTime(1500); expect(cache.get('key')).toBe('value2'); // After 600ms more (2100ms from second set), should expire vi.advanceTimersByTime(600); expect(cache.get('key')).toBeNull(); }); it('should handle zero TTL', () => { cache.set('key', 'value', 0); // Should expire immediately vi.advanceTimersByTime(1); expect(cache.get('key')).toBeNull(); }); it('should handle very long TTL', () => { const oneDayInMs = 24 * 60 * 60 * 1000; cache.set('key', 'value', oneDayInMs); // Should be available after 23 hours vi.advanceTimersByTime(23 * 60 * 60 * 1000); expect(cache.get('key')).toBe('value'); // Should still be available at exactly 24 hours vi.advanceTimersByTime(60 * 60 * 1000); expect(cache.get('key')).toBe('value'); // Should expire after 24 hours + 1ms vi.advanceTimersByTime(1); expect(cache.get('key')).toBeNull(); }); }); describe('has', () => { it('should return true for existing key', () => { cache.set('key', 'value'); expect(cache.has('key')).toBe(true); }); it('should return false for non-existing key', () => { expect(cache.has('nonexistent')).toBe(false); }); it('should return false for expired key', () => { cache.set('key', 'value', 1000); expect(cache.has('key')).toBe(true); vi.advanceTimersByTime(1001); expect(cache.has('key')).toBe(false); }); it('should return false for null values', () => { cache.set('key', null); expect(cache.has('key')).toBe(false); }); }); describe('delete', () => { it('should delete existing key', () => { cache.set('key', 'value'); expect(cache.get('key')).toBe('value'); cache.delete('key'); expect(cache.get('key')).toBeNull(); }); it('should handle deleting non-existing key', () => { expect(() => cache.delete('nonexistent')).not.toThrow(); expect(cache.get('nonexistent')).toBeNull(); }); it('should only delete specified key', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); cache.set('key3', 'value3'); cache.delete('key2'); expect(cache.get('key1')).toBe('value1'); expect(cache.get('key2')).toBeNull(); expect(cache.get('key3')).toBe('value3'); }); }); describe('clear', () => { it('should clear all items from cache', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); cache.set('key3', 'value3'); cache.clear(); expect(cache.get('key1')).toBeNull(); expect(cache.get('key2')).toBeNull(); expect(cache.get('key3')).toBeNull(); }); it('should handle clearing empty cache', () => { expect(() => cache.clear()).not.toThrow(); }); it('should allow setting items after clear', () => { cache.set('key', 'value1'); cache.clear(); cache.set('key', 'value2'); expect(cache.get('key')).toBe('value2'); }); }); });