47 lines
1.8 KiB
JavaScript
47 lines
1.8 KiB
JavaScript
import { jest } from "@jest/globals";
|
|
|
|
// Manual DB mock: getPool() returns a fake client whose query() the test drives.
|
|
const query = jest.fn();
|
|
const release = jest.fn(async () => {});
|
|
jest.unstable_mockModule("../../db/index.js", () => ({ getPool: async () => ({ query, release }) }));
|
|
|
|
const { getCached } = await import("./cache.js");
|
|
|
|
beforeEach(() => {
|
|
query.mockReset();
|
|
release.mockClear();
|
|
});
|
|
|
|
describe("getCached", () => {
|
|
it("returns the cached value on a fresh hit without computing", async () => {
|
|
query.mockResolvedValueOnce({ rows: [{ data: { v: 1 } }] });
|
|
const compute = jest.fn(async () => ({ v: 999 }));
|
|
const out = await getCached(7, "storage", 1000, compute);
|
|
|
|
expect(out).toEqual({ v: 1 });
|
|
expect(compute).not.toHaveBeenCalled();
|
|
expect(query).toHaveBeenCalledTimes(1); // SELECT only
|
|
expect(query.mock.calls[0][1]).toEqual([7, "storage", 1000]);
|
|
});
|
|
|
|
it("computes and upserts on a miss", async () => {
|
|
query.mockResolvedValueOnce({ rows: [] }).mockResolvedValueOnce({ rows: [] }); // SELECT miss, then INSERT
|
|
const compute = jest.fn(async () => ({ v: 2 }));
|
|
const out = await getCached(7, "storage", 1000, compute);
|
|
|
|
expect(out).toEqual({ v: 2 });
|
|
expect(compute).toHaveBeenCalledTimes(1);
|
|
expect(query).toHaveBeenCalledTimes(2); // SELECT then INSERT
|
|
const [sql, params] = query.mock.calls[1];
|
|
expect(sql).toContain("ON CONFLICT (user_id, tag) DO UPDATE");
|
|
expect(params).toEqual([7, "storage", { v: 2 }]);
|
|
});
|
|
|
|
it("does not cache when compute throws", async () => {
|
|
query.mockResolvedValueOnce({ rows: [] });
|
|
const compute = jest.fn(async () => { throw new Error("nope"); });
|
|
await expect(getCached(7, "storage", 1000, compute)).rejects.toThrow("nope");
|
|
expect(query).toHaveBeenCalledTimes(1); // SELECT only; no INSERT
|
|
});
|
|
});
|