67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
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<unknown> =>
|
|
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 }
|
|
);
|
|
});
|
|
});
|