diff --git a/packages/shared/src/validators/access.test.ts b/packages/shared/src/validators/access.test.ts new file mode 100644 index 0000000000..b8d2ffcba4 --- /dev/null +++ b/packages/shared/src/validators/access.test.ts @@ -0,0 +1,140 @@ +import { describe, expect, it } from "vitest"; +import { authSessionSchema, currentUserProfileSchema } from "./access.js"; + +describe("currentUserProfileSchema", () => { + it("coerces empty-string name to null", () => { + const result = currentUserProfileSchema.safeParse({ + id: "u1", + email: "a@b.com", + name: "", + image: null, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.name).toBe(null); + }); + + it("coerces whitespace-only name to null", () => { + const result = currentUserProfileSchema.safeParse({ + id: "u1", + email: "a@b.com", + name: " ", + image: null, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.name).toBe(null); + }); + + it("preserves a real name unchanged", () => { + const result = currentUserProfileSchema.safeParse({ + id: "u1", + email: "a@b.com", + name: "Jane", + image: null, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.name).toBe("Jane"); + }); + + it("preserves null name as null", () => { + const result = currentUserProfileSchema.safeParse({ + id: "u1", + email: "a@b.com", + name: null, + image: null, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.name).toBe(null); + }); + + it("coerces empty-string email to null", () => { + const result = currentUserProfileSchema.safeParse({ + id: "u1", + email: "", + name: "Jane", + image: null, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.email).toBe(null); + }); + + it("coerces whitespace-only email to null", () => { + const result = currentUserProfileSchema.safeParse({ + id: "u1", + email: " ", + name: "Jane", + image: null, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.email).toBe(null); + }); + + it("preserves a real email unchanged", () => { + const result = currentUserProfileSchema.safeParse({ + id: "u1", + email: "a@b.com", + name: "Jane", + image: null, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.email).toBe("a@b.com"); + }); + + it("preserves null email as null", () => { + const result = currentUserProfileSchema.safeParse({ + id: "u1", + email: null, + name: "Jane", + image: null, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.email).toBe(null); + }); + + it("still rejects a malformed non-empty email", () => { + const result = currentUserProfileSchema.safeParse({ + id: "u1", + email: "not-an-email", + name: "Jane", + image: null, + }); + expect(result.success).toBe(false); + }); +}); + +describe("authSessionSchema", () => { + it("parses a session where user name is empty string (identity provider without a name)", () => { + const result = authSessionSchema.safeParse({ + session: { id: "s1", userId: "u1" }, + user: { id: "u1", email: "a@b.com", name: "", image: null }, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.user.name).toBe(null); + }); + + it("parses a session where user has a real name", () => { + const result = authSessionSchema.safeParse({ + session: { id: "s1", userId: "u1" }, + user: { id: "u1", email: "a@b.com", name: "Jane", image: null }, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.user.name).toBe("Jane"); + }); + + it("parses a session where user name is null", () => { + const result = authSessionSchema.safeParse({ + session: { id: "s1", userId: "u1" }, + user: { id: "u1", email: "a@b.com", name: null, image: null }, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.user.name).toBe(null); + }); + + it("parses a session where user email is empty string (identity provider without an email)", () => { + const result = authSessionSchema.safeParse({ + session: { id: "s1", userId: "u1" }, + user: { id: "u1", email: "", name: "Jane", image: null }, + }); + expect(result.success).toBe(true); + expect(result.success && result.data.user.email).toBe(null); + }); +}); diff --git a/packages/shared/src/validators/access.ts b/packages/shared/src/validators/access.ts index 243e7ddd3b..021fba0bb7 100644 --- a/packages/shared/src/validators/access.ts +++ b/packages/shared/src/validators/access.ts @@ -179,8 +179,14 @@ const profileImageSchema = z export const currentUserProfileSchema = z.object({ id: z.string().min(1), - email: z.string().email().nullable(), - name: z.string().min(1).max(120).nullable(), + email: z.preprocess( + (v) => (typeof v === "string" && v.trim() === "" ? null : v), + z.string().email().nullable(), + ), + name: z.preprocess( + (v) => (typeof v === "string" && v.trim() === "" ? null : v), + z.string().min(1).max(120).nullable(), + ), image: profileImageSchema.nullable(), });