feat: enforce guest availability on host-initiated reschedules (non-collective) - CAL-4531 #28849
feat: enforce guest availability on host-initiated reschedules (non-collective) - CAL-4531 #28849simbabimba-dev wants to merge 8 commits intocalcom:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses CAL-4531 (#16378) by enforcing guest availability during host-initiated, non-collective reschedules, preventing hosts from selecting slots that would double-book the guest.
Changes:
- Propagates and normalizes
rescheduledBythrough the schedule/reschedule flow and input schema. - Resolves the reschedule “guest” (first non-organizer attendee), including verified secondary-email matching, and injects them into the existing availability pipeline to enforce conflicts.
- Adds/updates unit + integration tests for host vs attendee initiated reschedule, secondary-email handling, collective bypass, and current-slot preservation.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/trpc/server/routers/viewer/slots/util.ts | Adds guest resolution + host-initiated gating, and injects guest into availability calculation for reschedule slot filtering. |
| packages/trpc/server/routers/viewer/slots/util.test.ts | Adds unit tests for _getRescheduleGuestUser behavior. |
| packages/trpc/server/routers/viewer/slots/types.ts | Adds rescheduledBy to schedule input schema with normalization. |
| packages/features/users/repositories/UserRepository.ts | Adds findAvailabilityUserByEmail with verified secondary-email fallback for guest resolution. |
| packages/features/bookings/repositories/BookingRepository.ts | Adds seat-reference UID lookup to resolve seated-event reschedule UIDs to underlying booking. |
| apps/web/test/lib/getSchedule.test.ts | Adds integration tests covering reschedule guest filtering behavior and edge cases. |
| apps/web/modules/schedules/hooks/useSchedule.ts | Threads rescheduledBy from query params into schedule input. |
| apps/web/lib/reschedule/[uid]/getServerSideProps.ts | Ensures rescheduledBy is derived from session when present (and propagated into the destination URL). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📝 WalkthroughWalkthroughAdds a 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
🧹 Nitpick comments (1)
packages/trpc/server/routers/viewer/slots/util.test.ts (1)
102-323: Extract a helper to avoid repeated private-method casting.The same
service as unknown as { _getRescheduleGuestUser... }block is repeated across tests (for example, Line 102 and Line 148 onward). A single typed helper would reduce duplication and keep each test focused on behavior.♻️ Suggested cleanup
+const callGetRescheduleGuestUser = (args: { + rescheduleUid?: string | null; + organizerEmails: string[]; + schedulingType: SchedulingType | null; + rescheduledBy?: string | null; +}) => + ( + service as unknown as { + _getRescheduleGuestUser: (input: typeof args) => Promise<unknown>; + } + )._getRescheduleGuestUser(args); -const result = await ( - service as unknown as { - _getRescheduleGuestUser: (args: { - rescheduleUid?: string | null; - organizerEmails: string[]; - schedulingType: SchedulingType | null; - rescheduledBy?: string | null; - }) => Promise<unknown>; - } -)._getRescheduleGuestUser({ +const result = await callGetRescheduleGuestUser({ rescheduleUid: "BOOKING_TO_RESCHEDULE_UID", organizerEmails: ["host@example.com"], schedulingType: SchedulingType.ROUND_ROBIN, rescheduledBy: "host@example.com", });🤖 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.test.ts` around lines 102 - 323, Tests repeatedly cast service to an inline type to call the private method _getRescheduleGuestUser; extract a single typed helper (e.g., const typedService = service as unknown as { _getRescheduleGuestUser: (...) => Promise<unknown> } or a function getRescheduleGuestUser(args) that delegates to (typedService)._getRescheduleGuestUser) at the top of the test file and replace all repeated casts with calls to that helper so each test calls typedService._getRescheduleGuestUser(...) or getRescheduleGuestUser(...) instead of repeating the long cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/trpc/server/routers/viewer/slots/util.test.ts`:
- Around line 102-323: Tests repeatedly cast service to an inline type to call
the private method _getRescheduleGuestUser; extract a single typed helper (e.g.,
const typedService = service as unknown as { _getRescheduleGuestUser: (...) =>
Promise<unknown> } or a function getRescheduleGuestUser(args) that delegates to
(typedService)._getRescheduleGuestUser) at the top of the test file and replace
all repeated casts with calls to that helper so each test calls
typedService._getRescheduleGuestUser(...) or getRescheduleGuestUser(...) instead
of repeating the long cast.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bd6991c2-3e9a-4d2b-849b-ab0498d5194c
📒 Files selected for processing (8)
apps/web/lib/reschedule/[uid]/getServerSideProps.tsapps/web/modules/schedules/hooks/useSchedule.tsapps/web/test/lib/getSchedule.test.tspackages/features/bookings/repositories/BookingRepository.tspackages/features/users/repositories/UserRepository.tspackages/trpc/server/routers/viewer/slots/types.tspackages/trpc/server/routers/viewer/slots/util.test.tspackages/trpc/server/routers/viewer/slots/util.ts
…hedules Address follow-up review feedback for CAL-4531. - Prevent injected reschedule guests from forcing team-style aggregation for schedulingType=null - Aggregate host availability as single-host, then intersect with guest oooExcluded ranges - Add regression test for single-host OOO semantics - Keep util.test dependency typing CI-safe via explicit cast
… reschedule tests
|
Hey @dhairyashiil @keithwillcode, PR is ready for review. |
… in getRescheduleGuestUser
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/trpc/server/routers/viewer/slots/util.test.ts (1)
380-404: Add two small edge-case tests to lock the non-collective default path.You already test
SchedulingType.COLLECTIVEandrescheduledBy: null; I’d also add explicit coverage forschedulingType: null(host-initiated should still enforce) and missingrescheduleUid(should early-returnnull).Proposed test additions
+ it("returns guest for host-initiated reschedule when schedulingType is null (non-collective)", async () => { + findByUidIncludeEventType.mockResolvedValue({ + user: { email: "host@example.com" }, + attendees: [{ email: "guest@example.com" }], + }); + findAvailabilityUserByEmail.mockResolvedValue({ + id: 102, + email: "guest@example.com", + }); + + const result = await getRescheduleGuestUser({ + rescheduleUid: "BOOKING_TO_RESCHEDULE_UID", + organizerEmails: ["host@example.com"], + schedulingType: null, + rescheduledBy: "host@example.com", + }); + + expect(result).toEqual({ + id: 102, + email: "guest@example.com", + }); + }); + + it("returns null when rescheduleUid is missing", async () => { + const result = await getRescheduleGuestUser({ + organizerEmails: ["host@example.com"], + schedulingType: SchedulingType.ROUND_ROBIN, + rescheduledBy: "host@example.com", + }); + + expect(result).toBeNull(); + expect(findByUidIncludeEventType).not.toHaveBeenCalled(); + expect(findBySeatReferenceUidIncludeEventType).not.toHaveBeenCalled(); + expect(findAvailabilityUserByEmail).not.toHaveBeenCalled(); + });🤖 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.test.ts` around lines 380 - 404, Tests lack coverage for two edge cases in getRescheduleGuestUser: when schedulingType is null and when rescheduleUid is missing; add two new tests mirroring the existing ones that assert the function returns null and does not call findByUidIncludeEventType or findAvailabilityUserByEmail. Specifically, add one test calling getRescheduleGuestUser with schedulingType: null (organizerEmails and rescheduledBy set to host@example.com) and another with rescheduleUid: null (schedulingType set to SchedulingType.ROUND_ROBIN and rescheduledBy provided), and ensure both expect null result and that findByUidIncludeEventType and findAvailabilityUserByEmail were not called.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/trpc/server/routers/viewer/slots/util.test.ts`:
- Around line 380-404: Tests lack coverage for two edge cases in
getRescheduleGuestUser: when schedulingType is null and when rescheduleUid is
missing; add two new tests mirroring the existing ones that assert the function
returns null and does not call findByUidIncludeEventType or
findAvailabilityUserByEmail. Specifically, add one test calling
getRescheduleGuestUser with schedulingType: null (organizerEmails and
rescheduledBy set to host@example.com) and another with rescheduleUid: null
(schedulingType set to SchedulingType.ROUND_ROBIN and rescheduledBy provided),
and ensure both expect null result and that findByUidIncludeEventType and
findAvailabilityUserByEmail were not called.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 799cfbad-738e-4da7-a391-2d94573e8e71
📒 Files selected for processing (1)
packages/trpc/server/routers/viewer/slots/util.test.ts
|
@cubic-dev-ai review this |
Summary
This PR fixes CAL-4531 (Fixes #16378) by enforcing guest availability when a host reschedules a booking.
When the host initiates a non-collective reschedule, slots are filtered against guest availability to prevent guest double-booking.
This implementation is intentionally scoped and reuses the existing availability engine instead of introducing a separate guestBusyTimes pipeline.
Changes Made
Why this approach
Comparison to PR #26811 and PR #27710
Type of Change
Test Plan
Scope Note
Current scope resolves the first non-organizer attendee for guest enforcement.
If product needs multi-attendee enforcement, that can be added in a follow-up without changing this PR core design.
Demo
I will shortly add a video demo showing:
Before the fix:
In this booking, the pro user is the host and the free user is the attendee. When the host initiates the reschedule, available time slots are currently not evaluated against the attendee’s existing availability, so conflicting slots are still returned, resulting in a potential double booking.
After the fix:
Now, The host (pro user) is prevented from selecting time slots that conflict with the attendee’s existing bookings. This ensures attendee availability is properly enforced during host-initiated reschedules, eliminating the possibility of double-booking.
Fixes #16378
Refs CAL-4531
Related: #26811, #27710
/claim #16378