175 lines
5.4 KiB
TypeScript
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
|
|
);
|
|
}
|
|
});
|
|
});
|