import assert from 'node:assert/strict'; import { EventEmitter } from 'node:events'; import { createRedisCache, withCacheInvalidation } from '../src/cache/redis-cache.mjs'; class FakeRedisClient extends EventEmitter { constructor({ connectError = null } = {}) { super(); this.connectError = connectError; this.isOpen = false; this.isReady = false; this.values = new Map(); } async connect() { if (this.connectError) throw this.connectError; this.isOpen = true; this.isReady = true; this.emit('ready'); } async get(key) { return this.values.has(key) ? this.values.get(key) : null; } async set(key, value, options = {}) { if (options.NX && this.values.has(key)) return null; this.values.set(key, value); return 'OK'; } async incr(key) { const next = Number(this.values.get(key) || 0) + 1; this.values.set(key, String(next)); return next; } async quit() { this.isReady = false; this.isOpen = false; } destroy() { this.isReady = false; this.isOpen = false; } } const silentLogger = { warn() {} }; { const client = new FakeRedisClient(); const cache = await createRedisCache({ env: { REDIS_URL: 'redis://test', REDIS_CACHE_PREFIX: 'test', REDIS_CACHE_TTL_SECONDS: '30' }, logger: silentLogger, clientFactory: () => client }); let loads = 0; const load = async () => ({ version: ++loads }); assert.deepEqual(await cache.remember('public', 'home', load), { version: 1 }); assert.deepEqual(await cache.remember('public', 'home', load), { version: 1 }); assert.equal(loads, 1, '相同缓存键应只读取一次数据源'); await cache.invalidate('public'); assert.deepEqual(await cache.remember('public', 'home', load), { version: 2 }); assert.equal(loads, 2, '命名空间失效后应重新读取数据源'); let resultLoads = 0; const loadResults = async () => ({ version: ++resultLoads }); assert.deepEqual(await cache.remember('results', 'candidate:1', loadResults), { version: 1 }); const database = withCacheInvalidation({ client: 'test', async read() { return {}; }, async save() { return 'saved'; }, async close() {} }, cache, method => method === 'save' ? ['public', 'results'] : ['public']); assert.equal(await database.save(), 'saved'); assert.deepEqual(await cache.remember('public', 'home', load), { version: 3 }); assert.equal(loads, 3, '数据库写入后应让公开缓存失效'); assert.deepEqual(await cache.remember('results', 'candidate:1', loadResults), { version: 2 }); assert.equal(resultLoads, 2, '成绩相关写入后应让成绩缓存失效'); await cache.invalidate('results'); assert.deepEqual(await cache.remember('results', 'candidate:1', loadResults), { version: 3 }); assert.equal(resultLoads, 3, '后台手动刷新后应重新生成成绩缓存'); await cache.close(); } { const cache = await createRedisCache({ env: {}, logger: silentLogger }); let loads = 0; await cache.remember('public', 'home', async () => ++loads); await cache.remember('public', 'home', async () => ++loads); assert.equal(loads, 2, '未配置 Redis 时应始终回源'); assert.equal(cache.status, 'disabled'); } { const client = new FakeRedisClient({ connectError: new Error('connection refused') }); const cache = await createRedisCache({ env: { REDIS_URL: 'redis://unavailable' }, logger: silentLogger, clientFactory: () => client }); assert.equal(cache.status, 'unavailable'); assert.equal(await cache.remember('public', 'home', async () => 'database'), 'database'); } console.log('Redis 缓存测试通过');