koprogo_api/application/use_cases/
owner_use_cases.rs

1use crate::application::dto::{CreateOwnerDto, OwnerFilters, OwnerResponseDto, PageRequest};
2use crate::application::ports::OwnerRepository;
3use crate::domain::entities::Owner;
4use std::sync::Arc;
5use uuid::Uuid;
6
7pub struct OwnerUseCases {
8    repository: Arc<dyn OwnerRepository>,
9}
10
11impl OwnerUseCases {
12    pub fn new(repository: Arc<dyn OwnerRepository>) -> Self {
13        Self { repository }
14    }
15
16    pub async fn create_owner(&self, dto: CreateOwnerDto) -> Result<OwnerResponseDto, String> {
17        let organization_id = Uuid::parse_str(&dto.organization_id)
18            .map_err(|_| "Invalid organization_id format".to_string())?;
19
20        // Vérifier si l'email existe déjà
21        if (self.repository.find_by_email(&dto.email).await?).is_some() {
22            return Err("Email already exists".to_string());
23        }
24
25        let mut owner = Owner::new(
26            organization_id,
27            dto.first_name,
28            dto.last_name,
29            dto.email,
30            dto.phone,
31            dto.address,
32            dto.city,
33            dto.postal_code,
34            dto.country,
35        )?;
36
37        // Link to user account if provided
38        if let Some(user_id_str) = dto.user_id {
39            if !user_id_str.is_empty() {
40                owner.user_id = Some(
41                    Uuid::parse_str(&user_id_str)
42                        .map_err(|_| "Invalid user_id format".to_string())?,
43                );
44            }
45        }
46
47        let created = self.repository.create(&owner).await?;
48        Ok(self.to_response_dto(&created))
49    }
50
51    pub async fn get_owner(&self, id: Uuid) -> Result<Option<OwnerResponseDto>, String> {
52        let owner = self.repository.find_by_id(id).await?;
53        Ok(owner.map(|o| self.to_response_dto(&o)))
54    }
55
56    pub async fn find_owner_by_user_id(
57        &self,
58        user_id: Uuid,
59    ) -> Result<Option<OwnerResponseDto>, String> {
60        let owner = self.repository.find_by_user_id(user_id).await?;
61        Ok(owner.map(|o| self.to_response_dto(&o)))
62    }
63
64    pub async fn find_owner_by_user_id_and_organization(
65        &self,
66        user_id: Uuid,
67        organization_id: Uuid,
68    ) -> Result<Option<OwnerResponseDto>, String> {
69        let owner = self
70            .repository
71            .find_by_user_id_and_organization(user_id, organization_id)
72            .await?;
73        Ok(owner.map(|o| self.to_response_dto(&o)))
74    }
75
76    pub async fn list_owners(&self) -> Result<Vec<OwnerResponseDto>, String> {
77        let owners = self.repository.find_all().await?;
78        Ok(owners.iter().map(|o| self.to_response_dto(o)).collect())
79    }
80
81    pub async fn list_owners_paginated(
82        &self,
83        page_request: &PageRequest,
84        organization_id: Option<Uuid>,
85    ) -> Result<(Vec<OwnerResponseDto>, i64), String> {
86        let filters = OwnerFilters {
87            organization_id,
88            ..Default::default()
89        };
90
91        let (owners, total) = self
92            .repository
93            .find_all_paginated(page_request, &filters)
94            .await?;
95
96        let dtos = owners.iter().map(|o| self.to_response_dto(o)).collect();
97        Ok((dtos, total))
98    }
99
100    pub async fn update_owner(
101        &self,
102        id: Uuid,
103        first_name: String,
104        last_name: String,
105        email: String,
106        phone: Option<String>,
107    ) -> Result<OwnerResponseDto, String> {
108        // Get existing owner
109        let mut owner = self
110            .repository
111            .find_by_id(id)
112            .await?
113            .ok_or("Owner not found".to_string())?;
114
115        // Check if email is being changed and if the new email already exists
116        if owner.email != email {
117            if let Some(existing) = self.repository.find_by_email(&email).await? {
118                if existing.id != id {
119                    return Err("Email already exists".to_string());
120                }
121            }
122        }
123
124        // Update owner fields
125        owner.first_name = first_name;
126        owner.last_name = last_name;
127        owner.email = email;
128        owner.phone = phone;
129
130        // Save updated owner
131        let updated = self.repository.update(&owner).await?;
132        Ok(self.to_response_dto(&updated))
133    }
134
135    fn to_response_dto(&self, owner: &Owner) -> OwnerResponseDto {
136        OwnerResponseDto {
137            id: owner.id.to_string(),
138            organization_id: owner.organization_id.to_string(),
139            user_id: owner.user_id.map(|id| id.to_string()),
140            first_name: owner.first_name.clone(),
141            last_name: owner.last_name.clone(),
142            email: owner.email.clone(),
143            phone: owner.phone.clone(),
144            address: owner.address.clone(),
145            city: owner.city.clone(),
146            postal_code: owner.postal_code.clone(),
147            country: owner.country.clone(),
148        }
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use async_trait::async_trait;
156    use std::collections::HashMap;
157    use std::sync::Mutex;
158
159    struct MockOwnerRepository {
160        items: Mutex<HashMap<Uuid, Owner>>,
161    }
162
163    impl MockOwnerRepository {
164        fn new() -> Self {
165            Self {
166                items: Mutex::new(HashMap::new()),
167            }
168        }
169    }
170
171    #[async_trait]
172    impl OwnerRepository for MockOwnerRepository {
173        async fn create(&self, owner: &Owner) -> Result<Owner, String> {
174            let mut items = self.items.lock().unwrap();
175            items.insert(owner.id, owner.clone());
176            Ok(owner.clone())
177        }
178
179        async fn find_by_id(&self, id: Uuid) -> Result<Option<Owner>, String> {
180            let items = self.items.lock().unwrap();
181            Ok(items.get(&id).cloned())
182        }
183
184        async fn find_by_user_id(&self, user_id: Uuid) -> Result<Option<Owner>, String> {
185            let items = self.items.lock().unwrap();
186            Ok(items.values().find(|o| o.user_id == Some(user_id)).cloned())
187        }
188
189        async fn find_by_user_id_and_organization(
190            &self,
191            user_id: Uuid,
192            organization_id: Uuid,
193        ) -> Result<Option<Owner>, String> {
194            let items = self.items.lock().unwrap();
195            Ok(items
196                .values()
197                .find(|o| o.user_id == Some(user_id) && o.organization_id == organization_id)
198                .cloned())
199        }
200
201        async fn find_by_email(&self, email: &str) -> Result<Option<Owner>, String> {
202            let items = self.items.lock().unwrap();
203            Ok(items.values().find(|o| o.email == email).cloned())
204        }
205
206        async fn find_all(&self) -> Result<Vec<Owner>, String> {
207            let items = self.items.lock().unwrap();
208            Ok(items.values().cloned().collect())
209        }
210
211        async fn find_all_paginated(
212            &self,
213            page_request: &PageRequest,
214            _filters: &OwnerFilters,
215        ) -> Result<(Vec<Owner>, i64), String> {
216            let items = self.items.lock().unwrap();
217            let all: Vec<Owner> = items.values().cloned().collect();
218            let total = all.len() as i64;
219            let offset = page_request.offset() as usize;
220            let limit = page_request.limit() as usize;
221            let page = all.into_iter().skip(offset).take(limit).collect();
222            Ok((page, total))
223        }
224
225        async fn update(&self, owner: &Owner) -> Result<Owner, String> {
226            let mut items = self.items.lock().unwrap();
227            items.insert(owner.id, owner.clone());
228            Ok(owner.clone())
229        }
230
231        async fn delete(&self, id: Uuid) -> Result<bool, String> {
232            let mut items = self.items.lock().unwrap();
233            Ok(items.remove(&id).is_some())
234        }
235    }
236
237    fn make_use_cases(repo: MockOwnerRepository) -> OwnerUseCases {
238        OwnerUseCases::new(Arc::new(repo))
239    }
240
241    fn make_create_dto(org_id: Uuid) -> CreateOwnerDto {
242        CreateOwnerDto {
243            organization_id: org_id.to_string(),
244            first_name: "Jean".to_string(),
245            last_name: "Dupont".to_string(),
246            email: "jean.dupont@example.com".to_string(),
247            phone: Some("+32470123456".to_string()),
248            address: "Rue de la Loi 16".to_string(),
249            city: "Bruxelles".to_string(),
250            postal_code: "1000".to_string(),
251            country: "Belgique".to_string(),
252            user_id: None,
253        }
254    }
255
256    #[tokio::test]
257    async fn test_create_owner_success() {
258        let repo = MockOwnerRepository::new();
259        let use_cases = make_use_cases(repo);
260        let org_id = Uuid::new_v4();
261
262        let result = use_cases.create_owner(make_create_dto(org_id)).await;
263
264        assert!(result.is_ok());
265        let dto = result.unwrap();
266        assert_eq!(dto.first_name, "Jean");
267        assert_eq!(dto.last_name, "Dupont");
268        assert_eq!(dto.email, "jean.dupont@example.com");
269        assert_eq!(dto.organization_id, org_id.to_string());
270    }
271
272    #[tokio::test]
273    async fn test_create_owner_duplicate_email() {
274        let repo = MockOwnerRepository::new();
275        let org_id = Uuid::new_v4();
276
277        // Pre-populate with an existing owner having the same email
278        let existing = Owner::new(
279            org_id,
280            "Marie".to_string(),
281            "Martin".to_string(),
282            "jean.dupont@example.com".to_string(),
283            None,
284            "Av. Louise 100".to_string(),
285            "Bruxelles".to_string(),
286            "1050".to_string(),
287            "Belgique".to_string(),
288        )
289        .unwrap();
290        repo.items.lock().unwrap().insert(existing.id, existing);
291
292        let use_cases = make_use_cases(repo);
293        let result = use_cases.create_owner(make_create_dto(org_id)).await;
294
295        assert!(result.is_err());
296        assert_eq!(result.unwrap_err(), "Email already exists");
297    }
298
299    #[tokio::test]
300    async fn test_get_owner_found() {
301        let repo = MockOwnerRepository::new();
302        let org_id = Uuid::new_v4();
303        let owner = Owner::new(
304            org_id,
305            "Jean".to_string(),
306            "Dupont".to_string(),
307            "jean@example.com".to_string(),
308            None,
309            "Rue Test 1".to_string(),
310            "Bruxelles".to_string(),
311            "1000".to_string(),
312            "Belgique".to_string(),
313        )
314        .unwrap();
315        let owner_id = owner.id;
316        repo.items.lock().unwrap().insert(owner.id, owner);
317
318        let use_cases = make_use_cases(repo);
319        let result = use_cases.get_owner(owner_id).await;
320
321        assert!(result.is_ok());
322        let dto = result.unwrap();
323        assert!(dto.is_some());
324        assert_eq!(dto.unwrap().first_name, "Jean");
325    }
326
327    #[tokio::test]
328    async fn test_get_owner_not_found() {
329        let repo = MockOwnerRepository::new();
330        let use_cases = make_use_cases(repo);
331
332        let result = use_cases.get_owner(Uuid::new_v4()).await;
333
334        assert!(result.is_ok());
335        assert!(result.unwrap().is_none());
336    }
337
338    #[tokio::test]
339    async fn test_list_owners() {
340        let repo = MockOwnerRepository::new();
341        let org_id = Uuid::new_v4();
342
343        let owner1 = Owner::new(
344            org_id,
345            "Jean".to_string(),
346            "Dupont".to_string(),
347            "jean@example.com".to_string(),
348            None,
349            "Rue A".to_string(),
350            "Bruxelles".to_string(),
351            "1000".to_string(),
352            "Belgique".to_string(),
353        )
354        .unwrap();
355        let owner2 = Owner::new(
356            org_id,
357            "Marie".to_string(),
358            "Martin".to_string(),
359            "marie@example.com".to_string(),
360            None,
361            "Rue B".to_string(),
362            "Liege".to_string(),
363            "4000".to_string(),
364            "Belgique".to_string(),
365        )
366        .unwrap();
367
368        {
369            let mut items = repo.items.lock().unwrap();
370            items.insert(owner1.id, owner1);
371            items.insert(owner2.id, owner2);
372        }
373
374        let use_cases = make_use_cases(repo);
375        let result = use_cases.list_owners().await;
376
377        assert!(result.is_ok());
378        assert_eq!(result.unwrap().len(), 2);
379    }
380
381    #[tokio::test]
382    async fn test_update_owner_success() {
383        let repo = MockOwnerRepository::new();
384        let org_id = Uuid::new_v4();
385        let owner = Owner::new(
386            org_id,
387            "Jean".to_string(),
388            "Dupont".to_string(),
389            "jean@example.com".to_string(),
390            None,
391            "Rue Test".to_string(),
392            "Bruxelles".to_string(),
393            "1000".to_string(),
394            "Belgique".to_string(),
395        )
396        .unwrap();
397        let owner_id = owner.id;
398        repo.items.lock().unwrap().insert(owner.id, owner);
399
400        let use_cases = make_use_cases(repo);
401        let result = use_cases
402            .update_owner(
403                owner_id,
404                "Pierre".to_string(),
405                "Durand".to_string(),
406                "pierre@example.com".to_string(),
407                Some("+32470999999".to_string()),
408            )
409            .await;
410
411        assert!(result.is_ok());
412        let dto = result.unwrap();
413        assert_eq!(dto.first_name, "Pierre");
414        assert_eq!(dto.last_name, "Durand");
415        assert_eq!(dto.email, "pierre@example.com");
416        assert_eq!(dto.phone, Some("+32470999999".to_string()));
417    }
418
419    #[tokio::test]
420    async fn test_update_owner_email_conflict() {
421        let repo = MockOwnerRepository::new();
422        let org_id = Uuid::new_v4();
423
424        let owner1 = Owner::new(
425            org_id,
426            "Jean".to_string(),
427            "Dupont".to_string(),
428            "jean@example.com".to_string(),
429            None,
430            "Rue A".to_string(),
431            "Bruxelles".to_string(),
432            "1000".to_string(),
433            "Belgique".to_string(),
434        )
435        .unwrap();
436        let owner1_id = owner1.id;
437
438        let owner2 = Owner::new(
439            org_id,
440            "Marie".to_string(),
441            "Martin".to_string(),
442            "marie@example.com".to_string(),
443            None,
444            "Rue B".to_string(),
445            "Liege".to_string(),
446            "4000".to_string(),
447            "Belgique".to_string(),
448        )
449        .unwrap();
450
451        {
452            let mut items = repo.items.lock().unwrap();
453            items.insert(owner1.id, owner1);
454            items.insert(owner2.id, owner2);
455        }
456
457        let use_cases = make_use_cases(repo);
458
459        // Try to update owner1's email to owner2's email
460        let result = use_cases
461            .update_owner(
462                owner1_id,
463                "Jean".to_string(),
464                "Dupont".to_string(),
465                "marie@example.com".to_string(), // Already taken by owner2
466                None,
467            )
468            .await;
469
470        assert!(result.is_err());
471        assert_eq!(result.unwrap_err(), "Email already exists");
472    }
473}