fix: Delegation Credentials - Use Cal Video if set as the default conferencing app (#21431)
* fix google-meet being used instead of Default Cal Video preference if set * Review addressed --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
co-authored by
Udit Takkar
parent
d5bd08d279
commit
0e9b419a07
@@ -15,7 +15,7 @@ const sortUsersByDynamicList = <TUser extends { username: string | null }>(
|
||||
});
|
||||
};
|
||||
|
||||
const _getLocationValuesForDb = <
|
||||
export const _getLocationValuesForDb = <
|
||||
TUser extends {
|
||||
username: string | null;
|
||||
metadata: Prisma.JsonValue;
|
||||
@@ -42,9 +42,15 @@ const _getLocationValuesForDb = <
|
||||
credentials: firstDynamicGroupMember.credentials,
|
||||
});
|
||||
|
||||
const defaultConferencingApp = firstDynamicGroupMemberMetadata?.defaultConferencingApp;
|
||||
|
||||
const hasMemberSetConferencingPreference =
|
||||
!!defaultConferencingApp?.appSlug || !!defaultConferencingApp?.appLink;
|
||||
|
||||
firstDynamicGroupMemberDefaultLocationUrl =
|
||||
firstDynamicGroupMemberMetadata?.defaultConferencingApp?.appLink ||
|
||||
firstDynamicGroupMemberDelegationCredentialConferencingAppLocation;
|
||||
(hasMemberSetConferencingPreference
|
||||
? defaultConferencingApp?.appLink
|
||||
: firstDynamicGroupMemberDelegationCredentialConferencingAppLocation) ?? null;
|
||||
|
||||
locationBodyString = firstDynamicGroupMemberDefaultLocationUrl || locationBodyString;
|
||||
}
|
||||
|
||||
@@ -925,5 +925,140 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
timeout
|
||||
);
|
||||
|
||||
test(
|
||||
`should use Cal Video as the location if that is the default conferencing app set by the user. It must not use Google Meet coming from Delegation credential.`,
|
||||
async () => {
|
||||
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
||||
|
||||
const org = await createOrganization({
|
||||
name: "Test Org",
|
||||
slug: "testorg",
|
||||
});
|
||||
|
||||
const payloadToMakePartOfOrganization = [
|
||||
{
|
||||
membership: {
|
||||
accepted: true,
|
||||
role: MembershipRole.ADMIN,
|
||||
},
|
||||
team: {
|
||||
id: org.id,
|
||||
name: "Test Org",
|
||||
slug: "testorg",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const booker = getBooker({
|
||||
email: "booker@example.com",
|
||||
name: "Booker",
|
||||
});
|
||||
|
||||
const groupUser1 = getOrganizer({
|
||||
name: "group-user-1",
|
||||
username: "group-user-1",
|
||||
email: "group-user-1@example.com",
|
||||
id: 101,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
teams: payloadToMakePartOfOrganization,
|
||||
credentials: [],
|
||||
destinationCalendar: TestData.selectedCalendars.google,
|
||||
metadata: {
|
||||
defaultConferencingApp: {
|
||||
appSlug: "daily-video",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const groupUser2 = getOrganizer({
|
||||
name: "group-user-2",
|
||||
username: "group-user-2",
|
||||
email: "group-user-2@example.com",
|
||||
id: 102,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
teams: payloadToMakePartOfOrganization,
|
||||
credentials: [],
|
||||
destinationCalendar: TestData.selectedCalendars.google,
|
||||
metadata: {
|
||||
defaultConferencingApp: {
|
||||
appSlug: "daily-video",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await createDelegationCredential(org.id);
|
||||
|
||||
await createBookingScenario(
|
||||
getScenarioData({
|
||||
eventTypes: [],
|
||||
users: [groupUser1, groupUser2],
|
||||
apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]],
|
||||
})
|
||||
);
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
videoMeetingData: {
|
||||
id: "MOCK_ID",
|
||||
password: "MOCK_PASS",
|
||||
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
||||
},
|
||||
});
|
||||
|
||||
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar", {
|
||||
create: {
|
||||
id: "GOOGLE_CALENDAR_EVENT_ID",
|
||||
uid: "MOCK_ID",
|
||||
appSpecificData: {
|
||||
googleCalendar: {
|
||||
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
|
||||
const mockBookingData = getMockRequestDataForDynamicGroupBooking({
|
||||
data: {
|
||||
start: `${plus1DateString}T05:00:00.000Z`,
|
||||
end: `${plus1DateString}T05:30:00.000Z`,
|
||||
eventTypeId: 0,
|
||||
eventTypeSlug: "group-user-1+group-user-2",
|
||||
user: "group-user-1+group-user-2",
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
// location: { optionValue: "", value: BookingLocations.CalVideo },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: null,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
location: BookingLocations.CalVideo,
|
||||
references: [
|
||||
{
|
||||
type: appStoreMetadata.dailyvideo.type,
|
||||
// Verify Delegation credential was not used
|
||||
delegationCredentialId: null,
|
||||
},
|
||||
],
|
||||
iCalUID: createdBooking.iCalUID,
|
||||
});
|
||||
},
|
||||
timeout
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
|
||||
import { _getLocationValuesForDb } from "../getLocationValuesForDb";
|
||||
|
||||
vi.mock("@calcom/prisma/zod-utils", () => ({
|
||||
userMetadata: { parse: (metadata: any) => metadata },
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/delegationCredential/server", () => ({
|
||||
getFirstDelegationConferencingCredentialAppLocation: ({
|
||||
credentials,
|
||||
}: {
|
||||
credentials: CredentialForCalendarService[];
|
||||
}) => (credentials[0] && (credentials[0] as any).__test__appLink) || null,
|
||||
}));
|
||||
|
||||
type TestUser = {
|
||||
username: string | null;
|
||||
metadata: Prisma.JsonValue;
|
||||
credentials: CredentialForCalendarService[];
|
||||
};
|
||||
|
||||
describe("_getLocationValuesForDb", () => {
|
||||
it("returns the provided location for single user bookings", () => {
|
||||
const users: TestUser[] = [
|
||||
{
|
||||
username: "alice",
|
||||
metadata: {},
|
||||
credentials: [],
|
||||
},
|
||||
];
|
||||
const result = _getLocationValuesForDb({
|
||||
dynamicUserList: ["alice"],
|
||||
users,
|
||||
location: "https://meet.example.com/alice",
|
||||
});
|
||||
expect(result.locationBodyString).toBe("https://meet.example.com/alice");
|
||||
expect(result.organizerOrFirstDynamicGroupMemberDefaultLocationUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses the first group member's default conferencing appLink if set", () => {
|
||||
const users: TestUser[] = [
|
||||
{
|
||||
username: "bob",
|
||||
metadata: {
|
||||
defaultConferencingApp: {
|
||||
appSlug: "zoom",
|
||||
appLink: "https://zoom.us/bob",
|
||||
},
|
||||
},
|
||||
credentials: [],
|
||||
},
|
||||
{
|
||||
username: "carol",
|
||||
metadata: {},
|
||||
credentials: [],
|
||||
},
|
||||
];
|
||||
const result = _getLocationValuesForDb({
|
||||
dynamicUserList: ["bob", "carol"],
|
||||
users,
|
||||
location: "https://meet.example.com/group",
|
||||
});
|
||||
expect(result.locationBodyString).toBe("https://zoom.us/bob");
|
||||
expect(result.organizerOrFirstDynamicGroupMemberDefaultLocationUrl).toBe("https://zoom.us/bob");
|
||||
});
|
||||
|
||||
it("uses the first group member's first delegation credential's conferencing app if no preference is set", () => {
|
||||
const users: TestUser[] = [
|
||||
{
|
||||
username: "dave",
|
||||
metadata: {},
|
||||
credentials: [
|
||||
{
|
||||
__test__appLink: "https://meet.google.com/dave",
|
||||
} as unknown as CredentialForCalendarService,
|
||||
],
|
||||
},
|
||||
{
|
||||
username: "eve",
|
||||
metadata: {},
|
||||
credentials: [],
|
||||
},
|
||||
];
|
||||
const result = _getLocationValuesForDb({
|
||||
dynamicUserList: ["dave", "eve"],
|
||||
users,
|
||||
location: "https://meet.example.com/group",
|
||||
});
|
||||
expect(result.locationBodyString).toBe("https://meet.google.com/dave");
|
||||
expect(result.organizerOrFirstDynamicGroupMemberDefaultLocationUrl).toBe("https://meet.google.com/dave");
|
||||
});
|
||||
|
||||
it("falls back to provided location if no preferences or credentials are set", () => {
|
||||
const users: TestUser[] = [
|
||||
{
|
||||
username: "frank",
|
||||
metadata: {},
|
||||
credentials: [],
|
||||
},
|
||||
{
|
||||
username: "grace",
|
||||
metadata: {},
|
||||
credentials: [],
|
||||
},
|
||||
];
|
||||
const result = _getLocationValuesForDb({
|
||||
dynamicUserList: ["frank", "grace"],
|
||||
users,
|
||||
location: "https://meet.example.com/group",
|
||||
});
|
||||
expect(result.locationBodyString).toBe("https://meet.example.com/group");
|
||||
expect(result.organizerOrFirstDynamicGroupMemberDefaultLocationUrl).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to provided location if preference is set but has no appLink", () => {
|
||||
const users: TestUser[] = [
|
||||
{
|
||||
username: "frank",
|
||||
metadata: {
|
||||
defaultConferencingApp: {
|
||||
appSlug: "daily-video",
|
||||
},
|
||||
},
|
||||
credentials: [],
|
||||
},
|
||||
{
|
||||
username: "grace",
|
||||
metadata: {},
|
||||
credentials: [],
|
||||
},
|
||||
];
|
||||
const result = _getLocationValuesForDb({
|
||||
dynamicUserList: ["frank", "grace"],
|
||||
users,
|
||||
location: "https://meet.example.com/group",
|
||||
});
|
||||
expect(result.locationBodyString).toBe("https://meet.example.com/group");
|
||||
expect(result.organizerOrFirstDynamicGroupMemberDefaultLocationUrl).toBeNull();
|
||||
});
|
||||
|
||||
it("sorts users according to dynamicUserList before picking the first member", () => {
|
||||
const users: TestUser[] = [
|
||||
{
|
||||
username: "zara",
|
||||
metadata: {
|
||||
defaultConferencingApp: {
|
||||
appSlug: "teams",
|
||||
appLink: "https://teams.microsoft.com/zara",
|
||||
},
|
||||
},
|
||||
credentials: [],
|
||||
},
|
||||
{
|
||||
username: "yanni",
|
||||
metadata: {},
|
||||
credentials: [],
|
||||
},
|
||||
];
|
||||
// yanni is first in dynamicUserList, but zara is first in users array
|
||||
const result = _getLocationValuesForDb({
|
||||
dynamicUserList: ["yanni", "zara"],
|
||||
users,
|
||||
location: "https://meet.example.com/group",
|
||||
});
|
||||
// After sorting, yanni is first, has no preference, so fallback to location
|
||||
expect(result.locationBodyString).toBe("https://meet.example.com/group");
|
||||
expect(result.organizerOrFirstDynamicGroupMemberDefaultLocationUrl).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user