import type { Principal, PrincipalId, Scope, } from "@suaveplan/api-types/principal"; import type { TenantId } from "@suaveplan/api-types/tenant"; import { describe, expect, it } from "@suaveplan/testing/runner"; import { JwtTenantResolverError, TENANT_RESOLVER_JWT_CODES, } from "../error-codes/error-codes.js"; import { createJwtTenantResolver } from "./resolver.js"; function principalWith(overrides: Partial = {}): Principal { return { id: "u_1" as PrincipalId, scopes: [] as readonly Scope[], claims: {}, ...overrides, }; } function reqWith(principal: Principal | null): Request { const req = new Request("https://example.com/x"); Object.defineProperty(req, "principal", { value: principal, enumerable: true, }); return req; } describe("createJwtTenantResolver", () => { it("returns TenantContext when principal carries tenantId", { timeout: 5000, }, async () => { const resolver = createJwtTenantResolver(); const req = reqWith(principalWith({ tenantId: "acme" as TenantId })); const ctx = await resolver.resolve(req); expect(ctx).toEqual({ id: "acme", slug: "acme" }); }); it("returns null when principal lacks tenantId (advance chain)", { timeout: 5000, }, async () => { const resolver = createJwtTenantResolver(); const req = reqWith(principalWith()); const ctx = await resolver.resolve(req); expect(ctx).toBeNull(); }); it("returns null when principal is absent (advance chain)", { timeout: 5000, }, async () => { const resolver = createJwtTenantResolver(); const req = reqWith(null); const ctx = await resolver.resolve(req); expect(ctx).toBeNull(); }); it("returns null when default extractor cannot find principal property", { timeout: 5000, }, async () => { const resolver = createJwtTenantResolver(); const req = new Request("https://example.com/y"); const ctx = await resolver.resolve(req); expect(ctx).toBeNull(); }); it("reads a custom claimKey from Principal.claims", { timeout: 5000, }, async () => { const resolver = createJwtTenantResolver({ claimKey: "tid" }); const req = reqWith(principalWith({ claims: { tid: "acme" } })); const ctx = await resolver.resolve(req); expect(ctx).toEqual({ id: "acme", slug: "acme" }); }); it("treats empty-string claim as absent", { timeout: 5000 }, async () => { const resolver = createJwtTenantResolver({ claimKey: "tid" }); const req = reqWith(principalWith({ claims: { tid: "" } })); expect(await resolver.resolve(req)).toBeNull(); }); it("treats non-string claim as absent", { timeout: 5000 }, async () => { const resolver = createJwtTenantResolver({ claimKey: "tid" }); const req = reqWith(principalWith({ claims: { tid: 42 } })); expect(await resolver.resolve(req)).toBeNull(); }); it("requirePrincipal=true throws when principal is null", { timeout: 5000, }, async () => { const resolver = createJwtTenantResolver({ requirePrincipal: true }); const req = reqWith(null); await expect(resolver.resolve(req)).rejects.toBeInstanceOf( JwtTenantResolverError ); await resolver .resolve( reqWith( principalWith({ tenantId: "ok" as TenantId, }) ) ) .then((ctx) => expect(ctx).toEqual({ id: "ok", slug: "ok" })); }); it("requirePrincipal=true throws when claim is missing", { timeout: 5000, }, async () => { const resolver = createJwtTenantResolver({ requirePrincipal: true, claimKey: "tid", }); const req = reqWith(principalWith({ claims: {} })); let thrown: unknown; try { await resolver.resolve(req); } catch (e) { thrown = e; } expect(thrown).toBeInstanceOf(JwtTenantResolverError); expect((thrown as JwtTenantResolverError).code).toBe( TENANT_RESOLVER_JWT_CODES.PRINCIPAL_MISSING_TENANT_ID ); }); it("honours an explicit getPrincipal extractor", { timeout: 5000, }, async () => { const store = new WeakMap(); const resolver = createJwtTenantResolver({ getPrincipal: (r) => store.get(r) ?? null, }); const req = new Request("https://example.com/z"); store.set(req, principalWith({ tenantId: "via-map" as TenantId })); const ctx = await resolver.resolve(req); expect(ctx).toEqual({ id: "via-map", slug: "via-map" }); }); it("uses the configured id", { timeout: 5000 }, () => { expect(createJwtTenantResolver().id).toBe("jwt"); expect(createJwtTenantResolver({ id: "primary" }).id).toBe("primary"); }); it("propagates an INVALID_OPTIONS error for bad input", { timeout: 5000, }, () => { expect(() => createJwtTenantResolver({ claimKey: "" })).toThrow( JwtTenantResolverError ); }); it("treats undefined principal from extractor as null", { timeout: 5000, }, async () => { const resolver = createJwtTenantResolver({ getPrincipal: () => undefined, }); const req = new Request("https://example.com/u"); expect(await resolver.resolve(req)).toBeNull(); }); });