2556 lines
80 KiB
TypeScript
2556 lines
80 KiB
TypeScript
import prismaMock from "../../../../../../tests/libs/__mocks__/prisma";
|
|
|
|
import {
|
|
getBooker,
|
|
TestData,
|
|
getOrganizer,
|
|
createBookingScenario,
|
|
getGoogleCalendarCredential,
|
|
Timezones,
|
|
getScenarioData,
|
|
mockSuccessfulVideoMeetingCreation,
|
|
BookingLocations,
|
|
getDate,
|
|
getMockBookingAttendee,
|
|
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
|
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
|
import { getMockRequestDataForBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking";
|
|
import { getMockRequestDataForCancelBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForCancelBooking";
|
|
import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown";
|
|
|
|
import { describe, test, vi, expect } from "vitest";
|
|
|
|
import { appStoreMetadata } from "@calcom/app-store/apps.metadata.generated";
|
|
import { ErrorCode } from "@calcom/lib/errorCodes";
|
|
import { SchedulingType } from "@calcom/prisma/enums";
|
|
import { BookingStatus } from "@calcom/prisma/enums";
|
|
|
|
import * as handleSeatsModule from "../handleSeats";
|
|
|
|
describe("handleSeats", () => {
|
|
setupAndTeardown();
|
|
|
|
describe("Correct parameters being passed into handleSeats from handleNewBooking", () => {
|
|
vi.mock("./handleSeats");
|
|
test("On new booking handleSeats is not called", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
const spy = vi.spyOn(handleSeatsModule, "default");
|
|
|
|
const booker = getBooker({
|
|
email: "booker@example.com",
|
|
name: "Booker",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
await handleNewBooking(req);
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("handleSeats is called when a new attendee is added", async () => {
|
|
const spy = vi.spyOn(handleSeatsModule, "default");
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "booker@example.com",
|
|
name: "Booker",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const bookingId = 1;
|
|
const bookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
const bookingScenario = await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: bookingStartTime,
|
|
endTime: bookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
bookingUid: bookingUid,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
const createdBooking = await handleNewBooking(req);
|
|
|
|
expect(createdBooking.metadata).toHaveProperty("videoCallUrl");
|
|
|
|
const handleSeatsCall = spy.mock.calls[0][0];
|
|
|
|
expect(handleSeatsCall).toEqual(
|
|
expect.objectContaining({
|
|
bookerEmail: booker.email,
|
|
reqBodyUser: reqBookingUser,
|
|
tAttendees: expect.any(Function),
|
|
additionalNotes: expect.anything(),
|
|
noEmail: undefined,
|
|
})
|
|
);
|
|
|
|
const bookingScenarioEventType = bookingScenario.eventTypes[0];
|
|
expect(handleSeatsCall.eventTypeInfo).toEqual(
|
|
expect.objectContaining({
|
|
eventTitle: bookingScenarioEventType.title,
|
|
eventDescription: bookingScenarioEventType.description,
|
|
length: bookingScenarioEventType.length,
|
|
})
|
|
);
|
|
|
|
expect(handleSeatsCall.eventType).toEqual(
|
|
expect.objectContaining({
|
|
id: bookingScenarioEventType.id,
|
|
slug: bookingScenarioEventType.slug,
|
|
workflows: bookingScenarioEventType.workflows,
|
|
seatsPerTimeSlot: bookingScenarioEventType.seatsPerTimeSlot,
|
|
seatsShowAttendees: bookingScenarioEventType.seatsShowAttendees,
|
|
})
|
|
);
|
|
|
|
expect(handleSeatsCall.evt).toEqual(
|
|
expect.objectContaining({
|
|
startTime: bookingStartTime,
|
|
})
|
|
);
|
|
|
|
expect(handleSeatsCall.invitee).toEqual([
|
|
expect.objectContaining({
|
|
email: booker.email,
|
|
name: booker.name,
|
|
}),
|
|
]);
|
|
});
|
|
|
|
test("handleSeats is called on rescheduling a seated event", async () => {
|
|
const spy = vi.spyOn(handleSeatsModule, "default");
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "booker@example.com",
|
|
name: "Booker",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const bookingId = 1;
|
|
const bookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
const bookingScenario = await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: bookingStartTime,
|
|
endTime: bookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
rescheduleUid: bookingUid,
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
bookingUid: bookingUid,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
await handleNewBooking(req);
|
|
|
|
const handleSeatsCall = spy.mock.calls[0][0];
|
|
|
|
expect(handleSeatsCall).toEqual(
|
|
expect.objectContaining({
|
|
rescheduleUid: bookingUid,
|
|
bookerEmail: booker.email,
|
|
reqBodyUser: reqBookingUser,
|
|
tAttendees: expect.any(Function),
|
|
additionalNotes: expect.anything(),
|
|
noEmail: undefined,
|
|
})
|
|
);
|
|
|
|
const bookingScenarioEventType = bookingScenario.eventTypes[0];
|
|
expect(handleSeatsCall.eventTypeInfo).toEqual(
|
|
expect.objectContaining({
|
|
eventTitle: bookingScenarioEventType.title,
|
|
eventDescription: bookingScenarioEventType.description,
|
|
length: bookingScenarioEventType.length,
|
|
})
|
|
);
|
|
|
|
expect(handleSeatsCall.eventType).toEqual(
|
|
expect.objectContaining({
|
|
id: bookingScenarioEventType.id,
|
|
slug: bookingScenarioEventType.slug,
|
|
workflows: bookingScenarioEventType.workflows,
|
|
seatsPerTimeSlot: bookingScenarioEventType.seatsPerTimeSlot,
|
|
seatsShowAttendees: bookingScenarioEventType.seatsShowAttendees,
|
|
})
|
|
);
|
|
|
|
expect(handleSeatsCall.evt).toEqual(
|
|
expect.objectContaining({
|
|
startTime: bookingStartTime,
|
|
})
|
|
);
|
|
|
|
expect(handleSeatsCall.invitee).toEqual([
|
|
expect.objectContaining({
|
|
email: booker.email,
|
|
name: booker.name,
|
|
}),
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("As an attendee", () => {
|
|
describe("Creating a new booking", () => {
|
|
test("Attendee should be added to existing seated event", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "seat2@example.com",
|
|
name: "Seat 2",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const bookingId = 1;
|
|
const bookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: bookingStartTime,
|
|
endTime: bookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
bookingUid: bookingUid,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
await handleNewBooking(req);
|
|
|
|
const newAttendee = await prismaMock.attendee.findFirst({
|
|
where: {
|
|
email: booker.email,
|
|
bookingId: bookingId,
|
|
},
|
|
include: {
|
|
bookingSeat: true,
|
|
},
|
|
});
|
|
|
|
// Check for the existence of the new attendee w/ booking seat
|
|
expect(newAttendee?.bookingSeat).toEqual(
|
|
expect.objectContaining({
|
|
referenceUid: expect.any(String),
|
|
data: expect.any(Object),
|
|
bookingId: bookingId,
|
|
})
|
|
);
|
|
});
|
|
|
|
// Testing in case of a wave of people book a time slot at the same time
|
|
test("Attendee should be added to existing seated event when bookingUid is not present", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "seat2@example.com",
|
|
name: "Seat 2",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const bookingId = 1;
|
|
const bookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: bookingStartTime,
|
|
endTime: bookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
await handleNewBooking(req);
|
|
|
|
const newAttendee = await prismaMock.attendee.findFirst({
|
|
where: {
|
|
email: booker.email,
|
|
bookingId: bookingId,
|
|
},
|
|
include: {
|
|
bookingSeat: true,
|
|
},
|
|
});
|
|
|
|
// Check for the existence of the new attendee w/ booking seat
|
|
expect(newAttendee?.bookingSeat).toEqual(
|
|
expect.objectContaining({
|
|
referenceUid: expect.any(String),
|
|
data: expect.any(Object),
|
|
bookingId: bookingId,
|
|
})
|
|
);
|
|
});
|
|
|
|
test("If attendee is already a part of the booking then throw an error", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "seat1@example.com",
|
|
name: "Seat 1",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const bookingId = 1;
|
|
const bookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: bookingStartTime,
|
|
endTime: bookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@example.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
bookingUid: bookingUid,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
await expect(() => handleNewBooking(req)).rejects.toThrowError(ErrorCode.AlreadySignedUpForBooking);
|
|
});
|
|
|
|
test("If event is already full, fail", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "seat3@example.com",
|
|
name: "Seat 3",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const bookingId = 1;
|
|
const bookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 2,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: bookingStartTime,
|
|
endTime: bookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: 2,
|
|
name: "Seat 2",
|
|
email: "seat2@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-2",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
bookingUid: bookingUid,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
await expect(() => handleNewBooking(req)).rejects.toThrowError(ErrorCode.BookingSeatsFull);
|
|
});
|
|
|
|
test("Verify Seat Availability Calculation Based on Booked Seats, Not Total Attendees", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "seat2@example.com",
|
|
name: "Seat 2",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
defaultScheduleId: null,
|
|
schedules: [TestData.schedules.IstMorningShift],
|
|
credentials: [getGoogleCalendarCredential()],
|
|
selectedCalendars: [TestData.selectedCalendars.google],
|
|
destinationCalendar: {
|
|
integration: TestData.apps["google-calendar"].type,
|
|
externalId: "organizer@google-calendar.com",
|
|
},
|
|
});
|
|
|
|
const otherTeamMembers = [
|
|
{
|
|
name: "Other Team Member 1",
|
|
username: "other-team-member-1",
|
|
timeZone: Timezones["+5:30"],
|
|
defaultScheduleId: null,
|
|
email: "other-team-member-1@example.com",
|
|
id: 102,
|
|
schedules: [TestData.schedules.IstEveningShift],
|
|
credentials: [getGoogleCalendarCredential()],
|
|
selectedCalendars: [TestData.selectedCalendars.google],
|
|
destinationCalendar: {
|
|
integration: TestData.apps["google-calendar"].type,
|
|
externalId: "other-team-member-1@google-calendar.com",
|
|
},
|
|
},
|
|
];
|
|
|
|
const bookingId = 1;
|
|
const bookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "collective-team-seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
schedulingType: SchedulingType.COLLECTIVE,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
{
|
|
id: 102,
|
|
},
|
|
],
|
|
destinationCalendar: {
|
|
integration: TestData.apps["google-calendar"].type,
|
|
externalId: "event-type-1@google-calendar.com",
|
|
},
|
|
seatsPerTimeSlot: 2,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: bookingStartTime,
|
|
endTime: bookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Other Team Member 1",
|
|
email: "other-team-member-1@example.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: 2,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
usersApartFromOrganizer: otherTeamMembers,
|
|
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
bookingUid: bookingUid,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
await handleNewBooking(req);
|
|
|
|
const newAttendee = await prismaMock.attendee.findFirst({
|
|
where: {
|
|
email: booker.email,
|
|
bookingId: bookingId,
|
|
},
|
|
include: {
|
|
bookingSeat: true,
|
|
},
|
|
});
|
|
|
|
// Check for the existence of the new attendee with booking seat
|
|
expect(newAttendee?.bookingSeat).toEqual(
|
|
expect.objectContaining({
|
|
referenceUid: expect.any(String),
|
|
data: expect.any(Object),
|
|
bookingId: bookingId,
|
|
})
|
|
);
|
|
|
|
// Verify that the booking seat count is now 2 out of 2
|
|
const bookingSeatCount = await prismaMock.bookingSeat.count({
|
|
where: {
|
|
bookingId: bookingId,
|
|
},
|
|
});
|
|
|
|
expect(bookingSeatCount).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("Rescheduling a booking", () => {
|
|
test("When rescheduling to an existing booking, move attendee", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const attendeeToReschedule = getMockBookingAttendee({
|
|
id: 2,
|
|
name: "Seat 2",
|
|
email: "seat2@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-2",
|
|
data: {},
|
|
},
|
|
});
|
|
|
|
const booker = getBooker({
|
|
email: attendeeToReschedule.email,
|
|
name: attendeeToReschedule.name,
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const firstBookingId = 1;
|
|
const firstBookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const firstBookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const firstBookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
const secondBookingId = 2;
|
|
const secondBookingUid = "def456";
|
|
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
|
const secondBookingStartTime = `${plus2DateString}T04:00:00Z`;
|
|
const secondBookingEndTime = `${plus2DateString}T04:30:00Z`;
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: firstBookingId,
|
|
uid: firstBookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: firstBookingStartTime,
|
|
endTime: firstBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
attendeeToReschedule,
|
|
],
|
|
},
|
|
{
|
|
id: secondBookingId,
|
|
uid: secondBookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: secondBookingStartTime,
|
|
endTime: secondBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 3,
|
|
name: "Seat 3",
|
|
email: "seat3@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-3",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
rescheduleUid: "booking-seat-2",
|
|
start: secondBookingStartTime,
|
|
end: secondBookingEndTime,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
await handleNewBooking(req);
|
|
|
|
// Ensure that the attendee is no longer a part of the old booking
|
|
const oldBookingAttendees = await prismaMock.attendee.findMany({
|
|
where: {
|
|
bookingId: firstBookingId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
expect(oldBookingAttendees).not.toContain({ id: attendeeToReschedule.id });
|
|
expect(oldBookingAttendees).toHaveLength(1);
|
|
|
|
// Ensure that the attendee is a part of the new booking
|
|
const newBookingAttendees = await prismaMock.attendee.findMany({
|
|
where: {
|
|
bookingId: secondBookingId,
|
|
},
|
|
select: {
|
|
email: true,
|
|
},
|
|
});
|
|
|
|
expect(newBookingAttendees).toContainEqual({ email: attendeeToReschedule.email });
|
|
expect(newBookingAttendees).toHaveLength(2);
|
|
|
|
// Ensure that the attendeeSeat is also updated to the new booking
|
|
const attendeeSeat = await prismaMock.bookingSeat.findFirst({
|
|
where: {
|
|
attendeeId: attendeeToReschedule.id,
|
|
},
|
|
select: {
|
|
bookingId: true,
|
|
},
|
|
});
|
|
|
|
expect(attendeeSeat?.bookingId).toEqual(secondBookingId);
|
|
});
|
|
|
|
test("When rescheduling to an empty timeslot, create a new booking", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const attendeeToReschedule = getMockBookingAttendee({
|
|
id: 2,
|
|
name: "Seat 2",
|
|
email: "seat2@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-2",
|
|
data: {},
|
|
},
|
|
});
|
|
|
|
const booker = getBooker({
|
|
email: attendeeToReschedule.email,
|
|
name: attendeeToReschedule.name,
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const firstBookingId = 1;
|
|
const firstBookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const firstBookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const firstBookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
|
const secondBookingStartTime = `${plus2DateString}T04:00:00Z`;
|
|
const secondBookingEndTime = `${plus2DateString}T04:30:00Z`;
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: firstBookingId,
|
|
uid: firstBookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: firstBookingStartTime,
|
|
endTime: firstBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
attendeeToReschedule,
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
rescheduleUid: "booking-seat-2",
|
|
start: secondBookingStartTime,
|
|
end: secondBookingEndTime,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
const createdBooking = await handleNewBooking(req);
|
|
|
|
// Ensure that the attendee is no longer a part of the old booking
|
|
const oldBookingAttendees = await prismaMock.attendee.findMany({
|
|
where: {
|
|
bookingId: firstBookingId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
expect(oldBookingAttendees).not.toContain({ id: attendeeToReschedule.id });
|
|
expect(oldBookingAttendees).toHaveLength(1);
|
|
|
|
expect(createdBooking.id).not.toEqual(firstBookingId);
|
|
|
|
// Ensure that the attendee and bookingSeat is also updated to the new booking
|
|
const attendee = await prismaMock.attendee.findFirst({
|
|
where: {
|
|
bookingId: createdBooking.id,
|
|
},
|
|
include: {
|
|
bookingSeat: true,
|
|
},
|
|
});
|
|
|
|
expect(attendee?.bookingSeat?.bookingId).toEqual(createdBooking.id);
|
|
});
|
|
|
|
test("When last attendee is rescheduled, delete old booking", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const attendeeToReschedule = getMockBookingAttendee({
|
|
id: 2,
|
|
name: "Seat 2",
|
|
email: "seat2@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-2",
|
|
data: {},
|
|
},
|
|
});
|
|
|
|
const booker = getBooker({
|
|
email: attendeeToReschedule.email,
|
|
name: attendeeToReschedule.name,
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const firstBookingId = 1;
|
|
const firstBookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const firstBookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const firstBookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
|
const secondBookingStartTime = `${plus2DateString}T04:00:00Z`;
|
|
const secondBookingEndTime = `${plus2DateString}T04:30:00Z`;
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: firstBookingId,
|
|
uid: firstBookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: firstBookingStartTime,
|
|
endTime: firstBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [attendeeToReschedule],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
rescheduleUid: "booking-seat-2",
|
|
start: secondBookingStartTime,
|
|
end: secondBookingEndTime,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
const createdBooking = await handleNewBooking(req);
|
|
|
|
// Ensure that the old booking is cancelled
|
|
const oldBooking = await prismaMock.booking.findFirst({
|
|
where: {
|
|
id: firstBookingId,
|
|
},
|
|
select: {
|
|
status: true,
|
|
},
|
|
});
|
|
|
|
expect(oldBooking?.status).toEqual(BookingStatus.CANCELLED);
|
|
|
|
// Ensure that the attendee and attendeeSeat is also updated to the new booking
|
|
const attendeeSeat = await prismaMock.attendee.findFirst({
|
|
where: {
|
|
bookingId: createdBooking.id,
|
|
},
|
|
include: {
|
|
bookingSeat: true,
|
|
},
|
|
});
|
|
|
|
expect(attendeeSeat?.bookingSeat?.bookingId).toEqual(createdBooking.id);
|
|
});
|
|
});
|
|
|
|
describe("Canceling a booking", async () => {
|
|
test("When canceling a booking, only remove that single attendee", async () => {
|
|
const handleCancelBooking = (await import("@calcom/features/bookings/lib/handleCancelBooking"))
|
|
.default;
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const bookingId = 1;
|
|
const bookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
const attendeeIdToBeCancelled = 2;
|
|
const bookingSeatToBeCancelledUid = "booking-seat-2";
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 4,
|
|
seatsShowAttendees: false,
|
|
owner: organizer.id,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
userId: organizer.id,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: bookingStartTime,
|
|
endTime: bookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: attendeeIdToBeCancelled,
|
|
name: "Seat 2",
|
|
email: "seat2@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: bookingSeatToBeCancelledUid,
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const mockCancelBookingData = getMockRequestDataForCancelBooking({
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
seatReferenceUid: bookingSeatToBeCancelledUid,
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockCancelBookingData,
|
|
});
|
|
|
|
req.userId = organizer.id;
|
|
|
|
await handleCancelBooking(req);
|
|
|
|
// Ensure that the booking has been cancelled
|
|
const cancelledBooking = await prismaMock.booking.findFirst({
|
|
where: {
|
|
id: bookingId,
|
|
},
|
|
select: {
|
|
status: true,
|
|
},
|
|
});
|
|
|
|
// Check that the booking is still accepted
|
|
expect(cancelledBooking?.status).toEqual(BookingStatus.ACCEPTED);
|
|
|
|
// Check that the booking does not contain the cancelled attendee
|
|
const attendees = await prismaMock.attendee.findMany({
|
|
where: {
|
|
bookingId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
expect(attendees).not.toContain({ id: attendeeIdToBeCancelled });
|
|
|
|
const bookingSeats = await prismaMock.bookingSeat.findMany({
|
|
where: {
|
|
bookingId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
expect(bookingSeats).not.toContain({ referenceUid: bookingSeatToBeCancelledUid });
|
|
});
|
|
|
|
test("When last attendee cancels a booking, delete event", async () => {
|
|
const handleCancelBooking = (await import("@calcom/features/bookings/lib/handleCancelBooking"))
|
|
.default;
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const bookingId = 1;
|
|
const bookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
const attendeeIdToBeCancelled = 1;
|
|
const bookingSeatToBeCancelledUid = "booking-seat-1";
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 4,
|
|
seatsShowAttendees: false,
|
|
owner: organizer.id,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
userId: organizer.id,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: bookingStartTime,
|
|
endTime: bookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: attendeeIdToBeCancelled,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: bookingSeatToBeCancelledUid,
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const mockCancelBookingData = getMockRequestDataForCancelBooking({
|
|
id: bookingId,
|
|
uid: bookingUid,
|
|
seatReferenceUid: bookingSeatToBeCancelledUid,
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockCancelBookingData,
|
|
});
|
|
|
|
req.userId = organizer.id;
|
|
|
|
await handleCancelBooking(req);
|
|
|
|
// Ensure that the booking has been cancelled
|
|
const cancelledBooking = await prismaMock.booking.findFirst({
|
|
where: {
|
|
id: bookingId,
|
|
},
|
|
select: {
|
|
status: true,
|
|
},
|
|
});
|
|
|
|
// Check that the booking is still accepted
|
|
expect(cancelledBooking?.status).toEqual(BookingStatus.CANCELLED);
|
|
|
|
// Check that the booking does not contain the cancelled attendee
|
|
const attendees = await prismaMock.attendee.findMany({
|
|
where: {
|
|
bookingId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
expect(attendees).not.toContain({ id: attendeeIdToBeCancelled });
|
|
|
|
const bookingSeats = await prismaMock.bookingSeat.findMany({
|
|
where: {
|
|
bookingId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
expect(bookingSeats).not.toContain({ referenceUid: bookingSeatToBeCancelledUid });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("As an owner", () => {
|
|
describe("Rescheduling a booking", () => {
|
|
test("When rescheduling to new timeslot, ensure all attendees are moved", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "booker@example.com",
|
|
name: "Booker",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const firstBookingId = 1;
|
|
const firstBookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const firstBookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const firstBookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
|
const secondBookingStartTime = `${plus2DateString}T04:00:00Z`;
|
|
const secondBookingEndTime = `${plus2DateString}T04:30:00Z`;
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: firstBookingId,
|
|
uid: firstBookingUid,
|
|
eventTypeId: 1,
|
|
userId: organizer.id,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: firstBookingStartTime,
|
|
endTime: firstBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: 2,
|
|
name: "Seat 2",
|
|
email: "seat2@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-2",
|
|
data: {},
|
|
},
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: 3,
|
|
name: "Seat 3",
|
|
email: "seat3@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-3",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
rescheduleUid: firstBookingUid,
|
|
start: secondBookingStartTime,
|
|
end: secondBookingEndTime,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
req.userId = organizer.id;
|
|
|
|
const rescheduledBooking = await handleNewBooking(req);
|
|
|
|
// Ensure that the booking has been moved
|
|
expect(rescheduledBooking?.startTime).toEqual(secondBookingStartTime);
|
|
expect(rescheduledBooking?.endTime).toEqual(secondBookingEndTime);
|
|
|
|
// Ensure that the attendees are still a part of the event
|
|
const attendees = await prismaMock.attendee.findMany({
|
|
where: {
|
|
bookingId: rescheduledBooking?.id,
|
|
},
|
|
});
|
|
|
|
expect(attendees).toHaveLength(3);
|
|
|
|
// Ensure that the bookingSeats are still a part of the event
|
|
const bookingSeats = await prismaMock.bookingSeat.findMany({
|
|
where: {
|
|
bookingId: rescheduledBooking?.id,
|
|
},
|
|
});
|
|
|
|
expect(bookingSeats).toHaveLength(3);
|
|
});
|
|
|
|
test("When rescheduling to existing booking, merge attendees ", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "booker@example.com",
|
|
name: "Booker",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const firstBookingId = 1;
|
|
const firstBookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const firstBookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const firstBookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
const secondBookingId = 2;
|
|
const secondBookingUid = "def456";
|
|
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
|
const secondBookingStartTime = `${plus2DateString}T04:00:00Z`;
|
|
const secondBookingEndTime = `${plus2DateString}T04:30:00Z`;
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 4,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: firstBookingId,
|
|
uid: firstBookingUid,
|
|
eventTypeId: 1,
|
|
userId: organizer.id,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: firstBookingStartTime,
|
|
endTime: firstBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: 2,
|
|
name: "Seat 2",
|
|
email: "seat2@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-2",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
id: secondBookingId,
|
|
uid: secondBookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: secondBookingStartTime,
|
|
endTime: secondBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 3,
|
|
name: "Seat 3",
|
|
email: "seat3@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-3",
|
|
data: {},
|
|
},
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: 4,
|
|
name: "Seat 4",
|
|
email: "seat4@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-4",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
rescheduleUid: firstBookingUid,
|
|
start: secondBookingStartTime,
|
|
end: secondBookingEndTime,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
req.userId = organizer.id;
|
|
|
|
const rescheduledBooking = await handleNewBooking(req);
|
|
|
|
// Ensure that the booking has been moved
|
|
expect(rescheduledBooking?.startTime).toEqual(new Date(secondBookingStartTime));
|
|
expect(rescheduledBooking?.endTime).toEqual(new Date(secondBookingEndTime));
|
|
|
|
// Ensure that the attendees are still a part of the event
|
|
const attendees = await prismaMock.attendee.findMany({
|
|
where: {
|
|
bookingId: rescheduledBooking?.id,
|
|
},
|
|
});
|
|
|
|
expect(attendees).toHaveLength(4);
|
|
|
|
// Ensure that the bookingSeats are still a part of the event
|
|
const bookingSeats = await prismaMock.bookingSeat.findMany({
|
|
where: {
|
|
bookingId: rescheduledBooking?.id,
|
|
},
|
|
});
|
|
|
|
expect(bookingSeats).toHaveLength(4);
|
|
|
|
// Ensure that the previous booking has been canceled
|
|
const originalBooking = await prismaMock.booking.findFirst({
|
|
where: {
|
|
id: firstBookingId,
|
|
},
|
|
select: {
|
|
status: true,
|
|
},
|
|
});
|
|
|
|
expect(originalBooking?.status).toEqual(BookingStatus.CANCELLED);
|
|
});
|
|
test("When merging more attendees than seats, fail ", async () => {
|
|
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
|
|
|
const booker = getBooker({
|
|
email: "booker@example.com",
|
|
name: "Booker",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const firstBookingId = 1;
|
|
const firstBookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const firstBookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const firstBookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
const secondBookingId = 2;
|
|
const secondBookingUid = "def456";
|
|
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
|
const secondBookingStartTime = `${plus2DateString}T04:00:00Z`;
|
|
const secondBookingEndTime = `${plus2DateString}T04:30:00Z`;
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 3,
|
|
seatsShowAttendees: false,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: firstBookingId,
|
|
uid: firstBookingUid,
|
|
eventTypeId: 1,
|
|
userId: organizer.id,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: firstBookingStartTime,
|
|
endTime: firstBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: 2,
|
|
name: "Seat 2",
|
|
email: "seat2@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-2",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
id: secondBookingId,
|
|
uid: secondBookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: secondBookingStartTime,
|
|
endTime: secondBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 3,
|
|
name: "Seat 3",
|
|
email: "seat3@test.com",
|
|
locale: "en",
|
|
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-3",
|
|
data: {},
|
|
},
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: 4,
|
|
name: "Seat 4",
|
|
email: "seat4@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-4",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const reqBookingUser = "seatedAttendee";
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
rescheduleUid: firstBookingUid,
|
|
start: secondBookingStartTime,
|
|
end: secondBookingEndTime,
|
|
user: reqBookingUser,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
req.userId = organizer.id;
|
|
|
|
// const rescheduledBooking = await handleNewBooking(req);
|
|
await expect(() => handleNewBooking(req)).rejects.toThrowError(ErrorCode.NotEnoughAvailableSeats);
|
|
});
|
|
});
|
|
|
|
describe("Cancelling a booking", () => {
|
|
test("When owner cancels booking, cancel booking for all attendees", async () => {
|
|
const handleCancelBooking = (await import("@calcom/features/bookings/lib/handleCancelBooking"))
|
|
.default;
|
|
|
|
const booker = getBooker({
|
|
email: "booker@example.com",
|
|
name: "Booker",
|
|
});
|
|
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const firstBookingId = 1;
|
|
const firstBookingUid = "abc123";
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const firstBookingStartTime = `${plus1DateString}T04:00:00Z`;
|
|
const firstBookingEndTime = `${plus1DateString}T04:30:00Z`;
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slug: "seated-event",
|
|
slotInterval: 30,
|
|
length: 30,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
seatsPerTimeSlot: 4,
|
|
seatsShowAttendees: false,
|
|
owner: organizer.id,
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
id: firstBookingId,
|
|
uid: firstBookingUid,
|
|
eventTypeId: 1,
|
|
userId: organizer.id,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: firstBookingStartTime,
|
|
endTime: firstBookingEndTime,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 1,
|
|
name: "Seat 1",
|
|
email: "seat1@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-1",
|
|
data: {},
|
|
},
|
|
}),
|
|
getMockBookingAttendee({
|
|
id: 2,
|
|
name: "Seat 2",
|
|
email: "seat2@test.com",
|
|
locale: "en",
|
|
timeZone: "America/Toronto",
|
|
bookingSeat: {
|
|
referenceUid: "booking-seat-2",
|
|
data: {},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
organizer,
|
|
})
|
|
);
|
|
|
|
mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey: "dailyvideo",
|
|
videoMeetingData: {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
|
},
|
|
});
|
|
|
|
const mockBookingData = getMockRequestDataForBooking({
|
|
data: {
|
|
eventTypeId: 1,
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: BookingLocations.CalVideo },
|
|
},
|
|
rescheduleUid: firstBookingUid,
|
|
},
|
|
});
|
|
|
|
const { req } = createMockNextJsRequest({
|
|
method: "POST",
|
|
body: mockBookingData,
|
|
});
|
|
|
|
req.userId = organizer.id;
|
|
|
|
await handleCancelBooking(req);
|
|
|
|
// Ensure that the booking has been cancelled
|
|
const cancelledBooking = await prismaMock.booking.findFirst({
|
|
where: {
|
|
id: firstBookingId,
|
|
},
|
|
select: {
|
|
status: true,
|
|
},
|
|
});
|
|
|
|
expect(cancelledBooking?.status).toEqual(BookingStatus.CANCELLED);
|
|
});
|
|
});
|
|
});
|
|
});
|