Files
suaveplan/packages/api-stack/api/api-tenant/src/context/context.test.ts
2026-05-27 23:29:53 +01:00

143 lines
4.8 KiB
TypeScript

import { describe, expect, it } from "@suaveplan/testing/runner";
import type { Tenant } from "../types/tenant.js";
import { createTenantContext, NullTenantContext } from "./context.js";
function makeTenant(over: Partial<Tenant> = {}): Tenant {
return {
id: "t_1",
name: "Acme",
slug: "acme",
status: "active",
settings: {},
enabledPlugins: ["payment"],
domains: [],
createdAt: new Date(0),
updatedAt: new Date(0),
...over,
};
}
describe("createTenantContext", () => {
it("exposes tenant and tenantId", { timeout: 5000 }, () => {
const ctx = createTenantContext(makeTenant());
expect(ctx.tenant.slug).toBe("acme");
expect(ctx.tenantId).toBe("t_1");
});
it("isPluginEnabled honours enabledPlugins", { timeout: 5000 }, () => {
const ctx = createTenantContext(makeTenant());
expect(ctx.isPluginEnabled("payment")).toBe(true);
expect(ctx.isPluginEnabled("ghost")).toBe(false);
});
it("getSetting walks dot-paths and returns default", {
timeout: 5000,
}, () => {
const ctx = createTenantContext(
makeTenant({
settings: {
custom: { nested: { value: 42 } },
timezone: "UTC",
},
})
);
expect(ctx.getSetting<number>("custom.nested.value")).toBe(42);
expect(ctx.getSetting<string>("timezone")).toBe("UTC");
expect(ctx.getSetting<number>("missing.path", 7)).toBe(7);
expect(ctx.getSetting<number>("missing.path")).toBeUndefined();
});
it("getSetting returns default when leaf is nullish", {
timeout: 5000,
}, () => {
const ctx = createTenantContext(
makeTenant({
settings: {
custom: {
explicitNull: null,
zero: 0,
} as Record<string, unknown>,
},
})
);
expect(ctx.getSetting<number>("custom.explicitNull", 42)).toBe(42);
// Leaf 0 is non-nullish; default ignored.
expect(ctx.getSetting<number>("custom.zero", 9)).toBe(0);
});
it("getSetting returns default when intermediate is null", {
timeout: 5000,
}, () => {
const ctx = createTenantContext(
makeTenant({
settings: {
custom: { nullSlot: null } as Record<string, unknown>,
},
})
);
expect(ctx.getSetting<number>("custom.nullSlot.x", 9)).toBe(9);
});
it("isFeatureEnabled reads features boolean", { timeout: 5000 }, () => {
const ctx = createTenantContext(
makeTenant({ settings: { features: { x: true, y: false } } })
);
expect(ctx.isFeatureEnabled("x")).toBe(true);
expect(ctx.isFeatureEnabled("y")).toBe(false);
expect(ctx.isFeatureEnabled("missing")).toBe(false);
});
it("isFeatureEnabled is false when no features map", {
timeout: 5000,
}, () => {
const ctx = createTenantContext(makeTenant());
expect(ctx.isFeatureEnabled("x")).toBe(false);
});
it("getLimit reads limits map", { timeout: 5000 }, () => {
const ctx = createTenantContext(
makeTenant({
settings: { limits: { maxUsers: 10, maxApiCalls: 1000 } },
})
);
expect(ctx.getLimit("maxUsers")).toBe(10);
expect(ctx.getLimit("maxApiCalls")).toBe(1000);
expect(ctx.getLimit("unknownKey")).toBeUndefined();
});
it("getLimit returns undefined when no limits", { timeout: 5000 }, () => {
const ctx = createTenantContext(makeTenant());
expect(ctx.getLimit("maxUsers")).toBeUndefined();
});
});
describe("NullTenantContext", () => {
it("NullTenantContext fail-closes plugin check", { timeout: 5000 }, () => {
expect(NullTenantContext.isPluginEnabled("payment")).toBe(false);
expect(NullTenantContext.isPluginEnabled("anything")).toBe(false);
});
it("NullTenantContext fail-closes feature check", {
timeout: 5000,
}, () => {
expect(NullTenantContext.isFeatureEnabled("x")).toBe(false);
});
it("getSetting returns supplied default", { timeout: 5000 }, () => {
expect(NullTenantContext.getSetting<number>("any.key", 7)).toBe(7);
expect(NullTenantContext.getSetting<number>("any.key")).toBeUndefined();
});
it("getLimit returns undefined", { timeout: 5000 }, () => {
expect(NullTenantContext.getLimit("maxUsers")).toBeUndefined();
});
it("tenantId is empty string", { timeout: 5000 }, () => {
expect(NullTenantContext.tenantId).toBe("");
});
it("is frozen", { timeout: 5000 }, () => {
expect(Object.isFrozen(NullTenantContext)).toBe(true);
});
});