Use a 20ms TTL with a 40ms wait so the expiry window is long enough for the immediate second read to hit the in-memory cache reliably.
67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("@/lib/redis", () => ({
|
|
redis: null,
|
|
}));
|
|
|
|
import { cached, invalidateMemory } from "./cache";
|
|
|
|
describe("cached (memory-only, no Redis)", () => {
|
|
it("computes once and serves the cached value", async () => {
|
|
const fn = vi.fn(async () => 42);
|
|
const key = `once-${Math.random()}`;
|
|
expect(await cached(key, 10_000, fn)).toBe(42);
|
|
expect(await cached(key, 10_000, fn)).toBe(42);
|
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("dedupes concurrent misses into a single computation", async () => {
|
|
let resolveFn: (value: number) => void = () => {};
|
|
const fn = vi.fn(
|
|
() =>
|
|
new Promise<number>((resolve) => {
|
|
resolveFn = resolve;
|
|
}),
|
|
);
|
|
const key = `dedupe-${Math.random()}`;
|
|
const p1 = cached(key, 10_000, fn);
|
|
const p2 = cached(key, 10_000, fn);
|
|
const p3 = cached(key, 10_000, fn);
|
|
resolveFn(7);
|
|
expect(await Promise.all([p1, p2, p3])).toEqual([7, 7, 7]);
|
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("recomputes after the TTL expires", async () => {
|
|
let count = 0;
|
|
const fn = vi.fn(async () => ++count);
|
|
const key = `ttl-${Math.random()}`;
|
|
const ttl = 20;
|
|
expect(await cached(key, ttl, fn)).toBe(1);
|
|
expect(await cached(key, ttl, fn)).toBe(1);
|
|
await new Promise((r) => setTimeout(r, 40));
|
|
expect(await cached(key, ttl, fn)).toBe(2);
|
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("invalidates a key so the next read recomputes", async () => {
|
|
let count = 0;
|
|
const fn = vi.fn(async () => ++count);
|
|
const key = `inv-${Math.random()}`;
|
|
expect(await cached(key, 10_000, fn)).toBe(1);
|
|
invalidateMemory(key);
|
|
expect(await cached(key, 10_000, fn)).toBe(2);
|
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("evicts the oldest entry when the in-memory cache is full", async () => {
|
|
const fn = vi.fn(async () => 1);
|
|
for (let i = 0; i < 600; i++) {
|
|
await cached(`bulk-${i}`, 10_000, fn);
|
|
}
|
|
// Key "bulk-0" was evicted (insertion order), so it must recompute.
|
|
await cached("bulk-0", 10_000, fn);
|
|
expect(fn).toHaveBeenCalledTimes(601);
|
|
});
|
|
});
|