import { PgCache } from "@umt/db"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { createCacheModule } from "../../src/modules/cache/index.ts"; import { bearer, createHarness, type Harness, pgAvailable } from "../auth-settings/harness.ts"; import { DEV_KEY, json } from "../helpers.ts"; // 设置 → 缓存:通用缓存(kv_cache)按区统计与单区清空(GET /cache/regions、POST /cache/regions/{region}/clear) const hasPg = await pgAvailable(); describe.skipIf(!hasPg)("通用缓存按区管理(真实 PG)", () => { let h: Harness; let admin: ReturnType; beforeAll(async () => { h = await createHarness({ extraModules: [createCacheModule()] }); admin = bearer(h.app, DEV_KEY); }); afterAll(async () => { await h?.close(); }); it("按区列出未过期条数、已过期条数、占用字节、最近写入;清空一个区不影响其它区", async () => { const cache = new PgCache(h.db); await cache.set("douban.subject", "1", { title: "a" }); await cache.set("douban.subject", "2", { title: "b" }); await cache.set("site.detail", "x", { body: "y".repeat(200) }); // 写入后立刻过期的一条(ttl 1 秒,等它过期) await cache.set("site.detail", "old", { v: 1 }, { ttl: 1 }); await new Promise((r) => setTimeout(r, 1100)); const res = await admin("GET", "/cache/regions"); expect(res.status).toBe(200); const items = (await json(res)).data.items as { region: string; entries: number; expired: number; bytes: number; lastWriteAt: string | null; }[]; const douban = items.find((x) => x.region === "douban.subject")!; const site = items.find((x) => x.region === "site.detail")!; expect(douban).toMatchObject({ entries: 2, expired: 0 }); expect(douban.bytes).toBeGreaterThan(0); expect(Date.parse(douban.lastWriteAt!)).toBeGreaterThan(Date.now() - 60_000); expect(site).toMatchObject({ entries: 1, expired: 1 }); expect(site.bytes).toBeGreaterThan(200); const cleared = await json(await admin("POST", "/cache/regions/site.detail/clear")); expect(cleared.data.deleted).toBe(2); const after = (await json(await admin("GET", "/cache/regions"))).data.items as { region: string }[]; expect(after.map((x) => x.region)).toContain("douban.subject"); expect(after.map((x) => x.region)).not.toContain("site.detail"); expect(await cache.count("douban.subject")).toBe(2); }); it("区名不合法时拒绝(422),不执行删除", async () => { const res = await admin("POST", "/cache/regions/BAD%20NAME/clear"); expect(res.status).toBe(422); }); });