From c5cb2dfd5fc39c88019c27cd44abd9edf33a1a70 Mon Sep 17 00:00:00 2001 From: Shaunak Das Date: Tue, 7 Oct 2025 13:05:53 +0530 Subject: [PATCH 1/8] fix: zod errors on payments api --- apps/api/v1/lib/validations/payment.ts | 1 + apps/api/v1/pages/api/payments/[id].ts | 32 ++++++++++++-------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/apps/api/v1/lib/validations/payment.ts b/apps/api/v1/lib/validations/payment.ts index b8d544730a565b..4a942cd86ca93f 100644 --- a/apps/api/v1/lib/validations/payment.ts +++ b/apps/api/v1/lib/validations/payment.ts @@ -2,6 +2,7 @@ import { PaymentSchema } from "@calcom/prisma/zod/modelSchema/PaymentSchema"; export const schemaPaymentPublic = PaymentSchema.pick({ id: true, + uid: true, amount: true, success: true, refunded: true, diff --git a/apps/api/v1/pages/api/payments/[id].ts b/apps/api/v1/pages/api/payments/[id].ts index 35ab674c07d8a7..23f7ce9aa95a1e 100644 --- a/apps/api/v1/pages/api/payments/[id].ts +++ b/apps/api/v1/pages/api/payments/[id].ts @@ -46,25 +46,23 @@ export async function paymentById( if (safeQuery.success && method === "GET") { const userWithBookings = await prisma.user.findUnique({ where: { id: userId }, - // eslint-disable-next-line @calcom/eslint/no-prisma-include-true include: { bookings: true }, }); - await prisma.payment - .findUnique({ where: { id: safeQuery.data.id } }) - .then((data) => schemaPaymentPublic.parse(data)) - .then((payment) => { - if (!userWithBookings?.bookings.map((b) => b.id).includes(payment.bookingId)) { - res.status(401).json({ message: "Unauthorized" }); - } else { - res.status(200).json({ payment }); - } - }) - .catch((error: Error) => - res.status(404).json({ - message: `Payment with id: ${safeQuery.data.id} not found`, - error, - }) - ); + const data = await prisma.payment.findUnique({ where: { id: safeQuery.data.id } }); + + if (!data) { + return res.status(404).json({ + message: `Payment with id: ${safeQuery.data.id} not found`, + }); + } + + const payment = schemaPaymentPublic.parse(data); + + if (!userWithBookings?.bookings.map((b) => b.id).includes(payment.bookingId)) { + return res.status(401).json({ message: "Unauthorized" }); + } + + res.status(200).json({ payment }); } } export default withMiddleware("HTTP_GET")(withValidQueryIdTransformParseInt(paymentById)); From e747a1e45166da942973eb63a95ae91274334651 Mon Sep 17 00:00:00 2001 From: Shaunak Das Date: Tue, 7 Oct 2025 15:48:18 +0530 Subject: [PATCH 2/8] address ai review --- apps/api/v1/pages/api/payments/[id].ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/v1/pages/api/payments/[id].ts b/apps/api/v1/pages/api/payments/[id].ts index 23f7ce9aa95a1e..90e99a82075ef3 100644 --- a/apps/api/v1/pages/api/payments/[id].ts +++ b/apps/api/v1/pages/api/payments/[id].ts @@ -46,7 +46,7 @@ export async function paymentById( if (safeQuery.success && method === "GET") { const userWithBookings = await prisma.user.findUnique({ where: { id: userId }, - include: { bookings: true }, + select: { bookings: true }, }); const data = await prisma.payment.findUnique({ where: { id: safeQuery.data.id } }); From 3a033110b795c90dd719e1706cf15e280fc6aa7e Mon Sep 17 00:00:00 2001 From: Shaunak Das Date: Tue, 7 Oct 2025 16:35:55 +0530 Subject: [PATCH 3/8] fix: add docs and test cases --- apps/api/v1/pages/api/docs.ts | 85 ++++-- apps/api/v1/pages/api/payments/[id].ts | 11 +- apps/api/v1/pages/api/payments/index.ts | 30 ++- .../v1/test/lib/payments/[id]/_get.test.ts | 247 ++++++++++++++++++ apps/api/v1/test/lib/payments/_get.test.ts | 183 +++++++++++++ 5 files changed, 529 insertions(+), 27 deletions(-) create mode 100644 apps/api/v1/test/lib/payments/[id]/_get.test.ts create mode 100644 apps/api/v1/test/lib/payments/_get.test.ts diff --git a/apps/api/v1/pages/api/docs.ts b/apps/api/v1/pages/api/docs.ts index e0f33493d6cc1b..8aa16a745e63ca 100644 --- a/apps/api/v1/pages/api/docs.ts +++ b/apps/api/v1/pages/api/docs.ts @@ -33,6 +33,73 @@ const swaggerHandler = withSwagger({ $ref: "#/components/schemas/Recording", }, }, + Payment: { + type: "object", + properties: { + id: { + type: "number", + example: 1, + description: "Payment ID", + }, + uid: { + type: "string", + example: "payment_abc123", + description: "Payment UID used for abandoned-cart recovery", + }, + amount: { + type: "number", + example: 5000, + description: "Payment amount in cents", + }, + success: { + type: "boolean", + example: true, + description: "Whether the payment was successful", + }, + refunded: { + type: "boolean", + example: false, + description: "Whether the payment was refunded", + }, + fee: { + type: "number", + example: 150, + description: "Payment processing fee in cents", + }, + paymentOption: { + type: "string", + example: "ON_BOOKING", + description: "Payment option type", + }, + currency: { + type: "string", + example: "USD", + description: "Payment currency", + }, + bookingId: { + type: "number", + example: 123, + description: "Associated booking ID", + }, + }, + required: [ + "id", + "uid", + "amount", + "success", + "refunded", + "fee", + "paymentOption", + "currency", + "bookingId", + ], + }, + ArrayOfPayments: { + type: "array", + items: { + $ref: "#/components/schemas/Payment", + }, + }, Recording: { properties: { id: { @@ -135,23 +202,7 @@ const swaggerHandler = withSwagger({ }, }, payment: { - type: Array, - items: { - properties: { - id: { - type: "number", - example: 1, - }, - success: { - type: "boolean", - example: true, - }, - paymentOption: { - type: "string", - example: "ON_BOOKING", - }, - }, - }, + $ref: "#/components/schemas/ArrayOfPayments", }, }, }, diff --git a/apps/api/v1/pages/api/payments/[id].ts b/apps/api/v1/pages/api/payments/[id].ts index 90e99a82075ef3..08dbb7f82caeb3 100644 --- a/apps/api/v1/pages/api/payments/[id].ts +++ b/apps/api/v1/pages/api/payments/[id].ts @@ -33,8 +33,15 @@ import { * responses: * 200: * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * payment: + * $ref: "#/components/schemas/Payment" * 401: - * description: Authorization information is missing or invalid. + * description: Authorization information is missing or invalid. * 404: * description: Payment was not found */ @@ -58,7 +65,7 @@ export async function paymentById( const payment = schemaPaymentPublic.parse(data); - if (!userWithBookings?.bookings.map((b) => b.id).includes(payment.bookingId)) { + if (!userWithBookings || !userWithBookings?.bookings.map((b) => b.id).includes(payment.bookingId)) { return res.status(401).json({ message: "Unauthorized" }); } diff --git a/apps/api/v1/pages/api/payments/index.ts b/apps/api/v1/pages/api/payments/index.ts index d556245753e058..31316c544eeefa 100644 --- a/apps/api/v1/pages/api/payments/index.ts +++ b/apps/api/v1/pages/api/payments/index.ts @@ -13,11 +13,25 @@ import { schemaPaymentPublic } from "~/lib/validations/payment"; * summary: Find all payments * tags: * - payments + * parameters: + * - in: query + * name: apiKey + * required: true + * schema: + * type: string + * description: Your API key * responses: * 200: * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * payments: + * $ref: "#/components/schemas/ArrayOfPayments" * 401: - * description: Authorization information is missing or invalid. + * description: Authorization information is missing or invalid. * 404: * description: No payments were found */ @@ -32,13 +46,13 @@ async function allPayments({ userId }: NextApiRequest, res: NextApiResponse schemaPaymentPublic.parse(payment)); - if (payments) res.status(200).json({ payments }); - else - (error: Error) => - res.status(404).json({ - message: "No Payments were found", - error, - }); + if (payments) { + res.status(200).json({ payments }); + } else { + res.status(404).json({ + message: "No Payments were found", + }); + } } // NO POST FOR PAYMENTS FOR NOW export default withMiddleware("HTTP_GET")(allPayments); diff --git a/apps/api/v1/test/lib/payments/[id]/_get.test.ts b/apps/api/v1/test/lib/payments/[id]/_get.test.ts new file mode 100644 index 00000000000000..ac3a32d76ea6d4 --- /dev/null +++ b/apps/api/v1/test/lib/payments/[id]/_get.test.ts @@ -0,0 +1,247 @@ +import { createMocks } from "node-mocks-http"; +import { describe, expect, it, beforeEach, afterEach, vi } from "vitest"; + +import prisma from "@calcom/prisma"; + +import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt"; + +import paymentById from "../../../../pages/api/payments/[id]"; + +// Mock the withMiddleware function +vi.mock("~/lib/helpers/withMiddleware", () => ({ + withMiddleware: (_method: string) => (handler: unknown) => handler, +})); + +// Mock the query validation +vi.mock("~/lib/validations/shared/queryIdTransformParseInt", () => ({ + schemaQueryIdParseInt: { + safeParse: vi.fn(), + }, + withValidQueryIdTransformParseInt: (handler: unknown) => handler, +})); + +// Mock prisma +vi.mock("@calcom/prisma", () => ({ + default: { + user: { + findUnique: vi.fn(), + }, + payment: { + findUnique: vi.fn(), + }, + }, +})); + +const mockPrisma = prisma as unknown as { + user: { findUnique: ReturnType }; + payment: { findUnique: ReturnType }; +}; +const mockSchemaQueryIdParseInt = schemaQueryIdParseInt as unknown as { + safeParse: ReturnType; +}; + +describe("GET /api/payments/[id]", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("should return 200 with payment object when payment exists", async () => { + const mockUser = { + id: 1, + bookings: [{ id: 1 }], + }; + + const mockPayment = { + id: 1, + uid: "payment_123", + amount: 5000, + success: true, + refunded: false, + fee: 150, + paymentOption: "ON_BOOKING", + currency: "USD", + bookingId: 1, + }; + + mockSchemaQueryIdParseInt.safeParse.mockReturnValue({ + success: true, + data: { id: 1 }, + }); + + mockPrisma.user.findUnique.mockResolvedValue(mockUser); + mockPrisma.payment.findUnique.mockResolvedValue(mockPayment); + + const { req, res } = createMocks({ + method: "GET", + query: { apiKey: "test_key", id: "1" }, + userId: 1, + }); + + await paymentById(req, res); + + expect(res._getStatusCode()).toBe(200); + const responseData = JSON.parse(res._getData()); + expect(responseData).toEqual({ + payment: { + id: 1, + uid: "payment_123", + amount: 5000, + success: true, + refunded: false, + fee: 150, + paymentOption: "ON_BOOKING", + currency: "USD", + bookingId: 1, + }, + }); + }); + + it("should return 404 when payment not found", async () => { + const mockUser = { + id: 1, + bookings: [{ id: 1 }], + }; + + mockSchemaQueryIdParseInt.safeParse.mockReturnValue({ + success: true, + data: { id: 999 }, + }); + + mockPrisma.user.findUnique.mockResolvedValue(mockUser); + mockPrisma.payment.findUnique.mockResolvedValue(null); + + const { req, res } = createMocks({ + method: "GET", + query: { apiKey: "test_key", id: "999" }, + userId: 1, + }); + + await paymentById(req, res); + + expect(res._getStatusCode()).toBe(404); + const responseData = JSON.parse(res._getData()); + expect(responseData).toEqual({ + message: "Payment with id: 999 not found", + }); + }); + + it("should return 401 when user not authorized for payment", async () => { + const mockUser = { + id: 1, + bookings: [{ id: 2 }], // Different booking ID + }; + + const mockPayment = { + id: 1, + uid: "payment_123", + amount: 5000, + success: true, + refunded: false, + fee: 150, + paymentOption: "ON_BOOKING", + currency: "USD", + bookingId: 1, // Different from user's bookings + }; + + mockSchemaQueryIdParseInt.safeParse.mockReturnValue({ + success: true, + data: { id: 1 }, + }); + + mockPrisma.user.findUnique.mockResolvedValue(mockUser); + mockPrisma.payment.findUnique.mockResolvedValue(mockPayment); + + const { req, res } = createMocks({ + method: "GET", + query: { apiKey: "test_key", id: "1" }, + userId: 1, + }); + + await paymentById(req, res); + + expect(res._getStatusCode()).toBe(401); + const responseData = JSON.parse(res._getData()); + expect(responseData).toEqual({ + message: "Unauthorized", + }); + }); + + it("should not process request when invalid query parameters", async () => { + mockSchemaQueryIdParseInt.safeParse.mockReturnValue({ + success: false, + }); + + const { req, res } = createMocks({ + method: "GET", + query: { apiKey: "test_key", id: "invalid" }, + userId: 1, + }); + + await paymentById(req, res); + + // When query parsing fails, the function returns early without setting status + expect(res._getStatusCode()).toBe(200); + }); + + it("should include paymentUid (uid field) in response", async () => { + const mockUser = { + id: 1, + bookings: [{ id: 1 }], + }; + + const mockPayment = { + id: 1, + uid: "payment_abc123", + amount: 5000, + success: true, + refunded: false, + fee: 150, + paymentOption: "ON_BOOKING", + currency: "USD", + bookingId: 1, + }; + + mockSchemaQueryIdParseInt.safeParse.mockReturnValue({ + success: true, + data: { id: 1 }, + }); + + mockPrisma.user.findUnique.mockResolvedValue(mockUser); + mockPrisma.payment.findUnique.mockResolvedValue(mockPayment); + + const { req, res } = createMocks({ + method: "GET", + query: { apiKey: "test_key", id: "1" }, + userId: 1, + }); + + await paymentById(req, res); + + expect(res._getStatusCode()).toBe(200); + const responseData = JSON.parse(res._getData()); + expect(responseData.payment).toHaveProperty("uid"); + expect(responseData.payment.uid).toBe("payment_abc123"); + }); + + it("should not process request for non-GET methods", async () => { + mockSchemaQueryIdParseInt.safeParse.mockReturnValue({ + success: true, + data: { id: 1 }, + }); + + const { req, res } = createMocks({ + method: "POST", + query: { apiKey: "test_key", id: "1" }, + userId: 1, + }); + + await paymentById(req, res); + + // When method is not GET, the function returns early without setting status + expect(res._getStatusCode()).toBe(200); + }); +}); diff --git a/apps/api/v1/test/lib/payments/_get.test.ts b/apps/api/v1/test/lib/payments/_get.test.ts new file mode 100644 index 00000000000000..7750b21d9e6579 --- /dev/null +++ b/apps/api/v1/test/lib/payments/_get.test.ts @@ -0,0 +1,183 @@ +import { createMocks } from "node-mocks-http"; +import { describe, expect, it, beforeEach, afterEach, vi } from "vitest"; + +import prisma from "@calcom/prisma"; + +import allPayments from "../../../pages/api/payments/index"; + +// Mock the withMiddleware function +vi.mock("~/lib/helpers/withMiddleware", () => ({ + withMiddleware: (_method: string) => (handler: unknown) => handler, +})); + +// Mock prisma +vi.mock("@calcom/prisma", () => ({ + default: { + user: { + findUnique: vi.fn(), + }, + payment: { + findMany: vi.fn(), + }, + }, +})); + +const mockPrisma = prisma as unknown as { + user: { findUnique: ReturnType }; + payment: { findMany: ReturnType }; +}; + +describe("GET /api/payments", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("should return 200 with payments array when payments exist", async () => { + const mockUser = { + id: 1, + bookings: [{ id: 1 }, { id: 2 }], + }; + + const mockPayments = [ + { + id: 1, + uid: "payment_123", + amount: 5000, + success: true, + refunded: false, + fee: 150, + paymentOption: "ON_BOOKING", + currency: "USD", + bookingId: 1, + }, + { + id: 2, + uid: "payment_456", + amount: 3000, + success: true, + refunded: false, + fee: 90, + paymentOption: "ON_BOOKING", + currency: "USD", + bookingId: 2, + }, + ]; + + mockPrisma.user.findUnique.mockResolvedValue(mockUser); + mockPrisma.payment.findMany.mockResolvedValue(mockPayments); + + const { req, res } = createMocks({ + method: "GET", + query: { apiKey: "test_key" }, + userId: 1, + }); + + await allPayments(req, res); + + expect(res._getStatusCode()).toBe(200); + const responseData = JSON.parse(res._getData()); + expect(responseData).toEqual({ + payments: [ + { + id: 1, + uid: "payment_123", + amount: 5000, + success: true, + refunded: false, + fee: 150, + paymentOption: "ON_BOOKING", + currency: "USD", + bookingId: 1, + }, + { + id: 2, + uid: "payment_456", + amount: 3000, + success: true, + refunded: false, + fee: 90, + paymentOption: "ON_BOOKING", + currency: "USD", + bookingId: 2, + }, + ], + }); + }); + + it("should return 200 with empty array when no payments found", async () => { + const mockUser = { + id: 1, + bookings: [{ id: 1 }], + }; + + mockPrisma.user.findUnique.mockResolvedValue(mockUser); + mockPrisma.payment.findMany.mockResolvedValue([]); + + const { req, res } = createMocks({ + method: "GET", + query: { apiKey: "test_key" }, + userId: 1, + }); + + await allPayments(req, res); + + expect(res._getStatusCode()).toBe(200); + const responseData = JSON.parse(res._getData()); + expect(responseData).toEqual({ + payments: [], + }); + }); + + it("should throw error when user not found", async () => { + mockPrisma.user.findUnique.mockResolvedValue(null); + + const { req, res } = createMocks({ + method: "GET", + query: { apiKey: "test_key" }, + userId: 1, + }); + + await expect(allPayments(req, res)).rejects.toThrow("No user found"); + }); + + it("should include paymentUid (uid field) in response", async () => { + const mockUser = { + id: 1, + bookings: [{ id: 1 }], + }; + + const mockPayments = [ + { + id: 1, + uid: "payment_abc123", + amount: 5000, + success: true, + refunded: false, + fee: 150, + paymentOption: "ON_BOOKING", + currency: "USD", + bookingId: 1, + }, + ]; + + mockPrisma.user.findUnique.mockResolvedValue(mockUser); + mockPrisma.payment.findMany.mockResolvedValue(mockPayments); + + const { req, res } = createMocks({ + method: "GET", + query: { apiKey: "test_key" }, + userId: 1, + }); + + await allPayments(req, res); + + expect(res._getStatusCode()).toBe(200); + const responseData = JSON.parse(res._getData()); + expect(responseData.payments[0]).toHaveProperty("uid"); + expect(responseData.payments[0].uid).toBe("payment_abc123"); + }); +}); From 786d236c390922b1d6a2d13f6948491dad72217f Mon Sep 17 00:00:00 2001 From: Shaunak Das Date: Tue, 7 Oct 2025 16:45:35 +0530 Subject: [PATCH 4/8] fix: address ai review --- apps/api/v1/pages/api/payments/index.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/apps/api/v1/pages/api/payments/index.ts b/apps/api/v1/pages/api/payments/index.ts index 31316c544eeefa..49b617b2cf1820 100644 --- a/apps/api/v1/pages/api/payments/index.ts +++ b/apps/api/v1/pages/api/payments/index.ts @@ -32,8 +32,7 @@ import { schemaPaymentPublic } from "~/lib/validations/payment"; * $ref: "#/components/schemas/ArrayOfPayments" * 401: * description: Authorization information is missing or invalid. - * 404: - * description: No payments were found + * */ async function allPayments({ userId }: NextApiRequest, res: NextApiResponse) { const userWithBookings = await prisma.user.findUnique({ @@ -46,13 +45,7 @@ async function allPayments({ userId }: NextApiRequest, res: NextApiResponse schemaPaymentPublic.parse(payment)); - if (payments) { - res.status(200).json({ payments }); - } else { - res.status(404).json({ - message: "No Payments were found", - }); - } + res.status(200).json({ payments }); } // NO POST FOR PAYMENTS FOR NOW export default withMiddleware("HTTP_GET")(allPayments); From 737ddb3def0c3b6ed55fc3d762107b19f42c31b2 Mon Sep 17 00:00:00 2001 From: Shaunak Das Date: Tue, 7 Oct 2025 17:05:32 +0530 Subject: [PATCH 5/8] fix: fix type check issue --- apps/api/v1/test/lib/payments/[id]/_get.test.ts | 13 +++++++------ apps/api/v1/test/lib/payments/_get.test.ts | 11 +++++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/api/v1/test/lib/payments/[id]/_get.test.ts b/apps/api/v1/test/lib/payments/[id]/_get.test.ts index ac3a32d76ea6d4..c2d8b87732e95a 100644 --- a/apps/api/v1/test/lib/payments/[id]/_get.test.ts +++ b/apps/api/v1/test/lib/payments/[id]/_get.test.ts @@ -1,3 +1,4 @@ +import type { NextApiRequest, NextApiResponse } from "next"; import { createMocks } from "node-mocks-http"; import { describe, expect, it, beforeEach, afterEach, vi } from "vitest"; @@ -81,7 +82,7 @@ describe("GET /api/payments/[id]", () => { userId: 1, }); - await paymentById(req, res); + await paymentById(req as unknown as NextApiRequest, res as unknown as NextApiResponse); expect(res._getStatusCode()).toBe(200); const responseData = JSON.parse(res._getData()); @@ -120,7 +121,7 @@ describe("GET /api/payments/[id]", () => { userId: 1, }); - await paymentById(req, res); + await paymentById(req as unknown as NextApiRequest, res as unknown as NextApiResponse); expect(res._getStatusCode()).toBe(404); const responseData = JSON.parse(res._getData()); @@ -161,7 +162,7 @@ describe("GET /api/payments/[id]", () => { userId: 1, }); - await paymentById(req, res); + await paymentById(req as unknown as NextApiRequest, res as unknown as NextApiResponse); expect(res._getStatusCode()).toBe(401); const responseData = JSON.parse(res._getData()); @@ -181,7 +182,7 @@ describe("GET /api/payments/[id]", () => { userId: 1, }); - await paymentById(req, res); + await paymentById(req as unknown as NextApiRequest, res as unknown as NextApiResponse); // When query parsing fails, the function returns early without setting status expect(res._getStatusCode()).toBe(200); @@ -219,7 +220,7 @@ describe("GET /api/payments/[id]", () => { userId: 1, }); - await paymentById(req, res); + await paymentById(req as unknown as NextApiRequest, res as unknown as NextApiResponse); expect(res._getStatusCode()).toBe(200); const responseData = JSON.parse(res._getData()); @@ -239,7 +240,7 @@ describe("GET /api/payments/[id]", () => { userId: 1, }); - await paymentById(req, res); + await paymentById(req as unknown as NextApiRequest, res as unknown as NextApiResponse); // When method is not GET, the function returns early without setting status expect(res._getStatusCode()).toBe(200); diff --git a/apps/api/v1/test/lib/payments/_get.test.ts b/apps/api/v1/test/lib/payments/_get.test.ts index 7750b21d9e6579..96ada102839bf1 100644 --- a/apps/api/v1/test/lib/payments/_get.test.ts +++ b/apps/api/v1/test/lib/payments/_get.test.ts @@ -1,3 +1,4 @@ +import type { NextApiRequest, NextApiResponse } from "next"; import { createMocks } from "node-mocks-http"; import { describe, expect, it, beforeEach, afterEach, vi } from "vitest"; @@ -76,7 +77,7 @@ describe("GET /api/payments", () => { userId: 1, }); - await allPayments(req, res); + await allPayments(req as unknown as NextApiRequest, res as unknown as NextApiResponse); expect(res._getStatusCode()).toBe(200); const responseData = JSON.parse(res._getData()); @@ -123,7 +124,7 @@ describe("GET /api/payments", () => { userId: 1, }); - await allPayments(req, res); + await allPayments(req as unknown as NextApiRequest, res as unknown as NextApiResponse); expect(res._getStatusCode()).toBe(200); const responseData = JSON.parse(res._getData()); @@ -141,7 +142,9 @@ describe("GET /api/payments", () => { userId: 1, }); - await expect(allPayments(req, res)).rejects.toThrow("No user found"); + await expect( + allPayments(req as unknown as NextApiRequest, res as unknown as NextApiResponse) + ).rejects.toThrow("No user found"); }); it("should include paymentUid (uid field) in response", async () => { @@ -173,7 +176,7 @@ describe("GET /api/payments", () => { userId: 1, }); - await allPayments(req, res); + await allPayments(req as unknown as NextApiRequest, res as unknown as NextApiResponse); expect(res._getStatusCode()).toBe(200); const responseData = JSON.parse(res._getData()); From 62b4924918195bbdca3189309e66cec03da3f788 Mon Sep 17 00:00:00 2001 From: Shaunak Das Date: Tue, 7 Oct 2025 17:29:55 +0530 Subject: [PATCH 6/8] fix: address ai review --- apps/api/v1/pages/api/payments/index.ts | 6 +++++- apps/api/v1/test/lib/payments/_get.test.ts | 12 ++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/api/v1/pages/api/payments/index.ts b/apps/api/v1/pages/api/payments/index.ts index 49b617b2cf1820..2162d1080a3164 100644 --- a/apps/api/v1/pages/api/payments/index.ts +++ b/apps/api/v1/pages/api/payments/index.ts @@ -32,6 +32,8 @@ import { schemaPaymentPublic } from "~/lib/validations/payment"; * $ref: "#/components/schemas/ArrayOfPayments" * 401: * description: Authorization information is missing or invalid. + * 404: + * description: User not found * */ async function allPayments({ userId }: NextApiRequest, res: NextApiResponse) { @@ -39,7 +41,9 @@ async function allPayments({ userId }: NextApiRequest, res: NextApiResponse booking.id); const data = await prisma.payment.findMany({ where: { bookingId: { in: bookingIds } } }); diff --git a/apps/api/v1/test/lib/payments/_get.test.ts b/apps/api/v1/test/lib/payments/_get.test.ts index 96ada102839bf1..d31170796fa7aa 100644 --- a/apps/api/v1/test/lib/payments/_get.test.ts +++ b/apps/api/v1/test/lib/payments/_get.test.ts @@ -133,7 +133,7 @@ describe("GET /api/payments", () => { }); }); - it("should throw error when user not found", async () => { + it("should return 404 when user not found", async () => { mockPrisma.user.findUnique.mockResolvedValue(null); const { req, res } = createMocks({ @@ -142,9 +142,13 @@ describe("GET /api/payments", () => { userId: 1, }); - await expect( - allPayments(req as unknown as NextApiRequest, res as unknown as NextApiResponse) - ).rejects.toThrow("No user found"); + await allPayments(req as unknown as NextApiRequest, res as unknown as NextApiResponse); + + expect(res._getStatusCode()).toBe(404); + const responseData = JSON.parse(res._getData()); + expect(responseData).toEqual({ + message: "User not found", + }); }); it("should include paymentUid (uid field) in response", async () => { From 99e1cfaf3cc3232906a10df40483c82a250469a0 Mon Sep 17 00:00:00 2001 From: Shaunak Das Date: Tue, 7 Oct 2025 18:09:57 +0530 Subject: [PATCH 7/8] fix: address ai review --- apps/api/v1/pages/api/payments/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/v1/pages/api/payments/index.ts b/apps/api/v1/pages/api/payments/index.ts index 2162d1080a3164..34f980ffa41c0c 100644 --- a/apps/api/v1/pages/api/payments/index.ts +++ b/apps/api/v1/pages/api/payments/index.ts @@ -39,7 +39,7 @@ import { schemaPaymentPublic } from "~/lib/validations/payment"; async function allPayments({ userId }: NextApiRequest, res: NextApiResponse) { const userWithBookings = await prisma.user.findUnique({ where: { id: userId }, - include: { bookings: true }, + select: { bookings: true }, }); if (!userWithBookings) { return res.status(404).json({ message: "User not found" }); From 1436acea86411341f52fbb457dd8a8607ec3ce9f Mon Sep 17 00:00:00 2001 From: Shaunak Das Date: Fri, 10 Oct 2025 17:32:40 +0530 Subject: [PATCH 8/8] fix: fix test failure issue --- packages/embeds/embed-core/src/embed-iframe.ts | 5 +++++ .../embeds/embed-core/src/embed-iframe/lib/utils.ts | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/embeds/embed-core/src/embed-iframe.ts b/packages/embeds/embed-core/src/embed-iframe.ts index 9f0a2ff72950dc..f7be73daa08a59 100644 --- a/packages/embeds/embed-core/src/embed-iframe.ts +++ b/packages/embeds/embed-core/src/embed-iframe.ts @@ -279,6 +279,11 @@ export const useEmbedType = () => { }; function makeBodyVisible() { + // Check if we're in a browser environment before accessing document + if (typeof document === "undefined") { + return; + } + if (document.body.style.visibility !== "visible") { document.body.style.visibility = "visible"; } diff --git a/packages/embeds/embed-core/src/embed-iframe/lib/utils.ts b/packages/embeds/embed-core/src/embed-iframe/lib/utils.ts index c1dfa155909831..6aeec9ca95ba9a 100644 --- a/packages/embeds/embed-core/src/embed-iframe/lib/utils.ts +++ b/packages/embeds/embed-core/src/embed-iframe/lib/utils.ts @@ -12,6 +12,11 @@ export function isBookerReady() { } function isSkeletonSupportedPageType() { + // Check if we're in a browser environment before accessing document + if (typeof document === "undefined") { + return false; + } + const url = new URL(document.URL); const pageType = url.searchParams.get("cal.embed.pageType"); // Any non-empty pageType is skeleton supported because generateSkeleton() @@ -44,6 +49,11 @@ export function isLinkReady({ embedStore }: { embedStore: typeof import("./embed * Moves the queuedFormResponse to the routingFormResponse record to mark it as an actual response now. */ export const recordResponseIfQueued = async (params: Record) => { + // Check if we're in a browser environment before accessing document + if (typeof document === "undefined") { + return null; + } + const url = new URL(document.URL); let routingFormResponseId: number | null = null; const queuedFormResponseIdParam = url.searchParams.get("cal.queuedFormResponseId");