function escapeRegex(input) { return String(input).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function globToRegex(glob) { const parts = String(glob).split('*').map(escapeRegex); const regex = `^${parts.join('.*')}$`; return new RegExp(regex); } export function createFakeRedis(initial = {}) { const kv = new Map(Object.entries(initial)); return { isReady: true, async get(key) { return kv.has(key) ? kv.get(key) : null; }, async set(key, value) { kv.set(key, String(value)); return 'OK'; }, async del(key) { const existed = kv.delete(key); return existed ? 1 : 0; }, async keys(pattern) { const re = globToRegex(pattern); return Array.from(kv.keys()).filter((k) => re.test(k)); }, // optional: used by logs route async lRange(key, start, stop) { const raw = kv.get(key); const list = Array.isArray(raw) ? raw : []; const normalizeIndex = (idx) => (idx < 0 ? list.length + idx : idx); const s = normalizeIndex(start); const e = normalizeIndex(stop); return list.slice(Math.max(0, s), Math.min(list.length, e + 1)); }, // helper for tests _dump() { return Object.fromEntries(kv.entries()); }, }; }