Initial Commit
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
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> = {}): 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<Request, Principal | null>();
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user