import { afterEach, describe, expect, it } from "@suaveplan/testing/runner"; import { Hono } from "hono"; import { createTenantMiddleware } from "../middleware/middleware.js"; import { headerResolver } from "../resolver/resolver.js"; import type { Tenant } from "../types/tenant.js"; import { getCurrentTenant, getCurrentTenantId, isTenantStorageEnabled, runWithTenant, tenantStorageMiddleware, withTenantStorage, } from "./storage.js"; function makeTenant(id = "t_1"): Tenant { return { id, name: "Acme", slug: id, status: "active", settings: {}, enabledPlugins: [], domains: [], createdAt: new Date(0), updatedAt: new Date(0), }; } afterEach(() => { withTenantStorage(false); }); describe("withTenantStorage toggle", () => { it("defaults to off", { timeout: 5000 }, () => { expect(isTenantStorageEnabled()).toBe(false); }); it("toggles on/off", { timeout: 5000 }, () => { withTenantStorage(true); expect(isTenantStorageEnabled()).toBe(true); withTenantStorage(false); expect(isTenantStorageEnabled()).toBe(false); }); }); describe("AsyncLocalStorage thread", () => { it("ALS off keeps getCurrentTenant undefined", { timeout: 5000, }, async () => { const app = new Hono(); let seen: Tenant | undefined; app.use( "*", createTenantMiddleware({ resolver: headerResolver("x-tenant-id", async () => makeTenant() ), }) ); app.use("*", tenantStorageMiddleware()); app.get("/", async (c) => { await new Promise((r) => setImmediate(r)); seen = getCurrentTenant(); return c.text("ok"); }); await app.request("/", { headers: { "x-tenant-id": "acme" } }); expect(seen).toBeUndefined(); }); it("ALS on threads tenant through async stack", { timeout: 5000, }, async () => { withTenantStorage(true); const app = new Hono(); let seenId: string | undefined; app.use( "*", createTenantMiddleware({ resolver: headerResolver("x-tenant-id", async () => makeTenant("acme-deep") ), }) ); app.use("*", tenantStorageMiddleware()); app.get("/", async (c) => { // Cross several async boundaries before reading. await new Promise((r) => setImmediate(r)); await new Promise((r) => setTimeout(r, 1)); seenId = getCurrentTenantId(); return c.text("ok"); }); await app.request("/", { headers: { "x-tenant-id": "acme" } }); expect(seenId).toBe("acme-deep"); }); it("middleware is no-op when tenant unresolved (ALS on)", { timeout: 5000, }, async () => { withTenantStorage(true); const app = new Hono(); app.use("*", tenantStorageMiddleware()); app.get("/", (c) => { expect(getCurrentTenant()).toBeUndefined(); return c.text("ok"); }); const res = await app.request("/"); expect(res.status).toBe(200); }); it("runWithTenant runs inside ALS scope", { timeout: 5000, }, async () => { withTenantStorage(true); const t = makeTenant("explicit"); const id = runWithTenant(t, () => getCurrentTenantId()); expect(id).toBe("explicit"); }); it("runWithTenant runs fn directly when ALS off", { timeout: 5000, }, () => { const t = makeTenant(); const id = runWithTenant(t, () => getCurrentTenantId()); expect(id).toBeUndefined(); }); it("middleware bypasses ALS when toggle is off", { timeout: 5000, }, async () => { const app = new Hono(); app.use("*", tenantStorageMiddleware()); app.get("/", (c) => c.text("ok")); const res = await app.request("/"); expect(res.status).toBe(200); expect(isTenantStorageEnabled()).toBe(false); }); });