fix: email and phone field validation (#24432)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
co-authored by
Udit Takkar
parent
e5f14c9316
commit
f9b294cfc2
@@ -3788,6 +3788,10 @@
|
||||
"configure_agent_to_handle_incoming_calls": "Configure agent to handle incoming calls",
|
||||
"incoming_calls": "Incoming Calls",
|
||||
"outgoing_calls": "Outgoing Calls",
|
||||
"booking_fields_email_and_phone_both_hidden": "Both Email and Attendee Phone Number cannot be hidden",
|
||||
"booking_fields_email_or_phone_required": "At least Email or Attendee Phone Number must be a required field",
|
||||
"booking_fields_phone_required_when_email_hidden": "Attendee Phone Number must be required when Email is hidden",
|
||||
"booking_fields_email_required_when_phone_hidden": "Email must be required when Attendee Phone Number is hidden",
|
||||
"inbound_agent_setup_success": "Inbound agent setup successful",
|
||||
"inbound_agent_configured": "Inbound agent configured",
|
||||
"setup_inbound_agent": "Set up Inbound Agent",
|
||||
|
||||
@@ -176,6 +176,66 @@ describe("getBookingResponsesSchema", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test(`hidden required email field should not be validated`, async () => {
|
||||
const schema = getBookingResponsesSchema({
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
required: true,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone",
|
||||
required: true,
|
||||
},
|
||||
] as z.infer<typeof eventTypeBookingFields> & z.BRAND<"HAS_SYSTEM_FIELDS">,
|
||||
view: "ALL_VIEWS",
|
||||
});
|
||||
const parsedResponses = await schema.safeParseAsync({
|
||||
name: "John",
|
||||
email: "",
|
||||
attendeePhoneNumber: "+919999999999",
|
||||
});
|
||||
expect(parsedResponses.success).toBe(true);
|
||||
});
|
||||
|
||||
test(`hidden required phone field should not be validated`, async () => {
|
||||
const schema = getBookingResponsesSchema({
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone",
|
||||
required: true,
|
||||
hidden: true,
|
||||
},
|
||||
] as z.infer<typeof eventTypeBookingFields> & z.BRAND<"HAS_SYSTEM_FIELDS">,
|
||||
view: "ALL_VIEWS",
|
||||
});
|
||||
const parsedResponses = await schema.safeParseAsync({
|
||||
name: "John",
|
||||
email: "john@example.com",
|
||||
attendeePhoneNumber: "",
|
||||
});
|
||||
expect(parsedResponses.success).toBe(true);
|
||||
});
|
||||
|
||||
test(`firstName is required and lastName is optional by default`, async () => {
|
||||
const schema = getBookingResponsesSchema({
|
||||
bookingFields: [
|
||||
|
||||
@@ -174,7 +174,7 @@ function preprocess<T extends z.ZodType>({
|
||||
}
|
||||
|
||||
if (bookingField.type === "email") {
|
||||
if (!bookingField.hidden && checkOptional ? true : bookingField.required) {
|
||||
if (!bookingField.hidden && (checkOptional || bookingField.required)) {
|
||||
// Email RegExp to validate if the input is a valid email
|
||||
if (!emailSchema.safeParse(value).success) {
|
||||
ctx.addIssue({
|
||||
|
||||
@@ -7,7 +7,7 @@ import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import type { authedProcedure } from "../../../procedures/authedProcedure";
|
||||
import { createEventPbacProcedure } from "../util";
|
||||
import { createEventPbacProcedure, ensureEmailOrPhoneNumberIsPresent } from "../util";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@calcom/features/pbac/services/permission-check.service");
|
||||
@@ -513,4 +513,163 @@ describe("createEventPbacProcedure", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ensureEmailOrPhoneNumberIsPresent", () => {
|
||||
it("should throw error when both email and phone are hidden", () => {
|
||||
const fields = [
|
||||
{
|
||||
name: "email",
|
||||
type: "email" as const,
|
||||
required: true,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone" as const,
|
||||
required: true,
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).toThrow(TRPCError);
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).toThrow(
|
||||
expect.objectContaining({
|
||||
code: "BAD_REQUEST",
|
||||
message: "booking_fields_email_and_phone_both_hidden",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error when neither email nor phone is required", () => {
|
||||
const fields = [
|
||||
{
|
||||
name: "email",
|
||||
type: "email" as const,
|
||||
required: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone" as const,
|
||||
required: false,
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).toThrow(TRPCError);
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).toThrow(
|
||||
expect.objectContaining({
|
||||
code: "BAD_REQUEST",
|
||||
message: "booking_fields_email_or_phone_required",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error when email is hidden and phone is not required", () => {
|
||||
const fields = [
|
||||
{
|
||||
name: "email",
|
||||
type: "email" as const,
|
||||
required: true,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone" as const,
|
||||
required: false,
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).toThrow(TRPCError);
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).toThrow(
|
||||
expect.objectContaining({
|
||||
code: "BAD_REQUEST",
|
||||
message: "booking_fields_phone_required_when_email_hidden",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error when phone is hidden and email is not required", () => {
|
||||
const fields = [
|
||||
{
|
||||
name: "email",
|
||||
type: "email" as const,
|
||||
required: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone" as const,
|
||||
required: true,
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).toThrow(TRPCError);
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).toThrow(
|
||||
expect.objectContaining({
|
||||
code: "BAD_REQUEST",
|
||||
message: "booking_fields_email_required_when_phone_hidden",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass when email is visible and required while phone is hidden", () => {
|
||||
const fields = [
|
||||
{
|
||||
name: "email",
|
||||
type: "email" as const,
|
||||
required: true,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone" as const,
|
||||
required: false,
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should pass when phone is visible and required while email is hidden", () => {
|
||||
const fields = [
|
||||
{
|
||||
name: "email",
|
||||
type: "email" as const,
|
||||
required: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone" as const,
|
||||
required: true,
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should pass when both email and phone are visible and required", () => {
|
||||
const fields = [
|
||||
{
|
||||
name: "email",
|
||||
type: "email" as const,
|
||||
required: true,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone" as const,
|
||||
required: true,
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => ensureEmailOrPhoneNumberIsPresent(fields)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -274,13 +274,25 @@ export function ensureEmailOrPhoneNumberIsPresent(fields: TUpdateInputSchema["bo
|
||||
if (emailField?.hidden && attendeePhoneNumberField?.hidden) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `Both Email and Attendee Phone Number cannot be hidden`,
|
||||
message: "booking_fields_email_and_phone_both_hidden",
|
||||
});
|
||||
}
|
||||
if (!emailField?.required && !attendeePhoneNumberField?.required) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `At least Email or Attendee Phone Number need to be required field.`,
|
||||
message: "booking_fields_email_or_phone_required",
|
||||
});
|
||||
}
|
||||
if (emailField?.hidden && !attendeePhoneNumberField?.required) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "booking_fields_phone_required_when_email_hidden",
|
||||
});
|
||||
}
|
||||
if (attendeePhoneNumberField?.hidden && !emailField?.required) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "booking_fields_email_required_when_phone_hidden",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user