diff --git a/apps/web/playwright/bookings-list.e2e.ts b/apps/web/playwright/bookings-list.e2e.ts index 75eae49531..2960d0ebeb 100644 --- a/apps/web/playwright/bookings-list.e2e.ts +++ b/apps/web/playwright/bookings-list.e2e.ts @@ -4,6 +4,7 @@ import { BookingStatus } from "@calcom/prisma/client"; import type { Fixtures } from "./lib/fixtures"; import { test } from "./lib/fixtures"; +import { setupManagedEvent } from "./lib/testUtils"; test.afterEach(({ users }) => users.deleteAll()); @@ -150,6 +151,61 @@ test.describe("Bookings", () => { await moreGuests.click(); await expect(firstGuestInMore).toBeChecked({ checked: true }); }); + test("Team admin/owner can mark first attendee as no-show", async ({ + page, + users, + bookings, + webhooks, + }) => { + const { adminUser, memberUser, managedEvent } = await setupManagedEvent({ users }); + + const bookingFixture = await createBooking({ + title: "Managed Event Booking", + bookingsFixture: bookings, + // Create a booking 3 days ago + relativeDate: -3, + organizer: memberUser, + organizerEventType: managedEvent, + attendees: [ + { name: "First Guest", email: "first@cal.com", timeZone: "Europe/Berlin" }, + { name: "Second Guest", email: "second@cal.com", timeZone: "Europe/Berlin" }, + { name: "Third Guest", email: "third@cal.com", timeZone: "Europe/Berlin" }, + ], + }); + const booking = await bookingFixture.self(); + await adminUser.apiLogin(); + const { webhookReceiver, teamId } = await webhooks.createTeamReceiver(); + await page.goto(`/bookings/past`); + const pastBookings = page.locator('[data-testid="past-bookings"]'); + const firstPastBooking = pastBookings.locator('[data-testid="booking-item"]').nth(0); + const titleAndAttendees = firstPastBooking.locator('[data-testid="title-and-attendees"]'); + const firstGuest = firstPastBooking.locator('[data-testid="guest"]').nth(0); + await firstGuest.click(); + await expect(titleAndAttendees.locator('[data-testid="mark-no-show"]')).toBeVisible(); + await titleAndAttendees.locator('[data-testid="mark-no-show"]').click(); + await firstGuest.click(); + await expect(titleAndAttendees.locator('[data-testid="unmark-no-show"]')).toBeVisible(); + await expect(titleAndAttendees.locator('[data-testid="mark-no-show"]')).toBeHidden(); + await webhookReceiver.waitForRequestCount(1); + const [request] = webhookReceiver.requestList; + const body = request.body; + const dynamic = "[redacted/dynamic]"; + // @ts-expect-error we are modifying the object + body.createdAt = dynamic; + expect(body).toMatchObject({ + triggerEvent: "BOOKING_NO_SHOW_UPDATED", + createdAt: "[redacted/dynamic]", + payload: { + message: "first@cal.com marked as no-show", + attendees: [{ email: "first@cal.com", noShow: true }], + bookingUid: booking?.uid, + bookingId: booking?.id, + }, + }); + + // Close webhook receiver + webhookReceiver.close(); + }); }); }); diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts index 15fd6be921..2b5b199e61 100644 --- a/apps/web/playwright/lib/testUtils.ts +++ b/apps/web/playwright/lib/testUtils.ts @@ -175,6 +175,31 @@ export const createNewEventType = async (page: Page, args: { eventTitle: string }); }; +export async function setupManagedEvent({ + users, + unlockedFields, +}: { + users: Fixtures["users"]; + unlockedFields?: Record; +}) { + const teamMateName = "teammate-1"; + const teamEventTitle = "Managed"; + const adminUser = await users.create(null, { + hasTeam: true, + teammates: [{ name: teamMateName }], + teamEventTitle, + teamEventSlug: "managed", + schedulingType: "MANAGED", + addManagedEventToTeamMates: true, + managedEventUnlockedFields: unlockedFields, + }); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const memberUser = users.get().find((u) => u.name === teamMateName)!; + const { team } = await adminUser.getFirstTeamMembership(); + const managedEvent = await adminUser.getFirstTeamEvent(team.id, SchedulingType.MANAGED); + return { adminUser, memberUser, managedEvent, teamMateName, teamEventTitle, teamId: team.id }; +} + export const createNewSeatedEventType = async (page: Page, args: { eventTitle: string }) => { const eventTitle = args.eventTitle; await createNewEventType(page, { eventTitle }); diff --git a/apps/web/playwright/managed-event-types.e2e.ts b/apps/web/playwright/managed-event-types.e2e.ts index 13d0c6b485..96d35bc47a 100644 --- a/apps/web/playwright/managed-event-types.e2e.ts +++ b/apps/web/playwright/managed-event-types.e2e.ts @@ -1,46 +1,19 @@ import type { Locator, Page } from "@playwright/test"; import { expect } from "@playwright/test"; -import { SchedulingType } from "@calcom/prisma/enums"; - -import { test, type Fixtures } from "./lib/fixtures"; +import { test } from "./lib/fixtures"; import { bookTimeSlot, localize, submitAndWaitForResponse, selectFirstAvailableTimeSlotNextMonth, + setupManagedEvent, } from "./lib/testUtils"; test.afterEach(async ({ users }) => { await users.deleteAll(); }); -/** So we can test different areas in parallel and avoiding the creation flow each time */ -async function setupManagedEvent({ - users, - unlockedFields, -}: { - users: Fixtures["users"]; - unlockedFields?: Record; -}) { - const teamMateName = "teammate-1"; - const teamEventTitle = "Managed"; - const adminUser = await users.create(null, { - hasTeam: true, - teammates: [{ name: teamMateName }], - teamEventTitle, - teamEventSlug: "managed", - schedulingType: "MANAGED", - addManagedEventToTeamMates: true, - managedEventUnlockedFields: unlockedFields, - }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const memberUser = users.get().find((u) => u.name === teamMateName)!; - const { team } = await adminUser.getFirstTeamMembership(); - const managedEvent = await adminUser.getFirstTeamEvent(team.id, SchedulingType.MANAGED); - return { adminUser, memberUser, managedEvent, teamMateName, teamEventTitle, teamId: team.id }; -} - /** Short hand to get elements by translation key */ const getByKey = async (page: Page, key: string) => page.getByText((await localize("en"))(key)); diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts index 986b1914c5..89bd95fdce 100644 --- a/packages/lib/server/repository/booking.ts +++ b/packages/lib/server/repository/booking.ts @@ -337,6 +337,23 @@ export class BookingRepository { }, }, }, + { + eventType: { + parent: { + team: { + members: { + some: { + userId, + accepted: true, + role: { + in: ["ADMIN", "OWNER"], + }, + }, + }, + }, + }, + }, + }, ], }, });