Files
suaveplan/packages/api-stack/api/api-auth-jwt/src/claims-mapper/claims-mapper.test.ts
2026-05-27 23:29:53 +01:00

175 lines
5.4 KiB
TypeScript

import { describe, expect, it } from "@suaveplan/testing/runner";
import {
AUTH_JWT_CODES,
type AuthJwtError,
} from "../error-codes/error-codes.js";
import { defaultClaimsToPrincipal, runClaimsMapper } from "./claims-mapper.js";
describe("defaultClaimsToPrincipal", () => {
it("projects sub + scope + tenant_id", { timeout: 5000 }, () => {
const p = defaultClaimsToPrincipal({
sub: "user_1",
scope: "billing:read profile:read",
tenant_id: "acme",
});
expect(p?.id).toBe("user_1");
expect(p?.scopes).toEqual(["billing:read", "profile:read"]);
expect(p?.tenantId).toBe("acme");
});
it("supports array-form scopes", { timeout: 5000 }, () => {
const p = defaultClaimsToPrincipal({
sub: "user_2",
scopes: ["a", "b"],
});
expect(p?.scopes).toEqual(["a", "b"]);
expect(p?.tenantId).toBeUndefined();
});
it("falls back to `tid` claim for tenant", { timeout: 5000 }, () => {
const p = defaultClaimsToPrincipal({
sub: "user_3",
tid: "ms-tenant",
});
expect(p?.tenantId).toBe("ms-tenant");
});
it("returns null when sub missing", { timeout: 5000 }, () => {
expect(defaultClaimsToPrincipal({})).toBeNull();
expect(defaultClaimsToPrincipal({ sub: "" })).toBeNull();
expect(defaultClaimsToPrincipal({ sub: 123 as never })).toBeNull();
});
it("filters empty scope segments", { timeout: 5000 }, () => {
const p = defaultClaimsToPrincipal({
sub: "u",
scope: "a b",
});
expect(p?.scopes).toEqual(["a", "b"]);
});
it("ignores non-string scopes array entries", { timeout: 5000 }, () => {
const p = defaultClaimsToPrincipal({
sub: "u",
scopes: ["a", 2 as never, "b"],
});
expect(p?.scopes).toEqual(["a", "b"]);
});
it("ignores non-string scope claim", { timeout: 5000 }, () => {
const p = defaultClaimsToPrincipal({
sub: "u",
scope: 7 as never,
});
expect(p?.scopes).toEqual([]);
});
it("ignores non-string tenant_id", { timeout: 5000 }, () => {
const p = defaultClaimsToPrincipal({
sub: "u",
tenant_id: 7 as never,
});
expect(p?.tenantId).toBeUndefined();
});
});
describe("runClaimsMapper", () => {
it("passes through valid Principal", { timeout: 5000 }, () => {
const p = runClaimsMapper(defaultClaimsToPrincipal, { sub: "u" });
expect(p?.id).toBe("u");
});
it("propagates null", { timeout: 5000 }, () => {
expect(runClaimsMapper(() => null, { sub: "u" })).toBeNull();
});
it("throws AUTH_JWT.INVALID_CLAIMS_MAPPER when mapper throws", {
timeout: 5000,
}, () => {
try {
runClaimsMapper(
() => {
throw new Error("boom");
},
{ sub: "u" }
);
throw new Error("expected throw");
} catch (err) {
expect((err as AuthJwtError).code).toBe(
AUTH_JWT_CODES.INVALID_CLAIMS_MAPPER
);
}
});
it("throws when mapper returns non-Principal", { timeout: 5000 }, () => {
try {
runClaimsMapper(() => ({ bogus: 1 }) as never, { sub: "u" });
throw new Error("expected throw");
} catch (err) {
expect((err as AuthJwtError).code).toBe(
AUTH_JWT_CODES.INVALID_CLAIMS_MAPPER
);
}
});
it("rejects mapper returning a non-object", { timeout: 5000 }, () => {
try {
runClaimsMapper(() => "u" as never, { sub: "u" });
throw new Error("expected throw");
} catch (err) {
expect((err as AuthJwtError).code).toBe(
AUTH_JWT_CODES.INVALID_CLAIMS_MAPPER
);
}
});
it("rejects mapper returning Principal-shape with null claims", {
timeout: 5000,
}, () => {
try {
runClaimsMapper(
() =>
({
id: "u",
scopes: [],
claims: null,
}) as never,
{ sub: "u" }
);
throw new Error("expected throw");
} catch (err) {
expect((err as AuthJwtError).code).toBe(
AUTH_JWT_CODES.INVALID_CLAIMS_MAPPER
);
}
});
it("rejects mapper missing string id", { timeout: 5000 }, () => {
try {
runClaimsMapper(
() => ({ id: 1, scopes: [], claims: {} }) as never,
{ sub: "u" }
);
throw new Error("expected throw");
} catch (err) {
expect((err as AuthJwtError).code).toBe(
AUTH_JWT_CODES.INVALID_CLAIMS_MAPPER
);
}
});
it("rejects mapper with non-array scopes", { timeout: 5000 }, () => {
try {
runClaimsMapper(
() => ({ id: "u", scopes: "a", claims: {} }) as never,
{ sub: "u" }
);
throw new Error("expected throw");
} catch (err) {
expect((err as AuthJwtError).code).toBe(
AUTH_JWT_CODES.INVALID_CLAIMS_MAPPER
);
}
});
});