koprogo_api/application/use_cases/
skill_use_cases.rs

1use crate::application::dto::{
2    CategoryCount, CreateSkillDto, ExpertiseCount, SkillResponseDto, SkillStatisticsDto,
3    SkillSummaryDto, UpdateSkillDto,
4};
5use crate::application::ports::{OwnerRepository, SkillRepository};
6use crate::domain::entities::{ExpertiseLevel, Skill, SkillCategory};
7use std::sync::Arc;
8use uuid::Uuid;
9
10pub struct SkillUseCases {
11    skill_repo: Arc<dyn SkillRepository>,
12    owner_repo: Arc<dyn OwnerRepository>,
13}
14
15impl SkillUseCases {
16    pub fn new(skill_repo: Arc<dyn SkillRepository>, owner_repo: Arc<dyn OwnerRepository>) -> Self {
17        Self {
18            skill_repo,
19            owner_repo,
20        }
21    }
22
23    /// Resolve user_id to owner via organization lookup
24    async fn resolve_owner(
25        &self,
26        user_id: Uuid,
27        organization_id: Uuid,
28    ) -> Result<crate::domain::entities::Owner, String> {
29        self.owner_repo
30            .find_by_user_id_and_organization(user_id, organization_id)
31            .await?
32            .ok_or_else(|| "Owner not found for this user in the organization".to_string())
33    }
34
35    /// Create a new skill
36    ///
37    /// # Authorization
38    /// - Owner must exist in the system
39    pub async fn create_skill(
40        &self,
41        user_id: Uuid,
42        organization_id: Uuid,
43        dto: CreateSkillDto,
44    ) -> Result<SkillResponseDto, String> {
45        // Resolve user_id to owner
46        let owner = self.resolve_owner(user_id, organization_id).await?;
47        let owner_id = owner.id;
48
49        // Create skill entity (validates business rules)
50        let skill = Skill::new(
51            owner_id,
52            dto.building_id,
53            dto.skill_category,
54            dto.skill_name,
55            dto.expertise_level,
56            dto.description,
57            dto.is_available_for_help,
58            dto.hourly_rate_credits,
59            dto.years_of_experience,
60            dto.certifications,
61        )?;
62
63        // Persist skill
64        let created = self.skill_repo.create(&skill).await?;
65
66        // Return enriched response
67        let owner_name = format!("{} {}", owner.first_name, owner.last_name);
68        Ok(SkillResponseDto::from_skill(created, owner_name))
69    }
70
71    /// Get skill by ID with owner name enrichment
72    pub async fn get_skill(&self, skill_id: Uuid) -> Result<SkillResponseDto, String> {
73        let skill = self
74            .skill_repo
75            .find_by_id(skill_id)
76            .await?
77            .ok_or("Skill not found".to_string())?;
78
79        // Enrich with owner name
80        let owner = self
81            .owner_repo
82            .find_by_id(skill.owner_id)
83            .await?
84            .ok_or("Owner not found".to_string())?;
85
86        let owner_name = format!("{} {}", owner.first_name, owner.last_name);
87        Ok(SkillResponseDto::from_skill(skill, owner_name))
88    }
89
90    /// List all skills for a building
91    ///
92    /// # Returns
93    /// - Skills sorted by available (DESC), expertise (DESC), skill_name (ASC)
94    pub async fn list_building_skills(
95        &self,
96        building_id: Uuid,
97    ) -> Result<Vec<SkillSummaryDto>, String> {
98        let skills = self.skill_repo.find_by_building(building_id).await?;
99        self.enrich_skills_summary(skills).await
100    }
101
102    /// List available skills for a building (marketplace view)
103    ///
104    /// # Returns
105    /// - Only available skills (is_available_for_help = true)
106    pub async fn list_available_skills(
107        &self,
108        building_id: Uuid,
109    ) -> Result<Vec<SkillSummaryDto>, String> {
110        let skills = self
111            .skill_repo
112            .find_available_by_building(building_id)
113            .await?;
114        self.enrich_skills_summary(skills).await
115    }
116
117    /// List all skills created by an owner
118    pub async fn list_owner_skills(&self, owner_id: Uuid) -> Result<Vec<SkillSummaryDto>, String> {
119        let skills = self.skill_repo.find_by_owner(owner_id).await?;
120        self.enrich_skills_summary(skills).await
121    }
122
123    /// List skills by category (HomeRepair, Languages, Technology, etc.)
124    pub async fn list_skills_by_category(
125        &self,
126        building_id: Uuid,
127        category: SkillCategory,
128    ) -> Result<Vec<SkillSummaryDto>, String> {
129        let skills = self
130            .skill_repo
131            .find_by_category(building_id, category)
132            .await?;
133        self.enrich_skills_summary(skills).await
134    }
135
136    /// List skills by expertise level (Beginner, Intermediate, Advanced, Expert)
137    pub async fn list_skills_by_expertise(
138        &self,
139        building_id: Uuid,
140        level: ExpertiseLevel,
141    ) -> Result<Vec<SkillSummaryDto>, String> {
142        let skills = self
143            .skill_repo
144            .find_by_expertise(building_id, level)
145            .await?;
146        self.enrich_skills_summary(skills).await
147    }
148
149    /// List free/volunteer skills for a building
150    pub async fn list_free_skills(
151        &self,
152        building_id: Uuid,
153    ) -> Result<Vec<SkillSummaryDto>, String> {
154        let skills = self.skill_repo.find_free_by_building(building_id).await?;
155        self.enrich_skills_summary(skills).await
156    }
157
158    /// List professional skills for a building (Expert level OR certifications)
159    pub async fn list_professional_skills(
160        &self,
161        building_id: Uuid,
162    ) -> Result<Vec<SkillSummaryDto>, String> {
163        let skills = self
164            .skill_repo
165            .find_professional_by_building(building_id)
166            .await?;
167        self.enrich_skills_summary(skills).await
168    }
169
170    /// Update a skill
171    ///
172    /// # Authorization
173    /// - Only owner can update their skill
174    pub async fn update_skill(
175        &self,
176        skill_id: Uuid,
177        user_id: Uuid,
178        organization_id: Uuid,
179        dto: UpdateSkillDto,
180    ) -> Result<SkillResponseDto, String> {
181        let owner = self.resolve_owner(user_id, organization_id).await?;
182        let mut skill = self
183            .skill_repo
184            .find_by_id(skill_id)
185            .await?
186            .ok_or("Skill not found".to_string())?;
187
188        // Authorization: only owner can update
189        if skill.owner_id != owner.id {
190            return Err("Unauthorized: only owner can update skill".to_string());
191        }
192
193        // Update skill (domain validates business rules)
194        skill.update(
195            dto.skill_name,
196            dto.expertise_level,
197            dto.description,
198            dto.is_available_for_help,
199            dto.hourly_rate_credits,
200            dto.years_of_experience,
201            dto.certifications,
202        )?;
203
204        // Persist changes
205        let updated = self.skill_repo.update(&skill).await?;
206
207        // Return enriched response
208        self.get_skill(updated.id).await
209    }
210
211    /// Mark skill as available for help
212    ///
213    /// # Authorization
214    /// - Only owner can mark their skill as available
215    pub async fn mark_skill_available(
216        &self,
217        skill_id: Uuid,
218        user_id: Uuid,
219        organization_id: Uuid,
220    ) -> Result<SkillResponseDto, String> {
221        let owner = self.resolve_owner(user_id, organization_id).await?;
222        let mut skill = self
223            .skill_repo
224            .find_by_id(skill_id)
225            .await?
226            .ok_or("Skill not found".to_string())?;
227
228        // Authorization: only owner can mark available
229        if skill.owner_id != owner.id {
230            return Err("Unauthorized: only owner can mark skill as available".to_string());
231        }
232
233        // Mark available
234        skill.mark_available();
235
236        // Persist changes
237        let updated = self.skill_repo.update(&skill).await?;
238
239        // Return enriched response
240        self.get_skill(updated.id).await
241    }
242
243    /// Mark skill as unavailable for help
244    ///
245    /// # Authorization
246    /// - Only owner can mark their skill as unavailable
247    pub async fn mark_skill_unavailable(
248        &self,
249        skill_id: Uuid,
250        user_id: Uuid,
251        organization_id: Uuid,
252    ) -> Result<SkillResponseDto, String> {
253        let owner = self.resolve_owner(user_id, organization_id).await?;
254        let mut skill = self
255            .skill_repo
256            .find_by_id(skill_id)
257            .await?
258            .ok_or("Skill not found".to_string())?;
259
260        // Authorization: only owner can mark unavailable
261        if skill.owner_id != owner.id {
262            return Err("Unauthorized: only owner can mark skill as unavailable".to_string());
263        }
264
265        // Mark unavailable
266        skill.mark_unavailable();
267
268        // Persist changes
269        let updated = self.skill_repo.update(&skill).await?;
270
271        // Return enriched response
272        self.get_skill(updated.id).await
273    }
274
275    /// Delete a skill
276    ///
277    /// # Authorization
278    /// - Only owner can delete their skill
279    pub async fn delete_skill(
280        &self,
281        skill_id: Uuid,
282        user_id: Uuid,
283        organization_id: Uuid,
284    ) -> Result<(), String> {
285        let owner = self.resolve_owner(user_id, organization_id).await?;
286        let skill = self
287            .skill_repo
288            .find_by_id(skill_id)
289            .await?
290            .ok_or("Skill not found".to_string())?;
291
292        // Authorization: only owner can delete
293        if skill.owner_id != owner.id {
294            return Err("Unauthorized: only owner can delete skill".to_string());
295        }
296
297        // Delete skill
298        self.skill_repo.delete(skill_id).await?;
299
300        Ok(())
301    }
302
303    /// Get skill statistics for a building
304    pub async fn get_skill_statistics(
305        &self,
306        building_id: Uuid,
307    ) -> Result<SkillStatisticsDto, String> {
308        let total_skills = self.skill_repo.count_by_building(building_id).await?;
309        let available_skills = self
310            .skill_repo
311            .count_available_by_building(building_id)
312            .await?;
313
314        // Calculate free/paid skills
315        let skills = self.skill_repo.find_by_building(building_id).await?;
316        let free_skills = skills.iter().filter(|s| s.is_free()).count() as i64;
317        let paid_skills = total_skills - free_skills;
318        let professional_skills = skills.iter().filter(|s| s.is_professional()).count() as i64;
319
320        // Count by category
321        let mut skills_by_category = Vec::new();
322        for category in [
323            SkillCategory::HomeRepair,
324            SkillCategory::Languages,
325            SkillCategory::Technology,
326            SkillCategory::Education,
327            SkillCategory::Arts,
328            SkillCategory::Sports,
329            SkillCategory::Cooking,
330            SkillCategory::Gardening,
331            SkillCategory::Health,
332            SkillCategory::Legal,
333            SkillCategory::Financial,
334            SkillCategory::PetCare,
335            SkillCategory::Other,
336        ] {
337            let count = self
338                .skill_repo
339                .count_by_category(building_id, category.clone())
340                .await?;
341            if count > 0 {
342                skills_by_category.push(CategoryCount { category, count });
343            }
344        }
345
346        // Count by expertise level
347        let mut skills_by_expertise = Vec::new();
348        for level in [
349            ExpertiseLevel::Beginner,
350            ExpertiseLevel::Intermediate,
351            ExpertiseLevel::Advanced,
352            ExpertiseLevel::Expert,
353        ] {
354            let count = self
355                .skill_repo
356                .count_by_expertise(building_id, level.clone())
357                .await?;
358            if count > 0 {
359                skills_by_expertise.push(ExpertiseCount { level, count });
360            }
361        }
362
363        Ok(SkillStatisticsDto {
364            total_skills,
365            available_skills,
366            free_skills,
367            paid_skills,
368            professional_skills,
369            skills_by_category,
370            skills_by_expertise,
371        })
372    }
373
374    /// Helper method to enrich skills with owner names
375    async fn enrich_skills_summary(
376        &self,
377        skills: Vec<Skill>,
378    ) -> Result<Vec<SkillSummaryDto>, String> {
379        let mut enriched = Vec::new();
380
381        for skill in skills {
382            // Get owner name
383            let owner = self.owner_repo.find_by_id(skill.owner_id).await?;
384            let owner_name = if let Some(owner) = owner {
385                format!("{} {}", owner.first_name, owner.last_name)
386            } else {
387                "Unknown Owner".to_string()
388            };
389
390            enriched.push(SkillSummaryDto::from_skill(skill, owner_name));
391        }
392
393        Ok(enriched)
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400    use crate::application::dto::{OwnerFilters, PageRequest};
401    use crate::application::ports::{OwnerRepository, SkillRepository};
402    use crate::domain::entities::{ExpertiseLevel, Owner, Skill, SkillCategory};
403    use async_trait::async_trait;
404    use std::collections::HashMap;
405    use std::sync::{Arc, Mutex};
406    use uuid::Uuid;
407
408    // ── Mock SkillRepository ────────────────────────────────────────────────
409    struct MockSkillRepo {
410        skills: Mutex<HashMap<Uuid, Skill>>,
411    }
412
413    impl MockSkillRepo {
414        fn new() -> Self {
415            Self {
416                skills: Mutex::new(HashMap::new()),
417            }
418        }
419    }
420
421    #[async_trait]
422    impl SkillRepository for MockSkillRepo {
423        async fn create(&self, skill: &Skill) -> Result<Skill, String> {
424            let mut map = self.skills.lock().unwrap();
425            map.insert(skill.id, skill.clone());
426            Ok(skill.clone())
427        }
428
429        async fn find_by_id(&self, id: Uuid) -> Result<Option<Skill>, String> {
430            Ok(self.skills.lock().unwrap().get(&id).cloned())
431        }
432
433        async fn find_by_building(&self, building_id: Uuid) -> Result<Vec<Skill>, String> {
434            Ok(self
435                .skills
436                .lock()
437                .unwrap()
438                .values()
439                .filter(|s| s.building_id == building_id)
440                .cloned()
441                .collect())
442        }
443
444        async fn find_available_by_building(
445            &self,
446            building_id: Uuid,
447        ) -> Result<Vec<Skill>, String> {
448            Ok(self
449                .skills
450                .lock()
451                .unwrap()
452                .values()
453                .filter(|s| s.building_id == building_id && s.is_available_for_help)
454                .cloned()
455                .collect())
456        }
457
458        async fn find_by_owner(&self, owner_id: Uuid) -> Result<Vec<Skill>, String> {
459            Ok(self
460                .skills
461                .lock()
462                .unwrap()
463                .values()
464                .filter(|s| s.owner_id == owner_id)
465                .cloned()
466                .collect())
467        }
468
469        async fn find_by_category(
470            &self,
471            building_id: Uuid,
472            category: SkillCategory,
473        ) -> Result<Vec<Skill>, String> {
474            Ok(self
475                .skills
476                .lock()
477                .unwrap()
478                .values()
479                .filter(|s| s.building_id == building_id && s.skill_category == category)
480                .cloned()
481                .collect())
482        }
483
484        async fn find_by_expertise(
485            &self,
486            building_id: Uuid,
487            level: ExpertiseLevel,
488        ) -> Result<Vec<Skill>, String> {
489            Ok(self
490                .skills
491                .lock()
492                .unwrap()
493                .values()
494                .filter(|s| s.building_id == building_id && s.expertise_level == level)
495                .cloned()
496                .collect())
497        }
498
499        async fn find_free_by_building(&self, building_id: Uuid) -> Result<Vec<Skill>, String> {
500            Ok(self
501                .skills
502                .lock()
503                .unwrap()
504                .values()
505                .filter(|s| s.building_id == building_id && s.is_free())
506                .cloned()
507                .collect())
508        }
509
510        async fn find_professional_by_building(
511            &self,
512            building_id: Uuid,
513        ) -> Result<Vec<Skill>, String> {
514            Ok(self
515                .skills
516                .lock()
517                .unwrap()
518                .values()
519                .filter(|s| s.building_id == building_id && s.is_professional())
520                .cloned()
521                .collect())
522        }
523
524        async fn update(&self, skill: &Skill) -> Result<Skill, String> {
525            let mut map = self.skills.lock().unwrap();
526            map.insert(skill.id, skill.clone());
527            Ok(skill.clone())
528        }
529
530        async fn delete(&self, id: Uuid) -> Result<(), String> {
531            self.skills.lock().unwrap().remove(&id);
532            Ok(())
533        }
534
535        async fn count_by_building(&self, building_id: Uuid) -> Result<i64, String> {
536            Ok(self
537                .skills
538                .lock()
539                .unwrap()
540                .values()
541                .filter(|s| s.building_id == building_id)
542                .count() as i64)
543        }
544
545        async fn count_available_by_building(&self, building_id: Uuid) -> Result<i64, String> {
546            Ok(self
547                .skills
548                .lock()
549                .unwrap()
550                .values()
551                .filter(|s| s.building_id == building_id && s.is_available_for_help)
552                .count() as i64)
553        }
554
555        async fn count_by_category(
556            &self,
557            building_id: Uuid,
558            category: SkillCategory,
559        ) -> Result<i64, String> {
560            Ok(self
561                .skills
562                .lock()
563                .unwrap()
564                .values()
565                .filter(|s| s.building_id == building_id && s.skill_category == category)
566                .count() as i64)
567        }
568
569        async fn count_by_expertise(
570            &self,
571            building_id: Uuid,
572            level: ExpertiseLevel,
573        ) -> Result<i64, String> {
574            Ok(self
575                .skills
576                .lock()
577                .unwrap()
578                .values()
579                .filter(|s| s.building_id == building_id && s.expertise_level == level)
580                .count() as i64)
581        }
582    }
583
584    // ── Mock OwnerRepository ────────────────────────────────────────────────
585    struct MockOwnerRepo {
586        owners: Mutex<HashMap<Uuid, Owner>>,
587    }
588
589    impl MockOwnerRepo {
590        fn new() -> Self {
591            Self {
592                owners: Mutex::new(HashMap::new()),
593            }
594        }
595        fn add_owner(&self, owner: Owner) {
596            self.owners.lock().unwrap().insert(owner.id, owner);
597        }
598    }
599
600    #[async_trait]
601    impl OwnerRepository for MockOwnerRepo {
602        async fn create(&self, owner: &Owner) -> Result<Owner, String> {
603            self.owners.lock().unwrap().insert(owner.id, owner.clone());
604            Ok(owner.clone())
605        }
606        async fn find_by_id(&self, id: Uuid) -> Result<Option<Owner>, String> {
607            Ok(self.owners.lock().unwrap().get(&id).cloned())
608        }
609        async fn find_by_user_id(&self, user_id: Uuid) -> Result<Option<Owner>, String> {
610            Ok(self
611                .owners
612                .lock()
613                .unwrap()
614                .values()
615                .find(|o| o.user_id == Some(user_id))
616                .cloned())
617        }
618        async fn find_by_user_id_and_organization(
619            &self,
620            user_id: Uuid,
621            org_id: Uuid,
622        ) -> Result<Option<Owner>, String> {
623            Ok(self
624                .owners
625                .lock()
626                .unwrap()
627                .values()
628                .find(|o| o.user_id == Some(user_id) && o.organization_id == org_id)
629                .cloned())
630        }
631        async fn find_by_email(&self, email: &str) -> Result<Option<Owner>, String> {
632            Ok(self
633                .owners
634                .lock()
635                .unwrap()
636                .values()
637                .find(|o| o.email == email)
638                .cloned())
639        }
640        async fn find_all(&self) -> Result<Vec<Owner>, String> {
641            Ok(self.owners.lock().unwrap().values().cloned().collect())
642        }
643        async fn find_all_paginated(
644            &self,
645            _p: &PageRequest,
646            _f: &OwnerFilters,
647        ) -> Result<(Vec<Owner>, i64), String> {
648            let v: Vec<_> = self.owners.lock().unwrap().values().cloned().collect();
649            let c = v.len() as i64;
650            Ok((v, c))
651        }
652        async fn update(&self, owner: &Owner) -> Result<Owner, String> {
653            self.owners.lock().unwrap().insert(owner.id, owner.clone());
654            Ok(owner.clone())
655        }
656        async fn delete(&self, id: Uuid) -> Result<bool, String> {
657            Ok(self.owners.lock().unwrap().remove(&id).is_some())
658        }
659    }
660
661    // ── Helpers ─────────────────────────────────────────────────────────────
662    fn create_test_owner(user_id: Uuid, org_id: Uuid) -> Owner {
663        let mut owner = Owner::new(
664            org_id,
665            "Marie".to_string(),
666            "Lefevre".to_string(),
667            "marie@test.com".to_string(),
668            None,
669            "Rue Haute 5".to_string(),
670            "Brussels".to_string(),
671            "1000".to_string(),
672            "Belgium".to_string(),
673        )
674        .unwrap();
675        owner.user_id = Some(user_id);
676        owner
677    }
678
679    fn setup() -> (SkillUseCases, Uuid, Uuid, Uuid) {
680        let user_id = Uuid::new_v4();
681        let org_id = Uuid::new_v4();
682        let building_id = Uuid::new_v4();
683
684        let skill_repo = Arc::new(MockSkillRepo::new());
685        let owner_repo = Arc::new(MockOwnerRepo::new());
686
687        let owner = create_test_owner(user_id, org_id);
688        owner_repo.add_owner(owner);
689
690        let uc = SkillUseCases::new(
691            skill_repo as Arc<dyn SkillRepository>,
692            owner_repo as Arc<dyn OwnerRepository>,
693        );
694
695        (uc, user_id, org_id, building_id)
696    }
697
698    fn make_create_dto(building_id: Uuid) -> CreateSkillDto {
699        CreateSkillDto {
700            building_id,
701            skill_category: SkillCategory::Technology,
702            skill_name: "Web Development".to_string(),
703            expertise_level: ExpertiseLevel::Advanced,
704            description: "Full-stack web development with React and Node".to_string(),
705            is_available_for_help: true,
706            hourly_rate_credits: Some(10),
707            years_of_experience: Some(5),
708            certifications: Some("AWS Certified".to_string()),
709        }
710    }
711
712    // ── Tests ───────────────────────────────────────────────────────────────
713
714    #[tokio::test]
715    async fn test_create_skill_success() {
716        let (uc, user_id, org_id, building_id) = setup();
717        let dto = make_create_dto(building_id);
718        let result = uc.create_skill(user_id, org_id, dto).await;
719        assert!(result.is_ok());
720        let resp = result.unwrap();
721        assert_eq!(resp.skill_name, "Web Development");
722        assert_eq!(resp.owner_name, "Marie Lefevre");
723    }
724
725    #[tokio::test]
726    async fn test_get_skill_success() {
727        let (uc, user_id, org_id, building_id) = setup();
728        let dto = make_create_dto(building_id);
729        let created = uc.create_skill(user_id, org_id, dto).await.unwrap();
730
731        let result = uc.get_skill(created.id).await;
732        assert!(result.is_ok());
733        assert_eq!(result.unwrap().id, created.id);
734    }
735
736    #[tokio::test]
737    async fn test_get_skill_not_found() {
738        let (uc, _, _, _) = setup();
739        let result = uc.get_skill(Uuid::new_v4()).await;
740        assert!(result.is_err());
741        assert_eq!(result.unwrap_err(), "Skill not found");
742    }
743
744    #[tokio::test]
745    async fn test_delete_skill_success() {
746        let (uc, user_id, org_id, building_id) = setup();
747        let dto = make_create_dto(building_id);
748        let created = uc.create_skill(user_id, org_id, dto).await.unwrap();
749
750        let result = uc.delete_skill(created.id, user_id, org_id).await;
751        assert!(result.is_ok());
752    }
753
754    #[tokio::test]
755    async fn test_delete_skill_wrong_owner() {
756        let (uc, user_id, org_id, building_id) = setup();
757        let dto = make_create_dto(building_id);
758        let created = uc.create_skill(user_id, org_id, dto).await.unwrap();
759
760        // Unknown user
761        let other = Uuid::new_v4();
762        let result = uc.delete_skill(created.id, other, org_id).await;
763        assert!(result.is_err());
764        assert!(result.unwrap_err().contains("Owner not found"));
765    }
766
767    #[tokio::test]
768    async fn test_list_building_skills() {
769        let (uc, user_id, org_id, building_id) = setup();
770
771        let dto1 = make_create_dto(building_id);
772        let mut dto2 = make_create_dto(building_id);
773        dto2.skill_name = "Plumbing".to_string();
774        dto2.skill_category = SkillCategory::HomeRepair;
775        dto2.description = "Residential plumbing repair and installation".to_string();
776
777        uc.create_skill(user_id, org_id, dto1).await.unwrap();
778        uc.create_skill(user_id, org_id, dto2).await.unwrap();
779
780        let result = uc.list_building_skills(building_id).await;
781        assert!(result.is_ok());
782        assert_eq!(result.unwrap().len(), 2);
783    }
784
785    #[tokio::test]
786    async fn test_owner_not_found() {
787        let skill_repo = Arc::new(MockSkillRepo::new());
788        let owner_repo = Arc::new(MockOwnerRepo::new());
789        // No owner added
790
791        let uc = SkillUseCases::new(
792            skill_repo as Arc<dyn SkillRepository>,
793            owner_repo as Arc<dyn OwnerRepository>,
794        );
795
796        let dto = make_create_dto(Uuid::new_v4());
797        let result = uc.create_skill(Uuid::new_v4(), Uuid::new_v4(), dto).await;
798        assert!(result.is_err());
799        assert!(result.unwrap_err().contains("Owner not found"));
800    }
801}