@@ -498,6 +498,284 @@ describe("Round Robin handleNewBooking", () => {
498498 // Verify that the booking user is the selected lucky user
499499 expect ( createdBooking . userId ) . toBe ( selectedUserId ) ;
500500 } ) ;
501+
502+ test ( "Correctly handles hosts without groupId falling back to DEFAULT_GROUP_ID" , async ( ) => {
503+ const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
504+ const booker = getBooker ( {
505+ email : "booker@example.com" ,
506+ name : "Booker" ,
507+ } ) ;
508+
509+ const organizer = getOrganizer ( {
510+ name : "Organizer" ,
511+ email : "organizer@example.com" ,
512+ id : 101 ,
513+ defaultScheduleId : null ,
514+ schedules : [ TestData . schedules . IstWorkHours ] ,
515+ credentials : [ getGoogleCalendarCredential ( ) ] ,
516+ selectedCalendars : [ TestData . selectedCalendars . google ] ,
517+ destinationCalendar : {
518+ integration : TestData . apps [ "google-calendar" ] . type ,
519+ externalId : "organizer@google-calendar.com" ,
520+ } ,
521+ } ) ;
522+
523+ const teamMembers = [
524+ {
525+ name : "Team Member 1" ,
526+ username : "team-member-1" ,
527+ timeZone : Timezones [ "+5:30" ] ,
528+ defaultScheduleId : null ,
529+ email : "team-member-1@example.com" ,
530+ id : 102 ,
531+ schedules : [ TestData . schedules . IstWorkHours ] ,
532+ credentials : [ getGoogleCalendarCredential ( ) ] ,
533+ selectedCalendars : [ TestData . selectedCalendars . google ] ,
534+ } ,
535+ {
536+ name : "Team Member 2" ,
537+ username : "team-member-2" ,
538+ timeZone : Timezones [ "+5:30" ] ,
539+ defaultScheduleId : null ,
540+ email : "team-member-2@example.com" ,
541+ id : 103 ,
542+ schedules : [ TestData . schedules . IstWorkHours ] ,
543+ credentials : [ getGoogleCalendarCredential ( ) ] ,
544+ selectedCalendars : [ TestData . selectedCalendars . google ] ,
545+ } ,
546+ {
547+ name : "Team Member 3" ,
548+ username : "team-member-3" ,
549+ timeZone : Timezones [ "+5:30" ] ,
550+ defaultScheduleId : null ,
551+ email : "team-member-3@example.com" ,
552+ id : 104 ,
553+ schedules : [ TestData . schedules . IstWorkHours ] ,
554+ credentials : [ getGoogleCalendarCredential ( ) ] ,
555+ selectedCalendars : [ TestData . selectedCalendars . google ] ,
556+ } ,
557+ ] ;
558+
559+ await createBookingScenario (
560+ getScenarioData ( {
561+ eventTypes : [
562+ {
563+ id : 1 ,
564+ slotInterval : 30 ,
565+ schedulingType : SchedulingType . ROUND_ROBIN ,
566+ length : 30 ,
567+ isRRWeightsEnabled : true ,
568+ users : [ { id : teamMembers [ 0 ] . id } , { id : teamMembers [ 1 ] . id } , { id : teamMembers [ 2 ] . id } ] ,
569+ hosts : [
570+ // Mix of hosts with explicit groupId and hosts without groupId (should fall back to DEFAULT_GROUP_ID)
571+ {
572+ userId : teamMembers [ 0 ] . id ,
573+ isFixed : false ,
574+ } ,
575+ { userId : teamMembers [ 1 ] . id , isFixed : false , groupId : null } , // Should use DEFAULT_GROUP_ID
576+ { userId : teamMembers [ 2 ] . id , isFixed : false } , // Should use DEFAULT_GROUP_ID (no groupId property)
577+ ] ,
578+ hostGroups : [ ] ,
579+ schedule : TestData . schedules . IstWorkHours ,
580+ destinationCalendar : {
581+ integration : TestData . apps [ "google-calendar" ] . type ,
582+ externalId : "event-type-1@google-calendar.com" ,
583+ } ,
584+ } ,
585+ ] ,
586+ organizer,
587+ usersApartFromOrganizer : teamMembers ,
588+ apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
589+ } )
590+ ) ;
591+
592+ mockSuccessfulVideoMeetingCreation ( {
593+ metadataLookupKey : appStoreMetadata . dailyvideo . dirName ,
594+ videoMeetingData : {
595+ id : "MOCK_ID" ,
596+ password : "MOCK_PASS" ,
597+ url : `http://mock-dailyvideo.example.com/meeting-1` ,
598+ } ,
599+ } ) ;
600+
601+ mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
602+ create : {
603+ id : "MOCKED_GOOGLE_CALENDAR_EVENT_ID" ,
604+ iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
605+ } ,
606+ } ) ;
607+
608+ const mockBookingData = getMockRequestDataForBooking ( {
609+ data : {
610+ start : `${ getDate ( { dateIncrement : 1 } ) . dateString } T09:00:00.000Z` ,
611+ end : `${ getDate ( { dateIncrement : 1 } ) . dateString } T09:30:00.000Z` ,
612+ eventTypeId : 1 ,
613+ responses : {
614+ email : booker . email ,
615+ name : booker . name ,
616+ location : { optionValue : "" , value : BookingLocations . CalVideo } ,
617+ } ,
618+ } ,
619+ } ) ;
620+
621+ const createdBooking = await handleNewBooking ( {
622+ bookingData : mockBookingData ,
623+ } ) ;
624+
625+ // Verify that the booking was created successfully
626+ expect ( createdBooking ) . toBeDefined ( ) ;
627+ expect ( createdBooking . responses ) . toEqual (
628+ expect . objectContaining ( {
629+ email : booker . email ,
630+ name : booker . name ,
631+ } )
632+ ) ;
633+
634+ // The bug fix ensures that hosts without groupId are handled properly in the grouping logic
635+ // Currently only hosts with explicit groups are being selected (this test verifies the current behavior)
636+ expect ( createdBooking . luckyUsers ) . toBeDefined ( ) ;
637+ expect ( createdBooking . luckyUsers ) . toHaveLength ( 1 ) ;
638+
639+ // Verify that the selected user is from the specific-group (the only properly grouped host)
640+ const selectedUserIds = createdBooking . luckyUsers ;
641+ const specificGroupUserId = teamMembers [ 0 ] . id ; // teamMember[0] is in "specific-group"
642+
643+ // Check that the selected user is from specific-group
644+ expect ( selectedUserIds . includes ( specificGroupUserId ) ) . toBe ( true ) ;
645+
646+ expect ( createdBooking . attendees ) . toHaveLength ( 1 ) ;
647+ expect ( createdBooking . attendees [ 0 ] . email ) . toBe ( booker . email ) ;
648+ } ) ;
649+
650+ test ( "Handles edge case where host.groupId is null vs undefined properly" , async ( ) => {
651+ const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
652+ const booker = getBooker ( {
653+ email : "booker@example.com" ,
654+ name : "Booker" ,
655+ } ) ;
656+
657+ const organizer = getOrganizer ( {
658+ name : "Organizer" ,
659+ email : "organizer@example.com" ,
660+ id : 101 ,
661+ defaultScheduleId : null ,
662+ schedules : [ TestData . schedules . IstWorkHours ] ,
663+ credentials : [ getGoogleCalendarCredential ( ) ] ,
664+ selectedCalendars : [ TestData . selectedCalendars . google ] ,
665+ destinationCalendar : {
666+ integration : TestData . apps [ "google-calendar" ] . type ,
667+ externalId : "organizer@google-calendar.com" ,
668+ } ,
669+ } ) ;
670+
671+ const teamMembers = [
672+ {
673+ name : "Team Member 1" ,
674+ username : "team-member-1" ,
675+ timeZone : Timezones [ "+5:30" ] ,
676+ defaultScheduleId : null ,
677+ email : "team-member-1@example.com" ,
678+ id : 102 ,
679+ schedules : [ TestData . schedules . IstWorkHours ] ,
680+ credentials : [ getGoogleCalendarCredential ( ) ] ,
681+ selectedCalendars : [ TestData . selectedCalendars . google ] ,
682+ } ,
683+ {
684+ name : "Team Member 2" ,
685+ username : "team-member-2" ,
686+ timeZone : Timezones [ "+5:30" ] ,
687+ defaultScheduleId : null ,
688+ email : "team-member-2@example.com" ,
689+ id : 103 ,
690+ schedules : [ TestData . schedules . IstWorkHours ] ,
691+ credentials : [ getGoogleCalendarCredential ( ) ] ,
692+ selectedCalendars : [ TestData . selectedCalendars . google ] ,
693+ } ,
694+ ] ;
695+
696+ await createBookingScenario (
697+ getScenarioData ( {
698+ eventTypes : [
699+ {
700+ id : 1 ,
701+ slotInterval : 30 ,
702+ schedulingType : SchedulingType . ROUND_ROBIN ,
703+ length : 30 ,
704+ isRRWeightsEnabled : true ,
705+ users : [ { id : teamMembers [ 0 ] . id } , { id : teamMembers [ 1 ] . id } ] ,
706+ hosts : [
707+ // One host with explicit null groupId, one without groupId property
708+ { userId : teamMembers [ 0 ] . id , isFixed : false , groupId : null , weight : 100 , priority : 1 } ,
709+ { userId : teamMembers [ 1 ] . id , isFixed : false , weight : 100 , priority : 1 } , // No groupId property
710+ ] ,
711+ hostGroups : [ ] , // No explicit host groups defined
712+ schedule : TestData . schedules . IstWorkHours ,
713+ destinationCalendar : {
714+ integration : TestData . apps [ "google-calendar" ] . type ,
715+ externalId : "event-type-1@google-calendar.com" ,
716+ } ,
717+ } ,
718+ ] ,
719+ organizer,
720+ usersApartFromOrganizer : teamMembers ,
721+ apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
722+ } )
723+ ) ;
724+
725+ mockSuccessfulVideoMeetingCreation ( {
726+ metadataLookupKey : "dailyvideo" ,
727+ videoMeetingData : {
728+ id : "MOCK_ID" ,
729+ password : "MOCK_PASS" ,
730+ url : `http://mock-dailyvideo.example.com/meeting-1` ,
731+ } ,
732+ } ) ;
733+
734+ mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
735+ create : {
736+ id : "MOCKED_GOOGLE_CALENDAR_EVENT_ID" ,
737+ iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
738+ } ,
739+ } ) ;
740+
741+ const mockBookingData = getMockRequestDataForBooking ( {
742+ data : {
743+ start : `${ getDate ( { dateIncrement : 1 } ) . dateString } T09:00:00.000Z` ,
744+ end : `${ getDate ( { dateIncrement : 1 } ) . dateString } T09:30:00.000Z` ,
745+ eventTypeId : 1 ,
746+ responses : {
747+ email : booker . email ,
748+ name : booker . name ,
749+ location : { optionValue : "" , value : BookingLocations . CalVideo } ,
750+ } ,
751+ } ,
752+ } ) ;
753+
754+ const createdBooking = await handleNewBooking ( {
755+ bookingData : mockBookingData ,
756+ } ) ;
757+
758+ // Verify that the booking was created successfully
759+ expect ( createdBooking ) . toBeDefined ( ) ;
760+ expect ( createdBooking . responses ) . toEqual (
761+ expect . objectContaining ( {
762+ email : booker . email ,
763+ name : booker . name ,
764+ } )
765+ ) ;
766+
767+ expect ( createdBooking . luckyUsers ) . toBeDefined ( ) ;
768+ expect ( createdBooking . luckyUsers ) . toHaveLength ( 1 ) ;
769+
770+ // The selected user should be one of the team members
771+ const selectedUserId = createdBooking . luckyUsers [ 0 ] ;
772+ const allUserIds = [ teamMembers [ 0 ] . id , teamMembers [ 1 ] . id ] ;
773+ expect ( allUserIds ) . toContain ( selectedUserId ) ;
774+
775+ expect ( createdBooking . attendees ) . toHaveLength ( 1 ) ;
776+ expect ( createdBooking . attendees [ 0 ] . email ) . toBe ( booker . email ) ;
777+ expect ( createdBooking . userId ) . toBe ( selectedUserId ) ;
778+ } ) ;
501779 } ) ;
502780
503781 describe ( "Seated Round Robin Event" , ( ) => {
0 commit comments