1use crate::application::dto::{
2 BookingStatisticsDto, CreateResourceBookingDto, ResourceBookingResponseDto,
3 UpdateResourceBookingDto,
4};
5use crate::application::ports::{OwnerRepository, ResourceBookingRepository};
6use crate::domain::entities::{BookingStatus, ResourceBooking, ResourceType};
7use chrono::Utc;
8use std::sync::Arc;
9use uuid::Uuid;
10
11pub struct ResourceBookingUseCases {
16 booking_repo: Arc<dyn ResourceBookingRepository>,
17 owner_repo: Arc<dyn OwnerRepository>,
18}
19
20impl ResourceBookingUseCases {
21 pub fn new(
22 booking_repo: Arc<dyn ResourceBookingRepository>,
23 owner_repo: Arc<dyn OwnerRepository>,
24 ) -> Self {
25 Self {
26 booking_repo,
27 owner_repo,
28 }
29 }
30
31 async fn resolve_owner(
33 &self,
34 user_id: Uuid,
35 organization_id: Uuid,
36 ) -> Result<crate::domain::entities::Owner, String> {
37 self.owner_repo
38 .find_by_user_id_and_organization(user_id, organization_id)
39 .await?
40 .ok_or_else(|| "Owner not found for this user in the organization".to_string())
41 }
42
43 pub async fn create_booking(
54 &self,
55 user_id: Uuid,
56 organization_id: Uuid,
57 dto: CreateResourceBookingDto,
58 ) -> Result<ResourceBookingResponseDto, String> {
59 let owner = self.resolve_owner(user_id, organization_id).await?;
60 let booked_by = owner.id;
61 let booking = ResourceBooking::new(
63 dto.building_id,
64 dto.resource_type.clone(),
65 dto.resource_name.clone(),
66 booked_by,
67 dto.start_time,
68 dto.end_time,
69 dto.notes.clone(),
70 dto.recurring_pattern.clone(),
71 dto.recurrence_end_date,
72 dto.max_duration_hours,
73 dto.max_advance_days,
74 )?;
75
76 let conflicts = self
78 .booking_repo
79 .find_conflicts(
80 dto.building_id,
81 dto.resource_type,
82 &dto.resource_name,
83 dto.start_time,
84 dto.end_time,
85 None, )
87 .await?;
88
89 if !conflicts.is_empty() {
90 return Err(format!(
91 "Booking conflicts with {} existing booking(s) for this resource",
92 conflicts.len()
93 ));
94 }
95
96 let created = self.booking_repo.create(&booking).await?;
98
99 self.enrich_booking_response(created).await
101 }
102
103 pub async fn get_booking(
105 &self,
106 booking_id: Uuid,
107 ) -> Result<ResourceBookingResponseDto, String> {
108 let booking = self
109 .booking_repo
110 .find_by_id(booking_id)
111 .await?
112 .ok_or("Booking not found".to_string())?;
113
114 self.enrich_booking_response(booking).await
115 }
116
117 pub async fn list_building_bookings(
119 &self,
120 building_id: Uuid,
121 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
122 let bookings = self.booking_repo.find_by_building(building_id).await?;
123 self.enrich_bookings_response(bookings).await
124 }
125
126 pub async fn list_by_resource_type(
128 &self,
129 building_id: Uuid,
130 resource_type: ResourceType,
131 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
132 let bookings = self
133 .booking_repo
134 .find_by_building_and_resource_type(building_id, resource_type)
135 .await?;
136 self.enrich_bookings_response(bookings).await
137 }
138
139 pub async fn list_by_resource(
141 &self,
142 building_id: Uuid,
143 resource_type: ResourceType,
144 resource_name: String,
145 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
146 let bookings = self
147 .booking_repo
148 .find_by_resource(building_id, resource_type, &resource_name)
149 .await?;
150 self.enrich_bookings_response(bookings).await
151 }
152
153 pub async fn list_user_bookings(
155 &self,
156 user_id: Uuid,
157 organization_id: Uuid,
158 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
159 let owner = self.resolve_owner(user_id, organization_id).await?;
160 let bookings = self.booking_repo.find_by_user(owner.id).await?;
161 self.enrich_bookings_response(bookings).await
162 }
163
164 pub async fn list_user_bookings_by_status(
166 &self,
167 user_id: Uuid,
168 organization_id: Uuid,
169 status: BookingStatus,
170 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
171 let owner = self.resolve_owner(user_id, organization_id).await?;
172 let bookings = self
173 .booking_repo
174 .find_by_user_and_status(owner.id, status)
175 .await?;
176 self.enrich_bookings_response(bookings).await
177 }
178
179 pub async fn list_building_bookings_by_status(
181 &self,
182 building_id: Uuid,
183 status: BookingStatus,
184 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
185 let bookings = self
186 .booking_repo
187 .find_by_building_and_status(building_id, status)
188 .await?;
189 self.enrich_bookings_response(bookings).await
190 }
191
192 pub async fn list_upcoming_bookings(
194 &self,
195 building_id: Uuid,
196 limit: Option<i64>,
197 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
198 let bookings = self.booking_repo.find_upcoming(building_id, limit).await?;
199 self.enrich_bookings_response(bookings).await
200 }
201
202 pub async fn list_active_bookings(
204 &self,
205 building_id: Uuid,
206 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
207 let bookings = self.booking_repo.find_active(building_id).await?;
208 self.enrich_bookings_response(bookings).await
209 }
210
211 pub async fn list_past_bookings(
213 &self,
214 building_id: Uuid,
215 limit: Option<i64>,
216 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
217 let bookings = self.booking_repo.find_past(building_id, limit).await?;
218 self.enrich_bookings_response(bookings).await
219 }
220
221 pub async fn update_booking(
228 &self,
229 booking_id: Uuid,
230 user_id: Uuid,
231 organization_id: Uuid,
232 dto: UpdateResourceBookingDto,
233 ) -> Result<ResourceBookingResponseDto, String> {
234 let owner = self.resolve_owner(user_id, organization_id).await?;
235 let mut booking = self
236 .booking_repo
237 .find_by_id(booking_id)
238 .await?
239 .ok_or("Booking not found".to_string())?;
240
241 if booking.booked_by != owner.id {
243 return Err("Only the booking owner can update this booking".to_string());
244 }
245
246 booking.update_details(dto.resource_name, dto.notes)?;
248
249 let updated = self.booking_repo.update(&booking).await?;
251
252 self.enrich_booking_response(updated).await
254 }
255
256 pub async fn cancel_booking(
261 &self,
262 booking_id: Uuid,
263 user_id: Uuid,
264 organization_id: Uuid,
265 ) -> Result<ResourceBookingResponseDto, String> {
266 let owner = self.resolve_owner(user_id, organization_id).await?;
267 let mut booking = self
268 .booking_repo
269 .find_by_id(booking_id)
270 .await?
271 .ok_or("Booking not found".to_string())?;
272
273 booking.cancel(owner.id)?;
275
276 let updated = self.booking_repo.update(&booking).await?;
278
279 self.enrich_booking_response(updated).await
281 }
282
283 pub async fn complete_booking(
288 &self,
289 booking_id: Uuid,
290 ) -> Result<ResourceBookingResponseDto, String> {
291 let mut booking = self
292 .booking_repo
293 .find_by_id(booking_id)
294 .await?
295 .ok_or("Booking not found".to_string())?;
296
297 booking.complete()?;
299
300 let updated = self.booking_repo.update(&booking).await?;
302
303 self.enrich_booking_response(updated).await
305 }
306
307 pub async fn mark_no_show(
312 &self,
313 booking_id: Uuid,
314 ) -> Result<ResourceBookingResponseDto, String> {
315 let mut booking = self
316 .booking_repo
317 .find_by_id(booking_id)
318 .await?
319 .ok_or("Booking not found".to_string())?;
320
321 booking.mark_no_show()?;
323
324 let updated = self.booking_repo.update(&booking).await?;
326
327 self.enrich_booking_response(updated).await
329 }
330
331 pub async fn confirm_booking(
336 &self,
337 booking_id: Uuid,
338 ) -> Result<ResourceBookingResponseDto, String> {
339 let mut booking = self
340 .booking_repo
341 .find_by_id(booking_id)
342 .await?
343 .ok_or("Booking not found".to_string())?;
344
345 booking.confirm()?;
347
348 let updated = self.booking_repo.update(&booking).await?;
350
351 self.enrich_booking_response(updated).await
353 }
354
355 pub async fn delete_booking(
361 &self,
362 booking_id: Uuid,
363 user_id: Uuid,
364 organization_id: Uuid,
365 ) -> Result<(), String> {
366 let owner = self.resolve_owner(user_id, organization_id).await?;
367 let booking = self
368 .booking_repo
369 .find_by_id(booking_id)
370 .await?
371 .ok_or("Booking not found".to_string())?;
372
373 if booking.booked_by != owner.id {
375 return Err("Only the booking owner can delete this booking".to_string());
376 }
377
378 self.booking_repo.delete(booking_id).await
379 }
380
381 pub async fn check_conflicts(
387 &self,
388 building_id: Uuid,
389 resource_type: ResourceType,
390 resource_name: String,
391 start_time: chrono::DateTime<Utc>,
392 end_time: chrono::DateTime<Utc>,
393 exclude_booking_id: Option<Uuid>,
394 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
395 let conflicts = self
396 .booking_repo
397 .find_conflicts(
398 building_id,
399 resource_type,
400 &resource_name,
401 start_time,
402 end_time,
403 exclude_booking_id,
404 )
405 .await?;
406
407 self.enrich_bookings_response(conflicts).await
408 }
409
410 pub async fn get_statistics(&self, building_id: Uuid) -> Result<BookingStatisticsDto, String> {
412 self.booking_repo.get_statistics(building_id).await
413 }
414
415 async fn enrich_booking_response(
417 &self,
418 booking: ResourceBooking,
419 ) -> Result<ResourceBookingResponseDto, String> {
420 let owner = self
422 .owner_repo
423 .find_by_id(booking.booked_by)
424 .await?
425 .ok_or("Booking owner not found".to_string())?;
426
427 let booked_by_name = format!("{} {}", owner.first_name, owner.last_name);
428
429 Ok(ResourceBookingResponseDto::from_entity(
430 booking,
431 booked_by_name,
432 ))
433 }
434
435 async fn enrich_bookings_response(
437 &self,
438 bookings: Vec<ResourceBooking>,
439 ) -> Result<Vec<ResourceBookingResponseDto>, String> {
440 let mut result = Vec::with_capacity(bookings.len());
441
442 for booking in bookings {
443 let enriched = self.enrich_booking_response(booking).await?;
444 result.push(enriched);
445 }
446
447 Ok(result)
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use crate::application::dto::{BookingStatisticsDto, OwnerFilters, PageRequest};
455 use crate::application::ports::{OwnerRepository, ResourceBookingRepository};
456 use crate::domain::entities::{
457 BookingStatus, Owner, RecurringPattern, ResourceBooking, ResourceType,
458 };
459 use async_trait::async_trait;
460 use chrono::{DateTime, Duration, Utc};
461 use std::collections::HashMap;
462 use std::sync::{Arc, Mutex};
463 use uuid::Uuid;
464
465 struct MockBookingRepo {
467 bookings: Mutex<HashMap<Uuid, ResourceBooking>>,
468 }
469
470 impl MockBookingRepo {
471 fn new() -> Self {
472 Self {
473 bookings: Mutex::new(HashMap::new()),
474 }
475 }
476 }
477
478 #[async_trait]
479 impl ResourceBookingRepository for MockBookingRepo {
480 async fn create(&self, booking: &ResourceBooking) -> Result<ResourceBooking, String> {
481 let mut map = self.bookings.lock().unwrap();
482 map.insert(booking.id, booking.clone());
483 Ok(booking.clone())
484 }
485
486 async fn find_by_id(&self, id: Uuid) -> Result<Option<ResourceBooking>, String> {
487 let map = self.bookings.lock().unwrap();
488 Ok(map.get(&id).cloned())
489 }
490
491 async fn find_by_building(
492 &self,
493 building_id: Uuid,
494 ) -> Result<Vec<ResourceBooking>, String> {
495 let map = self.bookings.lock().unwrap();
496 Ok(map
497 .values()
498 .filter(|b| b.building_id == building_id)
499 .cloned()
500 .collect())
501 }
502
503 async fn find_by_building_and_resource_type(
504 &self,
505 building_id: Uuid,
506 resource_type: ResourceType,
507 ) -> Result<Vec<ResourceBooking>, String> {
508 let map = self.bookings.lock().unwrap();
509 Ok(map
510 .values()
511 .filter(|b| b.building_id == building_id && b.resource_type == resource_type)
512 .cloned()
513 .collect())
514 }
515
516 async fn find_by_resource(
517 &self,
518 building_id: Uuid,
519 resource_type: ResourceType,
520 resource_name: &str,
521 ) -> Result<Vec<ResourceBooking>, String> {
522 let map = self.bookings.lock().unwrap();
523 Ok(map
524 .values()
525 .filter(|b| {
526 b.building_id == building_id
527 && b.resource_type == resource_type
528 && b.resource_name == resource_name
529 })
530 .cloned()
531 .collect())
532 }
533
534 async fn find_by_user(&self, user_id: Uuid) -> Result<Vec<ResourceBooking>, String> {
535 let map = self.bookings.lock().unwrap();
536 Ok(map
537 .values()
538 .filter(|b| b.booked_by == user_id)
539 .cloned()
540 .collect())
541 }
542
543 async fn find_by_user_and_status(
544 &self,
545 user_id: Uuid,
546 status: BookingStatus,
547 ) -> Result<Vec<ResourceBooking>, String> {
548 let map = self.bookings.lock().unwrap();
549 Ok(map
550 .values()
551 .filter(|b| b.booked_by == user_id && b.status == status)
552 .cloned()
553 .collect())
554 }
555
556 async fn find_by_building_and_status(
557 &self,
558 building_id: Uuid,
559 status: BookingStatus,
560 ) -> Result<Vec<ResourceBooking>, String> {
561 let map = self.bookings.lock().unwrap();
562 Ok(map
563 .values()
564 .filter(|b| b.building_id == building_id && b.status == status)
565 .cloned()
566 .collect())
567 }
568
569 async fn find_upcoming(
570 &self,
571 building_id: Uuid,
572 _limit: Option<i64>,
573 ) -> Result<Vec<ResourceBooking>, String> {
574 let map = self.bookings.lock().unwrap();
575 let now = Utc::now();
576 Ok(map
577 .values()
578 .filter(|b| {
579 b.building_id == building_id
580 && b.start_time > now
581 && matches!(b.status, BookingStatus::Pending | BookingStatus::Confirmed)
582 })
583 .cloned()
584 .collect())
585 }
586
587 async fn find_active(&self, building_id: Uuid) -> Result<Vec<ResourceBooking>, String> {
588 let map = self.bookings.lock().unwrap();
589 let now = Utc::now();
590 Ok(map
591 .values()
592 .filter(|b| {
593 b.building_id == building_id
594 && b.status == BookingStatus::Confirmed
595 && now >= b.start_time
596 && now < b.end_time
597 })
598 .cloned()
599 .collect())
600 }
601
602 async fn find_past(
603 &self,
604 building_id: Uuid,
605 _limit: Option<i64>,
606 ) -> Result<Vec<ResourceBooking>, String> {
607 let map = self.bookings.lock().unwrap();
608 let now = Utc::now();
609 Ok(map
610 .values()
611 .filter(|b| b.building_id == building_id && b.end_time < now)
612 .cloned()
613 .collect())
614 }
615
616 async fn find_conflicts(
617 &self,
618 building_id: Uuid,
619 resource_type: ResourceType,
620 resource_name: &str,
621 start_time: DateTime<Utc>,
622 end_time: DateTime<Utc>,
623 exclude_booking_id: Option<Uuid>,
624 ) -> Result<Vec<ResourceBooking>, String> {
625 let map = self.bookings.lock().unwrap();
626 Ok(map
627 .values()
628 .filter(|b| {
629 b.building_id == building_id
630 && b.resource_type == resource_type
631 && b.resource_name == resource_name
632 && matches!(b.status, BookingStatus::Pending | BookingStatus::Confirmed)
633 && b.start_time < end_time
634 && start_time < b.end_time
635 && exclude_booking_id.map_or(true, |id| b.id != id)
636 })
637 .cloned()
638 .collect())
639 }
640
641 async fn update(&self, booking: &ResourceBooking) -> Result<ResourceBooking, String> {
642 let mut map = self.bookings.lock().unwrap();
643 map.insert(booking.id, booking.clone());
644 Ok(booking.clone())
645 }
646
647 async fn delete(&self, id: Uuid) -> Result<(), String> {
648 let mut map = self.bookings.lock().unwrap();
649 map.remove(&id);
650 Ok(())
651 }
652
653 async fn count_by_building(&self, building_id: Uuid) -> Result<i64, String> {
654 let map = self.bookings.lock().unwrap();
655 Ok(map
656 .values()
657 .filter(|b| b.building_id == building_id)
658 .count() as i64)
659 }
660
661 async fn count_by_building_and_status(
662 &self,
663 building_id: Uuid,
664 status: BookingStatus,
665 ) -> Result<i64, String> {
666 let map = self.bookings.lock().unwrap();
667 Ok(map
668 .values()
669 .filter(|b| b.building_id == building_id && b.status == status)
670 .count() as i64)
671 }
672
673 async fn count_by_resource(
674 &self,
675 building_id: Uuid,
676 resource_type: ResourceType,
677 resource_name: &str,
678 ) -> Result<i64, String> {
679 let map = self.bookings.lock().unwrap();
680 Ok(map
681 .values()
682 .filter(|b| {
683 b.building_id == building_id
684 && b.resource_type == resource_type
685 && b.resource_name == resource_name
686 })
687 .count() as i64)
688 }
689
690 async fn get_statistics(&self, building_id: Uuid) -> Result<BookingStatisticsDto, String> {
691 Ok(BookingStatisticsDto {
692 building_id,
693 total_bookings: 0,
694 confirmed_bookings: 0,
695 pending_bookings: 0,
696 cancelled_bookings: 0,
697 completed_bookings: 0,
698 no_show_bookings: 0,
699 active_bookings: 0,
700 upcoming_bookings: 0,
701 total_hours_booked: 0.0,
702 most_popular_resource: None,
703 })
704 }
705 }
706
707 struct MockOwnerRepo {
709 owners: Mutex<HashMap<Uuid, Owner>>,
710 }
711
712 impl MockOwnerRepo {
713 fn new() -> Self {
714 Self {
715 owners: Mutex::new(HashMap::new()),
716 }
717 }
718
719 fn add_owner(&self, owner: Owner) {
720 let mut map = self.owners.lock().unwrap();
721 map.insert(owner.id, owner);
722 }
723 }
724
725 #[async_trait]
726 impl OwnerRepository for MockOwnerRepo {
727 async fn create(&self, owner: &Owner) -> Result<Owner, String> {
728 let mut map = self.owners.lock().unwrap();
729 map.insert(owner.id, owner.clone());
730 Ok(owner.clone())
731 }
732
733 async fn find_by_id(&self, id: Uuid) -> Result<Option<Owner>, String> {
734 let map = self.owners.lock().unwrap();
735 Ok(map.get(&id).cloned())
736 }
737
738 async fn find_by_user_id(&self, user_id: Uuid) -> Result<Option<Owner>, String> {
739 let map = self.owners.lock().unwrap();
740 Ok(map.values().find(|o| o.user_id == Some(user_id)).cloned())
741 }
742
743 async fn find_by_user_id_and_organization(
744 &self,
745 user_id: Uuid,
746 organization_id: Uuid,
747 ) -> Result<Option<Owner>, String> {
748 let map = self.owners.lock().unwrap();
749 Ok(map
750 .values()
751 .find(|o| o.user_id == Some(user_id) && o.organization_id == organization_id)
752 .cloned())
753 }
754
755 async fn find_by_email(&self, email: &str) -> Result<Option<Owner>, String> {
756 let map = self.owners.lock().unwrap();
757 Ok(map.values().find(|o| o.email == email).cloned())
758 }
759
760 async fn find_all(&self) -> Result<Vec<Owner>, String> {
761 let map = self.owners.lock().unwrap();
762 Ok(map.values().cloned().collect())
763 }
764
765 async fn find_all_paginated(
766 &self,
767 _page_request: &PageRequest,
768 _filters: &OwnerFilters,
769 ) -> Result<(Vec<Owner>, i64), String> {
770 let map = self.owners.lock().unwrap();
771 let all: Vec<_> = map.values().cloned().collect();
772 let count = all.len() as i64;
773 Ok((all, count))
774 }
775
776 async fn update(&self, owner: &Owner) -> Result<Owner, String> {
777 let mut map = self.owners.lock().unwrap();
778 map.insert(owner.id, owner.clone());
779 Ok(owner.clone())
780 }
781
782 async fn delete(&self, id: Uuid) -> Result<bool, String> {
783 let mut map = self.owners.lock().unwrap();
784 Ok(map.remove(&id).is_some())
785 }
786 }
787
788 fn create_test_owner(user_id: Uuid, organization_id: Uuid) -> Owner {
790 let mut owner = Owner::new(
791 organization_id,
792 "Jean".to_string(),
793 "Dupont".to_string(),
794 "jean@test.com".to_string(),
795 None,
796 "Rue Test 1".to_string(),
797 "Brussels".to_string(),
798 "1000".to_string(),
799 "Belgium".to_string(),
800 )
801 .unwrap();
802 owner.user_id = Some(user_id);
803 owner
804 }
805
806 fn setup_use_cases() -> (
807 ResourceBookingUseCases,
808 Uuid,
809 Uuid,
810 Uuid,
811 Arc<MockBookingRepo>,
812 ) {
813 let user_id = Uuid::new_v4();
814 let organization_id = Uuid::new_v4();
815 let building_id = Uuid::new_v4();
816
817 let booking_repo = Arc::new(MockBookingRepo::new());
818 let owner_repo = Arc::new(MockOwnerRepo::new());
819
820 let owner = create_test_owner(user_id, organization_id);
821 owner_repo.add_owner(owner);
822
823 let use_cases = ResourceBookingUseCases::new(
824 booking_repo.clone() as Arc<dyn ResourceBookingRepository>,
825 owner_repo as Arc<dyn OwnerRepository>,
826 );
827
828 (
829 use_cases,
830 user_id,
831 organization_id,
832 building_id,
833 booking_repo,
834 )
835 }
836
837 fn make_create_dto(building_id: Uuid) -> CreateResourceBookingDto {
838 let start_time = Utc::now() + Duration::hours(2);
839 let end_time = start_time + Duration::hours(2);
840 CreateResourceBookingDto {
841 building_id,
842 resource_type: ResourceType::MeetingRoom,
843 resource_name: "Meeting Room A".to_string(),
844 start_time,
845 end_time,
846 notes: Some("Team standup".to_string()),
847 recurring_pattern: RecurringPattern::None,
848 recurrence_end_date: None,
849 max_duration_hours: None,
850 max_advance_days: None,
851 }
852 }
853
854 #[tokio::test]
857 async fn test_create_booking_success() {
858 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
859 let dto = make_create_dto(building_id);
860
861 let result = use_cases.create_booking(user_id, org_id, dto).await;
862 assert!(result.is_ok());
863 let response = result.unwrap();
864 assert_eq!(response.building_id, building_id);
865 assert_eq!(response.resource_name, "Meeting Room A");
866 assert_eq!(response.booked_by_name, "Jean Dupont");
867 }
868
869 #[tokio::test]
870 async fn test_create_booking_conflict_detected() {
871 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
872 let dto = make_create_dto(building_id);
873
874 use_cases
876 .create_booking(user_id, org_id, dto.clone())
877 .await
878 .unwrap();
879
880 let result = use_cases.create_booking(user_id, org_id, dto).await;
882 assert!(result.is_err());
883 assert!(result.unwrap_err().contains("conflicts with"));
884 }
885
886 #[tokio::test]
887 async fn test_get_booking_success() {
888 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
889 let dto = make_create_dto(building_id);
890
891 let created = use_cases
892 .create_booking(user_id, org_id, dto)
893 .await
894 .unwrap();
895
896 let result = use_cases.get_booking(created.id).await;
897 assert!(result.is_ok());
898 assert_eq!(result.unwrap().id, created.id);
899 }
900
901 #[tokio::test]
902 async fn test_get_booking_not_found() {
903 let (use_cases, _, _, _, _) = setup_use_cases();
904 let result = use_cases.get_booking(Uuid::new_v4()).await;
905 assert!(result.is_err());
906 assert_eq!(result.unwrap_err(), "Booking not found");
907 }
908
909 #[tokio::test]
910 async fn test_cancel_booking_success() {
911 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
912 let dto = make_create_dto(building_id);
913
914 let created = use_cases
915 .create_booking(user_id, org_id, dto)
916 .await
917 .unwrap();
918
919 let result = use_cases.cancel_booking(created.id, user_id, org_id).await;
920 assert!(result.is_ok());
921 let cancelled = result.unwrap();
922 assert_eq!(cancelled.status, BookingStatus::Cancelled);
923 }
924
925 #[tokio::test]
926 async fn test_cancel_booking_wrong_user() {
927 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
928 let dto = make_create_dto(building_id);
929
930 let created = use_cases
931 .create_booking(user_id, org_id, dto)
932 .await
933 .unwrap();
934
935 let other_user = Uuid::new_v4();
937 let result = use_cases
938 .cancel_booking(created.id, other_user, org_id)
939 .await;
940 assert!(result.is_err());
941 assert!(result.unwrap_err().contains("Owner not found"));
942 }
943
944 #[tokio::test]
945 async fn test_delete_booking_success() {
946 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
947 let dto = make_create_dto(building_id);
948
949 let created = use_cases
950 .create_booking(user_id, org_id, dto)
951 .await
952 .unwrap();
953
954 let result = use_cases.delete_booking(created.id, user_id, org_id).await;
955 assert!(result.is_ok());
956
957 let fetch = use_cases.get_booking(created.id).await;
959 assert!(fetch.is_err());
960 }
961
962 #[tokio::test]
963 async fn test_confirm_booking_success() {
964 let (use_cases, user_id, org_id, building_id, _booking_repo) = setup_use_cases();
965 let dto = make_create_dto(building_id);
966
967 let created = use_cases
968 .create_booking(user_id, org_id, dto)
969 .await
970 .unwrap();
971
972 let result = use_cases.confirm_booking(created.id).await;
974 assert!(result.is_ok());
975 let confirmed = result.unwrap();
976 assert_eq!(confirmed.status, BookingStatus::Confirmed);
977
978 let completed = use_cases.complete_booking(created.id).await;
980 assert!(completed.is_ok());
981 assert_eq!(completed.unwrap().status, BookingStatus::Completed);
982 }
983
984 #[tokio::test]
985 async fn test_list_building_bookings() {
986 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
987
988 let dto1 = make_create_dto(building_id);
989
990 let mut dto2 = make_create_dto(building_id);
991 dto2.resource_name = "Meeting Room B".to_string();
992
993 use_cases
994 .create_booking(user_id, org_id, dto1)
995 .await
996 .unwrap();
997 use_cases
998 .create_booking(user_id, org_id, dto2)
999 .await
1000 .unwrap();
1001
1002 let result = use_cases.list_building_bookings(building_id).await;
1003 assert!(result.is_ok());
1004 assert_eq!(result.unwrap().len(), 2);
1005 }
1006
1007 #[tokio::test]
1008 async fn test_owner_not_found_for_user() {
1009 let booking_repo = Arc::new(MockBookingRepo::new());
1010 let owner_repo = Arc::new(MockOwnerRepo::new());
1011 let use_cases = ResourceBookingUseCases::new(
1013 booking_repo as Arc<dyn ResourceBookingRepository>,
1014 owner_repo as Arc<dyn OwnerRepository>,
1015 );
1016
1017 let building_id = Uuid::new_v4();
1018 let dto = make_create_dto(building_id);
1019 let result = use_cases
1020 .create_booking(Uuid::new_v4(), Uuid::new_v4(), dto)
1021 .await;
1022 assert!(result.is_err());
1023 assert!(result.unwrap_err().contains("Owner not found"));
1024 }
1025}