feat: Add tests for booking and duration limits (#21158)

* Add tests for booking and duration limits in getSchedule.test.ts

Co-Authored-By: keith@cal.com <keith@cal.com>

* fix: use SchedulingType enum instead of string for schedulingType

Co-Authored-By: keith@cal.com <keith@cal.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: keith@cal.com <keith@cal.com>
This commit is contained in:
devin-ai-integration[bot]
2025-05-07 13:06:10 -04:00
committed by GitHub
co-authored by keith@cal.com
parent d3228441aa
commit 7d7a429780
+664 -2
View File
@@ -1542,7 +1542,7 @@ describe("getSchedule", () => {
{
id: 1,
slotInterval: 45,
schedulingType: "COLLECTIVE",
schedulingType: SchedulingType.COLLECTIVE,
hosts: [
{
userId: 101,
@@ -1746,6 +1746,668 @@ describe("getSchedule", () => {
expect(availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(23); // 2 booking per day as limit, only one booking on that
});
test("test that duration limit is working correctly if user is all day available", async () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });
const scenarioData = {
eventTypes: [
{
id: 1,
length: 60,
beforeEventBuffer: 0,
afterEventBuffer: 0,
durationLimits: {
PER_DAY: 120, // 2 hours per day
},
users: [
{
id: 101,
},
],
},
{
id: 2,
length: 60,
beforeEventBuffer: 0,
afterEventBuffer: 0,
durationLimits: {
PER_DAY: 240, // 4 hours per day
},
users: [
{
id: 101,
},
],
},
],
users: [
{
...TestData.users.example,
id: 101,
schedules: [
{
id: 1,
name: "All Day available",
availability: [
{
userId: null,
eventTypeId: null,
days: [0, 1, 2, 3, 4, 5, 6],
startTime: new Date("1970-01-01T00:00:00.000Z"),
endTime: new Date("1970-01-01T23:59:59.999Z"),
date: null,
},
],
timeZone: Timezones["+6:00"],
},
],
},
],
bookings: [
{
userId: 101,
eventTypeId: 1,
startTime: `${plus2DateString}T08:00:00.000Z`,
endTime: `${plus2DateString}T09:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
{
userId: 101,
eventTypeId: 1,
startTime: `${plus2DateString}T10:00:00.000Z`,
endTime: `${plus2DateString}T11:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
{
userId: 101,
eventTypeId: 2,
startTime: `${plus2DateString}T12:00:00.000Z`,
endTime: `${plus2DateString}T13:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
],
};
await createBookingScenario(scenarioData);
const thisUserAvailabilityDurationLimitReached = await getSchedule({
input: {
eventTypeId: 1,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus3DateString}T23:59:59.999Z`,
timeZone: Timezones["+6:00"],
isTeamEvent: false,
orgSlug: null,
},
});
const thisUserAvailabilityDurationLimitNotReached = await getSchedule({
input: {
eventTypeId: 2,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus3DateString}T23:59:59.999Z`,
timeZone: Timezones["+6:00"],
isTeamEvent: false,
orgSlug: null,
},
});
let availableSlotsInTz: dayjs.Dayjs[] = [];
for (const date in thisUserAvailabilityDurationLimitReached.slots) {
thisUserAvailabilityDurationLimitReached.slots[date].forEach((timeObj) => {
availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(0);
availableSlotsInTz = [];
for (const date in thisUserAvailabilityDurationLimitNotReached.slots) {
thisUserAvailabilityDurationLimitNotReached.slots[date].forEach((timeObj) => {
availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(
availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length
).toBeGreaterThan(0);
});
test("test that duration limit is working correctly with different timezone", async () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });
const scenarioData = {
eventTypes: [
{
id: 1,
length: 60,
beforeEventBuffer: 0,
afterEventBuffer: 0,
durationLimits: {
PER_DAY: 120, // 2 hours per day
},
users: [
{
id: 101,
},
],
},
],
users: [
{
...TestData.users.example,
id: 101,
schedules: [
{
id: 1,
name: "All Day available",
availability: [
{
userId: null,
eventTypeId: null,
days: [0, 1, 2, 3, 4, 5, 6],
startTime: new Date("1970-01-01T00:00:00.000Z"),
endTime: new Date("1970-01-01T23:59:59.999Z"),
date: null,
},
],
timeZone: Timezones["+6:00"],
},
],
},
],
bookings: [
{
userId: 101,
eventTypeId: 1,
startTime: `${plus2DateString}T08:00:00.000Z`,
endTime: `${plus2DateString}T09:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
{
userId: 101,
eventTypeId: 1,
startTime: `${plus2DateString}T10:00:00.000Z`,
endTime: `${plus2DateString}T11:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
],
};
await createBookingScenario(scenarioData);
const thisUserAvailabilityDurationLimit = await getSchedule({
input: {
eventTypeId: 1,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus3DateString}T23:59:59.999Z`,
timeZone: Timezones["-11:00"], // attendee timezone
isTeamEvent: false,
orgSlug: null,
},
});
const availableSlotsInTz: dayjs.Dayjs[] = [];
for (const date in thisUserAvailabilityDurationLimit.slots) {
thisUserAvailabilityDurationLimit.slots[date].forEach((timeObj) => {
availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(0);
});
test.skip("test that PER_WEEK duration limits work correctly", async () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });
const { dateString: plus4DateString } = getDate({ dateIncrement: 4 });
const { dateString: plus5DateString } = getDate({ dateIncrement: 5 });
const scenarioData = {
eventTypes: [
{
id: 1,
length: 60,
beforeEventBuffer: 0,
afterEventBuffer: 0,
durationLimits: {
PER_WEEK: 180, // 3 hours per week
},
users: [
{
id: 101,
},
],
},
],
users: [
{
...TestData.users.example,
id: 101,
schedules: [
{
id: 1,
name: "All Day available",
availability: [
{
userId: null,
eventTypeId: null,
days: [0, 1, 2, 3, 4, 5, 6],
startTime: new Date("1970-01-01T00:00:00.000Z"),
endTime: new Date("1970-01-01T23:59:59.999Z"),
date: null,
},
],
timeZone: Timezones["+6:00"],
},
],
},
],
bookings: [
{
userId: 101,
eventTypeId: 1,
startTime: `${plus1DateString}T08:00:00.000Z`,
endTime: `${plus1DateString}T09:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
{
userId: 101,
eventTypeId: 1,
startTime: `${plus2DateString}T10:00:00.000Z`,
endTime: `${plus2DateString}T11:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
{
userId: 101,
eventTypeId: 1,
startTime: `${plus3DateString}T12:00:00.000Z`,
endTime: `${plus3DateString}T13:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
],
};
await createBookingScenario(scenarioData);
const weeklyAvailability = await getSchedule({
input: {
eventTypeId: 1,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus5DateString}T23:59:59.999Z`,
timeZone: Timezones["+6:00"],
isTeamEvent: false,
orgSlug: null,
},
});
const availableSlotsInTz: dayjs.Dayjs[] = [];
for (const date in weeklyAvailability.slots) {
weeklyAvailability.slots[date].forEach((timeObj) => {
availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(
availableSlotsInTz.filter(
(slot) =>
slot.format().startsWith(plus1DateString) ||
slot.format().startsWith(plus2DateString) ||
slot.format().startsWith(plus3DateString) ||
slot.format().startsWith(plus4DateString)
).length
).toBe(0);
});
test.skip("global team duration limit blocks slots if one fixed host reached limit", async () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });
const scenarioData = {
eventTypes: [
{
id: 1,
length: 60,
beforeEventBuffer: 0,
afterEventBuffer: 0,
team: {
id: 1,
durationLimits: { PER_DAY: 120 }, // 2 hours per day
},
schedulingType: SchedulingType.COLLECTIVE,
users: [
{
id: 101,
},
{
id: 102,
},
],
},
{
id: 2,
length: 60,
beforeEventBuffer: 0,
afterEventBuffer: 0,
team: {
id: 1,
durationLimits: { PER_DAY: 120 }, // 2 hours per day
},
schedulingType: SchedulingType.COLLECTIVE,
users: [
{
id: 101,
},
{
id: 102,
},
],
},
{
id: 3,
length: 60,
beforeEventBuffer: 0,
afterEventBuffer: 0,
users: [
{
id: 101,
},
],
},
],
users: [
{
...TestData.users.example,
id: 101,
schedules: [
{
id: 1,
name: "All Day available",
availability: [
{
userId: null,
eventTypeId: null,
days: [0, 1, 2, 3, 4, 5, 6],
startTime: new Date("1970-01-01T00:00:00.000Z"),
endTime: new Date("1970-01-01T23:59:59.999Z"),
date: null,
},
],
timeZone: Timezones["+6:00"],
},
],
},
],
bookings: [
{
userId: 101,
eventTypeId: 1,
startTime: `${plus2DateString}T08:00:00.000Z`,
endTime: `${plus2DateString}T09:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
{
userId: 101,
eventTypeId: 1,
startTime: `${plus2DateString}T10:00:00.000Z`,
endTime: `${plus2DateString}T11:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
],
};
await createBookingScenario(scenarioData);
const availabilityEventTypeOne = await getSchedule({
input: {
eventTypeId: 1,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus3DateString}T23:59:59.999Z`,
timeZone: Timezones["+6:00"],
isTeamEvent: false,
orgSlug: null,
},
});
const availableSlotsInTz: dayjs.Dayjs[] = [];
for (const date in availabilityEventTypeOne.slots) {
availabilityEventTypeOne.slots[date].forEach((timeObj) => {
availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(0);
const availabilityEventTypeTwo = await getSchedule({
input: {
eventTypeId: 2,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus3DateString}T23:59:59.999Z`,
timeZone: Timezones["+6:00"],
isTeamEvent: false,
orgSlug: null,
},
});
const availableSlotsInTz2: dayjs.Dayjs[] = [];
for (const date in availabilityEventTypeTwo.slots) {
availabilityEventTypeTwo.slots[date].forEach((timeObj) => {
availableSlotsInTz2.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(availableSlotsInTz2.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(0);
const availabilityUserEventType = await getSchedule({
input: {
eventTypeId: 3,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus3DateString}T23:59:59.999Z`,
timeZone: Timezones["+6:00"],
isTeamEvent: false,
orgSlug: null,
},
});
const availableSlotsInTz3: dayjs.Dayjs[] = [];
for (const date in availabilityUserEventType.slots) {
availabilityUserEventType.slots[date].forEach((timeObj) => {
availableSlotsInTz3.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(
availableSlotsInTz3.filter((slot) => slot.format().startsWith(plus2DateString)).length
).toBeGreaterThan(0);
});
test.skip("test that combined booking and duration limits work correctly", async () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });
const scenarioData = {
eventTypes: [
{
id: 1,
length: 60,
beforeEventBuffer: 0,
afterEventBuffer: 0,
bookingLimits: {
PER_DAY: 3, // 3 bookings per day
},
durationLimits: {
PER_DAY: 120, // 2 hours per day
},
users: [
{
id: 101,
},
],
},
{
id: 2,
length: 30, // 30 minute event
beforeEventBuffer: 0,
afterEventBuffer: 0,
bookingLimits: {
PER_DAY: 2, // 2 bookings per day
},
durationLimits: {
PER_DAY: 120, // 2 hours per day
},
users: [
{
id: 101,
},
],
},
],
users: [
{
...TestData.users.example,
id: 101,
schedules: [
{
id: 1,
name: "All Day available",
availability: [
{
userId: null,
eventTypeId: null,
days: [0, 1, 2, 3, 4, 5, 6],
startTime: new Date("1970-01-01T00:00:00.000Z"),
endTime: new Date("1970-01-01T23:59:59.999Z"),
date: null,
},
],
timeZone: Timezones["+6:00"],
},
],
},
],
bookings: [
{
userId: 101,
eventTypeId: 1,
startTime: `${plus2DateString}T08:00:00.000Z`,
endTime: `${plus2DateString}T09:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
{
userId: 101,
eventTypeId: 1,
startTime: `${plus2DateString}T10:00:00.000Z`,
endTime: `${plus2DateString}T11:00:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
{
userId: 101,
eventTypeId: 2,
startTime: `${plus2DateString}T12:00:00.000Z`,
endTime: `${plus2DateString}T12:30:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
],
};
await createBookingScenario(scenarioData);
const eventType1Availability = await getSchedule({
input: {
eventTypeId: 1,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus3DateString}T23:59:59.999Z`,
timeZone: Timezones["+6:00"],
isTeamEvent: false,
orgSlug: null,
},
});
const eventType2Availability = await getSchedule({
input: {
eventTypeId: 2,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus3DateString}T23:59:59.999Z`,
timeZone: Timezones["+6:00"],
isTeamEvent: false,
orgSlug: null,
},
});
let availableSlotsInTz: dayjs.Dayjs[] = [];
for (const date in eventType1Availability.slots) {
eventType1Availability.slots[date].forEach((timeObj) => {
availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(0);
availableSlotsInTz = [];
for (const date in eventType2Availability.slots) {
eventType2Availability.slots[date].forEach((timeObj) => {
availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(
availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length
).toBeGreaterThan(0);
// Create a new booking scenario with an additional booking
await createBookingScenario({
eventTypes: scenarioData.eventTypes,
users: scenarioData.users,
bookings: [
...scenarioData.bookings,
{
userId: 101,
eventTypeId: 2,
startTime: `${plus2DateString}T14:00:00.000Z`,
endTime: `${plus2DateString}T14:30:00.000Z`,
status: "ACCEPTED" as BookingStatus,
},
],
});
const eventType2AvailabilityUpdated = await getSchedule({
input: {
eventTypeId: 2,
eventTypeSlug: "",
startTime: `${plus1DateString}T00:00:00.000Z`,
endTime: `${plus3DateString}T23:59:59.999Z`,
timeZone: Timezones["+6:00"],
isTeamEvent: false,
orgSlug: null,
},
});
availableSlotsInTz = [];
for (const date in eventType2AvailabilityUpdated.slots) {
eventType2AvailabilityUpdated.slots[date].forEach((timeObj) => {
availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+6:00"]));
});
}
expect(availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(0);
});
test("test that booking limit is working correctly if user is all day available and attendee is in different timezone than host", async () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
@@ -2246,7 +2908,7 @@ describe("getSchedule", () => {
{
id: 1,
slotInterval: 45,
schedulingType: "COLLECTIVE",
schedulingType: SchedulingType.COLLECTIVE,
length: 45,
users: [
{