From 57564381cb83e5904aacdca09378c427f3422191 Mon Sep 17 00:00:00 2001 From: Keith Williams Date: Fri, 11 Jul 2025 19:16:27 -0300 Subject: [PATCH] fix: validate eventTypeId is number in API v1 bookings endpoint (#22433) * fix: validate eventTypeId is number in API v1 bookings endpoint - Add validation to return 400 when eventTypeId is string instead of integer - Prevents string values from reaching Prisma and causing 500 errors - Follows existing validation patterns in the codebase - Add test case to verify string eventTypeId returns 400 Bad Request Co-Authored-By: keith@cal.com * test: add focused test for eventTypeId validation - Create separate test file for string eventTypeId validation - Ensures validation logic is properly tested without being blocked by pre-existing issues - Addresses cubic-dev-ai bot feedback about skipped tests - Both string and number eventTypeId scenarios are tested Co-Authored-By: keith@cal.com * fix: improve eventTypeId validation to only check defined values - Only validate eventTypeId when it's defined and not a number - Prevents interference with schema validation for missing/undefined values - Maintains 400 error response for string eventTypeId as required - Fixes test compatibility issues while preserving validation logic Co-Authored-By: keith@cal.com * test: move eventTypeId validation test outside of skipped block - Create dedicated test block for eventTypeId validation - Remove duplicate test from skipped block - Ensures validation logic is properly tested - Addresses PR feedback from keithwillcode Co-Authored-By: keith@cal.com * cleanup: remove duplicate eventTypeId validation test file - Remove apps/api/v1/test/lib/bookings/eventTypeId-validation.test.ts - Tests are now properly located in _post.test.ts outside skipped block - Addresses PR feedback from keithwillcode about duplicate tests Co-Authored-By: keith@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- apps/api/v1/pages/api/bookings/_post.ts | 7 ++++ apps/api/v1/test/lib/bookings/_post.test.ts | 38 +++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/apps/api/v1/pages/api/bookings/_post.ts b/apps/api/v1/pages/api/bookings/_post.ts index 9b6628d04a..fc173abf90 100644 --- a/apps/api/v1/pages/api/bookings/_post.ts +++ b/apps/api/v1/pages/api/bookings/_post.ts @@ -222,6 +222,13 @@ async function handler(req: NextApiRequest) { }; if (isSystemWideAdmin) userId = req.body.userId || userId; + if (req.body.eventTypeId !== undefined && typeof req.body.eventTypeId !== "number") { + throw new HttpError({ + statusCode: 400, + message: "Bad request, eventTypeId must be a number", + }); + } + if (isOrganizationOwnerOrAdmin) { const accessibleUsersIds = await getAccessibleUsers({ adminUserId: userId, diff --git a/apps/api/v1/test/lib/bookings/_post.test.ts b/apps/api/v1/test/lib/bookings/_post.test.ts index 9aa9b516d7..0afe571a68 100644 --- a/apps/api/v1/test/lib/bookings/_post.test.ts +++ b/apps/api/v1/test/lib/bookings/_post.test.ts @@ -25,6 +25,44 @@ vi.mock("@calcom/lib/server/i18n", () => { }; }); +describe("POST /api/bookings - eventTypeId validation", () => { + test("String eventTypeId should return 400", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + eventTypeId: "invalid-string", + }, + }); + + await handler(req, res); + + expect(res._getStatusCode()).toBe(400); + expect(JSON.parse(res._getData())).toEqual( + expect.objectContaining({ + message: "Bad request, eventTypeId must be a number", + }) + ); + }); + + test("Number eventTypeId should not trigger validation error", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + eventTypeId: 123, + }, + }); + + await handler(req, res); + + const statusCode = res._getStatusCode(); + const responseData = JSON.parse(res._getData()); + + if (statusCode === 400) { + expect(responseData.message).not.toBe("Bad request, eventTypeId must be a number"); + } + }); +}); + describe.skipIf(true)("POST /api/bookings", () => { describe("Errors", () => { test("Missing required data", async () => {