Skip to content

Commit aec091e

Browse files
committed
🚨 Add tests for cache (#1995)
1 parent 3ae32d1 commit aec091e

File tree

1 file changed

+259
-0
lines changed

1 file changed

+259
-0
lines changed

src/test/lib/clients/cache.test.ts

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
2+
import { Cache } from '$lib/clients/cache';
3+
4+
describe('Cache', () => {
5+
// Mock time functions
6+
beforeEach(() => {
7+
vi.useFakeTimers();
8+
});
9+
10+
afterEach(() => {
11+
vi.restoreAllMocks();
12+
vi.clearAllTimers();
13+
});
14+
15+
describe('constructor', () => {
16+
test('expects to create a cache with default values', () => {
17+
const cache = new Cache<string>();
18+
expect(cache.size).toBe(0);
19+
});
20+
21+
test('expects to throw error if TTL is not positive', () => {
22+
expect(() => new Cache<string>(-1)).toThrow('TTL must be positive');
23+
expect(() => new Cache<string>(-2)).toThrow('TTL must be positive');
24+
});
25+
26+
test('expects to throw error if max size is not positive', () => {
27+
expect(() => new Cache<string>(1000, 0)).toThrow('Max size must be positive');
28+
expect(() => new Cache<string>(1000, -1)).toThrow('Max size must be positive');
29+
});
30+
});
31+
32+
describe('set and get', () => {
33+
test('expects to set and get a value', () => {
34+
const cache = new Cache<string>();
35+
cache.set('key', 'value');
36+
expect(cache.get('key')).toBe('value');
37+
});
38+
39+
test('expects to return undefined for non-existent key', () => {
40+
const cache = new Cache<string>();
41+
expect(cache.get('nonexistent')).toBeUndefined();
42+
});
43+
44+
test('expects to throw error for invalid key', () => {
45+
const cache = new Cache<string>();
46+
expect(() => cache.set('', 'value')).toThrow('Invalid cache key');
47+
expect(() => cache.set('a'.repeat(256), 'value')).toThrow('Invalid cache key');
48+
// @ts-expect-error: Testing invalid input
49+
expect(() => cache.set(123, 'value')).toThrow('Invalid cache key');
50+
});
51+
52+
test('expects to return undefined and delete entry if it is expired', () => {
53+
const TTL = 1000;
54+
const cache = new Cache<string>(TTL);
55+
56+
cache.set('key', 'value');
57+
expect(cache.get('key')).toBe('value');
58+
59+
vi.advanceTimersByTime(TTL + 1);
60+
expect(cache.get('key')).toBeUndefined();
61+
expect(cache.size).toBe(0);
62+
});
63+
});
64+
65+
describe('key overwriting', () => {
66+
test('expects to overwrite value for existing key', () => {
67+
const cache = new Cache<string>();
68+
cache.set('key', 'value1');
69+
expect(cache.get('key')).toBe('value1');
70+
71+
cache.set('key', 'value2');
72+
expect(cache.get('key')).toBe('value2');
73+
expect(cache.size).toBe(1); // The size is same.
74+
});
75+
76+
test('expects to reset expiration when overwriting key', () => {
77+
const TTL = 1000;
78+
const cache = new Cache<string>(TTL);
79+
80+
vi.setSystemTime(new Date('2023-01-01T00:00:00.000Z'));
81+
cache.set('key', 'value1');
82+
83+
// 800 ms elapsed (not expired).
84+
vi.advanceTimersByTime(800);
85+
86+
// overwrite the key.
87+
cache.set('key', 'value2');
88+
89+
// Another 800 ms elapsed (total 1600 ms, expired if original value).
90+
vi.advanceTimersByTime(800);
91+
92+
// Overwritten values have not yet expired.
93+
expect(cache.get('key')).toBe('value2');
94+
});
95+
});
96+
97+
describe('LRU behavior', () => {
98+
test('expects to evict least recently accessed item when max size is reached', () => {
99+
const cache = new Cache<string>(1000, 3);
100+
101+
// Add three items to the cache.
102+
cache.set('key1', 'value1');
103+
cache.set('key2', 'value2');
104+
cache.set('key3', 'value3');
105+
106+
// Add a new item to exceed the max size (key1 expected to be evicted).
107+
cache.set('key4', 'value4');
108+
109+
// Expect key1 to be evicted, the others to be present.
110+
expect(cache.get('key1')).toBeUndefined();
111+
expect(cache.get('key2')).toBe('value2');
112+
expect(cache.get('key3')).toBe('value3');
113+
expect(cache.get('key4')).toBe('value4');
114+
});
115+
});
116+
117+
describe('size management', () => {
118+
test('expects to report correct size', () => {
119+
const cache = new Cache<string>();
120+
expect(cache.size).toBe(0);
121+
122+
cache.set('key1', 'value1');
123+
expect(cache.size).toBe(1);
124+
125+
cache.set('key2', 'value2');
126+
expect(cache.size).toBe(2);
127+
128+
cache.delete('key1');
129+
expect(cache.size).toBe(1);
130+
131+
cache.clear();
132+
expect(cache.size).toBe(0);
133+
});
134+
135+
test('expects to remove oldest entry when max size is reached', () => {
136+
const cache = new Cache<string>(1000, 2);
137+
138+
vi.setSystemTime(new Date('2023-01-01T00:00:00.000Z'));
139+
cache.set('key1', 'value1');
140+
141+
vi.setSystemTime(new Date('2023-01-01T00:00:01.000Z'));
142+
cache.set('key2', 'value2');
143+
144+
vi.setSystemTime(new Date('2023-01-01T00:00:02.000Z'));
145+
cache.set('key3', 'value3');
146+
147+
expect(cache.size).toBe(2);
148+
expect(cache.get('key1')).toBeUndefined();
149+
expect(cache.get('key2')).toBe('value2');
150+
expect(cache.get('key3')).toBe('value3');
151+
});
152+
});
153+
154+
describe('edge cases', () => {
155+
test('expects to handle different value types', () => {
156+
const cache = new Cache<unknown>();
157+
158+
// Save various types of values.
159+
cache.set('string', 'test');
160+
cache.set('number', 123);
161+
cache.set('boolean', true);
162+
cache.set('null', null);
163+
cache.set('object', { a: 1, b: 2 });
164+
cache.set('array', [1, 2, 3]);
165+
166+
// Validate the values are stored correctly.
167+
expect(cache.get('string')).toBe('test');
168+
expect(cache.get('number')).toBe(123);
169+
expect(cache.get('boolean')).toBeTruthy();
170+
expect(cache.get('null')).toBe(null);
171+
expect(cache.get('object')).toEqual({ a: 1, b: 2 });
172+
expect(cache.get('array')).toEqual([1, 2, 3]);
173+
});
174+
175+
test('expects to handle boundary TTL cases', () => {
176+
const TTL = 1000;
177+
const cache = new Cache<string>(TTL);
178+
179+
cache.set('key', 'value');
180+
181+
// TTL just not expired yet.
182+
vi.advanceTimersByTime(TTL);
183+
expect(cache.get('key')).toBe('value');
184+
185+
// TTL+1 has expired.
186+
vi.advanceTimersByTime(1);
187+
expect(cache.get('key')).toBeUndefined();
188+
});
189+
});
190+
191+
describe('health', () => {
192+
test('expects to report correct health information', () => {
193+
const cache = new Cache<string>();
194+
195+
vi.setSystemTime(new Date('2023-01-01T00:00:00.000Z'));
196+
cache.set('key1', 'value1');
197+
198+
vi.setSystemTime(new Date('2023-01-01T00:00:01.000Z'));
199+
cache.set('key2', 'value2');
200+
201+
const health = cache.health;
202+
expect(health.size).toBe(2);
203+
expect(health.oldestEntry).toBe(new Date('2023-01-01T00:00:00.000Z').getTime());
204+
});
205+
});
206+
207+
describe('delete and clear', () => {
208+
test('expects to delete an entry', () => {
209+
const cache = new Cache<string>();
210+
cache.set('key', 'value');
211+
expect(cache.get('key')).toBe('value');
212+
213+
cache.delete('key');
214+
expect(cache.get('key')).toBeUndefined();
215+
});
216+
217+
test('expects to clear all entries', () => {
218+
const cache = new Cache<string>();
219+
cache.set('key1', 'value1');
220+
cache.set('key2', 'value2');
221+
222+
cache.clear();
223+
expect(cache.size).toBe(0);
224+
expect(cache.get('key1')).toBeUndefined();
225+
expect(cache.get('key2')).toBeUndefined();
226+
});
227+
});
228+
229+
describe('automatic cleanup', () => {
230+
test('expects to automatically clean up expired entries', () => {
231+
const TTL = 1000;
232+
const cache = new Cache<string>(TTL);
233+
234+
cache.set('key1', 'value1');
235+
cache.set('key2', 'value2');
236+
expect(cache.size).toBe(2);
237+
238+
vi.advanceTimersByTime(TTL + 1);
239+
240+
// Force cleanup by triggering the interval callback
241+
vi.runOnlyPendingTimers();
242+
243+
expect(cache.size).toBe(0);
244+
});
245+
});
246+
247+
describe('dispose', () => {
248+
test('expects to dispose resources correctly', () => {
249+
const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
250+
const cache = new Cache<string>();
251+
252+
cache.set('key', 'value');
253+
cache.dispose();
254+
255+
expect(clearIntervalSpy).toHaveBeenCalled();
256+
expect(cache.size).toBe(0);
257+
});
258+
});
259+
});

0 commit comments

Comments
 (0)