File size: 6,103 Bytes
7a1ad33 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | /**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { createCache } from './cache.js';
describe('CacheService', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
describe('Basic operations', () => {
it('should store and retrieve values by default (Map)', () => {
const cache = createCache<string, string>({ storage: 'map' });
cache.set('key', 'value');
expect(cache.get('key')).toBe('value');
});
it('should return undefined for missing keys', () => {
const cache = createCache<string, string>({ storage: 'map' });
expect(cache.get('missing')).toBeUndefined();
});
it('should delete entries', () => {
const cache = createCache<string, string>({ storage: 'map' });
cache.set('key', 'value');
cache.delete('key');
expect(cache.get('key')).toBeUndefined();
});
it('should clear all entries (Map)', () => {
const cache = createCache<string, string>({ storage: 'map' });
cache.set('k1', 'v1');
cache.set('k2', 'v2');
cache.clear();
expect(cache.get('k1')).toBeUndefined();
expect(cache.get('k2')).toBeUndefined();
});
it('should throw on clear() for WeakMap', () => {
const cache = createCache<object, string>({ storage: 'weakmap' });
expect(() => cache.clear()).toThrow(
'clear() is not supported on WeakMap storage',
);
});
});
describe('TTL and Expiration', () => {
it('should expire entries based on defaultTtl', () => {
const cache = createCache<string, string>({
storage: 'map',
defaultTtl: 1000,
});
cache.set('key', 'value');
vi.advanceTimersByTime(500);
expect(cache.get('key')).toBe('value');
vi.advanceTimersByTime(600); // Total 1100
expect(cache.get('key')).toBeUndefined();
});
it('should expire entries based on specific ttl override', () => {
const cache = createCache<string, string>({
storage: 'map',
defaultTtl: 5000,
});
cache.set('key', 'value', 1000);
vi.advanceTimersByTime(1100);
expect(cache.get('key')).toBeUndefined();
});
it('should not expire if ttl is undefined', () => {
const cache = createCache<string, string>({ storage: 'map' });
cache.set('key', 'value');
vi.advanceTimersByTime(100000);
expect(cache.get('key')).toBe('value');
});
});
describe('getOrCreate', () => {
it('should return existing value if not expired', () => {
const cache = createCache<string, string>({ storage: 'map' });
cache.set('key', 'old');
const creator = vi.fn().mockReturnValue('new');
const result = cache.getOrCreate('key', creator);
expect(result).toBe('old');
expect(creator).not.toHaveBeenCalled();
});
it('should create and store value if missing', () => {
const cache = createCache<string, string>({ storage: 'map' });
const creator = vi.fn().mockReturnValue('new');
const result = cache.getOrCreate('key', creator);
expect(result).toBe('new');
expect(creator).toHaveBeenCalled();
expect(cache.get('key')).toBe('new');
});
it('should recreate value if expired', () => {
const cache = createCache<string, string>({
storage: 'map',
defaultTtl: 1000,
});
cache.set('key', 'old');
vi.advanceTimersByTime(1100);
const creator = vi.fn().mockReturnValue('new');
const result = cache.getOrCreate('key', creator);
expect(result).toBe('new');
expect(creator).toHaveBeenCalled();
});
});
describe('Promise Support', () => {
beforeEach(() => {
vi.useRealTimers();
});
it('should remove failed promises from cache by default', async () => {
const cache = createCache<string, Promise<string>>({ storage: 'map' });
const promise = Promise.reject(new Error('fail'));
// We need to catch it to avoid unhandled rejection in test
promise.catch(() => {});
cache.set('key', promise);
expect(cache.get('key')).toBe(promise);
// Wait for promise to settle
await new Promise((resolve) => setImmediate(resolve));
expect(cache.get('key')).toBeUndefined();
});
it('should NOT remove failed promises if deleteOnPromiseFailure is false', async () => {
const cache = createCache<string, Promise<string>>({
storage: 'map',
deleteOnPromiseFailure: false,
});
const promise = Promise.reject(new Error('fail'));
promise.catch(() => {});
cache.set('key', promise);
await new Promise((resolve) => setImmediate(resolve));
expect(cache.get('key')).toBe(promise);
});
it('should only delete the specific failed entry', async () => {
const cache = createCache<string, Promise<string>>({ storage: 'map' });
const failPromise = Promise.reject(new Error('fail'));
failPromise.catch(() => {});
cache.set('key', failPromise);
// Overwrite with a new success promise before failure settles
const successPromise = Promise.resolve('ok');
cache.set('key', successPromise);
await new Promise((resolve) => setImmediate(resolve));
// Should still be successPromise
expect(cache.get('key')).toBe(successPromise);
});
});
describe('WeakMap Storage', () => {
it('should work with object keys explicitly', () => {
const cache = createCache<object, string>({ storage: 'weakmap' });
const key = { id: 1 };
cache.set(key, 'value');
expect(cache.get(key)).toBe('value');
});
it('should default to Map for objects', () => {
const cache = createCache<object, string>();
const key = { id: 1 };
cache.set(key, 'value');
expect(cache.get(key)).toBe('value');
// clear() should NOT throw because default is Map
expect(() => cache.clear()).not.toThrow();
});
});
});
|