Repository: calcom/cal.com
Pull request #21431: fix: Delegation Credentials - Use Cal Video if set as the default conferencing app
Author: login: hariombalhara
Labels: name: 🐛 bug
description: Something isn't working
name: enterprise
description: area: enterprise, audit log, organisation, SAML, SSO
name: automated-tests
description: area: unit tests, e2e tests, playwright
name: core
description: area: core, team members only
name: ready-for-e2e
Created: 2025-05-21T06:15:54Z | Merged: 2025-05-22T12:21:25Z

## What does this PR do?

This PR fixes an issue where Google Meet was being used as the conferencing location even when a user had set a Default Cal Video preference. Now, if a user has a  Cal Video set as the preference, that  is respected and used as the location for group bookings, rather than falling back to Google Meet from delegation credentials.

- Fixes: Ensures the user's default conferencing app (e.g., Cal Video) is prioritized over Google Meet from delegation credentials. It wasn't working only in the case where a default conferencing app doesn't set any `appLink` which is true for dynamic link conferencing apps.
- Adds: New test coverage in `getLocationValueForDb.test.ts` to verify correct location selection logic.
- Adds: A bookingScenario test in `packages/features/bookings/lib/handleNewBooking/test/delegation-credential.test.ts`

## Visual Demo

N/A (Logic change, covered by automated tests)

## Mandatory Tasks

- [x] I have self-reviewed the code.
- [x] I have updated/added tests to verify the fix.
- [x] N/A for developer docs (no documentation change required).

## How should this be tested?

- Run the test suite in `packages/features/bookings/lib/handleNewBooking/test/`.
- Ensure that bookings for users with a default conferencing app set use that app as the location.
- Verify that if no default is set, the fallback to delegation credential or provided location works as expected.

## Checklist

- [x] My code follows the style guidelines of this project.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have checked that my changes generate no new warnings.



Diff:
diff --git a/packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts b/packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts
index 966b48d1e458a1..acbd66133c665a 100644
--- a/packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts
+++ b/packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts
@@ -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;
   }
diff --git a/packages/features/bookings/lib/handleNewBooking/test/delegation-credential.test.ts b/packages/features/bookings/lib/handleNewBooking/test/delegation-credential.test.ts
index 2adf102f791765..46edb0d2fa4bcb 100644
--- a/packages/features/bookings/lib/handleNewBooking/test/delegation-credential.test.ts
+++ b/packages/features/bookings/lib/handleNewBooking/test/delegation-credential.test.ts
@@ -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
+    );
   });
 });
diff --git a/packages/features/bookings/lib/handleNewBooking/test/getLocationValueForDb.test.ts b/packages/features/bookings/lib/handleNewBooking/test/getLocationValueForDb.test.ts
new file mode 100644
index 00000000000000..acef560d540d97
--- /dev/null
+++ b/packages/features/bookings/lib/handleNewBooking/test/getLocationValueForDb.test.ts
@@ -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();
+  });
+});


Reviews:
user: login: hariombalhara
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T06:26:41Z
user: login: hariombalhara
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T06:27:05Z
user: login: cubic-dev-ai[bot]
body: cubic reviewed 3 files and found no issues. Review PR in [cubic.dev](https://cubic.dev/pr/calcom/cal.com/pull/21431).
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T06:27:50Z
user: login: joeauyeung
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:35:34Z
user: login: hbjORbj
body: code nit
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T18:44:23Z
user: login: cubic-dev-ai[bot]
body: cubic reviewed 3 files and found no issues. Review PR in [cubic.dev](https://cubic.dev/pr/calcom/cal.com/pull/21431).
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T03:51:22Z
user: login: joeauyeung
body: Feedback addressed. Looks even better now
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T04:04:34Z
user: login: Udit-takkar
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T09:00:12Z

Review comments:
diff_hunk: @@ -42,9 +42,17 @@ const _getLocationValuesForDb = <
         credentials: firstDynamicGroupMember.credentials,
       });
 
-    firstDynamicGroupMemberDefaultLocationUrl =
-      firstDynamicGroupMemberMetadata?.defaultConferencingApp?.appLink ||
-      firstDynamicGroupMemberDelegationCredentialConferencingAppLocation;
+    const defaultConferencingApp = firstDynamicGroupMemberMetadata?.defaultConferencingApp;
+
+    const hasMemberSetConferencingPreference =
path: packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts
user: login: hariombalhara
body: Important difference here is that now appSlug and appLink both can individually decide if preference is set. Earlier it was just `appLink` which is set for static link conferencing apps but not for dynamic ones like Zoom, Cal Video 
created_at: 2025-05-21T06:26:41Z
author_association: MEMBER
diff_hunk: @@ -42,9 +42,17 @@ const _getLocationValuesForDb = <
         credentials: firstDynamicGroupMember.credentials,
       });
 
-    firstDynamicGroupMemberDefaultLocationUrl =
-      firstDynamicGroupMemberMetadata?.defaultConferencingApp?.appLink ||
-      firstDynamicGroupMemberDelegationCredentialConferencingAppLocation;
+    const defaultConferencingApp = firstDynamicGroupMemberMetadata?.defaultConferencingApp;
+
+    const hasMemberSetConferencingPreference =
+      !!defaultConferencingApp?.appSlug || !!defaultConferencingApp?.appLink;
+
+    if (!hasMemberSetConferencingPreference) {
+      firstDynamicGroupMemberDefaultLocationUrl =
+        firstDynamicGroupMemberDelegationCredentialConferencingAppLocation ?? null;
path: packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts
user: login: hariombalhara
body: We use the delegated conferencing app location only if user hasn't set preference.
created_at: 2025-05-21T06:27:05Z
author_association: MEMBER
diff_hunk: @@ -42,9 +42,17 @@ const _getLocationValuesForDb = <
         credentials: firstDynamicGroupMember.credentials,
       });
 
-    firstDynamicGroupMemberDefaultLocationUrl =
-      firstDynamicGroupMemberMetadata?.defaultConferencingApp?.appLink ||
-      firstDynamicGroupMemberDelegationCredentialConferencingAppLocation;
+    const defaultConferencingApp = firstDynamicGroupMemberMetadata?.defaultConferencingApp;
+
+    const hasMemberSetConferencingPreference =
+      !!defaultConferencingApp?.appSlug || !!defaultConferencingApp?.appLink;
+
+    if (!hasMemberSetConferencingPreference) {
+      firstDynamicGroupMemberDefaultLocationUrl =
+        firstDynamicGroupMemberDelegationCredentialConferencingAppLocation ?? null;
+    } else {
+      firstDynamicGroupMemberDefaultLocationUrl = defaultConferencingApp?.appLink ?? null;
+    }
path: packages/features/bookings/lib/handleNewBooking/getLocationValuesForDb.ts
user: login: hbjORbj
body: this would be cleaner
```
firstDynamicGroupMemberDefaultLocationUrl = (hasMemberSetConferencingPreference ? defaultConferencingApp?.appLink : firstDynamicGroupMemberDelegationCredentialConferencingAppLocation) ?? null
```
created_at: 2025-05-21T18:44:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-21T06:16:04Z
body: Hey there and thank you for opening this pull request! 👋🏼

We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.

Details:

```
No release type found in pull request title "fix google-meet being used instead of Default Cal Video preference if set". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

Available types:
 - feat: A new feature
 - fix: A bug fix
 - docs: Documentation only changes
 - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
 - refactor: A code change that neither fixes a bug nor adds a feature
 - perf: A code change that improves performance
 - test: Adding missing tests or correcting existing tests
 - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
 - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
 - chore: Other changes that don't modify src or test files
 - revert: Reverts a previous commit
```
<!-- Sticky Pull Request Commentpr-title-lint-error -->
author_association: CONTRIBUTOR
user: login: hariombalhara
created_at: 2025-05-21T06:16:15Z
body: * **#21431** <a href="https://app.graphite.dev/github/pr/calcom/cal.com/21431?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/calcom/cal.com/21431?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#21276** <a href="https://app.graphite.dev/github/pr/calcom/cal.com/21276?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#21270** <a href="https://app.graphite.dev/github/pr/calcom/cal.com/21270?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#21269** <a href="https://app.graphite.dev/github/pr/calcom/cal.com/21269?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* `main`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: MEMBER
user: login: vercel[bot]
created_at: 2025-05-21T06:16:36Z
body: [vc]: #u1+e9+0dWeJ8KaE3XD0sr2D/Jw+BezsLij+vW5DLcyg=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJjYWwiLCJyb290RGlyZWN0b3J5IjoiYXBwcy93ZWIiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9jYWwvY2FsLzloMlZ5bTR4UVRMM3Y1dWtzYWo3Z3hMZWpRY2IiLCJuZXh0Q29tbWl0U3RhdHVzIjoiSUdOT1JFRCIsInByZXZpZXdVcmwiOiJjYWwtZ2l0LTA1LTIxLWZpeGdvb2dsZS1tZWV0YmVpbmd1c2VkaW5zdGVhZG9mZGVmYXVsLWU5OGYzNi1jYWwudmVyY2VsLmFwcCJ9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **cal** | ⬜️ Ignored ([Inspect](https://vercel.com/cal/cal/9h2Vym4xQTL3v5uksaj7gxLejQcb)) | [Visit Preview](https://cal-git-05-21-fixgoogle-meetbeingusedinsteadofdefaul-e98f36-cal.vercel.app) |  | May 22, 2025 9:02am |
</details>


author_association: NONE
user: login: graphite-app[bot]
created_at: 2025-05-21T06:25:10Z
body: ### Graphite Automations
<details><summary><a href="https://app.graphite.dev/automations/?org=calcom&ruleId=qoTwHtX8MnP39HKxIt4V&ref=gt-automation-github-comment">"Add consumer team as reviewer"</a> took an action on this PR • (05/21/25)</summary><p>1 reviewer was added to this PR based on Keith Williams's automation.</p></details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-22T04:08:39Z
body: <!-- GENERATED-E2E-RESULTS -->
## E2E results are ready!
- [Workflow #61265.1 latest results](https://calcom.github.io/test-results/reports/05-21-fix_google-meet_being_used_instead_of_default_cal_video_preference_if_set/15182407725/1)
author_association: CONTRIBUTOR