koprogo_api/application/use_cases/
building_use_cases.rs

1use crate::application::dto::{
2    BuildingFilters, BuildingResponseDto, CreateBuildingDto, PageRequest, UpdateBuildingDto,
3};
4use crate::application::ports::BuildingRepository;
5use crate::domain::entities::Building;
6use std::sync::Arc;
7use uuid::Uuid;
8
9pub struct BuildingUseCases {
10    repository: Arc<dyn BuildingRepository>,
11}
12
13impl BuildingUseCases {
14    pub fn new(repository: Arc<dyn BuildingRepository>) -> Self {
15        Self { repository }
16    }
17
18    pub async fn create_building(
19        &self,
20        dto: CreateBuildingDto,
21    ) -> Result<BuildingResponseDto, String> {
22        let organization_id = Uuid::parse_str(&dto.organization_id)
23            .map_err(|_| "Invalid organization_id format".to_string())?;
24
25        let building = Building::new(
26            organization_id,
27            dto.name,
28            dto.address,
29            dto.city,
30            dto.postal_code,
31            dto.country,
32            dto.total_units,
33            dto.construction_year,
34        )?;
35
36        let created = self.repository.create(&building).await?;
37        Ok(self.to_response_dto(&created))
38    }
39
40    pub async fn get_building(&self, id: Uuid) -> Result<Option<BuildingResponseDto>, String> {
41        let building = self.repository.find_by_id(id).await?;
42        Ok(building.map(|b| self.to_response_dto(&b)))
43    }
44
45    pub async fn list_buildings(&self) -> Result<Vec<BuildingResponseDto>, String> {
46        let buildings = self.repository.find_all().await?;
47        Ok(buildings.iter().map(|b| self.to_response_dto(b)).collect())
48    }
49
50    pub async fn list_buildings_paginated(
51        &self,
52        page_request: &PageRequest,
53        organization_id: Option<Uuid>,
54    ) -> Result<(Vec<BuildingResponseDto>, i64), String> {
55        let filters = BuildingFilters {
56            organization_id,
57            ..Default::default()
58        };
59
60        let (buildings, total) = self
61            .repository
62            .find_all_paginated(page_request, &filters)
63            .await?;
64
65        let dtos = buildings.iter().map(|b| self.to_response_dto(b)).collect();
66        Ok((dtos, total))
67    }
68
69    pub async fn update_building(
70        &self,
71        id: Uuid,
72        dto: UpdateBuildingDto,
73    ) -> Result<BuildingResponseDto, String> {
74        let mut building = self
75            .repository
76            .find_by_id(id)
77            .await?
78            .ok_or_else(|| "Building not found".to_string())?;
79
80        building.update_info(dto.name, dto.address, dto.city, dto.postal_code);
81
82        let updated = self.repository.update(&building).await?;
83        Ok(self.to_response_dto(&updated))
84    }
85
86    pub async fn delete_building(&self, id: Uuid) -> Result<bool, String> {
87        self.repository.delete(id).await
88    }
89
90    fn to_response_dto(&self, building: &Building) -> BuildingResponseDto {
91        BuildingResponseDto {
92            id: building.id.to_string(),
93            name: building.name.clone(),
94            address: building.address.clone(),
95            city: building.city.clone(),
96            postal_code: building.postal_code.clone(),
97            country: building.country.clone(),
98            total_units: building.total_units,
99            construction_year: building.construction_year,
100            created_at: building.created_at.to_rfc3339(),
101            updated_at: building.updated_at.to_rfc3339(),
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::application::ports::BuildingRepository;
110    use async_trait::async_trait;
111    use mockall::mock;
112
113    mock! {
114        BuildingRepo {}
115
116        #[async_trait]
117        impl BuildingRepository for BuildingRepo {
118            async fn create(&self, building: &Building) -> Result<Building, String>;
119            async fn find_by_id(&self, id: Uuid) -> Result<Option<Building>, String>;
120            async fn find_all(&self) -> Result<Vec<Building>, String>;
121            async fn find_all_paginated(
122                &self,
123                page_request: &PageRequest,
124                filters: &BuildingFilters,
125            ) -> Result<(Vec<Building>, i64), String>;
126            async fn update(&self, building: &Building) -> Result<Building, String>;
127            async fn delete(&self, id: Uuid) -> Result<bool, String>;
128        }
129    }
130
131    #[tokio::test]
132    async fn test_create_building_success() {
133        let mut mock_repo = MockBuildingRepo::new();
134
135        mock_repo.expect_create().returning(|b| Ok(b.clone()));
136
137        let use_cases = BuildingUseCases::new(Arc::new(mock_repo));
138
139        let dto = CreateBuildingDto {
140            organization_id: Uuid::new_v4().to_string(),
141            name: "Test Building".to_string(),
142            address: "123 Test St".to_string(),
143            city: "Paris".to_string(),
144            postal_code: "75001".to_string(),
145            country: "France".to_string(),
146            total_units: 10,
147            construction_year: Some(2000),
148        };
149
150        let result = use_cases.create_building(dto).await;
151        assert!(result.is_ok());
152    }
153
154    #[tokio::test]
155    async fn test_create_building_validation_fails() {
156        let mock_repo = MockBuildingRepo::new();
157        let use_cases = BuildingUseCases::new(Arc::new(mock_repo));
158
159        let dto = CreateBuildingDto {
160            organization_id: Uuid::new_v4().to_string(),
161            name: "".to_string(), // Invalid: empty name
162            address: "123 Test St".to_string(),
163            city: "Paris".to_string(),
164            postal_code: "75001".to_string(),
165            country: "France".to_string(),
166            total_units: 10,
167            construction_year: Some(2000),
168        };
169
170        let result = use_cases.create_building(dto).await;
171        assert!(result.is_err());
172    }
173}