import { describe, expect, it } from "@suaveplan/testing/runner"; import fc from "fast-check"; import { Hono } from "hono"; import { headerResolver, type Tenant, TenantCache } from "../src/index.js"; function makeTenant(id: string): Tenant { return { id, name: id, slug: id, status: "active", settings: {}, enabledPlugins: [], domains: [], createdAt: new Date(0), updatedAt: new Date(0), }; } describe("property — cache TTL boundary invariant", () => { it("entry expires strictly after ttlMs boundary (fast-check 200 runs)", { timeout: 30000, }, async () => { await fc.assert( fc.asyncProperty( fc.integer({ min: 10, max: 10_000 }), fc.integer({ min: 0, max: 100_000 }), async (ttlMs, baseNow) => { let calls = 0; const inner = headerResolver("x-tenant-id", async (id) => { calls += 1; return makeTenant(id); }); let now = baseNow; const cached = TenantCache.wrap(inner, { ttlMs, clock: () => now, }); const app = new Hono(); app.get("/", async (c) => { await cached.resolve(c); return c.text("ok"); }); const hit = (): Promise => app.request("/", { headers: { "x-tenant-id": "k", host: "x" }, }); await hit(); // populate expect(calls).toBe(1); // Strictly inside TTL → cached. now = baseNow + ttlMs - 1; await hit(); expect(calls).toBe(1); // At the boundary (expiresAt > now is false when equal) → re-lookup. now = baseNow + ttlMs; await hit(); expect(calls).toBe(2); } ), { numRuns: 200 } ); }); });