138 lines
4.1 KiB
TypeScript
138 lines
4.1 KiB
TypeScript
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<void>((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<void>((r) => setImmediate(r));
|
|
await new Promise<void>((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);
|
|
});
|
|
});
|