-
Notifications
You must be signed in to change notification settings - Fork 8.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Refactor seats logic (#12905)
* Refactor createBooking * Type fix * Abstract handleSeats * Create Invitee type * Create OrganizerUser type * Abstract addVideoCallDataToEvt * Abstract createLoggerWithEventDetails * Abstract `handleAppStatus` from handler * Create ReqAppsStatus type * Move `deleteMeeting` and `getCalendar` * Set parameters for `handleSeats` * Typescript refactor * Change function params from req * Type fix * Move handleSeats * Abstract lastAttendeeDeleteBooking * Create function for rescheduling seated events * Fix imports on reschedule seats function * Fix imports * Import handleSeats function * Fix rescheduleUid type * Refactor owner reschedule to new time slot * Refactor combine two booking times together * Reschedule as an attendee * Refactor createNewSeat * Remove old handleSeats * Remove lastAttendeeDeleteBooking from handleNewBooking * Test for new attendee right params are passed * Unit test params for reschedule * Typo fix * Clean up * Create new seat test * Test when attendee already signs up for booking * Type fix * Test reschedule move attendee to existing booking * On reschedule create new booking * Test on last attendee cancel booking * Owner reschedule to new time slot * Owner rescheduling, merge two bookings together * Test: when merging more than available seats, then fail * Test: fail when event is full * Remove duplicate E2E tests * Clean up * Rename `addVideoCallDataToEvt` to `addVideoCallDataToEvent` * Refactor `calcAppsStatus` * Assign `evt` to resutl of `addVideoCallDataToEvent` * Use prisma.transaction when moving attendees * Clean create seat call * Use ErrorCode enum * Use attendeeRescheduledSeatedBooking function * Await function * Prevent double triggering of workflows * Use inviteeToAdd in createNewSeat * Remove unused error code * Remove old handleSeats file * Type fix * Type fix * Type fix * Type fix * Type fix * Type fix * Type fix * Type fix --------- Co-authored-by: Morgan <[email protected]> Co-authored-by: Peer Richelsen <[email protected]> Co-authored-by: Erik <[email protected]>
- Loading branch information
1 parent
5c6e104
commit a0f1ceb
Showing
16 changed files
with
2,917 additions
and
1,788 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
import { expect } from "@playwright/test"; | ||
import { uuid } from "short-uuid"; | ||
import { v4 as uuidv4 } from "uuid"; | ||
|
||
import { randomString } from "@calcom/lib/random"; | ||
|
@@ -8,7 +7,6 @@ import { BookingStatus } from "@calcom/prisma/enums"; | |
|
||
import { test } from "./lib/fixtures"; | ||
import { | ||
bookTimeSlot, | ||
createNewSeatedEventType, | ||
selectFirstAvailableTimeSlotNextMonth, | ||
createUserWithSeatedEventAndAttendees, | ||
|
@@ -29,75 +27,8 @@ test.describe("Booking with Seats", () => { | |
await expect(page.locator(`text=Event type updated successfully`)).toBeVisible(); | ||
}); | ||
|
||
test("Multiple Attendees can book a seated event time slot", async ({ users, page }) => { | ||
const slug = "my-2-seated-event"; | ||
const user = await users.create({ | ||
name: "Seated event user", | ||
eventTypes: [ | ||
{ | ||
title: "My 2-seated event", | ||
slug, | ||
length: 60, | ||
seatsPerTimeSlot: 2, | ||
seatsShowAttendees: true, | ||
}, | ||
], | ||
}); | ||
await page.goto(`/${user.username}/${slug}`); | ||
|
||
let bookingUrl = ""; | ||
|
||
await test.step("Attendee #1 can book a seated event time slot", async () => { | ||
await selectFirstAvailableTimeSlotNextMonth(page); | ||
await bookTimeSlot(page); | ||
await expect(page.locator("[data-testid=success-page]")).toBeVisible(); | ||
}); | ||
await test.step("Attendee #2 can book the same seated event time slot", async () => { | ||
await page.goto(`/${user.username}/${slug}`); | ||
await selectFirstAvailableTimeSlotNextMonth(page); | ||
|
||
await page.waitForURL(/bookingUid/); | ||
bookingUrl = page.url(); | ||
await bookTimeSlot(page, { email: "[email protected]", name: "Jane Doe" }); | ||
await expect(page.locator("[data-testid=success-page]")).toBeVisible(); | ||
}); | ||
await test.step("Attendee #3 cannot click on the same seated event time slot", async () => { | ||
await page.goto(`/${user.username}/${slug}`); | ||
|
||
await page.click('[data-testid="incrementMonth"]'); | ||
|
||
// TODO: Find out why the first day is always booked on tests | ||
await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).click(); | ||
await expect(page.locator('[data-testid="time"][data-disabled="true"]')).toBeVisible(); | ||
}); | ||
await test.step("Attendee #3 cannot book the same seated event time slot accessing via url", async () => { | ||
await page.goto(bookingUrl); | ||
|
||
await bookTimeSlot(page, { email: "[email protected]", name: "Rick" }); | ||
await expect(page.locator("[data-testid=success-page]")).toBeHidden(); | ||
}); | ||
|
||
await test.step("User owner should have only 1 booking with 3 attendees", async () => { | ||
// Make sure user owner has only 1 booking with 3 attendees | ||
const bookings = await prisma.booking.findMany({ | ||
where: { eventTypeId: user.eventTypes.find((e) => e.slug === slug)?.id }, | ||
select: { | ||
id: true, | ||
attendees: { | ||
select: { | ||
id: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
expect(bookings).toHaveLength(1); | ||
expect(bookings[0].attendees).toHaveLength(2); | ||
}); | ||
}); | ||
|
||
test(`Attendees can cancel a seated event time slot`, async ({ page, users, bookings }) => { | ||
const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ | ||
test(`Prevent attendees from cancel when having invalid URL params`, async ({ page, users, bookings }) => { | ||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ | ||
{ name: "John First", email: "[email protected]", timeZone: "Europe/Berlin" }, | ||
{ name: "Jane Second", email: "[email protected]", timeZone: "Europe/Berlin" }, | ||
{ name: "John Third", email: "[email protected]", timeZone: "Europe/Berlin" }, | ||
|
@@ -120,30 +51,6 @@ test.describe("Booking with Seats", () => { | |
data: bookingSeats, | ||
}); | ||
|
||
await test.step("Attendee #1 should be able to cancel their booking", async () => { | ||
await page.goto(`/booking/${booking.uid}?seatReferenceUid=${bookingSeats[0].referenceUid}`); | ||
|
||
await page.locator('[data-testid="cancel"]').click(); | ||
await page.fill('[data-testid="cancel_reason"]', "Double booked!"); | ||
await page.locator('[data-testid="confirm_cancel"]').click(); | ||
await page.waitForLoadState("networkidle"); | ||
|
||
await expect(page).toHaveURL(/\/booking\/.*/); | ||
|
||
const cancelledHeadline = page.locator('[data-testid="cancelled-headline"]'); | ||
await expect(cancelledHeadline).toBeVisible(); | ||
|
||
// Old booking should still exist, with one less attendee | ||
const updatedBooking = await prisma.booking.findFirst({ | ||
where: { id: bookingSeats[0].bookingId }, | ||
include: { attendees: true }, | ||
}); | ||
|
||
const attendeeIds = updatedBooking?.attendees.map(({ id }) => id); | ||
expect(attendeeIds).toHaveLength(2); | ||
expect(attendeeIds).not.toContain(bookingAttendees[0].id); | ||
}); | ||
|
||
await test.step("Attendee #2 shouldn't be able to cancel booking using only booking/uid", async () => { | ||
await page.goto(`/booking/${booking.uid}`); | ||
|
||
|
@@ -156,29 +63,6 @@ test.describe("Booking with Seats", () => { | |
// expect cancel button to don't be in the page | ||
await expect(page.locator("[text=Cancel]")).toHaveCount(0); | ||
}); | ||
|
||
await test.step("All attendees cancelling should delete the booking for the user", async () => { | ||
// The remaining 2 attendees cancel | ||
for (let i = 1; i < bookingSeats.length; i++) { | ||
await page.goto(`/booking/${booking.uid}?seatReferenceUid=${bookingSeats[i].referenceUid}`); | ||
|
||
await page.locator('[data-testid="cancel"]').click(); | ||
await page.fill('[data-testid="cancel_reason"]', "Double booked!"); | ||
await page.locator('[data-testid="confirm_cancel"]').click(); | ||
|
||
await expect(page).toHaveURL(/\/booking\/.*/); | ||
|
||
const cancelledHeadline = page.locator('[data-testid="cancelled-headline"]'); | ||
await expect(cancelledHeadline).toBeVisible(); | ||
} | ||
|
||
// Should expect old booking to be cancelled | ||
const updatedBooking = await prisma.booking.findFirst({ | ||
where: { id: bookingSeats[0].bookingId }, | ||
}); | ||
expect(updatedBooking).not.toBeNull(); | ||
expect(updatedBooking?.status).toBe(BookingStatus.CANCELLED); | ||
}); | ||
}); | ||
|
||
test("Owner shouldn't be able to cancel booking without login in", async ({ page, bookings, users }) => { | ||
|
@@ -224,181 +108,6 @@ test.describe("Booking with Seats", () => { | |
}); | ||
|
||
test.describe("Reschedule for booking with seats", () => { | ||
test("Should reschedule booking with seats", async ({ page, users, bookings }) => { | ||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ | ||
{ name: "John First", email: `first+seats-${uuid()}@cal.com`, timeZone: "Europe/Berlin" }, | ||
{ name: "Jane Second", email: `second+seats-${uuid()}@cal.com`, timeZone: "Europe/Berlin" }, | ||
{ name: "John Third", email: `third+seats-${uuid()}@cal.com`, timeZone: "Europe/Berlin" }, | ||
]); | ||
const bookingAttendees = await prisma.attendee.findMany({ | ||
where: { bookingId: booking.id }, | ||
select: { | ||
id: true, | ||
email: true, | ||
}, | ||
}); | ||
|
||
const bookingSeats = [ | ||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() }, | ||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() }, | ||
{ bookingId: booking.id, attendeeId: bookingAttendees[2].id, referenceUid: uuidv4() }, | ||
]; | ||
|
||
await prisma.bookingSeat.createMany({ | ||
data: bookingSeats, | ||
}); | ||
|
||
const references = await prisma.bookingSeat.findMany({ | ||
where: { bookingId: booking.id }, | ||
}); | ||
|
||
await page.goto(`/reschedule/${references[2].referenceUid}`); | ||
|
||
await selectFirstAvailableTimeSlotNextMonth(page); | ||
|
||
// expect input to be filled with attendee number 3 data | ||
const thirdAttendeeElement = await page.locator("input[name=name]"); | ||
const attendeeName = await thirdAttendeeElement.inputValue(); | ||
expect(attendeeName).toBe("John Third"); | ||
|
||
await page.locator('[data-testid="confirm-reschedule-button"]').click(); | ||
|
||
// should wait for URL but that path starts with booking/ | ||
await page.waitForURL(/\/booking\/.*/); | ||
|
||
await expect(page).toHaveURL(/\/booking\/.*/); | ||
|
||
// Should expect new booking to be created for John Third | ||
const newBooking = await prisma.booking.findFirst({ | ||
where: { | ||
attendees: { | ||
some: { email: bookingAttendees[2].email }, | ||
}, | ||
}, | ||
include: { seatsReferences: true, attendees: true }, | ||
}); | ||
expect(newBooking?.status).toBe(BookingStatus.PENDING); | ||
expect(newBooking?.attendees.length).toBe(1); | ||
expect(newBooking?.attendees[0].name).toBe("John Third"); | ||
expect(newBooking?.seatsReferences.length).toBe(1); | ||
|
||
// Should expect old booking to be accepted with two attendees | ||
const oldBooking = await prisma.booking.findFirst({ | ||
where: { uid: booking.uid }, | ||
include: { seatsReferences: true, attendees: true }, | ||
}); | ||
|
||
expect(oldBooking?.status).toBe(BookingStatus.ACCEPTED); | ||
expect(oldBooking?.attendees.length).toBe(2); | ||
expect(oldBooking?.seatsReferences.length).toBe(2); | ||
}); | ||
|
||
test("Should reschedule booking with seats and if everyone rescheduled it should be deleted", async ({ | ||
page, | ||
users, | ||
bookings, | ||
}) => { | ||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ | ||
{ name: "John First", email: "[email protected]", timeZone: "Europe/Berlin" }, | ||
{ name: "Jane Second", email: "[email protected]", timeZone: "Europe/Berlin" }, | ||
]); | ||
|
||
const bookingAttendees = await prisma.attendee.findMany({ | ||
where: { bookingId: booking.id }, | ||
select: { | ||
id: true, | ||
}, | ||
}); | ||
|
||
const bookingSeats = [ | ||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() }, | ||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() }, | ||
]; | ||
|
||
await prisma.bookingSeat.createMany({ | ||
data: bookingSeats, | ||
}); | ||
|
||
const references = await prisma.bookingSeat.findMany({ | ||
where: { bookingId: booking.id }, | ||
}); | ||
|
||
await page.goto(`/reschedule/${references[0].referenceUid}`); | ||
|
||
await selectFirstAvailableTimeSlotNextMonth(page); | ||
|
||
await page.locator('[data-testid="confirm-reschedule-button"]').click(); | ||
|
||
await page.waitForURL(/\/booking\/.*/); | ||
|
||
await page.goto(`/reschedule/${references[1].referenceUid}`); | ||
|
||
await selectFirstAvailableTimeSlotNextMonth(page); | ||
|
||
await page.locator('[data-testid="confirm-reschedule-button"]').click(); | ||
|
||
// Using waitForUrl here fails the assertion `expect(oldBooking?.status).toBe(BookingStatus.CANCELLED);` probably because waitForUrl is considered complete before waitForNavigation and till that time the booking is not cancelled | ||
await page.waitForURL(/\/booking\/.*/); | ||
|
||
// Should expect old booking to be cancelled | ||
const oldBooking = await prisma.booking.findFirst({ | ||
where: { uid: booking.uid }, | ||
include: { | ||
seatsReferences: true, | ||
attendees: true, | ||
eventType: { | ||
include: { users: true, hosts: true }, | ||
}, | ||
}, | ||
}); | ||
|
||
expect(oldBooking?.status).toBe(BookingStatus.CANCELLED); | ||
}); | ||
|
||
test("Should cancel with seats and have no attendees and cancelled", async ({ page, users, bookings }) => { | ||
const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ | ||
{ name: "John First", email: "[email protected]", timeZone: "Europe/Berlin" }, | ||
{ name: "Jane Second", email: "[email protected]", timeZone: "Europe/Berlin" }, | ||
]); | ||
await user.apiLogin(); | ||
|
||
const oldBooking = await prisma.booking.findFirst({ | ||
where: { uid: booking.uid }, | ||
include: { seatsReferences: true, attendees: true }, | ||
}); | ||
|
||
const bookingAttendees = await prisma.attendee.findMany({ | ||
where: { bookingId: booking.id }, | ||
select: { | ||
id: true, | ||
}, | ||
}); | ||
|
||
const bookingSeats = [ | ||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() }, | ||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() }, | ||
]; | ||
|
||
await prisma.bookingSeat.createMany({ | ||
data: bookingSeats, | ||
}); | ||
|
||
// Now we cancel the booking as the organizer | ||
await page.goto(`/booking/${booking.uid}?cancel=true`); | ||
|
||
await page.locator('[data-testid="confirm_cancel"]').click(); | ||
|
||
await expect(page).toHaveURL(/\/booking\/.*/); | ||
|
||
// Should expect old booking to be cancelled | ||
const updatedBooking = await prisma.booking.findFirst({ | ||
where: { uid: booking.uid }, | ||
include: { seatsReferences: true, attendees: true }, | ||
}); | ||
|
||
expect(oldBooking?.startTime).not.toBe(updatedBooking?.startTime); | ||
}); | ||
|
||
test("If rescheduled/cancelled booking with seats it should display the correct number of seats", async ({ | ||
page, | ||
users, | ||
|
@@ -457,7 +166,7 @@ test.describe("Reschedule for booking with seats", () => { | |
expect(await page.locator("text=9 / 10 Seats available").count()).toEqual(0); | ||
}); | ||
|
||
test("Should cancel with seats but event should be still accesible and with one less attendee/seat", async ({ | ||
test("Should cancel with seats but event should be still accessible and with one less attendee/seat", async ({ | ||
page, | ||
users, | ||
bookings, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.