Files
calendar/packages/lib/server/getLuckyUser.integration-test.ts
T
Hariom BalharaandGitHub e3bd90c4f2 fix: Handle calendar-cache with Delegation Credentials
Fixes CAL-5372

# Delegation Credentials with CalendarCache.

Following content is a snapshot of the [internal document](https://calendso.slack.com/docs/T08B8KA2BNF/F08L5JYU3V3)



**Problem-1 :** 

CalendarCache needs SelectedCalendar records to work but SelectedCalendar record is only created when a user connects their calendar and then enables some calendar for conflict checking. Because with Delegation, no manual connection is done by any of the members, we need a way to create SelectedCalendar records automatically.

**Problem-2**

CalendarCache connects to credential(regular credential) which doesn’t exist for Delegation Credential scenario. Also, DelegationCredential is common for all the members(different from Credential which is different for different members) of the organization and we need to identify to which user the CalendarCache belongs.  

**Solution for both problems**
- Create credential records for Delegation Credentials as well - Through Cron(new - we could schedule it every 5mins)
- Now create SelectedCalendar  records for those Credential records -  Through another Cron(new - we could schedule it every 5mins)
- Now CalendarCache records will automatically be created for those SelectedCalendar records -existing cron

## Fixed some Delegation Credentials bugs unrelated to calendar-cache
- If DestinationCalendar wasn't set(which is possible only with Delegation Credentials), then Google Meet wasn't used as a conferencing app - [Added a test]
- If no SelectedCalendar is there but Google Calendar connection exists(possible only with Delegation Credential) then we were not doing conflict checking. It is expected to not do it for Regular Credentials, but for Delegation Credential we must check for conflict in that case too [Added a test]
- Earlier if a user has Regular Credential as well as Delegation Credential for the same external id which is the member email(say member1@acme.com) then availability were retrieved twice because we weren't deduplicating credentials as it wasn't a trivial thing to do. Now that is being done.


**Env Variables:**
Note this PR doesn't introduce any new env variable. The existing env variable has been added to .env.example. But if this env variable isn't already set, it must be set.

`CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY={SAME_AS_SET_FOR_V2_API}`

**Deployment Plan:**
1. Add Observability for SelectedCalendar when _error_ field is set
2. Follow https://github.com/calcom/cal.com/blob/calendar-cache-dwd-support/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/delegation-credential/delegation-credential.md#setting-up-delegation-credential-for-google-calendar-api to enable Delegation Credential for i.cal.com
3. Note that to be able to see the option to enable Delegation Credential for an organization, you need to enable `teamFeature` and `feature` for `delegation-credential`

## Automation Tests
- Introduced tests for calendar-cache.repository.ts
   - Tests all methods of the repository
- Added more tests for handleNewBooking/delegation-credential flow.
   - Added test to verify the bug fix when no DestinationCalendar exists and Google Meet should be used still 
- Added more tests for Google Calendar/CalendarService targeting DelegationCredential
- Added more tests for getCalendarsEvents. 
    - To test the new logic of calling getAvailability still if there are no selectedCalendars in case of Delegation Credential
    - Also introduced tests for `getAvailabitlityWithTimezones` which was an existing function but now has some new changes.
- Added tests for deduplication logic in CalendarManager.ts

## How to Test
Enable Calendar Cache and Delegation Credential feature for acme org through `features` and `teamFeatures` tables.
- Enable Delegation Credential for acme org
- Enable atleast 1 calendar for conflict checking for one of the users(say owner1)
- Ensure GOOGLE_WEBHOOK_TOKEN is set in .env file
- Ensure GOOGLE_WEBHOOK_URL is set to ngrok url of webapp in .env file
- Hit cron endpoint `curl http://localhost:3000/api/calendar-cache/cron\?apiKey\={API_KEY}` that would cache the freebusy result for the selected calendars



Followup 
- https://github.com/calcom/cal.com/pull/20698
- https://github.com/calcom/cal.com/pull/18619/files#r2046795643
2025-04-28 18:11:29 -03:00

801 lines
26 KiB
TypeScript

import { describe, it, vi, expect, afterEach, beforeEach, beforeAll, afterAll } from "vitest";
import prisma from "@calcom/prisma";
import { getLuckyUser, getOrderedListOfLuckyUsers } from "./getLuckyUser";
let commonEventTypeId: number;
const userIds: number[] = [];
async function deleteUsers() {
await prisma.user.deleteMany({
where: {
id: {
in: userIds,
},
},
});
userIds.splice(0, userIds.length);
}
beforeAll(async () => {
const event = await prisma.eventType.create({
data: {
title: "Test Event",
slug: "test-event",
length: 15,
},
select: {
id: true,
},
});
commonEventTypeId = event.id;
});
beforeEach(() => {
// tell vitest we use mocked time
vi.useFakeTimers();
});
afterEach(async () => {
await deleteUsers();
vi.useRealTimers();
});
afterAll(async () => {
await prisma.eventType.delete({
where: {
id: commonEventTypeId,
},
});
});
type BookingPropsRelatedToLuckyUserAlgorithm = {
eventTypeId: number;
createdAt: Date;
noShowHost?: boolean | null;
};
type OptionalBookingProps = {
uid?: string;
title?: string;
startTime?: Date;
endTime?: Date;
attendees?: {
create: {
name: string;
email: string;
timeZone: string;
noShow?: boolean | null;
}[];
};
};
type UserProps = {
email: string;
createdDate?: Date;
};
const commonBookingData = {
startTime: new Date(),
endTime: new Date(),
};
const commonAttendeesData = [
{
name: "test-attendee",
email: "test-attendee@example.com",
timeZone: "Asia/Calcutta",
},
];
const createUserWithBookings = async ({
user: { email, createdDate },
bookings,
}: {
user: UserProps;
bookings: (BookingPropsRelatedToLuckyUserAlgorithm & OptionalBookingProps)[];
}) => {
const user = await prisma.user.create({
data: {
email,
createdDate,
bookings: {
create: bookings.map(({ eventTypeId, ...booking }, index) => ({
...commonBookingData,
attendees: {
create: commonAttendeesData,
},
uid: `uuid-${email}-booking${index + 1}`,
title: `${email} Booking ${index + 1}`,
eventTypeId,
...booking,
})),
},
},
include: {
bookings: true,
credentials: true,
},
});
userIds.push(user.id);
return user;
};
const createHostWithBookings = async ({
user: userData,
bookings,
priority = 2,
weight = null,
createdAt,
}: {
user: UserProps;
bookings: (BookingPropsRelatedToLuckyUserAlgorithm & OptionalBookingProps)[];
weight?: number | null;
priority?: number;
createdAt?: Date;
}) => {
const user = await createUserWithBookings({
user: {
createdDate: userData.createdDate || new Date(),
email: userData.email,
},
bookings,
});
const host = await prisma.host.create({
data: {
user: { connect: { id: user.id } },
eventType: { connect: { id: commonEventTypeId } },
weight,
priority,
createdAt: createdAt ?? new Date(),
},
include: {
user: {
include: {
bookings: true,
credentials: true,
},
},
},
});
return {
...host,
user: {
...host.user,
priority,
weight,
},
};
};
function expectLuckyUsers(luckyUsers: { email: string }[], expectedLuckyUsers: { email: string }[]) {
expect(luckyUsers.map((user) => user.email)).toEqual(expectedLuckyUsers.map((user) => user.email));
}
describe("getLuckyUser Integration tests", () => {
describe("should not consider no show bookings for round robin: ", () => {
it("When a host is no show, that is chosen when competing with another host that showed up for the booking", async () => {
const createOrganizerThatShowedUp = async (email: string) => {
return createHostWithBookings({
user: { email },
bookings: [{ eventTypeId: commonEventTypeId, createdAt: new Date("2022-01-25T05:30:00.000Z") }],
});
};
const createOrganizerThatDidntShowUp = async (email: string) => {
return createHostWithBookings({
user: { email },
bookings: [
{
eventTypeId: commonEventTypeId,
createdAt: new Date("2022-01-25T06:30:00.000Z"),
noShowHost: true,
},
],
});
};
const organizerHostThatShowedUp = await createOrganizerThatShowedUp("test-user1@example.com");
const organizerHostThatDidntShowUp = await createOrganizerThatDidntShowUp("test-user2@example.com");
const organizerThatShowedUp = organizerHostThatShowedUp.user;
const organizerThatDidntShowUp = organizerHostThatDidntShowUp.user;
const luckyUser = await getLuckyUser({
availableUsers: [organizerThatShowedUp, organizerThatDidntShowUp],
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled: false,
team: {},
},
allRRHosts: [],
routingFormResponse: null,
});
expect(luckyUser.email).toBe(organizerThatDidntShowUp.email);
});
it("When a attendee is a noShow for organizers booking, that organizer is competing with another host whose attendee showed up for the booking", async () => {
const organizerHostWhoseAttendeeShowedUp = await createHostWithBookings({
user: { email: "test-user1@example.com" },
bookings: [
{
eventTypeId: commonEventTypeId,
createdAt: new Date("2022-01-25T05:30:00.000Z"),
title: "Test User 1 Booking",
attendees: {
create: [
{
name: "test-attendee",
email: "test-attendee@example.com",
timeZone: "Asia/Calcutta",
},
],
},
},
],
});
const organizerWhoseAttendeeShowedUp = organizerHostWhoseAttendeeShowedUp.user;
const organizerHostWhoseAttendeeDidntShowUp = await createHostWithBookings({
user: { email: "test-user2@example.com" },
bookings: [
{
eventTypeId: commonEventTypeId,
createdAt: new Date("2022-01-25T06:30:00.000Z"),
attendees: {
create: [
{
noShow: true,
name: "test-attendee",
email: "test-attendee@example.com",
timeZone: "Asia/Calcutta",
},
],
},
},
],
});
const organizerWhoseAttendeeDidntShowUp = organizerHostWhoseAttendeeDidntShowUp.user;
expect(
getLuckyUser({
availableUsers: [organizerWhoseAttendeeShowedUp, organizerWhoseAttendeeDidntShowUp],
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled: false,
team: {},
},
allRRHosts: [],
routingFormResponse: null,
})
).resolves.toStrictEqual(organizerWhoseAttendeeDidntShowUp);
});
it("When a organizer is attendee (event types with fixed hosts) and no show, that organizer is competing other hosts", async () => {
const organizerHostWhoseAttendeeShowedUp = await createHostWithBookings({
user: { email: "test-user1@example.com" },
bookings: [
{
eventTypeId: commonEventTypeId,
createdAt: new Date("2022-01-25T05:30:00.000Z"),
title: "Test User 1 Booking",
attendees: {
create: [
{
name: "test-attendee",
email: "test-attendee@example.com",
timeZone: "Asia/Calcutta",
},
],
},
},
],
});
const organizerWhoseAttendeeShowedUp = organizerHostWhoseAttendeeShowedUp.user;
const fixedHostOrganizerHostWhoseAttendeeDidNotShowUp = await createHostWithBookings({
user: { email: "test-user2@example.com" },
bookings: [
{
eventTypeId: commonEventTypeId,
createdAt: new Date("2022-01-25T06:30:00.000Z"),
attendees: {
create: [
{
name: "test-attendee",
email: "test-attendee@example.com",
timeZone: "Asia/Calcutta",
},
],
},
},
// User2 is fixed user, User 3 was selected as round robin host but did not show up so this booking should be counted for User 3
{
uid: "uuid-test-user2-booking2",
title: `Test User 2 Booking 2`,
createdAt: new Date("2022-01-25T07:30:00.000Z"),
eventTypeId: commonEventTypeId,
attendees: {
create: [
{
name: "test-attendee",
email: "test-user3@example.com",
timeZone: "Asia/Calcutta",
noShow: true,
},
],
},
},
],
});
const fixedHostOrganizerWhoseAttendeeDidNotShowUp =
fixedHostOrganizerHostWhoseAttendeeDidNotShowUp.user;
const organizerHostWhoWasAttendeeAndDidntShowUp = await createHostWithBookings({
user: { email: `test-user3@example.com` },
bookings: [
{
uid: "uuid-test-user3-booking1",
title: `Test User 3 Booking`,
createdAt: new Date("2022-01-25T04:30:00.000Z"),
attendees: {
create: [
{
name: "test-attendee",
email: "test-attendee@example.com",
timeZone: "Asia/Calcutta",
},
],
},
eventTypeId: commonEventTypeId,
},
],
});
const organizerWhoWasAttendeeAndDidntShowUp = organizerHostWhoWasAttendeeAndDidntShowUp.user;
expect(
getLuckyUser({
availableUsers: [
organizerWhoseAttendeeShowedUp,
fixedHostOrganizerWhoseAttendeeDidNotShowUp,
organizerWhoWasAttendeeAndDidntShowUp,
],
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled: false,
team: {},
},
allRRHosts: [],
routingFormResponse: null,
})
).resolves.toStrictEqual(organizerWhoWasAttendeeAndDidntShowUp);
});
it("should consider booking when noShowHost is null", async () => {
const hostWithBookingThatHappenedLater = await createHostWithBookings({
user: { email: "test-user1@example.com" },
bookings: [
{
uid: "uuid-test-user1-booking1",
createdAt: new Date("2022-01-25T07:30:00.000Z"),
title: "Test user 1 Booking",
noShowHost: null,
eventTypeId: commonEventTypeId,
attendees: {
create: [
{
name: "test-attendee",
email: "test-attendee@example.com",
timeZone: "Asia/Calcutta",
},
],
},
},
],
});
const userWithBookingThatHappenedLater = hostWithBookingThatHappenedLater.user;
const hostWithBookingThatHappenedEarlier = await createHostWithBookings({
user: { email: "test-user2@example.com" },
bookings: [
{
uid: "uuid-test-user2-booking1",
title: "Test User 2 Booking",
createdAt: new Date("2022-01-25T06:30:00.000Z"),
noShowHost: null,
eventTypeId: commonEventTypeId,
attendees: {
create: [
{
name: "test-attendee",
email: "test-attendee@example.com",
timeZone: "Asia/Calcutta",
},
],
},
},
],
});
const userWithBookingThatHappenedEarlier = hostWithBookingThatHappenedEarlier.user;
expect(
getLuckyUser({
availableUsers: [userWithBookingThatHappenedLater, userWithBookingThatHappenedEarlier],
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled: false,
team: {},
},
allRRHosts: [],
routingFormResponse: null,
})
).resolves.toStrictEqual(userWithBookingThatHappenedEarlier);
});
});
});
describe("getOrderedListOfLuckyUsers Integration tests", () => {
beforeEach(() => {
vi.setSystemTime("2024-11-14T00:00:13Z");
});
it("should sort as per availableUsers if no other criteria like weight/priority/calibration (TODO: make it independent of availableUsers order)", async () => {
const [host1, host2, host3] = await Promise.all([
createHostWithBookings({
user: { email: "test-user1@example.com" },
bookings: [],
createdAt: new Date(),
}),
createHostWithBookings({
user: { email: "test-user2@example.com" },
bookings: [],
createdAt: new Date(),
}),
createHostWithBookings({
user: { email: "test-user3@example.com" },
bookings: [],
createdAt: new Date(),
}),
]);
const user1 = host1.user;
const user2 = host2.user;
const user3 = host3.user;
const { users: luckyUsers } = await getOrderedListOfLuckyUsers({
availableUsers: [user2, user1, user3],
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled: false,
team: {},
},
allRRHosts: [],
routingFormResponse: null,
});
expectLuckyUsers(luckyUsers, [user2, user1, user3]);
const { users: luckyUsers2 } = await getOrderedListOfLuckyUsers({
availableUsers: [user3, user1, user2],
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled: false,
team: {},
},
allRRHosts: [],
routingFormResponse: null,
});
expectLuckyUsers(luckyUsers2, [user3, user1, user2]);
});
describe("should sort as per weights", () => {
const isRRWeightsEnabled = true;
it("even if there are no bookings", async () => {
const [host1WithWeight100, host2WithWeight200, host3WithWeight100] = await Promise.all([
createHostWithBookings({
user: { email: "test-user1@example.com" },
bookings: [],
createdAt: new Date(),
weight: 100,
}),
createHostWithBookings({
user: { email: "test-user2@example.com" },
bookings: [],
createdAt: new Date(),
weight: 200,
}),
createHostWithBookings({
user: { email: "test-user3@example.com" },
bookings: [],
createdAt: new Date(),
weight: 100,
}),
]);
const user1WithWeight100 = host1WithWeight100.user;
const userWithHighestWeight = host2WithWeight200.user;
const user2WithWeight100 = host3WithWeight100.user;
const allRRHosts = [host1WithWeight100, host2WithWeight200, host3WithWeight100];
const { users: luckyUsers } = await getOrderedListOfLuckyUsers({
availableUsers: [userWithHighestWeight, user1WithWeight100, user2WithWeight100],
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled,
team: {},
},
allRRHosts,
routingFormResponse: null,
});
expectLuckyUsers(luckyUsers, [
// It has the highest weight
userWithHighestWeight,
// It has the same weight as the next one but comes earlier in availableUsers array
user1WithWeight100,
// It is the last choice
user2WithWeight100,
]);
const { users: luckyUsers2 } = await getOrderedListOfLuckyUsers({
availableUsers: [user2WithWeight100, userWithHighestWeight, user1WithWeight100],
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled,
team: {},
},
allRRHosts,
routingFormResponse: null,
});
expectLuckyUsers(luckyUsers2, [
// It has the highest weight and zero bookings.
userWithHighestWeight,
// It has the same weight as the next one but comes earlier in availableUsers array
user2WithWeight100,
// It is the last choice
user1WithWeight100,
]);
});
it("consider booking count for the current month", async () => {
const [
hostWithOneBookingAndWeight200,
hostWithTwoBookingsAndWeight100,
hostWithThreeBookingsAndWeight100,
] = await Promise.all([
createHostWithBookings({
user: { email: "test-user1@example.com" },
bookings: [{ eventTypeId: commonEventTypeId, createdAt: new Date() }],
createdAt: new Date(),
weight: 200,
}),
createHostWithBookings({
user: { email: "test-user2@example.com" },
bookings: [
{ eventTypeId: commonEventTypeId, createdAt: new Date() },
{ eventTypeId: commonEventTypeId, createdAt: new Date() },
],
createdAt: new Date(),
weight: 100,
}),
createHostWithBookings({
user: { email: "test-user3@example.com" },
bookings: [
{ eventTypeId: commonEventTypeId, createdAt: new Date() },
{ eventTypeId: commonEventTypeId, createdAt: new Date() },
{ eventTypeId: commonEventTypeId, createdAt: new Date() },
],
createdAt: new Date(),
weight: 100,
}),
]);
const userWithOneBookingAndWeight200 = hostWithOneBookingAndWeight200.user;
const userWithTwoBookingsAndWeight100 = hostWithTwoBookingsAndWeight100.user;
const userWithThreeBookingsAndWeight100 = hostWithThreeBookingsAndWeight100.user;
const availableUsers = [
userWithThreeBookingsAndWeight100,
userWithTwoBookingsAndWeight100,
userWithOneBookingAndWeight200,
];
const getLuckUserParams = {
availableUsers,
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled,
team: null,
},
allRRHosts: [
hostWithOneBookingAndWeight200,
hostWithTwoBookingsAndWeight100,
hostWithThreeBookingsAndWeight100,
],
routingFormResponse: null,
};
const { users: luckyUsers, perUserData } = await getOrderedListOfLuckyUsers({
...getLuckUserParams,
availableUsers: [getLuckUserParams.availableUsers[0], ...getLuckUserParams.availableUsers.slice(1)],
});
expectLuckyUsers(luckyUsers, [
// User with 1 booking is chosen first because it has higher weight and lesser bookings
userWithOneBookingAndWeight200,
// User with 2 bookings is chosen next because it has lesser bookings
userWithTwoBookingsAndWeight100,
// User with 3 bookings is chosen last because it has the most bookings
userWithThreeBookingsAndWeight100,
]);
if (!perUserData?.bookingShortfalls) {
throw new Error("bookingShortfalls is not defined");
}
expect(perUserData.bookingShortfalls[userWithThreeBookingsAndWeight100.id]).toBe(-1.5);
expect(perUserData.bookingShortfalls[userWithTwoBookingsAndWeight100.id]).toBe(-0.5);
expect(perUserData.bookingShortfalls[userWithOneBookingAndWeight200.id]).toBe(2);
});
it("not considering bookings that were created in previous months", async () => {
const [
hostWithOneBookingInPreviousMonthAndWeight200,
hostWithTwoBookingsInPreviousMonthAndWeight100,
hostWithThreeBookingsInPreviousMonthAndWeight100,
] = await Promise.all([
createHostWithBookings({
user: { email: "test-user1@example.com", createdDate: new Date() },
bookings: [{ eventTypeId: commonEventTypeId, createdAt: new Date("2024-10-01T00:00:00.000Z") }],
weight: 200,
createdAt: new Date(),
}),
createHostWithBookings({
user: { email: "test-user2@example.com", createdDate: new Date() },
bookings: [
{ eventTypeId: commonEventTypeId, createdAt: new Date("2024-10-01T00:00:00.000Z") },
{ eventTypeId: commonEventTypeId, createdAt: new Date("2024-10-01T00:00:00.000Z") },
],
weight: 100,
createdAt: new Date(),
}),
createHostWithBookings({
user: { email: "test-user3@example.com", createdDate: new Date() },
bookings: [
{ eventTypeId: commonEventTypeId, createdAt: new Date("2024-10-01T00:00:00.000Z") },
{ eventTypeId: commonEventTypeId, createdAt: new Date("2024-10-01T00:00:00.000Z") },
{ eventTypeId: commonEventTypeId, createdAt: new Date("2024-10-01T00:00:00.000Z") },
],
weight: 100,
createdAt: new Date(),
}),
]);
const userWithOneBookingInPreviousMonthAndWeight200 =
hostWithOneBookingInPreviousMonthAndWeight200.user;
const userWithTwoBookingsInPreviousMonthAndWeight100 =
hostWithTwoBookingsInPreviousMonthAndWeight100.user;
const userWithThreeBookingsInPreviousMonthAndWeight100 =
hostWithThreeBookingsInPreviousMonthAndWeight100.user;
const availableUsers = [
userWithThreeBookingsInPreviousMonthAndWeight100,
userWithTwoBookingsInPreviousMonthAndWeight100,
userWithOneBookingInPreviousMonthAndWeight200,
];
const getLuckUserParams = {
availableUsers,
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled,
team: null,
},
allRRHosts: [
hostWithOneBookingInPreviousMonthAndWeight200,
hostWithTwoBookingsInPreviousMonthAndWeight100,
hostWithThreeBookingsInPreviousMonthAndWeight100,
],
routingFormResponse: null,
};
const { users: luckyUsers, perUserData } = await getOrderedListOfLuckyUsers({
...getLuckUserParams,
availableUsers: [getLuckUserParams.availableUsers[0], ...getLuckUserParams.availableUsers.slice(1)],
});
expectLuckyUsers(luckyUsers, [
// User with 1 booking is chosen first because it has higher weight and lesser bookings
userWithOneBookingInPreviousMonthAndWeight200,
// User with 3 bookings is chosen next because it comes earlier in availableUsers array
userWithThreeBookingsInPreviousMonthAndWeight100,
// User with 2 bookings is chosen last because it comes later in availableUsers array
userWithTwoBookingsInPreviousMonthAndWeight100,
]);
if (!perUserData?.bookingShortfalls) {
throw new Error("bookingShortfalls is not defined");
}
// Because no one has any bookings in the current month, the booking shortfall should be 0 for all users
expect(perUserData.bookingShortfalls[userWithThreeBookingsInPreviousMonthAndWeight100.id]).toBe(0);
expect(perUserData.bookingShortfalls[userWithTwoBookingsInPreviousMonthAndWeight100.id]).toBe(0);
expect(perUserData.bookingShortfalls[userWithOneBookingInPreviousMonthAndWeight200.id]).toBe(0);
});
});
describe("should sort as per host creation data calibration", () => {
const isRRWeightsEnabled = true;
it("not considering bookings that were created in previous months", async () => {
const today = new Date();
const tenthOfTheMonth = new Date(today.getFullYear(), today.getMonth(), 10);
const secondsInDay = 24 * 60 * 60 * 1000;
const ninthOfTheMonth = new Date(tenthOfTheMonth.getTime() - secondsInDay);
const eighthOfTheMonth = new Date(tenthOfTheMonth.getTime() - 2 * secondsInDay);
const [host1, host2, host3] = await Promise.all([
createHostWithBookings({
user: { email: "test-user1@example.com", createdDate: tenthOfTheMonth },
bookings: [
{ eventTypeId: commonEventTypeId, createdAt: new Date(tenthOfTheMonth.getTime() + 1000) },
],
weight: 200,
createdAt: tenthOfTheMonth,
}),
createHostWithBookings({
user: { email: "test-user2@example.com", createdDate: ninthOfTheMonth },
bookings: [
{ eventTypeId: commonEventTypeId, createdAt: new Date(ninthOfTheMonth.getTime() + 1000) },
{ eventTypeId: commonEventTypeId, createdAt: new Date(ninthOfTheMonth.getTime() + 2000) },
],
weight: 100,
createdAt: ninthOfTheMonth,
}),
createHostWithBookings({
user: { email: "test-user3@example.com", createdDate: eighthOfTheMonth },
bookings: [
{ eventTypeId: commonEventTypeId, createdAt: new Date(eighthOfTheMonth.getTime() + 1000) },
{ eventTypeId: commonEventTypeId, createdAt: new Date(eighthOfTheMonth.getTime() + 2000) },
{ eventTypeId: commonEventTypeId, createdAt: new Date(eighthOfTheMonth.getTime() + 3000) },
],
weight: 100,
createdAt: eighthOfTheMonth,
}),
]);
const availableUsers = [host3.user, host2.user, host1.user];
const getLuckyUserParams = {
availableUsers,
eventType: {
id: commonEventTypeId,
isRRWeightsEnabled,
team: null,
},
allRRHosts: [host1, host2, host3],
routingFormResponse: null,
};
const { users: luckyUsers, perUserData } = await getOrderedListOfLuckyUsers({
...getLuckyUserParams,
availableUsers: [getLuckyUserParams.availableUsers[0], ...getLuckyUserParams.availableUsers.slice(1)],
});
if (!perUserData?.bookingShortfalls || !perUserData?.calibrations) {
throw new Error("bookingShortfalls or calibrations is not defined");
}
expect(perUserData.calibrations[host1.user.id]).toBe(2.5);
expect(perUserData.calibrations[host2.user.id]).toBe(3);
expect(perUserData.calibrations[host3.user.id]).toBe(0);
expectLuckyUsers(luckyUsers, [host1.user, host3.user, host2.user]);
});
});
});