yiekheng 17f9ee179f feat(db,web): pg_trgm + indexes + Postgres-backed cache and rate-limit
- Add cacheEntries and rateLimitBuckets tables to schema
- Generate migration 0002_left_jimmy_woo.sql with pg_trgm extension and all indexes
- Implement cache.ts (get/set/delete/getOrSet/sweep) backed by Postgres
- Implement rate-limit.ts (sliding-window UPSERT) backed by Postgres
- Implement search.ts (trigramMatch / trigramRank helpers)
- Add vitest 2.1.9 + vitest.config.ts; 7 unit tests pass (4 cache + 3 rate-limit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 23:03:10 +08:00

47 lines
1.3 KiB
TypeScript

import { describe, it, expect, beforeEach, afterAll } from "vitest";
import { cacheGet, cacheSet, cacheGetOrSet, cacheDelete } from "./cache";
import { db, pool } from "./db";
import { cacheEntries } from "@cmbot/db";
import { sql } from "drizzle-orm";
describe("cache helpers", () => {
beforeEach(async () => {
await db.delete(cacheEntries).where(sql`true`);
});
afterAll(async () => {
await pool.end();
});
it("set + get round-trip", async () => {
await cacheSet("k1", { hello: "world" }, 60);
const v = await cacheGet<{ hello: string }>("k1");
expect(v).toEqual({ hello: "world" });
});
it("getOrSet computes once, then returns cached", async () => {
let calls = 0;
const compute = async () => {
calls++;
return { stamp: 42 };
};
const a = await cacheGetOrSet("k2", 60, compute);
const b = await cacheGetOrSet("k2", 60, compute);
expect(a).toEqual({ stamp: 42 });
expect(b).toEqual({ stamp: 42 });
expect(calls).toBe(1);
});
it("expired entries are skipped on get", async () => {
await cacheSet("k3", { stale: true }, -1); // already expired
const v = await cacheGet("k3");
expect(v).toBeNull();
});
it("delete removes the entry", async () => {
await cacheSet("k4", { x: 1 }, 60);
await cacheDelete("k4");
expect(await cacheGet("k4")).toBeNull();
});
});