Initial Commit
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
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
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user