Files
Epicnabbo-Catalogus-Updated…/src/lib/services/news-cache.test.ts
T

81 lines
2.3 KiB
TypeScript

import { beforeEach, expect, it, vi } from "vitest";
const state = vi.hoisted(() => ({
revision: "0",
values: new Map<string, unknown>(),
get: vi.fn(),
set: vi.fn(),
status: "ready",
}));
vi.mock("@/lib/redis", () => ({ redis: state }));
vi.mock("@/lib/logger", () => ({ logger: { error: vi.fn() } }));
vi.mock("@/lib/cache", () => ({
cached: async (key: string, _ttl: number, query: () => Promise<unknown>) => {
if (state.values.has(key)) return state.values.get(key);
const result = await query();
state.values.set(key, result);
return result;
},
}));
import {
cacheNews,
invalidateNewsCache,
refreshNewsCacheForDelivery,
} from "./news-cache";
beforeEach(() => {
state.values.clear();
state.revision = "0";
state.status = "ready";
state.get.mockReset().mockImplementation(async () => state.revision);
state.set
.mockReset()
.mockImplementation(async (_key: string, value: string) => {
state.revision = value;
});
});
it("invalidates a previously cached public list", async () => {
const query = vi
.fn()
.mockResolvedValueOnce([])
.mockResolvedValueOnce(["new article"]);
expect(await cacheNews("list", 60000, query)).toEqual([]);
await invalidateNewsCache();
expect(await cacheNews("list", 60000, query)).toEqual(["new article"]);
});
it("does not let a stale in-flight read replace a newer revision", async () => {
let finish!: (value: string[]) => void;
const old = cacheNews(
"list",
60000,
() =>
new Promise<string[]>((resolve) => {
finish = resolve;
}),
);
await vi.waitFor(() => expect(finish).toBeDefined());
await invalidateNewsCache();
expect(await cacheNews("list", 60000, async () => ["new"])).toEqual(["new"]);
finish([]);
await old;
expect(await cacheNews("list", 60000, async () => ["wrong"])).toEqual([
"new",
]);
});
it("reads fresh data when Redis is unavailable", async () => {
state.get.mockRejectedValue(Error("offline"));
expect(await cacheNews("list", 60000, async () => ["fresh"])).toEqual([
"fresh",
]);
});
it("keeps failed durable cache invalidations retryable", async () => {
state.set.mockRejectedValue(Error("offline"));
await expect(refreshNewsCacheForDelivery()).rejects.toThrow("offline");
});
it("does not acknowledge an ended Redis connection as refreshed", async () => {
state.status = "end";
await expect(refreshNewsCacheForDelivery()).rejects.toThrow();
});