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.is_none_or(|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 async fn set_user_link(
788 &self,
789 owner_id: Uuid,
790 user_id: Option<Uuid>,
791 ) -> Result<bool, String> {
792 let mut map = self.owners.lock().unwrap();
793 if let Some(o) = map.get_mut(&owner_id) {
794 o.user_id = user_id;
795 Ok(true)
796 } else {
797 Ok(false)
798 }
799 }
800 }
801
802 fn create_test_owner(user_id: Uuid, organization_id: Uuid) -> Owner {
804 let mut owner = Owner::new(
805 organization_id,
806 "Jean".to_string(),
807 "Dupont".to_string(),
808 "jean@test.com".to_string(),
809 None,
810 "Rue Test 1".to_string(),
811 "Brussels".to_string(),
812 "1000".to_string(),
813 "Belgium".to_string(),
814 )
815 .unwrap();
816 owner.user_id = Some(user_id);
817 owner
818 }
819
820 fn setup_use_cases() -> (
821 ResourceBookingUseCases,
822 Uuid,
823 Uuid,
824 Uuid,
825 Arc<MockBookingRepo>,
826 ) {
827 let user_id = Uuid::new_v4();
828 let organization_id = Uuid::new_v4();
829 let building_id = Uuid::new_v4();
830
831 let booking_repo = Arc::new(MockBookingRepo::new());
832 let owner_repo = Arc::new(MockOwnerRepo::new());
833
834 let owner = create_test_owner(user_id, organization_id);
835 owner_repo.add_owner(owner);
836
837 let use_cases = ResourceBookingUseCases::new(
838 booking_repo.clone() as Arc<dyn ResourceBookingRepository>,
839 owner_repo as Arc<dyn OwnerRepository>,
840 );
841
842 (
843 use_cases,
844 user_id,
845 organization_id,
846 building_id,
847 booking_repo,
848 )
849 }
850
851 fn make_create_dto(building_id: Uuid) -> CreateResourceBookingDto {
852 let start_time = Utc::now() + Duration::hours(2);
853 let end_time = start_time + Duration::hours(2);
854 CreateResourceBookingDto {
855 building_id,
856 resource_type: ResourceType::MeetingRoom,
857 resource_name: "Meeting Room A".to_string(),
858 start_time,
859 end_time,
860 notes: Some("Team standup".to_string()),
861 recurring_pattern: RecurringPattern::None,
862 recurrence_end_date: None,
863 max_duration_hours: None,
864 max_advance_days: None,
865 }
866 }
867
868 #[tokio::test]
871 async fn test_create_booking_success() {
872 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
873 let dto = make_create_dto(building_id);
874
875 let result = use_cases.create_booking(user_id, org_id, dto).await;
876 assert!(result.is_ok());
877 let response = result.unwrap();
878 assert_eq!(response.building_id, building_id);
879 assert_eq!(response.resource_name, "Meeting Room A");
880 assert_eq!(response.booked_by_name, "Jean Dupont");
881 }
882
883 #[tokio::test]
884 async fn test_create_booking_conflict_detected() {
885 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
886 let dto = make_create_dto(building_id);
887
888 use_cases
890 .create_booking(user_id, org_id, dto.clone())
891 .await
892 .unwrap();
893
894 let result = use_cases.create_booking(user_id, org_id, dto).await;
896 assert!(result.is_err());
897 assert!(result.unwrap_err().contains("conflicts with"));
898 }
899
900 #[tokio::test]
901 async fn test_get_booking_success() {
902 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
903 let dto = make_create_dto(building_id);
904
905 let created = use_cases
906 .create_booking(user_id, org_id, dto)
907 .await
908 .unwrap();
909
910 let result = use_cases.get_booking(created.id).await;
911 assert!(result.is_ok());
912 assert_eq!(result.unwrap().id, created.id);
913 }
914
915 #[tokio::test]
916 async fn test_get_booking_not_found() {
917 let (use_cases, _, _, _, _) = setup_use_cases();
918 let result = use_cases.get_booking(Uuid::new_v4()).await;
919 assert!(result.is_err());
920 assert_eq!(result.unwrap_err(), "Booking not found");
921 }
922
923 #[tokio::test]
924 async fn test_cancel_booking_success() {
925 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
926 let dto = make_create_dto(building_id);
927
928 let created = use_cases
929 .create_booking(user_id, org_id, dto)
930 .await
931 .unwrap();
932
933 let result = use_cases.cancel_booking(created.id, user_id, org_id).await;
934 assert!(result.is_ok());
935 let cancelled = result.unwrap();
936 assert_eq!(cancelled.status, BookingStatus::Cancelled);
937 }
938
939 #[tokio::test]
940 async fn test_cancel_booking_wrong_user() {
941 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
942 let dto = make_create_dto(building_id);
943
944 let created = use_cases
945 .create_booking(user_id, org_id, dto)
946 .await
947 .unwrap();
948
949 let other_user = Uuid::new_v4();
951 let result = use_cases
952 .cancel_booking(created.id, other_user, org_id)
953 .await;
954 assert!(result.is_err());
955 assert!(result.unwrap_err().contains("Owner not found"));
956 }
957
958 #[tokio::test]
959 async fn test_delete_booking_success() {
960 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
961 let dto = make_create_dto(building_id);
962
963 let created = use_cases
964 .create_booking(user_id, org_id, dto)
965 .await
966 .unwrap();
967
968 let result = use_cases.delete_booking(created.id, user_id, org_id).await;
969 assert!(result.is_ok());
970
971 let fetch = use_cases.get_booking(created.id).await;
973 assert!(fetch.is_err());
974 }
975
976 #[tokio::test]
977 async fn test_confirm_booking_success() {
978 let (use_cases, user_id, org_id, building_id, _booking_repo) = setup_use_cases();
979 let dto = make_create_dto(building_id);
980
981 let created = use_cases
982 .create_booking(user_id, org_id, dto)
983 .await
984 .unwrap();
985
986 let result = use_cases.confirm_booking(created.id).await;
988 assert!(result.is_ok());
989 let confirmed = result.unwrap();
990 assert_eq!(confirmed.status, BookingStatus::Confirmed);
991
992 let completed = use_cases.complete_booking(created.id).await;
994 assert!(completed.is_ok());
995 assert_eq!(completed.unwrap().status, BookingStatus::Completed);
996 }
997
998 #[tokio::test]
999 async fn test_list_building_bookings() {
1000 let (use_cases, user_id, org_id, building_id, _) = setup_use_cases();
1001
1002 let dto1 = make_create_dto(building_id);
1003
1004 let mut dto2 = make_create_dto(building_id);
1005 dto2.resource_name = "Meeting Room B".to_string();
1006
1007 use_cases
1008 .create_booking(user_id, org_id, dto1)
1009 .await
1010 .unwrap();
1011 use_cases
1012 .create_booking(user_id, org_id, dto2)
1013 .await
1014 .unwrap();
1015
1016 let result = use_cases.list_building_bookings(building_id).await;
1017 assert!(result.is_ok());
1018 assert_eq!(result.unwrap().len(), 2);
1019 }
1020
1021 #[tokio::test]
1022 async fn test_owner_not_found_for_user() {
1023 let booking_repo = Arc::new(MockBookingRepo::new());
1024 let owner_repo = Arc::new(MockOwnerRepo::new());
1025 let use_cases = ResourceBookingUseCases::new(
1027 booking_repo as Arc<dyn ResourceBookingRepository>,
1028 owner_repo as Arc<dyn OwnerRepository>,
1029 );
1030
1031 let building_id = Uuid::new_v4();
1032 let dto = make_create_dto(building_id);
1033 let result = use_cases
1034 .create_booking(Uuid::new_v4(), Uuid::new_v4(), dto)
1035 .await;
1036 assert!(result.is_err());
1037 assert!(result.unwrap_err().contains("Owner not found"));
1038 }
1039}