* refactor: remove TRPCError handling from getServerErrorFromUnknown This removes the @trpc/server dependency from packages/lib by removing TRPCError handling from getServerErrorFromUnknown.ts. packages/lib is meant to be a general utility library that other packages depend on, so it shouldn't import tRPC. The callers that need to handle TRPCError (like onErrorHandler.ts and defaultResponder.ts) already handle it explicitly before calling getServerErrorFromUnknown, so this change has no behavioral impact. Changes: - Remove TRPCError and getHTTPStatusCodeFromError imports - Remove TRPCError instanceof check from getServerErrorFromUnknown - Update docstring to note that TRPCError is not handled - Remove TRPCError-related tests (they belong in the tRPC layer) - Fix lint warnings in Prisma error tests (replace 'as any' with proper type) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * wip --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
235 lines
8.0 KiB
TypeScript
235 lines
8.0 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import { ZodError } from "zod";
|
|
|
|
import { ErrorCode } from "@calcom/lib/errorCodes";
|
|
import { ErrorWithCode } from "@calcom/lib/errors";
|
|
import { Prisma } from "@calcom/prisma/client";
|
|
|
|
import { HttpError } from "../http-error";
|
|
import { TracedError } from "../tracing/error";
|
|
import { getServerErrorFromUnknown } from "./getServerErrorFromUnknown";
|
|
|
|
const test400Codes = [
|
|
ErrorCode.RequestBodyWithouEnd,
|
|
ErrorCode.MissingPaymentCredential,
|
|
ErrorCode.MissingPaymentAppId,
|
|
ErrorCode.AvailabilityNotFoundInSchedule,
|
|
ErrorCode.CancelledBookingsCannotBeRescheduled,
|
|
ErrorCode.BookingTimeOutOfBounds,
|
|
ErrorCode.BookingNotAllowedByRestrictionSchedule,
|
|
ErrorCode.BookerLimitExceeded,
|
|
ErrorCode.BookerLimitExceededReschedule,
|
|
ErrorCode.ChargeCardFailure,
|
|
];
|
|
|
|
const test404Codes = [
|
|
ErrorCode.EventTypeNotFound,
|
|
ErrorCode.BookingNotFound,
|
|
ErrorCode.RestrictionScheduleNotFound,
|
|
];
|
|
|
|
const test409Codes = [
|
|
ErrorCode.NoAvailableUsersFound,
|
|
ErrorCode.FixedHostsUnavailableForBooking,
|
|
ErrorCode.RoundRobinHostsUnavailableForBooking,
|
|
ErrorCode.AlreadySignedUpForBooking,
|
|
ErrorCode.BookingSeatsFull,
|
|
ErrorCode.NotEnoughAvailableSeats,
|
|
ErrorCode.BookingConflict,
|
|
ErrorCode.PaymentCreationFailure,
|
|
];
|
|
|
|
describe("getServerErrorFromUnknown", () => {
|
|
test("should handle a StripeInvalidRequestError", () => {
|
|
const stripeError = {
|
|
name: "StripeInvalidRequestError",
|
|
type: "StripeInvalidRequestError",
|
|
rawType: "invalid_request_error",
|
|
message: "No such customer: cus_12345; a valid customer ID is required.",
|
|
code: "resource_missing",
|
|
doc_url: "https://stripe.com/docs/error-codes/resource-missing",
|
|
statusCode: 400,
|
|
raw: {
|
|
error: { type: "invalid_request_error", message: "No such customer" },
|
|
},
|
|
headers: {
|
|
"content-type": "application/json",
|
|
"request-id": "req_abc123xyz",
|
|
},
|
|
requestId: "req_abc123xyz",
|
|
param: "customer",
|
|
};
|
|
Object.setPrototypeOf(stripeError, Error.prototype);
|
|
const result = getServerErrorFromUnknown(stripeError);
|
|
expect(result).toBeInstanceOf(HttpError);
|
|
expect(result.statusCode).toBe(400);
|
|
expect(result.message).toBe(stripeError.message);
|
|
expect(result.cause).toEqual(stripeError);
|
|
expect(result.name).toBe("HttpError");
|
|
});
|
|
|
|
test("ErrorWithCode should preserve data property", () => {
|
|
const rescheduleData = {
|
|
rescheduleUid: "abc123",
|
|
startTime: new Date("2024-01-15T10:00:00Z"),
|
|
attendees: [{ name: "John Doe", email: "john@example.com" }],
|
|
};
|
|
|
|
const error = new ErrorWithCode(
|
|
ErrorCode.BookerLimitExceededReschedule,
|
|
"Maximum booking limit reached, please reschedule an existing booking",
|
|
rescheduleData
|
|
);
|
|
|
|
const result = getServerErrorFromUnknown(error);
|
|
|
|
expect(result.statusCode).toBe(400);
|
|
expect(result.message).toBe("Maximum booking limit reached, please reschedule an existing booking");
|
|
expect(result.data).toEqual(rescheduleData);
|
|
});
|
|
|
|
test("Plain Error with ErrorCode message should work the same as ErrorWithCode", () => {
|
|
const error = new Error(ErrorCode.NoAvailableUsersFound);
|
|
|
|
const result = getServerErrorFromUnknown(error);
|
|
|
|
expect(result.statusCode).toBe(409);
|
|
expect(result.message).toBe(ErrorCode.NoAvailableUsersFound);
|
|
});
|
|
|
|
test("Generic Error without ErrorCode should return 500", () => {
|
|
const error = new Error("Some unexpected server error");
|
|
|
|
const result = getServerErrorFromUnknown(error);
|
|
|
|
expect(result.statusCode).toBe(500);
|
|
expect(result.message).toBe("Some unexpected server error");
|
|
});
|
|
|
|
test("Server-side ErrorCode should return 500", () => {
|
|
const error = new ErrorWithCode(ErrorCode.CreatingOauthClientError, "OAuth client creation failed");
|
|
|
|
const result = getServerErrorFromUnknown(error);
|
|
|
|
expect(result.statusCode).toBe(500);
|
|
expect(result.message).toBe("OAuth client creation failed");
|
|
});
|
|
});
|
|
|
|
describe("TracedError handling", () => {
|
|
test("should extract traceId and tracedData from TracedError", () => {
|
|
const originalError = new Error("Original error message");
|
|
const tracedData = { userId: "123", operation: "booking" };
|
|
const traceContext = {
|
|
traceId: "trace_abc123",
|
|
spanId: "span_123",
|
|
operation: "test_operation",
|
|
};
|
|
|
|
const tracedError = new TracedError(originalError, traceContext, tracedData);
|
|
|
|
const result = getServerErrorFromUnknown(tracedError);
|
|
|
|
expect(result.statusCode).toBe(500);
|
|
expect(result.message).toBe("Original error message");
|
|
expect(result.data).toEqual({ ...tracedData, traceId: traceContext.traceId });
|
|
});
|
|
|
|
test("should handle TracedError wrapping ErrorWithCode", () => {
|
|
const originalError = new ErrorWithCode(ErrorCode.BookingNotFound, "Booking not found");
|
|
const tracedData = { bookingId: "456" };
|
|
const traceContext = {
|
|
traceId: "trace_def456",
|
|
spanId: "span_456",
|
|
operation: "booking_lookup",
|
|
};
|
|
|
|
const tracedError = new TracedError(originalError, traceContext, tracedData);
|
|
|
|
const result = getServerErrorFromUnknown(tracedError);
|
|
|
|
expect(result.statusCode).toBe(404);
|
|
expect(result.message).toBe("Booking not found");
|
|
expect(result.data).toEqual({ ...tracedData, traceId: traceContext.traceId });
|
|
});
|
|
});
|
|
|
|
describe("ZodError handling", () => {
|
|
test("should handle ZodError with validation issues", () => {
|
|
const zodError = new ZodError([
|
|
{
|
|
code: "invalid_type",
|
|
expected: "string",
|
|
received: "number",
|
|
path: ["name"],
|
|
message: "Expected string, received number",
|
|
},
|
|
]);
|
|
|
|
const result = getServerErrorFromUnknown(zodError);
|
|
|
|
expect(result.statusCode).toBe(400);
|
|
expect(result.message).toBe("invalid_type in 'name': Expected string, received number");
|
|
expect(result.cause).toBe(zodError);
|
|
});
|
|
});
|
|
|
|
describe("Prisma error handling", () => {
|
|
test("should handle Prisma P2025 error (record not found)", () => {
|
|
const prismaError = new Error("Record to delete does not exist.") as any;
|
|
prismaError.code = "P2025";
|
|
prismaError.clientVersion = "5.0.0";
|
|
Object.setPrototypeOf(prismaError, Prisma.PrismaClientKnownRequestError.prototype);
|
|
|
|
const result = getServerErrorFromUnknown(prismaError);
|
|
|
|
expect(result.statusCode).toBe(404);
|
|
expect(result.message).toBe("Record to delete does not exist.");
|
|
expect(result.cause).toBe(prismaError);
|
|
});
|
|
|
|
test("should handle other Prisma errors as 400", () => {
|
|
const prismaError = new Error("Foreign key constraint failed") as any;
|
|
prismaError.code = "P2003";
|
|
prismaError.clientVersion = "5.0.0";
|
|
Object.setPrototypeOf(prismaError, Prisma.PrismaClientKnownRequestError.prototype);
|
|
|
|
const result = getServerErrorFromUnknown(prismaError);
|
|
|
|
expect(result.statusCode).toBe(400);
|
|
expect(result.message).toBe("Foreign key constraint failed");
|
|
});
|
|
});
|
|
|
|
test("should handle unknown type fallback", () => {
|
|
const result = getServerErrorFromUnknown({ someProperty: "value" });
|
|
|
|
expect(result.statusCode).toBe(500);
|
|
expect(result.message).toBe("Unhandled error of type 'object'. Please reach out for our customer support.");
|
|
expect(result.data).toBeUndefined();
|
|
});
|
|
|
|
test400Codes.forEach((errorCode) => {
|
|
test(`${errorCode} should return 400 Bad Request`, () => {
|
|
const error = new ErrorWithCode(errorCode, `Test message for ${errorCode}`);
|
|
const result = getServerErrorFromUnknown(error);
|
|
expect(result.statusCode).toBe(400);
|
|
});
|
|
});
|
|
|
|
test404Codes.forEach((errorCode) => {
|
|
test(`${errorCode} should return 404 Not Found`, () => {
|
|
const error = new ErrorWithCode(errorCode, `Test message for ${errorCode}`);
|
|
const result = getServerErrorFromUnknown(error);
|
|
expect(result.statusCode).toBe(404);
|
|
});
|
|
});
|
|
|
|
test409Codes.forEach((errorCode) => {
|
|
test(`${errorCode} should return 409 Conflict`, () => {
|
|
const error = new ErrorWithCode(errorCode, `Test message for ${errorCode}`);
|
|
const result = getServerErrorFromUnknown(error);
|
|
expect(result.statusCode).toBe(409);
|
|
});
|
|
});
|