feat(slots): check guest availability when host reschedules (#16378)#28911
feat(slots): check guest availability when host reschedules (#16378)#28911bcornish1797 wants to merge 3 commits intocalcom:mainfrom
Conversation
Split of calcom#28636 (Part B of 3). Plumbing only — adds a new `rescheduledBy` search param that flows from the URL through `useScheduleForEvent` → `useSchedule` → the tRPC `getSchedule` input schema. Nothing consumes the field yet; it will be read by `_getGuestBusyTimesForReschedule` in Part C to gate guest busy-time blocking on host-initiated reschedules. Concretely: - `apps/web/modules/schedules/hooks/useEvent.ts`: reads `searchParams.get("rescheduledBy")` alongside the existing `rescheduleUid` read and forwards it to `useSchedule`. - `apps/web/modules/schedules/hooks/useSchedule.ts`: adds `rescheduledBy` to `UseScheduleWithCacheArgs` and passes it through to the tRPC query input. - `packages/trpc/server/routers/viewer/slots/types.ts`: adds `rescheduledBy: z.string().nullish()` to `getScheduleSchemaObject`. Backwards compatible — the field is nullish on both the URL and the schema, so older clients keep working unchanged.
Split of calcom#28636 (Part A of 3). Pure additive infra — no call sites change and no existing behaviour is altered. This layer is the data-access foundation that Parts B (frontend `rescheduledBy` plumbing) and C (slots/util.ts business logic) build on. BookingRepository: - findByUidIncludeAttendeeEmails(uid): fetches an original booking's attendee emails and the host user's email, used to detect who the reschedule initiator is and resolve attendees to Cal.com users. - findByUserIdsAndDateRange({ userIds, userEmails, dateFrom, dateTo, excludeUid? }): finds ACCEPTED/PENDING bookings overlapping a date range by userId or attendee email (case-insensitive), with an excludeUid parameter applied at the database level so the caller cannot accidentally include the very booking being rescheduled. UserRepository: - findByEmails({ emails }): resolves a list of emails to Cal.com users, checking both primary email and verified secondary emails, case-insensitively, with input deduplication before the query and output deduplication by user id. Uses Promise.all to fan out the two lookups concurrently. Tests cover: empty-input short-circuits, primary vs secondary lookup, dedup across both lookups, case-insensitive normalization, excludeUid, OR clause composition, and the select shape used downstream.
…6378) Split of calcom#28636 (Part C of 3 — depends on Parts A and B). When the host reschedules a booking, check whether any attendee is a Cal.com user and collect their busy times so the host only sees mutually available slots. Attendee-initiated reschedules still see all slots — guest-availability gating only kicks in when `rescheduledBy` matches the host email. Flow: 1. `slots/util.ts` reads `rescheduleUid` and `rescheduledBy` from the tRPC input (threaded through in Part B) and fans out `getGuestBusyTimesForReschedule` in the existing booking-fetch Promise.all. 2. `_getGuestBusyTimesForReschedule` (wrapped with `withReporting`): - short-circuits when no rescheduleUid or schedulingType is COLLECTIVE (team members already coordinated via round-robin); - loads the original booking with attendee + host email via `BookingRepository.findByUidIncludeAttendeeEmails` (Part A); - compares `rescheduledBy` to the host email (case-insensitive). If it is an attendee, returns `[]` — no blocking; - resolves attendee emails to Cal.com users via `UserRepository.findByEmails` (Part A) and filters the booking query to those users only, so non-Cal.com guests do not pollute the OR-filter; - fetches overlapping bookings via `BookingRepository.findByUserIdsAndDateRange` with excludeUid at the DB level (Part A); - on any failure, returns `[]` and logs a `warn` so operators can detect regressions without paging on a non-blocking path. 3. `getUserAvailability.ts` accepts the resulting `guestBusyTimes` via a new optional `initialData.guestBusyTimes` field, formats them as EventBusyDetails with a `guest-availability` source, and merges them into the per-user busy window. No existing call sites are changed — the field is optional and defaults to `[]`. Scope: - COLLECTIVE scheduling: skipped (already coordinated at booking time). - Non-Cal.com guests: ignored (no Cal.com calendar to check). - ROUND_ROBIN: fully supported. - Multi-guest reschedules: all matched guests' busy times are merged. - Older clients (no `rescheduledBy` in URL): fall through to gate on `rescheduleUid` alone. No regression in existing reschedule flows. Tests (13 new, in `packages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.ts`): early exits, host-initiated collection, attendee-initiated no-op, case-insensitive host-email match, backwards compat when rescheduledBy is absent, multi-guest merging, excludeUid filtering, and graceful degradation on each failure mode.
|
Welcome to Cal.diy, @bcornish1797! Thanks for opening this pull request. A few things to keep in mind:
A maintainer will review your PR soon. Thanks for contributing! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 15cea3a17d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (rescheduledBy) { | ||
| const hostEmail = original.user?.email; | ||
| const isHostReschedule = hostEmail && rescheduledBy.toLowerCase() === hostEmail.toLowerCase(); | ||
| if (!isHostReschedule) { | ||
| return []; |
There was a problem hiding this comment.
Guard guest blocking when initiator is unknown
The host/attendee gate only runs inside if (rescheduledBy), so when rescheduledBy is missing this path defaults to applying guest busy-time blocking. That causes attendee-initiated reschedules from callers that still send only rescheduleUid (for example, apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots-input.service.ts maps bookingUidToReschedule but not rescheduledBy) to be treated like host reschedules, hiding slots attendees should still see.
Useful? React with 👍 / 👎.
| const calUserEmails = calUsers.map((u) => u.email); | ||
|
|
||
| const guestBookings = await this.dependencies.bookingRepo.findByUserIdsAndDateRange({ | ||
| userIds: calUsers.map((u) => u.id), | ||
| userEmails: calUserEmails, |
There was a problem hiding this comment.
Keep matched attendee aliases in busy-time email filter
findByEmails resolves users by both primary and secondary emails, but here userEmails is rebuilt from calUsers.map((u) => u.email) (primary email only). If an attendee is matched via a verified secondary email, findByUserIdsAndDateRange will not match bookings where they attend under that secondary address, so guest busy windows are missed and conflicting slots can be offered.
Useful? React with 👍 / 👎.
📝 WalkthroughWalkthroughThis pull request adds support for computing guest attendee busy times during rescheduling operations. It introduces a 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
packages/features/users/repositories/UserRepository.test.ts (1)
130-130: Avoid double-casting the Prisma mock.Line 130 uses
as unknown as PrismaClient, which bypasses type-safety. UserRepository only uses four properties (user,membership,secondaryEmail,team) and two methods ($queryRaw,$transaction), so narrow the type toPick<PrismaClient, "user" | "membership" | "secondaryEmail" | "team" | "$queryRaw" | "$transaction">to enable type-safe mock injection without unsafe casting.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/features/users/repositories/UserRepository.test.ts` at line 130, Replace the unsafe double-cast of the Prisma mock (currently cast as "as unknown as PrismaClient") with a narrow, type-safe Pick of PrismaClient: create the mock using Pick<PrismaClient, "user" | "membership" | "secondaryEmail" | "team" | "$queryRaw" | "$transaction"> and pass that to the UserRepository tests so TypeScript enforces the four model properties and two methods used ($queryRaw, $transaction) instead of bypassing type checks.packages/trpc/server/routers/viewer/slots/util.ts (1)
802-817: Consider memoizing guest-busy lookups across fallback passes.
calculateHostsAndAvailabilities()is reused in the round-robin fallback flow, so doing the original-booking lookup + attendee resolution + overlap query here can repeat the same DB work 2–3 times in one request. Moving this up to_getAvailableSlotsper date range, or memoizing by(rescheduleUid, rescheduledBy, schedulingType, dateFrom, dateTo), would keep the new logic off the hot path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trpc/server/routers/viewer/slots/util.ts` around lines 802 - 817, The guest-busy lookup (this.getGuestBusyTimesForReschedule) is being repeatedly called when calculateHostsAndAvailabilities is used in the round-robin fallback flow; memoize or hoist the lookup so it runs once per date-range/reschedule-key and reuse it across fallback passes. Implement a small in-memory cache keyed by the tuple (rescheduleUid, rescheduledBy, schedulingType, dateFrom.toISOString(), dateTo.toISOString()) inside _getAvailableSlots (or higher) and check the cache before calling getGuestBusyTimesForReschedule; ensure calculateHostsAndAvailabilities accepts the precomputed guestBusyTimes (or reads from the cache) so the DB/attendee-resolution work is not repeated.packages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.ts (2)
344-357: Assert the warn side of graceful degradation.This only protects the
[]fallback. If the catch stops emitting the warn, observability regresses and this test still passes. Please spy on the logger and assert the warning as part of this path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.ts` around lines 344 - 357, The test currently only verifies the [] fallback but doesn't assert that the logger.warn is called when the catch path is hit; update the test in getGuestBusyTimesForReschedule.test.ts to spy on the process/logger used by the implementation (e.g., spyOn(processLogger, 'warn') or the module logger used by getGuestBusyTimesForReschedule) before invoking the function, trigger the error path, call the function, and then assert the spy was called with the expected warning message; finally restore/clear the spy to avoid test leakage.
222-223: Drop these what-comments.The test names and assertions already make this intent clear, so these comments just add noise.
As per coding guidelines, "Only add code comments that explain why, not what" and "Never add comments that simply restate what the code does".
Also applies to: 244-245, 266-267
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.ts` around lines 222 - 223, Remove the redundant "what"-style inline comments in the test file getGuestBusyTimesForReschedule.test.ts: delete the comments that merely restate the test name/assertions (the ones flagged in the review and the two additional occurrences) so each test relies on its descriptive test name and assertions instead of duplicative commentary; ensure no functional code is changed, only remove those comment lines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/modules/schedules/hooks/useSchedule.ts`:
- Line 107: The API V2 input type GetAvailableSlotsInput_2024_04_15 and the
/slots/available handler need to accept and use a rescheduledBy field to match
tRPC behavior: add an optional rescheduledBy:string to
GetAvailableSlotsInput_2024_04_15, surface it through the handler that powers
/slots/available, and implement the same host-reschedule detection logic
(case-insensitive compare against host email) used by the tRPC slots endpoint so
guest busy-times are only blocked when the reschedule was initiated by the host;
alternatively, if divergence is intentional, update the API docs/comments in the
/slots/available handler and GetAvailableSlotsInput_2024_04_15 to explicitly
state the behavioral difference.
In `@packages/features/availability/lib/getUserAvailability.ts`:
- Around line 621-626: Replace the hardcoded "Guest busy" string in the
guestBusyTimesFormatted mapping with a localization key lookup: add a new key
(e.g. "availability.guestBusy") to packages/i18n/locales/en/common.json and use
the i18n accessor used across the codebase (the project's translate/i18n
function) in getUserAvailability.ts when building EventBusyDetails (replace the
literal in guestBusyTimesFormatted with the i18n call), ensuring fallback to the
English string if translation is missing and keeping the rest of the object
shape unchanged.
In `@packages/features/bookings/repositories/BookingRepository.test.ts`:
- Around line 120-149: In the userId-only test case in
BookingRepository.test.ts, explicitly assert the userId predicate so the test
fails if filtering by userIds is omitted: after calling the repository method
under test (the "userId-only" scenario), iterate the returned bookings and
assert booking.userId === expectedUserId for each result (and/or assert the set
of returned userIds equals the single expectedUserId); this ensures the test not
only checks status/date overlap but also enforces that only bookings for the
specified user are returned.
---
Nitpick comments:
In `@packages/features/users/repositories/UserRepository.test.ts`:
- Line 130: Replace the unsafe double-cast of the Prisma mock (currently cast as
"as unknown as PrismaClient") with a narrow, type-safe Pick of PrismaClient:
create the mock using Pick<PrismaClient, "user" | "membership" |
"secondaryEmail" | "team" | "$queryRaw" | "$transaction"> and pass that to the
UserRepository tests so TypeScript enforces the four model properties and two
methods used ($queryRaw, $transaction) instead of bypassing type checks.
In
`@packages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.ts`:
- Around line 344-357: The test currently only verifies the [] fallback but
doesn't assert that the logger.warn is called when the catch path is hit; update
the test in getGuestBusyTimesForReschedule.test.ts to spy on the process/logger
used by the implementation (e.g., spyOn(processLogger, 'warn') or the module
logger used by getGuestBusyTimesForReschedule) before invoking the function,
trigger the error path, call the function, and then assert the spy was called
with the expected warning message; finally restore/clear the spy to avoid test
leakage.
- Around line 222-223: Remove the redundant "what"-style inline comments in the
test file getGuestBusyTimesForReschedule.test.ts: delete the comments that
merely restate the test name/assertions (the ones flagged in the review and the
two additional occurrences) so each test relies on its descriptive test name and
assertions instead of duplicative commentary; ensure no functional code is
changed, only remove those comment lines.
In `@packages/trpc/server/routers/viewer/slots/util.ts`:
- Around line 802-817: The guest-busy lookup
(this.getGuestBusyTimesForReschedule) is being repeatedly called when
calculateHostsAndAvailabilities is used in the round-robin fallback flow;
memoize or hoist the lookup so it runs once per date-range/reschedule-key and
reuse it across fallback passes. Implement a small in-memory cache keyed by the
tuple (rescheduleUid, rescheduledBy, schedulingType, dateFrom.toISOString(),
dateTo.toISOString()) inside _getAvailableSlots (or higher) and check the cache
before calling getGuestBusyTimesForReschedule; ensure
calculateHostsAndAvailabilities accepts the precomputed guestBusyTimes (or reads
from the cache) so the DB/attendee-resolution work is not repeated.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2f7462f5-faf8-4914-91b4-d790ab281d88
📒 Files selected for processing (10)
apps/web/modules/schedules/hooks/useEvent.tsapps/web/modules/schedules/hooks/useSchedule.tspackages/features/availability/lib/getUserAvailability.tspackages/features/bookings/repositories/BookingRepository.test.tspackages/features/bookings/repositories/BookingRepository.tspackages/features/users/repositories/UserRepository.test.tspackages/features/users/repositories/UserRepository.tspackages/trpc/server/routers/viewer/slots/getGuestBusyTimesForReschedule.test.tspackages/trpc/server/routers/viewer/slots/types.tspackages/trpc/server/routers/viewer/slots/util.ts
| timeZone: timezone ?? "PLACEHOLDER_TIMEZONE", | ||
| duration: duration ? `${duration}` : undefined, | ||
| rescheduleUid, | ||
| rescheduledBy, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) Locate useApiV2AvailableSlots hook file"
HOOK_FILE="$(fd -i 'useApiV2AvailableSlots.ts' | head -n1 || true)"
if [ -z "${HOOK_FILE}" ]; then
echo "useApiV2AvailableSlots.ts not found"
else
echo "Found: ${HOOK_FILE}"
echo
echo "2) Inspect hook arg types and request serialization for rescheduledBy"
rg -n -C4 --type=ts '\brescheduledBy\b|useApiV2AvailableSlots|available.?slots|query|params|body' "${HOOK_FILE}" || true
fi
echo
echo "3) Find server-side API V2 available-slots schema/handler references to rescheduledBy"
rg -n -C4 --type=ts '\brescheduledBy\b' apps packages | rg -n 'available.?slots|slots|v2|schema|router|handler' || trueRepository: calcom/cal.diy
Length of output: 45708
🏁 Script executed:
# Find the platform types definition for GetAvailableSlotsInput_2024_04_15
fd -i 'platform-types' --type d | head -5Repository: calcom/cal.diy
Length of output: 40
🏁 Script executed:
# Search for GetAvailableSlotsInput_2024_04_15 definition
rg -n 'GetAvailableSlotsInput_2024_04_15' --type=ts -A 20 | head -100Repository: calcom/cal.diy
Length of output: 10179
🏁 Script executed:
# Find the API V2 slots handler
fd -i '*slots*' -type f -path '*/api/v2/*' | grep -E '\.(ts|controller|handler)' | head -20Repository: calcom/cal.diy
Length of output: 228
🏁 Script executed:
# Get complete GetAvailableSlotsInput_2024_04_15 type definition
cat packages/platform/types/slots/slots-2024-04-15/inputs/index.tsRepository: calcom/cal.diy
Length of output: 6106
🏁 Script executed:
# Check how API V2 available slots service handles the input
rg -n 'rescheduledBy' apps/api/v2/src/lib/services/available-slots.service.ts --context=5Repository: calcom/cal.diy
Length of output: 40
🏁 Script executed:
# Verify tRPC slots service actually uses rescheduledBy in implementation
rg -n 'rescheduledBy' packages/trpc/server/routers/viewer/slots/util.ts -B 2 -A 10 | head -60Repository: calcom/cal.diy
Length of output: 1891
🏁 Script executed:
# Check if there's any other API V2 slots endpoint or service that might handle reschedule logic
rg -rn 'available.*slot' apps/api/v2/src/lib/services/ --type=ts | grep -i 'reschedule\|service'Repository: calcom/cal.diy
Length of output: 40
API V2 /slots/available endpoint lacks rescheduledBy support, creating functional divergence with tRPC in host-reschedule guest-gating logic.
The API V2 GetAvailableSlotsInput_2024_04_15 type definition does not include a rescheduledBy field, so the endpoint cannot accept this parameter. Meanwhile, the tRPC slots endpoint uses rescheduledBy to determine whether guest busy-times should be included in available slots—only applying blocking when the reschedule is initiated by the host (matching emails case-insensitively). Without this parameter in API V2, the endpoint applies guest busy-time blocking uniformly, regardless of who initiated the reschedule.
To achieve parity, either:
- Add
rescheduledByto the API V2 input type and implement the same host-reschedule detection logic, or - Accept that API V2 diverges intentionally and document the difference.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/modules/schedules/hooks/useSchedule.ts` at line 107, The API V2
input type GetAvailableSlotsInput_2024_04_15 and the /slots/available handler
need to accept and use a rescheduledBy field to match tRPC behavior: add an
optional rescheduledBy:string to GetAvailableSlotsInput_2024_04_15, surface it
through the handler that powers /slots/available, and implement the same
host-reschedule detection logic (case-insensitive compare against host email)
used by the tRPC slots endpoint so guest busy-times are only blocked when the
reschedule was initiated by the host; alternatively, if divergence is
intentional, update the API docs/comments in the /slots/available handler and
GetAvailableSlotsInput_2024_04_15 to explicitly state the behavioral difference.
| const guestBusyTimesFormatted: EventBusyDetails[] = (initialData?.guestBusyTimes ?? []).map((t) => ({ | ||
| start: dayjs.utc(t.start).toISOString(), | ||
| end: dayjs.utc(t.end).toISOString(), | ||
| title: "Guest busy", | ||
| source: withSource ? "guest-availability" : "", | ||
| })); |
There was a problem hiding this comment.
Replace hardcoded "Guest busy" with an i18n key-backed string.
This introduces a user-facing English literal in a TypeScript path; please route it through localization resources instead of embedding raw text.
As per coding guidelines, "Add translations to packages/i18n/locales/en/common.json for all UI strings".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/features/availability/lib/getUserAvailability.ts` around lines 621 -
626, Replace the hardcoded "Guest busy" string in the guestBusyTimesFormatted
mapping with a localization key lookup: add a new key (e.g.
"availability.guestBusy") to packages/i18n/locales/en/common.json and use the
i18n accessor used across the codebase (the project's translate/i18n function)
in getUserAvailability.ts when building EventBusyDetails (replace the literal in
guestBusyTimesFormatted with the i18n call), ensuring fallback to the English
string if translation is missing and keeping the rest of the object shape
unchanged.
| it("should query bookings by userId when userIds are provided", async () => { | ||
| const mockBookings = [ | ||
| { | ||
| uid: "booking-1", | ||
| startTime: new Date("2026-04-10T09:00:00Z"), | ||
| endTime: new Date("2026-04-10T10:00:00Z"), | ||
| title: "Meeting", | ||
| userId: 10, | ||
| status: BookingStatus.ACCEPTED, | ||
| }, | ||
| ]; | ||
| mockPrismaClient.booking.findMany.mockResolvedValue(mockBookings); | ||
|
|
||
| const result = await repository.findByUserIdsAndDateRange({ | ||
| userIds: [10], | ||
| userEmails: [], | ||
| dateFrom, | ||
| dateTo, | ||
| }); | ||
|
|
||
| expect(result).toEqual(mockBookings); | ||
| expect(mockPrismaClient.booking.findMany).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| where: expect.objectContaining({ | ||
| status: { in: [BookingStatus.ACCEPTED, BookingStatus.PENDING] }, | ||
| AND: [{ startTime: { lt: dateTo } }, { endTime: { gt: dateFrom } }], | ||
| }), | ||
| }) | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Assert the userId predicate explicitly in the userId-only case.
Right now this test can pass even if the repository forgets to filter by userIds and only keeps status/date overlap.
Suggested test hardening
const result = await repository.findByUserIdsAndDateRange({
userIds: [10],
userEmails: [],
dateFrom,
dateTo,
});
expect(result).toEqual(mockBookings);
- expect(mockPrismaClient.booking.findMany).toHaveBeenCalledWith(
- expect.objectContaining({
- where: expect.objectContaining({
- status: { in: [BookingStatus.ACCEPTED, BookingStatus.PENDING] },
- AND: [{ startTime: { lt: dateTo } }, { endTime: { gt: dateFrom } }],
- }),
- })
- );
+ const callArgs = mockPrismaClient.booking.findMany.mock.calls[0][0];
+ expect(callArgs.where).toEqual(
+ expect.objectContaining({
+ status: { in: [BookingStatus.ACCEPTED, BookingStatus.PENDING] },
+ AND: [{ startTime: { lt: dateTo } }, { endTime: { gt: dateFrom } }],
+ })
+ );
+ expect(JSON.stringify(callArgs.where)).toContain('"userId"');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/features/bookings/repositories/BookingRepository.test.ts` around
lines 120 - 149, In the userId-only test case in BookingRepository.test.ts,
explicitly assert the userId predicate so the test fails if filtering by userIds
is omitted: after calling the repository method under test (the "userId-only"
scenario), iterate the returned bookings and assert booking.userId ===
expectedUserId for each result (and/or assert the set of returned userIds equals
the single expectedUserId); this ensures the test not only checks status/date
overlap but also enforces that only bookings for the specified user are
returned.
Third of three PRs splitting #28636. Depends on #28908 (Part A) and #28909 (Part B). This branch currently carries A+B+C so the test suite runs end to end; I'll rebase down to the Part C diff once A and B merge.
When the host reschedules a booking, the system now checks whether any attendee is a Cal.com user and fetches their busy times. Only mutually available slots are shown. If an attendee reschedules (rescheduledBy doesn't match the host email), all slots are shown, per @CarinaWolli's scope in #16378.
_getGuestBusyTimesForReschedule short-circuits on missing rescheduleUid or COLLECTIVE scheduling. Otherwise it loads the original booking, checks whether rescheduledBy matches the host email (case-insensitive), and fetches overlapping bookings for Cal.com-user attendees. Failures log at warn and don't block rescheduling. In getUserAvailability.ts, guestBusyTimes joins the EventBusyDetails list.
Tests in getGuestBusyTimesForReschedule.test.ts cover host vs attendee initiators, case-insensitive matching, backwards compat when rescheduledBy is absent, multi-guest merging, excludeUid, and every failure path.
/claim #16378 (bounty tracked on the original #28636)