@@ -12,6 +12,7 @@ import {
1212 BookingLocations ,
1313 getDate ,
1414 getMockBookingAttendee ,
15+ mockCalendarToHaveNoBusySlots ,
1516} from "@calcom/testing/lib/bookingScenario/bookingScenario" ;
1617import { createMockNextJsRequest } from "@calcom/testing/lib/bookingScenario/createMockNextJsRequest" ;
1718import { getMockRequestDataForBooking } from "@calcom/testing/lib/bookingScenario/getMockRequestDataForBooking" ;
@@ -21,6 +22,7 @@ import { setupAndTeardown } from "@calcom/testing/lib/bookingScenario/setupAndTe
2122import { describe , test , vi , expect } from "vitest" ;
2223
2324import { appStoreMetadata } from "@calcom/app-store/apps.metadata.generated" ;
25+ import * as emailManager from "@calcom/emails/email-manager" ;
2426import { ErrorCode } from "@calcom/lib/errorCodes" ;
2527import { SchedulingType } from "@calcom/prisma/enums" ;
2628import { BookingStatus } from "@calcom/prisma/enums" ;
@@ -1252,6 +1254,317 @@ describe("handleSeats", () => {
12521254 expect ( attendeeSeat ?. bookingId ) . toEqual ( secondBookingId ) ;
12531255 } ) ;
12541256
1257+ test ( "When rescheduling to an existing booking, reschedule email includes meeting URL" , async ( ) => {
1258+ const handleNewBooking = getNewBookingHandler ( ) ;
1259+ const sendRescheduledSeatEmailSpy = vi . spyOn ( emailManager , "sendRescheduledSeatEmailAndSMS" ) ;
1260+
1261+ const attendeeToReschedule = getMockBookingAttendee ( {
1262+ id : 2 ,
1263+ name : "Seat 2" ,
1264+ email : "seat2@test.com" ,
1265+ locale : "en" ,
1266+ timeZone : "America/Toronto" ,
1267+ bookingSeat : {
1268+ referenceUid : "booking-seat-2" ,
1269+ data : { } ,
1270+ } ,
1271+ } ) ;
1272+
1273+ const booker = getBooker ( {
1274+ email : attendeeToReschedule . email ,
1275+ name : attendeeToReschedule . name ,
1276+ } ) ;
1277+
1278+ const organizer = getOrganizer ( {
1279+ name : "Organizer" ,
1280+ email : "organizer@example.com" ,
1281+ id : 101 ,
1282+ schedules : [ TestData . schedules . IstWorkHours ] ,
1283+ } ) ;
1284+
1285+ const firstBookingId = 1 ;
1286+ const firstBookingUid = "abc123" ;
1287+ const { dateString : plus1DateString } = getDate ( { dateIncrement : 1 } ) ;
1288+ const firstBookingStartTime = `${ plus1DateString } T04:00:00Z` ;
1289+ const firstBookingEndTime = `${ plus1DateString } T04:30:00Z` ;
1290+
1291+ const secondBookingId = 2 ;
1292+ const secondBookingUid = "def456" ;
1293+ const { dateString : plus2DateString } = getDate ( { dateIncrement : 2 } ) ;
1294+ const secondBookingStartTime = `${ plus2DateString } T04:00:00Z` ;
1295+ const secondBookingEndTime = `${ plus2DateString } T04:30:00Z` ;
1296+
1297+ const meetingUrl = "http://mock-dailyvideo.example.com/meeting-with-url" ;
1298+
1299+ await createBookingScenario (
1300+ getScenarioData ( {
1301+ eventTypes : [
1302+ {
1303+ id : 1 ,
1304+ slug : "seated-event" ,
1305+ slotInterval : 30 ,
1306+ length : 30 ,
1307+ users : [
1308+ {
1309+ id : 101 ,
1310+ } ,
1311+ ] ,
1312+ seatsPerTimeSlot : 3 ,
1313+ seatsShowAttendees : false ,
1314+ } ,
1315+ ] ,
1316+ bookings : [
1317+ {
1318+ id : firstBookingId ,
1319+ uid : firstBookingUid ,
1320+ eventTypeId : 1 ,
1321+ status : BookingStatus . ACCEPTED ,
1322+ startTime : firstBookingStartTime ,
1323+ endTime : firstBookingEndTime ,
1324+ metadata : {
1325+ videoCallUrl : meetingUrl ,
1326+ } ,
1327+ references : [
1328+ {
1329+ type : appStoreMetadata . dailyvideo . type ,
1330+ uid : "MOCK_ID" ,
1331+ meetingId : "MOCK_ID" ,
1332+ meetingPassword : "MOCK_PASS" ,
1333+ meetingUrl : meetingUrl ,
1334+ credentialId : null ,
1335+ } ,
1336+ ] ,
1337+ attendees : [
1338+ getMockBookingAttendee ( {
1339+ id : 1 ,
1340+ name : "Seat 1" ,
1341+ email : "seat1@test.com" ,
1342+ locale : "en" ,
1343+ timeZone : "America/Toronto" ,
1344+ bookingSeat : {
1345+ referenceUid : "booking-seat-1" ,
1346+ data : { } ,
1347+ } ,
1348+ } ) ,
1349+ attendeeToReschedule ,
1350+ ] ,
1351+ } ,
1352+ {
1353+ id : secondBookingId ,
1354+ uid : secondBookingUid ,
1355+ eventTypeId : 1 ,
1356+ status : BookingStatus . ACCEPTED ,
1357+ startTime : secondBookingStartTime ,
1358+ endTime : secondBookingEndTime ,
1359+ metadata : {
1360+ videoCallUrl : meetingUrl ,
1361+ } ,
1362+ references : [
1363+ {
1364+ type : appStoreMetadata . dailyvideo . type ,
1365+ uid : "MOCK_ID_2" ,
1366+ meetingId : "MOCK_ID_2" ,
1367+ meetingPassword : "MOCK_PASS_2" ,
1368+ meetingUrl : meetingUrl ,
1369+ credentialId : null ,
1370+ } ,
1371+ ] ,
1372+ attendees : [
1373+ getMockBookingAttendee ( {
1374+ id : 3 ,
1375+ name : "Seat 3" ,
1376+ email : "seat3@test.com" ,
1377+ locale : "en" ,
1378+ timeZone : "America/Toronto" ,
1379+ bookingSeat : {
1380+ referenceUid : "booking-seat-3" ,
1381+ data : { } ,
1382+ } ,
1383+ } ) ,
1384+ ] ,
1385+ } ,
1386+ ] ,
1387+ organizer,
1388+ } )
1389+ ) ;
1390+
1391+ mockSuccessfulVideoMeetingCreation ( {
1392+ metadataLookupKey : "dailyvideo" ,
1393+ videoMeetingData : {
1394+ id : "MOCK_ID" ,
1395+ password : "MOCK_PASS" ,
1396+ url : meetingUrl ,
1397+ } ,
1398+ } ) ;
1399+
1400+ const reqBookingUser = "seatedAttendee" ;
1401+
1402+ const mockBookingData = getMockRequestDataForBooking ( {
1403+ data : {
1404+ eventTypeId : 1 ,
1405+ responses : {
1406+ email : booker . email ,
1407+ name : booker . name ,
1408+ location : { optionValue : "" , value : BookingLocations . CalVideo } ,
1409+ } ,
1410+ rescheduleUid : "booking-seat-2" ,
1411+ start : secondBookingStartTime ,
1412+ end : secondBookingEndTime ,
1413+ user : reqBookingUser ,
1414+ } ,
1415+ } ) ;
1416+
1417+ await handleNewBooking ( {
1418+ bookingData : mockBookingData ,
1419+ } ) ;
1420+
1421+ expect ( sendRescheduledSeatEmailSpy ) . toHaveBeenCalled ( ) ;
1422+ const calEvent = sendRescheduledSeatEmailSpy . mock . calls [ 0 ] [ 0 ] ;
1423+ expect ( calEvent . videoCallData ) . toBeDefined ( ) ;
1424+ expect ( calEvent . videoCallData ?. url ) . toBe ( meetingUrl ) ;
1425+ } ) ;
1426+
1427+ test ( "When rescheduling to an empty timeslot, reschedule email includes meeting URL" , async ( ) => {
1428+ const handleNewBooking = getNewBookingHandler ( ) ;
1429+ const sendRescheduledSeatEmailSpy = vi . spyOn ( emailManager , "sendRescheduledSeatEmailAndSMS" ) ;
1430+
1431+ const attendeeToReschedule = getMockBookingAttendee ( {
1432+ id : 2 ,
1433+ name : "Seat 2" ,
1434+ email : "seat2@test.com" ,
1435+ locale : "en" ,
1436+ timeZone : "America/Toronto" ,
1437+ bookingSeat : {
1438+ referenceUid : "booking-seat-2" ,
1439+ data : { } ,
1440+ } ,
1441+ } ) ;
1442+
1443+ const booker = getBooker ( {
1444+ email : attendeeToReschedule . email ,
1445+ name : attendeeToReschedule . name ,
1446+ } ) ;
1447+
1448+ const organizer = getOrganizer ( {
1449+ name : "Organizer" ,
1450+ email : "organizer@example.com" ,
1451+ id : 101 ,
1452+ schedules : [ TestData . schedules . IstWorkHours ] ,
1453+ } ) ;
1454+
1455+ const firstBookingId = 1 ;
1456+ const firstBookingUid = "abc123" ;
1457+ const { dateString : plus1DateString } = getDate ( { dateIncrement : 1 } ) ;
1458+ const firstBookingStartTime = `${ plus1DateString } T04:00:00Z` ;
1459+ const firstBookingEndTime = `${ plus1DateString } T04:30:00Z` ;
1460+
1461+ const { dateString : plus2DateString } = getDate ( { dateIncrement : 2 } ) ;
1462+ const secondBookingStartTime = `${ plus2DateString } T04:00:00Z` ;
1463+ const secondBookingEndTime = `${ plus2DateString } T04:30:00Z` ;
1464+
1465+ const meetingUrl = "http://mock-dailyvideo.example.com/meeting-with-url" ;
1466+
1467+ await createBookingScenario (
1468+ getScenarioData ( {
1469+ eventTypes : [
1470+ {
1471+ id : 1 ,
1472+ slug : "seated-event" ,
1473+ slotInterval : 30 ,
1474+ length : 30 ,
1475+ users : [
1476+ {
1477+ id : 101 ,
1478+ } ,
1479+ ] ,
1480+ seatsPerTimeSlot : 3 ,
1481+ seatsShowAttendees : false ,
1482+ } ,
1483+ ] ,
1484+ bookings : [
1485+ {
1486+ id : firstBookingId ,
1487+ uid : firstBookingUid ,
1488+ eventTypeId : 1 ,
1489+ status : BookingStatus . ACCEPTED ,
1490+ startTime : firstBookingStartTime ,
1491+ endTime : firstBookingEndTime ,
1492+ metadata : {
1493+ videoCallUrl : meetingUrl ,
1494+ } ,
1495+ references : [
1496+ {
1497+ type : appStoreMetadata . dailyvideo . type ,
1498+ uid : "MOCK_ID" ,
1499+ meetingId : "MOCK_ID" ,
1500+ meetingPassword : "MOCK_PASS" ,
1501+ meetingUrl : meetingUrl ,
1502+ credentialId : null ,
1503+ } ,
1504+ ] ,
1505+ attendees : [
1506+ getMockBookingAttendee ( {
1507+ id : 1 ,
1508+ name : "Seat 1" ,
1509+ email : "seat1@test.com" ,
1510+ locale : "en" ,
1511+ timeZone : "America/Toronto" ,
1512+ bookingSeat : {
1513+ referenceUid : "booking-seat-1" ,
1514+ data : { } ,
1515+ } ,
1516+ } ) ,
1517+ attendeeToReschedule ,
1518+ ] ,
1519+ } ,
1520+ ] ,
1521+ organizer,
1522+ } )
1523+ ) ;
1524+
1525+ mockSuccessfulVideoMeetingCreation ( {
1526+ metadataLookupKey : "dailyvideo" ,
1527+ videoMeetingData : {
1528+ id : "NEW_MOCK_ID" ,
1529+ password : "NEW_MOCK_PASS" ,
1530+ url : "http://mock-dailyvideo.example.com/new-meeting" ,
1531+ } ,
1532+ } ) ;
1533+
1534+ mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
1535+ create : {
1536+ id : "GOOGLE_CALENDAR_EVENT_ID" ,
1537+ iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
1538+ } ,
1539+ } ) ;
1540+
1541+ const reqBookingUser = "seatedAttendee" ;
1542+
1543+ const mockBookingData = getMockRequestDataForBooking ( {
1544+ data : {
1545+ eventTypeId : 1 ,
1546+ responses : {
1547+ email : booker . email ,
1548+ name : booker . name ,
1549+ location : { optionValue : "" , value : BookingLocations . CalVideo } ,
1550+ } ,
1551+ rescheduleUid : "booking-seat-2" ,
1552+ start : secondBookingStartTime ,
1553+ end : secondBookingEndTime ,
1554+ user : reqBookingUser ,
1555+ } ,
1556+ } ) ;
1557+
1558+ await handleNewBooking ( {
1559+ bookingData : mockBookingData ,
1560+ } ) ;
1561+
1562+ expect ( sendRescheduledSeatEmailSpy ) . toHaveBeenCalled ( ) ;
1563+ const calEvent = sendRescheduledSeatEmailSpy . mock . calls [ 0 ] [ 0 ] ;
1564+ expect ( calEvent . videoCallData ) . toBeDefined ( ) ;
1565+ expect ( calEvent . videoCallData ?. url ) . toBe ( meetingUrl ) ;
1566+ } ) ;
1567+
12551568 test ( "When rescheduling to an empty timeslot, create a new booking" , async ( ) => {
12561569 const handleNewBooking = getNewBookingHandler ( ) ;
12571570
0 commit comments