diff --git a/apps/web/playwright/booking-pages.e2e.ts b/apps/web/playwright/booking-pages.e2e.ts index 015460d9bb..5d83253fd2 100644 --- a/apps/web/playwright/booking-pages.e2e.ts +++ b/apps/web/playwright/booking-pages.e2e.ts @@ -93,10 +93,7 @@ test.describe("free user", () => { await page.goto(bookingUrl); // book same time spot again - await bookTimeSlot(page, { - /** FIXME: this should be a 409 conflict */ - expectedStatusCode: 500, - }); + await bookTimeSlot(page, { expectedStatusCode: 409 }); await page.locator("[data-testid=booking-fail]").waitFor({ state: "visible" }); }); diff --git a/packages/lib/server/defaultResponder.test.ts b/packages/lib/server/defaultResponder.test.ts index 25276230be..24926ad316 100644 --- a/packages/lib/server/defaultResponder.test.ts +++ b/packages/lib/server/defaultResponder.test.ts @@ -1,5 +1,9 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { describe, it, expect, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; + +import { ErrorCode } from "@calcom/lib/errorCodes"; + +import { TRPCError } from "@trpc/server"; import { defaultResponder } from "./defaultResponder"; @@ -19,4 +23,18 @@ describe("defaultResponder", () => { await defaultResponder(f)(req, res); expect(res.json).not.toHaveBeenCalled(); }); + it("should respond with status code 409 for NoAvailableUsersFound", async () => { + const f = vi.fn().mockRejectedValue(new Error(ErrorCode.NoAvailableUsersFound)); + const req = {} as NextApiRequest; + const res = { status: vi.fn().mockReturnThis(), json: vi.fn() } as unknown as NextApiResponse; + await defaultResponder(f)(req, res); + expect(res.status).toHaveBeenCalledWith(409); + }); + it("Rate limit should respond with a 429 status code", async () => { + const f = vi.fn().mockRejectedValue(new TRPCError({ code: "TOO_MANY_REQUESTS" })); + const req = {} as NextApiRequest; + const res = { status: vi.fn().mockReturnThis(), json: vi.fn() } as unknown as NextApiResponse; + await defaultResponder(f)(req, res); + expect(res.status).toHaveBeenCalledWith(429); + }); }); diff --git a/packages/lib/server/defaultResponder.ts b/packages/lib/server/defaultResponder.ts index 5b5106187a..e6fb0accee 100644 --- a/packages/lib/server/defaultResponder.ts +++ b/packages/lib/server/defaultResponder.ts @@ -27,9 +27,9 @@ export function defaultResponder( const error = getServerErrorFromUnknown(err); // we don't want to report Bad Request errors to Sentry / console if (!(error.statusCode >= 400 && error.statusCode < 500)) { - console.error(err); + console.error(error); const captureException = (await import("@sentry/nextjs")).captureException; - captureException(err); + captureException(error); } return res .status(error.statusCode) diff --git a/packages/lib/server/getServerErrorFromUnknown.ts b/packages/lib/server/getServerErrorFromUnknown.ts index ac64f086be..400829b5b0 100644 --- a/packages/lib/server/getServerErrorFromUnknown.ts +++ b/packages/lib/server/getServerErrorFromUnknown.ts @@ -3,6 +3,8 @@ import Stripe from "stripe"; import type { ZodIssue } from "zod"; import { ZodError } from "zod"; +import { ErrorCode } from "@calcom/lib/errorCodes"; + import { TRPCError } from "@trpc/server"; import { getHTTPStatusCodeFromError } from "@trpc/server/http"; @@ -70,7 +72,8 @@ export function getServerErrorFromUnknown(cause: unknown): HttpError { }; } if (cause instanceof Error) { - return getHttpError({ statusCode: 500, cause }); + const statusCode = getStatusCode(cause); + return getHttpError({ statusCode, cause }); } if (typeof cause === "string") { // @ts-expect-error https://github.com/tc39/proposal-error-cause @@ -83,6 +86,34 @@ export function getServerErrorFromUnknown(cause: unknown): HttpError { }); } +function getStatusCode(cause: Error): number { + switch (cause.message) { + case ErrorCode.RequestBodyWithouEnd: + case ErrorCode.MissingPaymentCredential: + case ErrorCode.MissingPaymentAppId: + case ErrorCode.AvailabilityNotFoundInSchedule: + return 400; + case ErrorCode.CancelledBookingsCannotBeRescheduled: + return 403; + case ErrorCode.NoAvailableUsersFound: + case ErrorCode.HostsUnavailableForBooking: + case ErrorCode.PaymentCreationFailure: + case ErrorCode.ChargeCardFailure: + case ErrorCode.AlreadySignedUpForBooking: + case ErrorCode.BookingSeatsFull: + case ErrorCode.NotEnoughAvailableSeats: + return 409; + case ErrorCode.EventTypeNotFound: + case ErrorCode.BookingNotFound: + return 404; + case ErrorCode.UnableToSubscribeToThePlatform: + case ErrorCode.UpdatingOauthClientError: + case ErrorCode.CreatingOauthClientError: + default: + return 500; + } +} + function getHttpError({ statusCode, cause }: { statusCode: number; cause: T }) { const redacted = redactError(cause); return new HttpError({ statusCode, message: redacted.message, cause: redacted });