Skip to main content

koprogo_api/application/use_cases/
technical_inspection_use_cases.rs

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            // Recalculate next_due_date when type changes
205            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            // Recalculate next_due_date when date changes
222            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    // ========== Mock Repository ==========
388
389    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    // ========== Helpers ==========
514
515    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    // ========== Tests ==========
543
544    #[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        // Create 2 inspections for building A
638        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        // Create 1 inspection for building B
648        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        // Delete should succeed
797        let result = uc.delete_technical_inspection(inspection_id).await;
798        assert!(result.is_ok());
799        assert!(result.unwrap());
800
801        // Should no longer be found
802        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        // Add 2 reports
831        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        // Add 1 photo
849        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}