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.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.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 std::collections::HashMap;
384 use std::sync::Mutex;
385
386 struct MockTechnicalInspectionRepository {
389 inspections: Mutex<HashMap<Uuid, TechnicalInspection>>,
390 }
391
392 impl MockTechnicalInspectionRepository {
393 fn new() -> Self {
394 Self {
395 inspections: Mutex::new(HashMap::new()),
396 }
397 }
398 }
399
400 #[async_trait]
401 impl TechnicalInspectionRepository for MockTechnicalInspectionRepository {
402 async fn create(
403 &self,
404 inspection: &TechnicalInspection,
405 ) -> Result<TechnicalInspection, String> {
406 let mut inspections = self.inspections.lock().unwrap();
407 inspections.insert(inspection.id, inspection.clone());
408 Ok(inspection.clone())
409 }
410
411 async fn find_by_id(&self, id: Uuid) -> Result<Option<TechnicalInspection>, String> {
412 let inspections = self.inspections.lock().unwrap();
413 Ok(inspections.get(&id).cloned())
414 }
415
416 async fn find_by_building(
417 &self,
418 building_id: Uuid,
419 ) -> Result<Vec<TechnicalInspection>, String> {
420 let inspections = self.inspections.lock().unwrap();
421 Ok(inspections
422 .values()
423 .filter(|i| i.building_id == building_id)
424 .cloned()
425 .collect())
426 }
427
428 async fn find_by_organization(
429 &self,
430 organization_id: Uuid,
431 ) -> Result<Vec<TechnicalInspection>, String> {
432 let inspections = self.inspections.lock().unwrap();
433 Ok(inspections
434 .values()
435 .filter(|i| i.organization_id == organization_id)
436 .cloned()
437 .collect())
438 }
439
440 async fn find_all_paginated(
441 &self,
442 _page_request: &PageRequest,
443 _filters: &TechnicalInspectionFilters,
444 ) -> Result<(Vec<TechnicalInspection>, i64), String> {
445 let inspections = self.inspections.lock().unwrap();
446 let all: Vec<TechnicalInspection> = inspections.values().cloned().collect();
447 let count = all.len() as i64;
448 Ok((all, count))
449 }
450
451 async fn find_overdue(
452 &self,
453 building_id: Uuid,
454 ) -> Result<Vec<TechnicalInspection>, String> {
455 let inspections = self.inspections.lock().unwrap();
456 Ok(inspections
457 .values()
458 .filter(|i| i.building_id == building_id && i.is_overdue())
459 .cloned()
460 .collect())
461 }
462
463 async fn find_upcoming(
464 &self,
465 building_id: Uuid,
466 days: i32,
467 ) -> Result<Vec<TechnicalInspection>, String> {
468 let inspections = self.inspections.lock().unwrap();
469 Ok(inspections
470 .values()
471 .filter(|i| {
472 i.building_id == building_id
473 && i.days_until_due() >= 0
474 && i.days_until_due() <= days as i64
475 })
476 .cloned()
477 .collect())
478 }
479
480 async fn find_by_type(
481 &self,
482 building_id: Uuid,
483 inspection_type: &str,
484 ) -> Result<Vec<TechnicalInspection>, String> {
485 let inspections = self.inspections.lock().unwrap();
486 Ok(inspections
487 .values()
488 .filter(|i| {
489 i.building_id == building_id
490 && format!("{:?}", i.inspection_type).to_lowercase()
491 == inspection_type.to_lowercase()
492 })
493 .cloned()
494 .collect())
495 }
496
497 async fn update(
498 &self,
499 inspection: &TechnicalInspection,
500 ) -> Result<TechnicalInspection, String> {
501 let mut inspections = self.inspections.lock().unwrap();
502 inspections.insert(inspection.id, inspection.clone());
503 Ok(inspection.clone())
504 }
505
506 async fn delete(&self, id: Uuid) -> Result<bool, String> {
507 let mut inspections = self.inspections.lock().unwrap();
508 Ok(inspections.remove(&id).is_some())
509 }
510 }
511
512 fn make_use_cases(repo: MockTechnicalInspectionRepository) -> TechnicalInspectionUseCases {
515 TechnicalInspectionUseCases::new(Arc::new(repo))
516 }
517
518 fn valid_create_dto(org_id: Uuid, building_id: Uuid) -> CreateTechnicalInspectionDto {
519 CreateTechnicalInspectionDto {
520 organization_id: org_id.to_string(),
521 building_id: building_id.to_string(),
522 title: "Inspection annuelle ascenseur".to_string(),
523 description: Some("Vérification complète de l'ascenseur".to_string()),
524 inspection_type: InspectionType::Elevator,
525 inspector_name: "Schindler Belgium".to_string(),
526 inspector_company: Some("Schindler SA".to_string()),
527 inspector_certification: Some("CERT-2026-001".to_string()),
528 inspection_date: "2026-03-01T10:00:00Z".to_string(),
529 result_summary: None,
530 defects_found: None,
531 recommendations: None,
532 compliant: None,
533 compliance_certificate_number: None,
534 compliance_valid_until: None,
535 cost: Some(450.0),
536 invoice_number: Some("INV-2026-100".to_string()),
537 notes: None,
538 }
539 }
540
541 #[tokio::test]
544 async fn test_create_technical_inspection_success() {
545 let repo = MockTechnicalInspectionRepository::new();
546 let uc = make_use_cases(repo);
547 let org_id = Uuid::new_v4();
548 let building_id = Uuid::new_v4();
549
550 let result = uc
551 .create_technical_inspection(valid_create_dto(org_id, building_id))
552 .await;
553
554 assert!(result.is_ok());
555 let dto = result.unwrap();
556 assert_eq!(dto.organization_id, org_id.to_string());
557 assert_eq!(dto.building_id, building_id.to_string());
558 assert_eq!(dto.title, "Inspection annuelle ascenseur");
559 assert_eq!(dto.inspector_name, "Schindler Belgium");
560 assert_eq!(dto.inspector_company, Some("Schindler SA".to_string()));
561 assert_eq!(dto.cost, Some(450.0));
562 assert_eq!(dto.status, InspectionStatus::Scheduled);
563 assert!(dto.reports.is_empty());
564 assert!(dto.photos.is_empty());
565 assert!(dto.certificates.is_empty());
566 }
567
568 #[tokio::test]
569 async fn test_create_technical_inspection_invalid_date_format() {
570 let repo = MockTechnicalInspectionRepository::new();
571 let uc = make_use_cases(repo);
572 let org_id = Uuid::new_v4();
573 let building_id = Uuid::new_v4();
574
575 let mut dto = valid_create_dto(org_id, building_id);
576 dto.inspection_date = "not-a-date".to_string();
577
578 let result = uc.create_technical_inspection(dto).await;
579 assert!(result.is_err());
580 assert_eq!(result.unwrap_err(), "Invalid inspection_date format");
581 }
582
583 #[tokio::test]
584 async fn test_create_technical_inspection_invalid_org_id() {
585 let repo = MockTechnicalInspectionRepository::new();
586 let uc = make_use_cases(repo);
587
588 let mut dto = valid_create_dto(Uuid::new_v4(), Uuid::new_v4());
589 dto.organization_id = "bad-uuid".to_string();
590
591 let result = uc.create_technical_inspection(dto).await;
592 assert!(result.is_err());
593 assert_eq!(result.unwrap_err(), "Invalid organization_id format");
594 }
595
596 #[tokio::test]
597 async fn test_get_technical_inspection_found() {
598 let repo = MockTechnicalInspectionRepository::new();
599 let uc = make_use_cases(repo);
600 let org_id = Uuid::new_v4();
601 let building_id = Uuid::new_v4();
602
603 let created = uc
604 .create_technical_inspection(valid_create_dto(org_id, building_id))
605 .await
606 .unwrap();
607 let inspection_id = Uuid::parse_str(&created.id).unwrap();
608
609 let result = uc.get_technical_inspection(inspection_id).await;
610 assert!(result.is_ok());
611 let found = result.unwrap();
612 assert!(found.is_some());
613 let found = found.unwrap();
614 assert_eq!(found.id, created.id);
615 assert_eq!(found.title, "Inspection annuelle ascenseur");
616 }
617
618 #[tokio::test]
619 async fn test_get_technical_inspection_not_found() {
620 let repo = MockTechnicalInspectionRepository::new();
621 let uc = make_use_cases(repo);
622
623 let result = uc.get_technical_inspection(Uuid::new_v4()).await;
624 assert!(result.is_ok());
625 assert!(result.unwrap().is_none());
626 }
627
628 #[tokio::test]
629 async fn test_list_technical_inspections_by_building() {
630 let repo = MockTechnicalInspectionRepository::new();
631 let uc = make_use_cases(repo);
632 let org_id = Uuid::new_v4();
633 let building_a = Uuid::new_v4();
634 let building_b = Uuid::new_v4();
635
636 let mut dto_a1 = valid_create_dto(org_id, building_a);
638 dto_a1.title = "Elevator inspection".to_string();
639 uc.create_technical_inspection(dto_a1).await.unwrap();
640
641 let mut dto_a2 = valid_create_dto(org_id, building_a);
642 dto_a2.title = "Boiler inspection".to_string();
643 dto_a2.inspection_type = InspectionType::Boiler;
644 uc.create_technical_inspection(dto_a2).await.unwrap();
645
646 let dto_b = valid_create_dto(org_id, building_b);
648 uc.create_technical_inspection(dto_b).await.unwrap();
649
650 let result = uc.list_technical_inspections_by_building(building_a).await;
651 assert!(result.is_ok());
652 let inspections = result.unwrap();
653 assert_eq!(inspections.len(), 2);
654 assert!(inspections
655 .iter()
656 .all(|i| i.building_id == building_a.to_string()));
657 }
658
659 #[tokio::test]
660 async fn test_mark_as_completed() {
661 let repo = MockTechnicalInspectionRepository::new();
662 let uc = make_use_cases(repo);
663 let org_id = Uuid::new_v4();
664 let building_id = Uuid::new_v4();
665
666 let created = uc
667 .create_technical_inspection(valid_create_dto(org_id, building_id))
668 .await
669 .unwrap();
670 let inspection_id = Uuid::parse_str(&created.id).unwrap();
671 assert_eq!(created.status, InspectionStatus::Scheduled);
672
673 let result = uc.mark_as_completed(inspection_id).await;
674 assert!(result.is_ok());
675 let completed = result.unwrap();
676 assert_eq!(completed.status, InspectionStatus::Completed);
677 }
678
679 #[tokio::test]
680 async fn test_mark_as_completed_not_found() {
681 let repo = MockTechnicalInspectionRepository::new();
682 let uc = make_use_cases(repo);
683
684 let result = uc.mark_as_completed(Uuid::new_v4()).await;
685 assert!(result.is_err());
686 assert_eq!(result.unwrap_err(), "Technical inspection not found");
687 }
688
689 #[tokio::test]
690 async fn test_add_report() {
691 let repo = MockTechnicalInspectionRepository::new();
692 let uc = make_use_cases(repo);
693 let org_id = Uuid::new_v4();
694 let building_id = Uuid::new_v4();
695
696 let created = uc
697 .create_technical_inspection(valid_create_dto(org_id, building_id))
698 .await
699 .unwrap();
700 let inspection_id = Uuid::parse_str(&created.id).unwrap();
701 assert!(created.reports.is_empty());
702
703 let result = uc
704 .add_report(
705 inspection_id,
706 AddReportDto {
707 report_path: "/uploads/reports/elevator-2026-03.pdf".to_string(),
708 },
709 )
710 .await;
711
712 assert!(result.is_ok());
713 let updated = result.unwrap();
714 assert_eq!(updated.reports.len(), 1);
715 assert_eq!(updated.reports[0], "/uploads/reports/elevator-2026-03.pdf");
716 }
717
718 #[tokio::test]
719 async fn test_add_report_not_found() {
720 let repo = MockTechnicalInspectionRepository::new();
721 let uc = make_use_cases(repo);
722
723 let result = uc
724 .add_report(
725 Uuid::new_v4(),
726 AddReportDto {
727 report_path: "/uploads/reports/test.pdf".to_string(),
728 },
729 )
730 .await;
731
732 assert!(result.is_err());
733 assert_eq!(result.unwrap_err(), "Technical inspection not found");
734 }
735
736 #[tokio::test]
737 async fn test_add_photo() {
738 let repo = MockTechnicalInspectionRepository::new();
739 let uc = make_use_cases(repo);
740 let org_id = Uuid::new_v4();
741 let building_id = Uuid::new_v4();
742
743 let created = uc
744 .create_technical_inspection(valid_create_dto(org_id, building_id))
745 .await
746 .unwrap();
747 let inspection_id = Uuid::parse_str(&created.id).unwrap();
748
749 let result = uc
750 .add_photo(
751 inspection_id,
752 AddInspectionPhotoDto {
753 photo_path: "/uploads/photos/elevator-panel.jpg".to_string(),
754 },
755 )
756 .await;
757
758 assert!(result.is_ok());
759 let updated = result.unwrap();
760 assert_eq!(updated.photos.len(), 1);
761 assert_eq!(updated.photos[0], "/uploads/photos/elevator-panel.jpg");
762 }
763
764 #[tokio::test]
765 async fn test_add_photo_not_found() {
766 let repo = MockTechnicalInspectionRepository::new();
767 let uc = make_use_cases(repo);
768
769 let result = uc
770 .add_photo(
771 Uuid::new_v4(),
772 AddInspectionPhotoDto {
773 photo_path: "/uploads/photos/test.jpg".to_string(),
774 },
775 )
776 .await;
777
778 assert!(result.is_err());
779 assert_eq!(result.unwrap_err(), "Technical inspection not found");
780 }
781
782 #[tokio::test]
783 async fn test_delete_technical_inspection() {
784 let repo = MockTechnicalInspectionRepository::new();
785 let uc = make_use_cases(repo);
786 let org_id = Uuid::new_v4();
787 let building_id = Uuid::new_v4();
788
789 let created = uc
790 .create_technical_inspection(valid_create_dto(org_id, building_id))
791 .await
792 .unwrap();
793 let inspection_id = Uuid::parse_str(&created.id).unwrap();
794
795 let result = uc.delete_technical_inspection(inspection_id).await;
797 assert!(result.is_ok());
798 assert!(result.unwrap());
799
800 let get_result = uc.get_technical_inspection(inspection_id).await;
802 assert!(get_result.is_ok());
803 assert!(get_result.unwrap().is_none());
804 }
805
806 #[tokio::test]
807 async fn test_delete_technical_inspection_not_found() {
808 let repo = MockTechnicalInspectionRepository::new();
809 let uc = make_use_cases(repo);
810
811 let result = uc.delete_technical_inspection(Uuid::new_v4()).await;
812 assert!(result.is_ok());
813 assert!(!result.unwrap());
814 }
815
816 #[tokio::test]
817 async fn test_add_multiple_reports_and_photos() {
818 let repo = MockTechnicalInspectionRepository::new();
819 let uc = make_use_cases(repo);
820 let org_id = Uuid::new_v4();
821 let building_id = Uuid::new_v4();
822
823 let created = uc
824 .create_technical_inspection(valid_create_dto(org_id, building_id))
825 .await
826 .unwrap();
827 let inspection_id = Uuid::parse_str(&created.id).unwrap();
828
829 uc.add_report(
831 inspection_id,
832 AddReportDto {
833 report_path: "/reports/report1.pdf".to_string(),
834 },
835 )
836 .await
837 .unwrap();
838 uc.add_report(
839 inspection_id,
840 AddReportDto {
841 report_path: "/reports/report2.pdf".to_string(),
842 },
843 )
844 .await
845 .unwrap();
846
847 uc.add_photo(
849 inspection_id,
850 AddInspectionPhotoDto {
851 photo_path: "/photos/photo1.jpg".to_string(),
852 },
853 )
854 .await
855 .unwrap();
856
857 let inspection = uc
858 .get_technical_inspection(inspection_id)
859 .await
860 .unwrap()
861 .unwrap();
862 assert_eq!(inspection.reports.len(), 2);
863 assert_eq!(inspection.photos.len(), 1);
864 }
865}