1use crate::application::dto::{
2 AddCertificateDto, AddInspectionPhotoDto, AddReportDto, CreateTechnicalInspectionDto,
3 InspectionStatusDto, PageRequest, TechnicalInspectionFilters,
4 TechnicalInspectionListResponseDto, TechnicalInspectionResponseDto,
5 UpdateTechnicalInspectionDto,
6};
7use crate::application::ports::TechnicalInspectionRepository;
8use crate::domain::entities::{InspectionStatus, TechnicalInspection};
9use chrono::DateTime;
10use std::sync::Arc;
11use uuid::Uuid;
12
13pub struct TechnicalInspectionUseCases {
14 repository: Arc<dyn TechnicalInspectionRepository>,
15}
16
17impl TechnicalInspectionUseCases {
18 pub fn new(repository: Arc<dyn TechnicalInspectionRepository>) -> Self {
19 Self { repository }
20 }
21
22 pub async fn create_technical_inspection(
23 &self,
24 dto: CreateTechnicalInspectionDto,
25 ) -> Result<TechnicalInspectionResponseDto, String> {
26 let organization_id = Uuid::parse_str(&dto.organization_id)
27 .map_err(|_| "Invalid organization_id format".to_string())?;
28 let building_id = Uuid::parse_str(&dto.building_id)
29 .map_err(|_| "Invalid building_id format".to_string())?;
30
31 let inspection_date = DateTime::parse_from_rfc3339(&dto.inspection_date)
32 .map_err(|_| "Invalid inspection_date format".to_string())?
33 .with_timezone(&chrono::Utc);
34
35 let compliance_valid_until = if let Some(ref date_str) = dto.compliance_valid_until {
36 Some(
37 DateTime::parse_from_rfc3339(date_str)
38 .map_err(|_| "Invalid compliance_valid_until format".to_string())?
39 .with_timezone(&chrono::Utc),
40 )
41 } else {
42 None
43 };
44
45 let inspection = TechnicalInspection::new(
46 organization_id,
47 building_id,
48 dto.title,
49 dto.description,
50 dto.inspection_type.clone(),
51 dto.inspector_name,
52 inspection_date,
53 );
54
55 let mut inspection = inspection;
56 inspection.inspector_company = dto.inspector_company;
57 inspection.inspector_certification = dto.inspector_certification;
58 inspection.result_summary = dto.result_summary;
59 inspection.defects_found = dto.defects_found;
60 inspection.recommendations = dto.recommendations;
61 inspection.compliant = dto.compliant;
62 inspection.compliance_certificate_number = dto.compliance_certificate_number;
63 inspection.compliance_valid_until = compliance_valid_until;
64 inspection.set_cost(dto.cost)?;
65 inspection.invoice_number = dto.invoice_number;
66 inspection.notes = dto.notes;
67
68 let created = self.repository.create(&inspection).await?;
69 Ok(self.to_response_dto(&created))
70 }
71
72 pub async fn get_technical_inspection(
73 &self,
74 id: Uuid,
75 ) -> Result<Option<TechnicalInspectionResponseDto>, String> {
76 let inspection = self.repository.find_by_id(id).await?;
77 Ok(inspection.map(|i| self.to_response_dto(&i)))
78 }
79
80 pub async fn list_technical_inspections_by_building(
81 &self,
82 building_id: Uuid,
83 ) -> Result<Vec<TechnicalInspectionResponseDto>, String> {
84 let inspections = self.repository.find_by_building(building_id).await?;
85 Ok(inspections
86 .iter()
87 .map(|i| self.to_response_dto(i))
88 .collect())
89 }
90
91 pub async fn list_technical_inspections_by_organization(
92 &self,
93 organization_id: Uuid,
94 ) -> Result<Vec<TechnicalInspectionResponseDto>, String> {
95 let inspections = self
96 .repository
97 .find_by_organization(organization_id)
98 .await?;
99 Ok(inspections
100 .iter()
101 .map(|i| self.to_response_dto(i))
102 .collect())
103 }
104
105 pub async fn list_technical_inspections_paginated(
106 &self,
107 page_request: &PageRequest,
108 filters: &TechnicalInspectionFilters,
109 ) -> Result<TechnicalInspectionListResponseDto, String> {
110 let (inspections, total) = self
111 .repository
112 .find_all_paginated(page_request, filters)
113 .await?;
114
115 let dtos = inspections
116 .iter()
117 .map(|i| self.to_response_dto(i))
118 .collect();
119
120 Ok(TechnicalInspectionListResponseDto {
121 inspections: dtos,
122 total,
123 page: page_request.page,
124 page_size: page_request.per_page,
125 })
126 }
127
128 pub async fn get_overdue_inspections(
129 &self,
130 building_id: Uuid,
131 ) -> Result<Vec<InspectionStatusDto>, String> {
132 let inspections = self.repository.find_overdue(building_id).await?;
133
134 Ok(inspections
135 .iter()
136 .map(|i| InspectionStatusDto {
137 inspection_id: i.id.to_string(),
138 title: i.title.clone(),
139 inspection_type: i.inspection_type.clone(),
140 next_due_date: i.next_due_date.to_rfc3339(),
141 status: i.status.clone(),
142 is_overdue: i.is_overdue(),
143 days_until_due: i.days_until_due(),
144 })
145 .collect())
146 }
147
148 pub async fn get_upcoming_inspections(
149 &self,
150 building_id: Uuid,
151 days: i32,
152 ) -> Result<Vec<InspectionStatusDto>, String> {
153 let inspections = self.repository.find_upcoming(building_id, days).await?;
154
155 Ok(inspections
156 .iter()
157 .map(|i| InspectionStatusDto {
158 inspection_id: i.id.to_string(),
159 title: i.title.clone(),
160 inspection_type: i.inspection_type.clone(),
161 next_due_date: i.next_due_date.to_rfc3339(),
162 status: i.status.clone(),
163 is_overdue: i.is_overdue(),
164 days_until_due: i.days_until_due(),
165 })
166 .collect())
167 }
168
169 pub async fn get_inspections_by_type(
170 &self,
171 building_id: Uuid,
172 inspection_type: &str,
173 ) -> Result<Vec<TechnicalInspectionResponseDto>, String> {
174 let inspections = self
175 .repository
176 .find_by_type(building_id, inspection_type)
177 .await?;
178
179 Ok(inspections
180 .iter()
181 .map(|i| self.to_response_dto(i))
182 .collect())
183 }
184
185 pub async fn update_technical_inspection(
186 &self,
187 id: Uuid,
188 dto: UpdateTechnicalInspectionDto,
189 ) -> Result<TechnicalInspectionResponseDto, String> {
190 let mut inspection = self
191 .repository
192 .find_by_id(id)
193 .await?
194 .ok_or_else(|| "Technical inspection not found".to_string())?;
195
196 if let Some(title) = dto.title {
197 inspection.title = title;
198 }
199 if let Some(description) = dto.description {
200 inspection.description = Some(description);
201 }
202 if let Some(inspection_type) = dto.inspection_type {
203 inspection.inspection_type = inspection_type;
204 inspection.next_due_date = inspection.calculate_next_due_date();
206 }
207 if let Some(inspector_name) = dto.inspector_name {
208 inspection.inspector_name = inspector_name;
209 }
210 if let Some(inspector_company) = dto.inspector_company {
211 inspection.inspector_company = Some(inspector_company);
212 }
213 if let Some(inspector_certification) = dto.inspector_certification {
214 inspection.inspector_certification = Some(inspector_certification);
215 }
216 if let Some(inspection_date_str) = dto.inspection_date {
217 let inspection_date = DateTime::parse_from_rfc3339(&inspection_date_str)
218 .map_err(|_| "Invalid inspection_date format".to_string())?
219 .with_timezone(&chrono::Utc);
220 inspection.inspection_date = inspection_date;
221 inspection.next_due_date = inspection.calculate_next_due_date();
223 }
224 if let Some(status) = dto.status {
225 inspection.status = status;
226 }
227 if let Some(result_summary) = dto.result_summary {
228 inspection.result_summary = Some(result_summary);
229 }
230 if let Some(defects_found) = dto.defects_found {
231 inspection.defects_found = Some(defects_found);
232 }
233 if let Some(recommendations) = dto.recommendations {
234 inspection.recommendations = Some(recommendations);
235 }
236 if let Some(compliant) = dto.compliant {
237 inspection.compliant = Some(compliant);
238 }
239 if let Some(compliance_certificate_number) = dto.compliance_certificate_number {
240 inspection.compliance_certificate_number = Some(compliance_certificate_number);
241 }
242 if let Some(compliance_valid_until_str) = dto.compliance_valid_until {
243 let compliance_valid_until = DateTime::parse_from_rfc3339(&compliance_valid_until_str)
244 .map_err(|_| "Invalid compliance_valid_until format".to_string())?
245 .with_timezone(&chrono::Utc);
246 inspection.compliance_valid_until = Some(compliance_valid_until);
247 }
248 if let Some(cost) = dto.cost {
249 inspection.set_cost(Some(cost))?;
250 }
251 if let Some(invoice_number) = dto.invoice_number {
252 inspection.invoice_number = Some(invoice_number);
253 }
254 if let Some(notes) = dto.notes {
255 inspection.notes = Some(notes);
256 }
257
258 inspection.updated_at = chrono::Utc::now();
259
260 let updated = self.repository.update(&inspection).await?;
261 Ok(self.to_response_dto(&updated))
262 }
263
264 pub async fn mark_as_completed(
265 &self,
266 id: Uuid,
267 ) -> Result<TechnicalInspectionResponseDto, String> {
268 let mut inspection = self
269 .repository
270 .find_by_id(id)
271 .await?
272 .ok_or_else(|| "Technical inspection not found".to_string())?;
273
274 inspection.status = InspectionStatus::Completed;
275 inspection.updated_at = chrono::Utc::now();
276
277 let updated = self.repository.update(&inspection).await?;
278 Ok(self.to_response_dto(&updated))
279 }
280
281 pub async fn add_report(
282 &self,
283 id: Uuid,
284 dto: AddReportDto,
285 ) -> Result<TechnicalInspectionResponseDto, String> {
286 let mut inspection = self
287 .repository
288 .find_by_id(id)
289 .await?
290 .ok_or_else(|| "Technical inspection not found".to_string())?;
291
292 inspection.add_report(dto.report_path);
293
294 let updated = self.repository.update(&inspection).await?;
295 Ok(self.to_response_dto(&updated))
296 }
297
298 pub async fn add_photo(
299 &self,
300 id: Uuid,
301 dto: AddInspectionPhotoDto,
302 ) -> Result<TechnicalInspectionResponseDto, String> {
303 let mut inspection = self
304 .repository
305 .find_by_id(id)
306 .await?
307 .ok_or_else(|| "Technical inspection not found".to_string())?;
308
309 inspection.add_photo(dto.photo_path);
310
311 let updated = self.repository.update(&inspection).await?;
312 Ok(self.to_response_dto(&updated))
313 }
314
315 pub async fn add_certificate(
316 &self,
317 id: Uuid,
318 dto: AddCertificateDto,
319 ) -> Result<TechnicalInspectionResponseDto, String> {
320 let mut inspection = self
321 .repository
322 .find_by_id(id)
323 .await?
324 .ok_or_else(|| "Technical inspection not found".to_string())?;
325
326 inspection.add_certificate(dto.certificate_path);
327
328 let updated = self.repository.update(&inspection).await?;
329 Ok(self.to_response_dto(&updated))
330 }
331
332 pub async fn delete_technical_inspection(&self, id: Uuid) -> Result<bool, String> {
333 self.repository.delete(id).await
334 }
335
336 fn to_response_dto(&self, inspection: &TechnicalInspection) -> TechnicalInspectionResponseDto {
337 TechnicalInspectionResponseDto {
338 id: inspection.id.to_string(),
339 organization_id: inspection.organization_id.to_string(),
340 building_id: inspection.building_id.to_string(),
341 title: inspection.title.clone(),
342 description: inspection.description.clone(),
343 inspection_type: inspection.inspection_type.clone(),
344 inspector_name: inspection.inspector_name.clone(),
345 inspector_company: inspection.inspector_company.clone(),
346 inspector_certification: inspection.inspector_certification.clone(),
347 inspection_date: inspection.inspection_date.to_rfc3339(),
348 next_due_date: inspection.next_due_date.to_rfc3339(),
349 status: inspection.status.clone(),
350 result_summary: inspection.result_summary.clone(),
351 defects_found: inspection.defects_found.clone(),
352 recommendations: inspection.recommendations.clone(),
353 compliant: inspection.compliant,
354 compliance_certificate_number: inspection.compliance_certificate_number.clone(),
355 compliance_valid_until: inspection
356 .compliance_valid_until
357 .as_ref()
358 .map(|d| d.to_rfc3339()),
359 cost: inspection.cost,
360 invoice_number: inspection.invoice_number.clone(),
361 reports: inspection.reports.clone(),
362 photos: inspection.photos.clone(),
363 certificates: inspection.certificates.clone(),
364 notes: inspection.notes.clone(),
365 is_overdue: inspection.is_overdue(),
366 days_until_due: inspection.days_until_due(),
367 created_at: inspection.created_at.to_rfc3339(),
368 updated_at: inspection.updated_at.to_rfc3339(),
369 }
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 use crate::application::dto::{
377 AddInspectionPhotoDto, AddReportDto, CreateTechnicalInspectionDto, PageRequest,
378 TechnicalInspectionFilters,
379 };
380 use crate::application::ports::TechnicalInspectionRepository;
381 use crate::domain::entities::{InspectionStatus, InspectionType, TechnicalInspection};
382 use async_trait::async_trait;
383 use rust_decimal_macros::dec;
384 use std::collections::HashMap;
385 use std::sync::Mutex;
386
387 struct MockTechnicalInspectionRepository {
390 inspections: Mutex<HashMap<Uuid, TechnicalInspection>>,
391 }
392
393 impl MockTechnicalInspectionRepository {
394 fn new() -> Self {
395 Self {
396 inspections: Mutex::new(HashMap::new()),
397 }
398 }
399 }
400
401 #[async_trait]
402 impl TechnicalInspectionRepository for MockTechnicalInspectionRepository {
403 async fn create(
404 &self,
405 inspection: &TechnicalInspection,
406 ) -> Result<TechnicalInspection, String> {
407 let mut inspections = self.inspections.lock().unwrap();
408 inspections.insert(inspection.id, inspection.clone());
409 Ok(inspection.clone())
410 }
411
412 async fn find_by_id(&self, id: Uuid) -> Result<Option<TechnicalInspection>, String> {
413 let inspections = self.inspections.lock().unwrap();
414 Ok(inspections.get(&id).cloned())
415 }
416
417 async fn find_by_building(
418 &self,
419 building_id: Uuid,
420 ) -> Result<Vec<TechnicalInspection>, String> {
421 let inspections = self.inspections.lock().unwrap();
422 Ok(inspections
423 .values()
424 .filter(|i| i.building_id == building_id)
425 .cloned()
426 .collect())
427 }
428
429 async fn find_by_organization(
430 &self,
431 organization_id: Uuid,
432 ) -> Result<Vec<TechnicalInspection>, String> {
433 let inspections = self.inspections.lock().unwrap();
434 Ok(inspections
435 .values()
436 .filter(|i| i.organization_id == organization_id)
437 .cloned()
438 .collect())
439 }
440
441 async fn find_all_paginated(
442 &self,
443 _page_request: &PageRequest,
444 _filters: &TechnicalInspectionFilters,
445 ) -> Result<(Vec<TechnicalInspection>, i64), String> {
446 let inspections = self.inspections.lock().unwrap();
447 let all: Vec<TechnicalInspection> = inspections.values().cloned().collect();
448 let count = all.len() as i64;
449 Ok((all, count))
450 }
451
452 async fn find_overdue(
453 &self,
454 building_id: Uuid,
455 ) -> Result<Vec<TechnicalInspection>, String> {
456 let inspections = self.inspections.lock().unwrap();
457 Ok(inspections
458 .values()
459 .filter(|i| i.building_id == building_id && i.is_overdue())
460 .cloned()
461 .collect())
462 }
463
464 async fn find_upcoming(
465 &self,
466 building_id: Uuid,
467 days: i32,
468 ) -> Result<Vec<TechnicalInspection>, String> {
469 let inspections = self.inspections.lock().unwrap();
470 Ok(inspections
471 .values()
472 .filter(|i| {
473 i.building_id == building_id
474 && i.days_until_due() >= 0
475 && i.days_until_due() <= days as i64
476 })
477 .cloned()
478 .collect())
479 }
480
481 async fn find_by_type(
482 &self,
483 building_id: Uuid,
484 inspection_type: &str,
485 ) -> Result<Vec<TechnicalInspection>, String> {
486 let inspections = self.inspections.lock().unwrap();
487 Ok(inspections
488 .values()
489 .filter(|i| {
490 i.building_id == building_id
491 && format!("{:?}", i.inspection_type).to_lowercase()
492 == inspection_type.to_lowercase()
493 })
494 .cloned()
495 .collect())
496 }
497
498 async fn update(
499 &self,
500 inspection: &TechnicalInspection,
501 ) -> Result<TechnicalInspection, String> {
502 let mut inspections = self.inspections.lock().unwrap();
503 inspections.insert(inspection.id, inspection.clone());
504 Ok(inspection.clone())
505 }
506
507 async fn delete(&self, id: Uuid) -> Result<bool, String> {
508 let mut inspections = self.inspections.lock().unwrap();
509 Ok(inspections.remove(&id).is_some())
510 }
511 }
512
513 fn make_use_cases(repo: MockTechnicalInspectionRepository) -> TechnicalInspectionUseCases {
516 TechnicalInspectionUseCases::new(Arc::new(repo))
517 }
518
519 fn valid_create_dto(org_id: Uuid, building_id: Uuid) -> CreateTechnicalInspectionDto {
520 CreateTechnicalInspectionDto {
521 organization_id: org_id.to_string(),
522 building_id: building_id.to_string(),
523 title: "Inspection annuelle ascenseur".to_string(),
524 description: Some("Vérification complète de l'ascenseur".to_string()),
525 inspection_type: InspectionType::Elevator,
526 inspector_name: "Schindler Belgium".to_string(),
527 inspector_company: Some("Schindler SA".to_string()),
528 inspector_certification: Some("CERT-2026-001".to_string()),
529 inspection_date: "2026-03-01T10:00:00Z".to_string(),
530 result_summary: None,
531 defects_found: None,
532 recommendations: None,
533 compliant: None,
534 compliance_certificate_number: None,
535 compliance_valid_until: None,
536 cost: Some(dec!(450.00)),
537 invoice_number: Some("INV-2026-100".to_string()),
538 notes: None,
539 }
540 }
541
542 #[tokio::test]
545 async fn test_create_technical_inspection_success() {
546 let repo = MockTechnicalInspectionRepository::new();
547 let uc = make_use_cases(repo);
548 let org_id = Uuid::new_v4();
549 let building_id = Uuid::new_v4();
550
551 let result = uc
552 .create_technical_inspection(valid_create_dto(org_id, building_id))
553 .await;
554
555 assert!(result.is_ok());
556 let dto = result.unwrap();
557 assert_eq!(dto.organization_id, org_id.to_string());
558 assert_eq!(dto.building_id, building_id.to_string());
559 assert_eq!(dto.title, "Inspection annuelle ascenseur");
560 assert_eq!(dto.inspector_name, "Schindler Belgium");
561 assert_eq!(dto.inspector_company, Some("Schindler SA".to_string()));
562 assert_eq!(dto.cost, Some(dec!(450.00)));
563 assert_eq!(dto.status, InspectionStatus::Scheduled);
564 assert!(dto.reports.is_empty());
565 assert!(dto.photos.is_empty());
566 assert!(dto.certificates.is_empty());
567 }
568
569 #[tokio::test]
570 async fn test_create_technical_inspection_invalid_date_format() {
571 let repo = MockTechnicalInspectionRepository::new();
572 let uc = make_use_cases(repo);
573 let org_id = Uuid::new_v4();
574 let building_id = Uuid::new_v4();
575
576 let mut dto = valid_create_dto(org_id, building_id);
577 dto.inspection_date = "not-a-date".to_string();
578
579 let result = uc.create_technical_inspection(dto).await;
580 assert!(result.is_err());
581 assert_eq!(result.unwrap_err(), "Invalid inspection_date format");
582 }
583
584 #[tokio::test]
585 async fn test_create_technical_inspection_invalid_org_id() {
586 let repo = MockTechnicalInspectionRepository::new();
587 let uc = make_use_cases(repo);
588
589 let mut dto = valid_create_dto(Uuid::new_v4(), Uuid::new_v4());
590 dto.organization_id = "bad-uuid".to_string();
591
592 let result = uc.create_technical_inspection(dto).await;
593 assert!(result.is_err());
594 assert_eq!(result.unwrap_err(), "Invalid organization_id format");
595 }
596
597 #[tokio::test]
598 async fn test_get_technical_inspection_found() {
599 let repo = MockTechnicalInspectionRepository::new();
600 let uc = make_use_cases(repo);
601 let org_id = Uuid::new_v4();
602 let building_id = Uuid::new_v4();
603
604 let created = uc
605 .create_technical_inspection(valid_create_dto(org_id, building_id))
606 .await
607 .unwrap();
608 let inspection_id = Uuid::parse_str(&created.id).unwrap();
609
610 let result = uc.get_technical_inspection(inspection_id).await;
611 assert!(result.is_ok());
612 let found = result.unwrap();
613 assert!(found.is_some());
614 let found = found.unwrap();
615 assert_eq!(found.id, created.id);
616 assert_eq!(found.title, "Inspection annuelle ascenseur");
617 }
618
619 #[tokio::test]
620 async fn test_get_technical_inspection_not_found() {
621 let repo = MockTechnicalInspectionRepository::new();
622 let uc = make_use_cases(repo);
623
624 let result = uc.get_technical_inspection(Uuid::new_v4()).await;
625 assert!(result.is_ok());
626 assert!(result.unwrap().is_none());
627 }
628
629 #[tokio::test]
630 async fn test_list_technical_inspections_by_building() {
631 let repo = MockTechnicalInspectionRepository::new();
632 let uc = make_use_cases(repo);
633 let org_id = Uuid::new_v4();
634 let building_a = Uuid::new_v4();
635 let building_b = Uuid::new_v4();
636
637 let mut dto_a1 = valid_create_dto(org_id, building_a);
639 dto_a1.title = "Elevator inspection".to_string();
640 uc.create_technical_inspection(dto_a1).await.unwrap();
641
642 let mut dto_a2 = valid_create_dto(org_id, building_a);
643 dto_a2.title = "Boiler inspection".to_string();
644 dto_a2.inspection_type = InspectionType::Boiler;
645 uc.create_technical_inspection(dto_a2).await.unwrap();
646
647 let dto_b = valid_create_dto(org_id, building_b);
649 uc.create_technical_inspection(dto_b).await.unwrap();
650
651 let result = uc.list_technical_inspections_by_building(building_a).await;
652 assert!(result.is_ok());
653 let inspections = result.unwrap();
654 assert_eq!(inspections.len(), 2);
655 assert!(inspections
656 .iter()
657 .all(|i| i.building_id == building_a.to_string()));
658 }
659
660 #[tokio::test]
661 async fn test_mark_as_completed() {
662 let repo = MockTechnicalInspectionRepository::new();
663 let uc = make_use_cases(repo);
664 let org_id = Uuid::new_v4();
665 let building_id = Uuid::new_v4();
666
667 let created = uc
668 .create_technical_inspection(valid_create_dto(org_id, building_id))
669 .await
670 .unwrap();
671 let inspection_id = Uuid::parse_str(&created.id).unwrap();
672 assert_eq!(created.status, InspectionStatus::Scheduled);
673
674 let result = uc.mark_as_completed(inspection_id).await;
675 assert!(result.is_ok());
676 let completed = result.unwrap();
677 assert_eq!(completed.status, InspectionStatus::Completed);
678 }
679
680 #[tokio::test]
681 async fn test_mark_as_completed_not_found() {
682 let repo = MockTechnicalInspectionRepository::new();
683 let uc = make_use_cases(repo);
684
685 let result = uc.mark_as_completed(Uuid::new_v4()).await;
686 assert!(result.is_err());
687 assert_eq!(result.unwrap_err(), "Technical inspection not found");
688 }
689
690 #[tokio::test]
691 async fn test_add_report() {
692 let repo = MockTechnicalInspectionRepository::new();
693 let uc = make_use_cases(repo);
694 let org_id = Uuid::new_v4();
695 let building_id = Uuid::new_v4();
696
697 let created = uc
698 .create_technical_inspection(valid_create_dto(org_id, building_id))
699 .await
700 .unwrap();
701 let inspection_id = Uuid::parse_str(&created.id).unwrap();
702 assert!(created.reports.is_empty());
703
704 let result = uc
705 .add_report(
706 inspection_id,
707 AddReportDto {
708 report_path: "/uploads/reports/elevator-2026-03.pdf".to_string(),
709 },
710 )
711 .await;
712
713 assert!(result.is_ok());
714 let updated = result.unwrap();
715 assert_eq!(updated.reports.len(), 1);
716 assert_eq!(updated.reports[0], "/uploads/reports/elevator-2026-03.pdf");
717 }
718
719 #[tokio::test]
720 async fn test_add_report_not_found() {
721 let repo = MockTechnicalInspectionRepository::new();
722 let uc = make_use_cases(repo);
723
724 let result = uc
725 .add_report(
726 Uuid::new_v4(),
727 AddReportDto {
728 report_path: "/uploads/reports/test.pdf".to_string(),
729 },
730 )
731 .await;
732
733 assert!(result.is_err());
734 assert_eq!(result.unwrap_err(), "Technical inspection not found");
735 }
736
737 #[tokio::test]
738 async fn test_add_photo() {
739 let repo = MockTechnicalInspectionRepository::new();
740 let uc = make_use_cases(repo);
741 let org_id = Uuid::new_v4();
742 let building_id = Uuid::new_v4();
743
744 let created = uc
745 .create_technical_inspection(valid_create_dto(org_id, building_id))
746 .await
747 .unwrap();
748 let inspection_id = Uuid::parse_str(&created.id).unwrap();
749
750 let result = uc
751 .add_photo(
752 inspection_id,
753 AddInspectionPhotoDto {
754 photo_path: "/uploads/photos/elevator-panel.jpg".to_string(),
755 },
756 )
757 .await;
758
759 assert!(result.is_ok());
760 let updated = result.unwrap();
761 assert_eq!(updated.photos.len(), 1);
762 assert_eq!(updated.photos[0], "/uploads/photos/elevator-panel.jpg");
763 }
764
765 #[tokio::test]
766 async fn test_add_photo_not_found() {
767 let repo = MockTechnicalInspectionRepository::new();
768 let uc = make_use_cases(repo);
769
770 let result = uc
771 .add_photo(
772 Uuid::new_v4(),
773 AddInspectionPhotoDto {
774 photo_path: "/uploads/photos/test.jpg".to_string(),
775 },
776 )
777 .await;
778
779 assert!(result.is_err());
780 assert_eq!(result.unwrap_err(), "Technical inspection not found");
781 }
782
783 #[tokio::test]
784 async fn test_delete_technical_inspection() {
785 let repo = MockTechnicalInspectionRepository::new();
786 let uc = make_use_cases(repo);
787 let org_id = Uuid::new_v4();
788 let building_id = Uuid::new_v4();
789
790 let created = uc
791 .create_technical_inspection(valid_create_dto(org_id, building_id))
792 .await
793 .unwrap();
794 let inspection_id = Uuid::parse_str(&created.id).unwrap();
795
796 let result = uc.delete_technical_inspection(inspection_id).await;
798 assert!(result.is_ok());
799 assert!(result.unwrap());
800
801 let get_result = uc.get_technical_inspection(inspection_id).await;
803 assert!(get_result.is_ok());
804 assert!(get_result.unwrap().is_none());
805 }
806
807 #[tokio::test]
808 async fn test_delete_technical_inspection_not_found() {
809 let repo = MockTechnicalInspectionRepository::new();
810 let uc = make_use_cases(repo);
811
812 let result = uc.delete_technical_inspection(Uuid::new_v4()).await;
813 assert!(result.is_ok());
814 assert!(!result.unwrap());
815 }
816
817 #[tokio::test]
818 async fn test_add_multiple_reports_and_photos() {
819 let repo = MockTechnicalInspectionRepository::new();
820 let uc = make_use_cases(repo);
821 let org_id = Uuid::new_v4();
822 let building_id = Uuid::new_v4();
823
824 let created = uc
825 .create_technical_inspection(valid_create_dto(org_id, building_id))
826 .await
827 .unwrap();
828 let inspection_id = Uuid::parse_str(&created.id).unwrap();
829
830 uc.add_report(
832 inspection_id,
833 AddReportDto {
834 report_path: "/reports/report1.pdf".to_string(),
835 },
836 )
837 .await
838 .unwrap();
839 uc.add_report(
840 inspection_id,
841 AddReportDto {
842 report_path: "/reports/report2.pdf".to_string(),
843 },
844 )
845 .await
846 .unwrap();
847
848 uc.add_photo(
850 inspection_id,
851 AddInspectionPhotoDto {
852 photo_path: "/photos/photo1.jpg".to_string(),
853 },
854 )
855 .await
856 .unwrap();
857
858 let inspection = uc
859 .get_technical_inspection(inspection_id)
860 .await
861 .unwrap()
862 .unwrap();
863 assert_eq!(inspection.reports.len(), 2);
864 assert_eq!(inspection.photos.len(), 1);
865 }
866}