From ac821f6b13eb1cf8035e2e722b16e4d2677f69a5 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 7 May 2025 14:23:25 +0200 Subject: [PATCH] chore: convert booking_denormalized e2e test to integration test (#21148) --- .../playwright/booking-denormalized.e2e.ts | 201 ------------- .../booking-denormalized.integration-test.ts | 265 ++++++++++++++++++ 2 files changed, 265 insertions(+), 201 deletions(-) delete mode 100644 apps/web/playwright/booking-denormalized.e2e.ts create mode 100644 packages/features/insights/__tests__/booking-denormalized.integration-test.ts diff --git a/apps/web/playwright/booking-denormalized.e2e.ts b/apps/web/playwright/booking-denormalized.e2e.ts deleted file mode 100644 index cd2dde7fdd..0000000000 --- a/apps/web/playwright/booking-denormalized.e2e.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { expect } from "@playwright/test"; - -import type { User, EventType } from "@calcom/prisma/client"; -import { BookingStatus } from "@calcom/prisma/enums"; - -import { test } from "./lib/fixtures"; - -test.describe.configure({ mode: "parallel" }); - -test.describe("BookingDenormalized", () => { - let userId: number; - let eventTypeId: number; - let bookingId: number; - let user: User; - let eventType: EventType; - - test.beforeEach(async ({ prisma }) => { - // Create test user with random email to avoid conflicts - const randomId = Math.floor(Math.random() * 1000000); - user = await prisma.user.create({ - data: { - email: `booking-denorm-${randomId}@example.com`, - username: `booking-denorm-testuser-${randomId}`, - name: "Test User", - }, - }); - userId = user.id; - - // Create test event type - eventType = await prisma.eventType.create({ - data: { - title: "Test Event", - slug: "test-event", - length: 60, - userId: user.id, - }, - }); - eventTypeId = eventType.id; - - // Create test booking with PENDING status (no idempotencyKey needed) - const booking = await prisma.booking.create({ - data: { - uid: `booking-denorm-${randomId}`, - title: "Test Booking", - startTime: new Date(), - endTime: new Date(Date.now() + 60 * 60 * 1000), // 1 hour later - userId: user.id, - eventTypeId: eventType.id, - status: BookingStatus.PENDING, - }, - }); - bookingId = booking.id; - }); - - test.afterEach(async ({ prisma }) => { - // Clean up test data in reverse order of creation to avoid foreign key constraints - if (bookingId) { - await prisma.booking.deleteMany({ - where: { id: bookingId }, - }); - } - if (eventTypeId) { - await prisma.eventType.deleteMany({ - where: { id: eventTypeId }, - }); - } - if (userId) { - await prisma.user.deleteMany({ - where: { id: userId }, - }); - } - }); - - test("should create denormalized entry when booking is created", async ({ prisma }) => { - const denormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ - where: { id: bookingId }, - }); - - expect(denormalizedBooking.eventTypeId).toBe(eventTypeId); - expect(denormalizedBooking.userId).toBe(userId); - expect(denormalizedBooking.title).toBe("Test Booking"); - expect(denormalizedBooking.eventLength).toBe(60); - expect(denormalizedBooking.userName).toBe("Test User"); - }); - - test("should update denormalized entry when booking is updated", async ({ prisma }) => { - await prisma.booking.update({ - where: { id: bookingId }, - data: { - title: "Updated Booking", - status: BookingStatus.CANCELLED, - }, - }); - - const updatedDenormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ - where: { id: bookingId }, - }); - - expect(updatedDenormalizedBooking.title).toBe("Updated Booking"); - expect(updatedDenormalizedBooking.status).toBe(BookingStatus.CANCELLED); - }); - - test("should update denormalized entry when user is updated", async ({ prisma }) => { - await prisma.user.update({ - where: { id: userId }, - data: { - name: "Updated User", - email: "updated@example.com", - }, - }); - - const userUpdatedDenormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ - where: { id: bookingId }, - }); - - expect(userUpdatedDenormalizedBooking.userName).toBe("Updated User"); - expect(userUpdatedDenormalizedBooking.userEmail).toBe("updated@example.com"); - }); - - test("should update denormalized entry when event type is updated", async ({ prisma }) => { - await prisma.eventType.update({ - where: { id: eventTypeId }, - data: { - length: 30, - }, - }); - - const eventTypeUpdatedDenormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ - where: { id: bookingId }, - }); - - expect(eventTypeUpdatedDenormalizedBooking.eventLength).toBe(30); - }); - - test("should delete denormalized entry when booking is deleted", async ({ prisma }) => { - await prisma.booking.delete({ - where: { id: bookingId }, - }); - - const deletedDenormalizedBooking = await prisma.bookingDenormalized.findUnique({ - where: { id: bookingId }, - }); - - expect(deletedDenormalizedBooking).toBeNull(); - }); - - test("should update denormalized entries when event type is deleted", async ({ prisma }) => { - // Create an additional booking for this test - const booking2 = await prisma.booking.create({ - data: { - uid: `test-booking-${Math.floor(Math.random() * 1000000)}`, - title: "Test Booking 2", - startTime: new Date(), - endTime: new Date(Date.now() + 60 * 60 * 1000), - userId: userId, - eventTypeId: eventTypeId, - status: BookingStatus.PENDING, - }, - }); - - await prisma.eventType.delete({ - where: { id: eventTypeId }, - }); - - const eventTypeDeletedDenormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ - where: { id: booking2.id }, - }); - - expect(eventTypeDeletedDenormalizedBooking.eventTypeId).toBeNull(); - expect(eventTypeDeletedDenormalizedBooking.eventLength).toBeNull(); - }); - - test("should delete denormalized entries when user is deleted", async ({ prisma }) => { - // Create an additional booking for this test - const booking2 = await prisma.booking.create({ - data: { - uid: `test-booking-${Math.floor(Math.random() * 1000000)}`, - title: "Test Booking 2", - startTime: new Date(), - endTime: new Date(Date.now() + 60 * 60 * 1000), - userId: userId, - eventTypeId: eventTypeId, - status: BookingStatus.PENDING, - }, - }); - - await prisma.user.delete({ - where: { id: userId }, - }); - - const userDeletedDenormalizedBooking1 = await prisma.bookingDenormalized.findUnique({ - where: { id: bookingId }, - }); - const userDeletedDenormalizedBooking2 = await prisma.bookingDenormalized.findUnique({ - where: { id: booking2.id }, - }); - - expect(userDeletedDenormalizedBooking1).toBeNull(); - expect(userDeletedDenormalizedBooking2).toBeNull(); - }); -}); diff --git a/packages/features/insights/__tests__/booking-denormalized.integration-test.ts b/packages/features/insights/__tests__/booking-denormalized.integration-test.ts new file mode 100644 index 0000000000..290b988254 --- /dev/null +++ b/packages/features/insights/__tests__/booking-denormalized.integration-test.ts @@ -0,0 +1,265 @@ +import { describe, expect, beforeEach, afterEach, it } from "vitest"; + +import prisma from "@calcom/prisma"; +import { BookingStatus, SchedulingType } from "@calcom/prisma/enums"; + +describe("BookingDenormalized", () => { + let userId: number; + let eventTypeId: number; + let bookingId: number; + const randomId = Math.floor(Math.random() * 1000000); + const email = `booking-denorm-${randomId}@example.com`; + + beforeEach(async () => { + // Create test user + const user = await prisma.user.create({ + data: { + email, + username: `booking-denorm-testuser-${randomId}`, + name: "Test User", + }, + }); + userId = user.id; + + // Create test event type + const eventType = await prisma.eventType.create({ + data: { + title: "Test Event", + slug: "test-event", + length: 60, + userId: user.id, + }, + }); + eventTypeId = eventType.id; + + // Create test booking + const booking = await prisma.booking.create({ + data: { + uid: `booking-denorm-${randomId}`, + title: "Test Booking", + startTime: new Date(), + endTime: new Date(Date.now() + 60 * 60 * 1000), + userId: user.id, + eventTypeId: eventType.id, + status: BookingStatus.PENDING, + }, + }); + bookingId = booking.id; + }); + + afterEach(async () => { + // Clean up test data in reverse order of creation + if (bookingId) { + await prisma.booking.deleteMany({ + where: { id: bookingId }, + }); + } + if (eventTypeId) { + await prisma.eventType.deleteMany({ + where: { id: eventTypeId }, + }); + } + if (userId) { + await prisma.user.deleteMany({ + where: { id: userId }, + }); + } + }); + + describe("Creation and Updates", () => { + it("should create denormalized entry when booking is created", async () => { + const denormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ + where: { id: bookingId }, + }); + + expect(denormalizedBooking.eventTypeId).toBe(eventTypeId); + expect(denormalizedBooking.userId).toBe(userId); + expect(denormalizedBooking.title).toBe("Test Booking"); + expect(denormalizedBooking.eventLength).toBe(60); + expect(denormalizedBooking.userName).toBe("Test User"); + }); + + it("should update denormalized entry when booking is updated", async () => { + await prisma.booking.update({ + where: { id: bookingId }, + data: { + title: "Updated Booking", + status: BookingStatus.CANCELLED, + }, + }); + + const updatedDenormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ + where: { id: bookingId }, + }); + + expect(updatedDenormalizedBooking.title).toBe("Updated Booking"); + expect(updatedDenormalizedBooking.status).toBe(BookingStatus.CANCELLED); + }); + + it("should update denormalized entry when user is updated", async () => { + await prisma.user.update({ + where: { id: userId }, + data: { + name: "Updated User", + email: "updated@example.com", + }, + }); + + const userUpdatedDenormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ + where: { id: bookingId }, + }); + + expect(userUpdatedDenormalizedBooking.userName).toBe("Updated User"); + expect(userUpdatedDenormalizedBooking.userEmail).toBe("updated@example.com"); + }); + + it("should update denormalized entry when event type is updated", async () => { + await prisma.eventType.update({ + where: { id: eventTypeId }, + data: { + length: 30, + schedulingType: SchedulingType.ROUND_ROBIN, + }, + }); + + const eventTypeUpdatedDenormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ + where: { id: bookingId }, + }); + + expect(eventTypeUpdatedDenormalizedBooking.eventLength).toBe(30); + }); + }); + + describe("Deletion Scenarios", () => { + it("should delete denormalized entry when booking is deleted", async () => { + await prisma.booking.delete({ + where: { id: bookingId }, + }); + + const deletedDenormalizedBooking = await prisma.bookingDenormalized.findUnique({ + where: { id: bookingId }, + }); + + expect(deletedDenormalizedBooking).toBeNull(); + }); + + it("should update denormalized entries when event type is deleted", async () => { + // Create an additional booking for this test + const booking2 = await prisma.booking.create({ + data: { + uid: `test-booking-${Math.floor(Math.random() * 1000000)}`, + title: "Test Booking 2", + startTime: new Date(), + endTime: new Date(Date.now() + 60 * 60 * 1000), + userId: userId, + eventTypeId: eventTypeId, + status: BookingStatus.PENDING, + }, + }); + + await prisma.eventType.delete({ + where: { id: eventTypeId }, + }); + + const eventTypeDeletedDenormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ + where: { id: booking2.id }, + }); + + expect(eventTypeDeletedDenormalizedBooking.eventTypeId).toBeNull(); + expect(eventTypeDeletedDenormalizedBooking.eventLength).toBeNull(); + + // Clean up the additional booking + await prisma.booking.delete({ + where: { id: booking2.id }, + }); + }); + + it("should delete denormalized entries when user is deleted", async () => { + // Create an additional booking for this test + const booking2 = await prisma.booking.create({ + data: { + uid: `test-booking-${Math.floor(Math.random() * 1000000)}`, + title: "Test Booking 2", + startTime: new Date(), + endTime: new Date(Date.now() + 60 * 60 * 1000), + userId: userId, + eventTypeId: eventTypeId, + status: BookingStatus.PENDING, + }, + }); + + await prisma.user.delete({ + where: { id: userId }, + }); + + const userDeletedDenormalizedBooking1 = await prisma.bookingDenormalized.findUnique({ + where: { id: bookingId }, + }); + const userDeletedDenormalizedBooking2 = await prisma.bookingDenormalized.findUnique({ + where: { id: booking2.id }, + }); + + expect(userDeletedDenormalizedBooking1).toBeNull(); + expect(userDeletedDenormalizedBooking2).toBeNull(); + }); + }); + + describe("Data Integrity", () => { + it("should maintain correct relationships across all tables", async () => { + const denormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ + where: { id: bookingId }, + }); + + const originalBooking = await prisma.booking.findUniqueOrThrow({ + where: { id: bookingId }, + }); + + // Verify relationships and data consistency + expect(denormalizedBooking.uid).toBe(originalBooking.uid); + expect(denormalizedBooking.eventTypeId).toBe(originalBooking.eventTypeId); + expect(denormalizedBooking.userId).toBe(originalBooking.userId); + expect(denormalizedBooking.status).toBe(originalBooking.status); + expect(denormalizedBooking.startTime.getTime()).toBe(originalBooking.startTime.getTime()); + expect(denormalizedBooking.endTime.getTime()).toBe(originalBooking.endTime.getTime()); + }); + }); + + describe("Edge Cases", () => { + it("should handle special characters in text fields", async () => { + const specialChars = "Test 'quotes' and \"double quotes\" and \\ backslashes"; + await prisma.booking.update({ + where: { id: bookingId }, + data: { + title: specialChars, + description: specialChars, + }, + }); + + const denormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ + where: { id: bookingId }, + }); + + expect(denormalizedBooking.title).toBe(specialChars); + expect(denormalizedBooking.description).toBe(specialChars); + }); + + it("should handle null values in optional fields", async () => { + await prisma.booking.update({ + where: { id: bookingId }, + data: { + description: null, + location: null, + eventTypeId: null, + }, + }); + + const denormalizedBooking = await prisma.bookingDenormalized.findUniqueOrThrow({ + where: { id: bookingId }, + }); + + expect(denormalizedBooking.description).toBeNull(); + expect(denormalizedBooking.location).toBeNull(); + expect(denormalizedBooking.eventTypeId).toBeNull(); + }); + }); +});