fix: return 400 instead of 500 for invalid email in iCal generation (#27217)

* fix: return 400 instead of 500 for invalid email in iCal generation

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* test: add unit tests for error handling in generateIcsString

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Benny JooandDevin AI authored and GitHub committed 2026-01-25 15:02:30 +00:00
1 parent 6e1070be31
commit 6a532c2271
2 files changed
+97 -2

No files matched your search

+89 -1
View File
@@ -1,6 +1,8 @@
import { describe, expect } from "vitest";
import { describe, expect, vi } from "vitest";
import { ORGANIZER_EMAIL_EXEMPT_DOMAINS } from "@calcom/lib/constants";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { ErrorWithCode } from "@calcom/lib/errors";
import { buildCalendarEvent, buildPerson } from "@calcom/lib/test/builder";
import { buildVideoCallData } from "@calcom/lib/test/builder";
import type { CalendarEvent } from "@calcom/types/Calendar";
@@ -166,4 +168,90 @@ describe("generateIcsString", () => {
expect(icsString).toEqual(expect.stringContaining(`LOCATION:${event.location}`));
});
});
describe("error handling", () => {
test("throws ErrorWithCode.BadRequest when ics library returns ValidationError", async () => {
// Mock the ics library to return a ValidationError
const ics = await import("ics");
const createEventSpy = vi.spyOn(ics, "createEvent");
// Simulate a Yup ValidationError (which has name: "ValidationError")
const validationError = {
name: "ValidationError",
message: "attendees[0].email must be a valid email",
errors: ["attendees[0].email must be a valid email"],
};
createEventSpy.mockReturnValueOnce({
error: validationError as unknown as Error,
value: undefined,
});
const event = buildCalendarEvent({
iCalSequence: 0,
attendees: [buildPerson()],
});
expect(() =>
generateIcsString({
event,
status: "CONFIRMED",
})
).toThrow(ErrorWithCode);
try {
generateIcsString({
event,
status: "CONFIRMED",
});
} catch (error) {
expect(error).toBeInstanceOf(ErrorWithCode);
expect((error as ErrorWithCode).code).toBe(ErrorCode.BadRequest);
expect((error as ErrorWithCode).message).toBe("attendees[0].email must be a valid email");
}
createEventSpy.mockRestore();
});
test("re-throws non-ValidationError errors as-is", async () => {
const ics = await import("ics");
const createEventSpy = vi.spyOn(ics, "createEvent");
// Simulate a different type of error
const genericError = new Error("Some other error");
createEventSpy.mockReturnValueOnce({
error: genericError,
value: undefined,
});
const event = buildCalendarEvent({
iCalSequence: 0,
attendees: [buildPerson()],
});
expect(() =>
generateIcsString({
event,
status: "CONFIRMED",
})
).toThrow(genericError);
createEventSpy.mockRestore();
});
test("returns ics string when there is no error", () => {
const event = buildCalendarEvent({
iCalSequence: 0,
attendees: [buildPerson()],
});
const icsString = generateIcsString({
event,
status: "CONFIRMED",
});
expect(icsString).toBeDefined();
expect(typeof icsString).toBe("string");
});
});
});
+8 -1
View File
@@ -6,6 +6,8 @@ import { RRule } from "rrule";
import { getRichDescription } from "@calcom/lib/CalEventParser";
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
import { ORGANIZER_EMAIL_EXEMPT_DOMAINS } from "@calcom/lib/constants";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { ErrorWithCode } from "@calcom/lib/errors";
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
export enum BookingAction {
@@ -55,7 +57,7 @@ const generateIcsString = ({
status: EventStatus;
partstat?: ParticipationStatus;
t?: TFunction;
}) => {
}): string | undefined => {
const location = getVideoCallUrlFromCalEvent(event) || event.location;
// Taking care of recurrence rule
@@ -111,6 +113,11 @@ const generateIcsString = ({
busyStatus: "BUSY",
});
if (icsEvent.error) {
// The ics library throws Yup ValidationError objects (not Error instances) for invalid data like invalid email formats.
// Convert these to ErrorWithCode.BadRequest so they return 400 instead of falling through to a generic 500.
if (icsEvent.error.name === "ValidationError") {
throw new ErrorWithCode(ErrorCode.BadRequest, icsEvent.error.message);
}
throw icsEvent.error;
}
return icsEvent.value;