Skip to main content

this/storage/
in_memory.rs

1//! In-memory implementations of DataService and LinkService for testing and development
2
3use crate::core::field::FieldValue;
4use crate::core::{Data, DataService, LinkService, link::LinkEntity};
5use anyhow::{Result, anyhow};
6use async_trait::async_trait;
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9use uuid::Uuid;
10
11/// In-memory implementation of DataService
12///
13/// Provides a generic in-memory store for any entity type implementing `Data`.
14/// Useful for testing, development, and prototyping without external dependencies.
15///
16/// Uses `Arc<RwLock<HashMap>>` for thread-safe concurrent access.
17///
18/// # Example
19///
20/// ```rust,ignore
21/// use this::storage::InMemoryDataService;
22///
23/// let service = InMemoryDataService::<MyEntity>::new();
24/// let entity = service.create(my_entity).await?;
25/// let found = service.get(&entity.id()).await?;
26/// ```
27pub struct InMemoryDataService<T: Data> {
28    data: Arc<RwLock<HashMap<Uuid, T>>>,
29}
30
31impl<T: Data> InMemoryDataService<T> {
32    /// Create a new empty in-memory data service
33    pub fn new() -> Self {
34        Self {
35            data: Arc::new(RwLock::new(HashMap::new())),
36        }
37    }
38}
39
40impl<T: Data> Clone for InMemoryDataService<T> {
41    fn clone(&self) -> Self {
42        Self {
43            data: Arc::clone(&self.data),
44        }
45    }
46}
47
48impl<T: Data> Default for InMemoryDataService<T> {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54#[async_trait]
55impl<T: Data> DataService<T> for InMemoryDataService<T> {
56    async fn create(&self, entity: T) -> Result<T> {
57        let mut data = self
58            .data
59            .write()
60            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
61
62        data.insert(entity.id(), entity.clone());
63
64        Ok(entity)
65    }
66
67    async fn get(&self, id: &Uuid) -> Result<Option<T>> {
68        let data = self
69            .data
70            .read()
71            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
72
73        Ok(data.get(id).cloned())
74    }
75
76    async fn list(&self) -> Result<Vec<T>> {
77        let data = self
78            .data
79            .read()
80            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
81
82        Ok(data.values().cloned().collect())
83    }
84
85    async fn update(&self, id: &Uuid, entity: T) -> Result<T> {
86        let mut data = self
87            .data
88            .write()
89            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
90
91        data.get(id)
92            .ok_or_else(|| anyhow!("Entity not found: {}", id))?;
93
94        data.insert(*id, entity.clone());
95
96        Ok(entity)
97    }
98
99    async fn delete(&self, id: &Uuid) -> Result<()> {
100        let mut data = self
101            .data
102            .write()
103            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
104
105        data.remove(id);
106
107        Ok(())
108    }
109
110    async fn search(&self, field: &str, value: &str) -> Result<Vec<T>> {
111        let data = self
112            .data
113            .read()
114            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
115
116        Ok(data
117            .values()
118            .filter(|entity| {
119                entity.field_value(field).is_some_and(|fv| match &fv {
120                    FieldValue::String(s) => s == value,
121                    FieldValue::Integer(i) => i.to_string() == value,
122                    FieldValue::Float(f) => f.to_string() == value,
123                    FieldValue::Boolean(b) => b.to_string() == value,
124                    FieldValue::Uuid(u) => u.to_string() == value,
125                    FieldValue::DateTime(dt) => dt.to_rfc3339() == value,
126                    FieldValue::Null => false,
127                })
128            })
129            .cloned()
130            .collect())
131    }
132}
133
134// ---------------------------------------------------------------------------
135// LinkService
136// ---------------------------------------------------------------------------
137
138/// In-memory link service implementation
139///
140/// Useful for testing and development. Uses RwLock for thread-safe access.
141#[derive(Clone)]
142pub struct InMemoryLinkService {
143    links: Arc<RwLock<HashMap<Uuid, LinkEntity>>>,
144}
145
146impl InMemoryLinkService {
147    /// Create a new in-memory link service
148    pub fn new() -> Self {
149        Self {
150            links: Arc::new(RwLock::new(HashMap::new())),
151        }
152    }
153}
154
155impl Default for InMemoryLinkService {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161#[async_trait]
162impl LinkService for InMemoryLinkService {
163    async fn create(&self, link: LinkEntity) -> Result<LinkEntity> {
164        let mut links = self
165            .links
166            .write()
167            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
168
169        links.insert(link.id, link.clone());
170
171        Ok(link)
172    }
173
174    async fn get(&self, id: &Uuid) -> Result<Option<LinkEntity>> {
175        let links = self
176            .links
177            .read()
178            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
179
180        Ok(links.get(id).cloned())
181    }
182
183    async fn list(&self) -> Result<Vec<LinkEntity>> {
184        let links = self
185            .links
186            .read()
187            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
188
189        Ok(links.values().cloned().collect())
190    }
191
192    async fn find_by_source(
193        &self,
194        source_id: &Uuid,
195        link_type: Option<&str>,
196        target_type: Option<&str>,
197    ) -> Result<Vec<LinkEntity>> {
198        let links = self
199            .links
200            .read()
201            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
202
203        Ok(links
204            .values()
205            .filter(|link| {
206                &link.source_id == source_id
207                    && link_type.is_none_or(|lt| link.link_type == lt)
208                    && target_type.is_none_or(|_tt| true) // TODO: Add target type to Link if needed
209            })
210            .cloned()
211            .collect())
212    }
213
214    async fn find_by_target(
215        &self,
216        target_id: &Uuid,
217        link_type: Option<&str>,
218        source_type: Option<&str>,
219    ) -> Result<Vec<LinkEntity>> {
220        let links = self
221            .links
222            .read()
223            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
224
225        Ok(links
226            .values()
227            .filter(|link| {
228                &link.target_id == target_id
229                    && link_type.is_none_or(|lt| link.link_type == lt)
230                    && source_type.is_none_or(|_st| true) // TODO: Add source type to Link if needed
231            })
232            .cloned()
233            .collect())
234    }
235
236    async fn update(&self, id: &Uuid, updated_link: LinkEntity) -> Result<LinkEntity> {
237        let mut links = self
238            .links
239            .write()
240            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
241
242        links.get_mut(id).ok_or_else(|| anyhow!("Link not found"))?;
243
244        links.insert(*id, updated_link.clone());
245
246        Ok(updated_link)
247    }
248
249    async fn delete(&self, id: &Uuid) -> Result<()> {
250        let mut links = self
251            .links
252            .write()
253            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
254
255        links.remove(id);
256
257        Ok(())
258    }
259
260    async fn delete_by_entity(&self, entity_id: &Uuid) -> Result<()> {
261        let mut links = self
262            .links
263            .write()
264            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
265
266        links.retain(|_, link| &link.source_id != entity_id && &link.target_id != entity_id);
267
268        Ok(())
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use crate::core::entity::Entity;
276    use crate::core::field::FieldValue;
277    use chrono::{DateTime, Utc};
278
279    // -----------------------------------------------------------------------
280    // Test entity for InMemoryDataService tests
281    // -----------------------------------------------------------------------
282
283    #[derive(Clone, Debug, PartialEq)]
284    struct TestDataEntity {
285        id: Uuid,
286        entity_name: String,
287        status: String,
288        created_at: DateTime<Utc>,
289        updated_at: DateTime<Utc>,
290    }
291
292    impl TestDataEntity {
293        fn new(name: &str) -> Self {
294            let now = Utc::now();
295            Self {
296                id: Uuid::new_v4(),
297                entity_name: name.to_string(),
298                status: "active".to_string(),
299                created_at: now,
300                updated_at: now,
301            }
302        }
303    }
304
305    impl Entity for TestDataEntity {
306        type Service = ();
307
308        fn resource_name() -> &'static str {
309            "test_data_entities"
310        }
311
312        fn resource_name_singular() -> &'static str {
313            "test_data_entity"
314        }
315
316        fn service_from_host(
317            _: &std::sync::Arc<dyn std::any::Any + Send + Sync>,
318        ) -> anyhow::Result<std::sync::Arc<Self::Service>> {
319            Ok(std::sync::Arc::new(()))
320        }
321
322        fn id(&self) -> Uuid {
323            self.id
324        }
325
326        fn entity_type(&self) -> &str {
327            "test_data"
328        }
329
330        fn created_at(&self) -> DateTime<Utc> {
331            self.created_at
332        }
333
334        fn updated_at(&self) -> DateTime<Utc> {
335            self.updated_at
336        }
337
338        fn deleted_at(&self) -> Option<DateTime<Utc>> {
339            None
340        }
341
342        fn status(&self) -> &str {
343            &self.status
344        }
345    }
346
347    impl crate::core::Data for TestDataEntity {
348        fn name(&self) -> &str {
349            &self.entity_name
350        }
351
352        fn indexed_fields() -> &'static [&'static str] {
353            &["entity_name", "status"]
354        }
355
356        fn field_value(&self, field: &str) -> Option<FieldValue> {
357            match field {
358                "entity_name" => Some(FieldValue::String(self.entity_name.clone())),
359                "status" => Some(FieldValue::String(self.status.clone())),
360                _ => None,
361            }
362        }
363    }
364
365    // -----------------------------------------------------------------------
366    // InMemoryDataService CRUD tests
367    // -----------------------------------------------------------------------
368
369    #[tokio::test]
370    async fn test_data_create_entity() {
371        let service = InMemoryDataService::<TestDataEntity>::new();
372        let entity = TestDataEntity::new("Alice");
373
374        let created = service.create(entity.clone()).await.unwrap();
375        assert_eq!(created.id, entity.id);
376        assert_eq!(created.entity_name, "Alice");
377    }
378
379    #[tokio::test]
380    async fn test_data_get_entity() {
381        let service = InMemoryDataService::<TestDataEntity>::new();
382        let entity = TestDataEntity::new("Bob");
383
384        service.create(entity.clone()).await.unwrap();
385
386        let retrieved = service.get(&entity.id).await.unwrap();
387        assert!(retrieved.is_some());
388        assert_eq!(retrieved.unwrap().entity_name, "Bob");
389    }
390
391    #[tokio::test]
392    async fn test_data_get_nonexistent() {
393        let service = InMemoryDataService::<TestDataEntity>::new();
394
395        let retrieved = service.get(&Uuid::new_v4()).await.unwrap();
396        assert!(retrieved.is_none());
397    }
398
399    #[tokio::test]
400    async fn test_data_list_entities() {
401        let service = InMemoryDataService::<TestDataEntity>::new();
402
403        service.create(TestDataEntity::new("Alice")).await.unwrap();
404        service.create(TestDataEntity::new("Bob")).await.unwrap();
405        service
406            .create(TestDataEntity::new("Charlie"))
407            .await
408            .unwrap();
409
410        let all = service.list().await.unwrap();
411        assert_eq!(all.len(), 3);
412    }
413
414    #[tokio::test]
415    async fn test_data_list_empty() {
416        let service = InMemoryDataService::<TestDataEntity>::new();
417
418        let all = service.list().await.unwrap();
419        assert!(all.is_empty());
420    }
421
422    #[tokio::test]
423    async fn test_data_update_entity() {
424        let service = InMemoryDataService::<TestDataEntity>::new();
425        let mut entity = TestDataEntity::new("Alice");
426
427        service.create(entity.clone()).await.unwrap();
428
429        entity.entity_name = "Alice Updated".to_string();
430        let updated = service.update(&entity.id, entity.clone()).await.unwrap();
431
432        assert_eq!(updated.entity_name, "Alice Updated");
433
434        // Verify persisted
435        let retrieved = service.get(&entity.id).await.unwrap().unwrap();
436        assert_eq!(retrieved.entity_name, "Alice Updated");
437    }
438
439    #[tokio::test]
440    async fn test_data_update_nonexistent() {
441        let service = InMemoryDataService::<TestDataEntity>::new();
442        let entity = TestDataEntity::new("Ghost");
443        let id = entity.id;
444
445        let result = service.update(&id, entity).await;
446        assert!(result.is_err());
447        assert!(result.unwrap_err().to_string().contains("not found"));
448    }
449
450    #[tokio::test]
451    async fn test_data_delete_entity() {
452        let service = InMemoryDataService::<TestDataEntity>::new();
453        let entity = TestDataEntity::new("Alice");
454
455        service.create(entity.clone()).await.unwrap();
456        assert!(service.get(&entity.id).await.unwrap().is_some());
457
458        service.delete(&entity.id).await.unwrap();
459        assert!(service.get(&entity.id).await.unwrap().is_none());
460    }
461
462    #[tokio::test]
463    async fn test_data_delete_nonexistent() {
464        let service = InMemoryDataService::<TestDataEntity>::new();
465
466        // Deleting a nonexistent entity should succeed silently
467        let result = service.delete(&Uuid::new_v4()).await;
468        assert!(result.is_ok());
469    }
470
471    // -----------------------------------------------------------------------
472    // InMemoryDataService search tests
473    // -----------------------------------------------------------------------
474
475    #[tokio::test]
476    async fn test_data_search_by_indexed_field() {
477        let service = InMemoryDataService::<TestDataEntity>::new();
478
479        service.create(TestDataEntity::new("Alice")).await.unwrap();
480        service.create(TestDataEntity::new("Bob")).await.unwrap();
481        service.create(TestDataEntity::new("Alice")).await.unwrap();
482
483        let results = service.search("entity_name", "Alice").await.unwrap();
484        assert_eq!(results.len(), 2);
485        assert!(results.iter().all(|e| e.entity_name == "Alice"));
486    }
487
488    #[tokio::test]
489    async fn test_data_search_no_results() {
490        let service = InMemoryDataService::<TestDataEntity>::new();
491
492        service.create(TestDataEntity::new("Alice")).await.unwrap();
493
494        let results = service.search("entity_name", "Zara").await.unwrap();
495        assert!(results.is_empty());
496    }
497
498    #[tokio::test]
499    async fn test_data_search_by_status() {
500        let service = InMemoryDataService::<TestDataEntity>::new();
501
502        let mut inactive = TestDataEntity::new("Inactive");
503        inactive.status = "inactive".to_string();
504
505        service.create(TestDataEntity::new("Active")).await.unwrap();
506        service.create(inactive).await.unwrap();
507
508        let results = service.search("status", "inactive").await.unwrap();
509        assert_eq!(results.len(), 1);
510        assert_eq!(results[0].entity_name, "Inactive");
511    }
512
513    #[tokio::test]
514    async fn test_data_search_unknown_field() {
515        let service = InMemoryDataService::<TestDataEntity>::new();
516
517        service.create(TestDataEntity::new("Alice")).await.unwrap();
518
519        // Unknown field returns no results (field_value returns None)
520        let results = service
521            .search("nonexistent_field", "anything")
522            .await
523            .unwrap();
524        assert!(results.is_empty());
525    }
526
527    #[tokio::test]
528    async fn test_data_clone_shares_state() {
529        let service = InMemoryDataService::<TestDataEntity>::new();
530        let cloned = service.clone();
531
532        service.create(TestDataEntity::new("Alice")).await.unwrap();
533
534        // Clone shares the same backing store
535        let all = cloned.list().await.unwrap();
536        assert_eq!(all.len(), 1);
537    }
538
539    // -----------------------------------------------------------------------
540    // Extended test entity for FieldValue variant coverage in search
541    // -----------------------------------------------------------------------
542
543    #[derive(Clone, Debug, PartialEq)]
544    struct ExtendedTestEntity {
545        id: Uuid,
546        entity_name: String,
547        status: String,
548        ref_id: Uuid,
549        created_at: DateTime<Utc>,
550        updated_at: DateTime<Utc>,
551    }
552
553    impl ExtendedTestEntity {
554        fn new(name: &str, ref_id: Uuid) -> Self {
555            let now = Utc::now();
556            Self {
557                id: Uuid::new_v4(),
558                entity_name: name.to_string(),
559                status: "active".to_string(),
560                ref_id,
561                created_at: now,
562                updated_at: now,
563            }
564        }
565    }
566
567    impl Entity for ExtendedTestEntity {
568        type Service = ();
569
570        fn resource_name() -> &'static str {
571            "extended_test_entities"
572        }
573
574        fn resource_name_singular() -> &'static str {
575            "extended_test_entity"
576        }
577
578        fn service_from_host(
579            _: &std::sync::Arc<dyn std::any::Any + Send + Sync>,
580        ) -> anyhow::Result<std::sync::Arc<Self::Service>> {
581            Ok(std::sync::Arc::new(()))
582        }
583
584        fn id(&self) -> Uuid {
585            self.id
586        }
587
588        fn entity_type(&self) -> &str {
589            "extended_test"
590        }
591
592        fn created_at(&self) -> DateTime<Utc> {
593            self.created_at
594        }
595
596        fn updated_at(&self) -> DateTime<Utc> {
597            self.updated_at
598        }
599
600        fn deleted_at(&self) -> Option<DateTime<Utc>> {
601            None
602        }
603
604        fn status(&self) -> &str {
605            &self.status
606        }
607    }
608
609    impl crate::core::Data for ExtendedTestEntity {
610        fn name(&self) -> &str {
611            &self.entity_name
612        }
613
614        fn indexed_fields() -> &'static [&'static str] {
615            &["entity_name", "status", "ref_id", "created_at"]
616        }
617
618        fn field_value(&self, field: &str) -> Option<FieldValue> {
619            match field {
620                "entity_name" => Some(FieldValue::String(self.entity_name.clone())),
621                "status" => Some(FieldValue::String(self.status.clone())),
622                "ref_id" => Some(FieldValue::Uuid(self.ref_id)),
623                "created_at" => Some(FieldValue::DateTime(self.created_at)),
624                _ => None,
625            }
626        }
627    }
628
629    #[tokio::test]
630    async fn test_data_search_by_uuid_field() {
631        let service = InMemoryDataService::<ExtendedTestEntity>::new();
632        let target_ref = Uuid::new_v4();
633        let other_ref = Uuid::new_v4();
634
635        service
636            .create(ExtendedTestEntity::new("Alpha", target_ref))
637            .await
638            .expect("create Alpha should succeed");
639        service
640            .create(ExtendedTestEntity::new("Beta", other_ref))
641            .await
642            .expect("create Beta should succeed");
643        service
644            .create(ExtendedTestEntity::new("Gamma", target_ref))
645            .await
646            .expect("create Gamma should succeed");
647
648        let results = service
649            .search("ref_id", &target_ref.to_string())
650            .await
651            .expect("search by ref_id should succeed");
652        assert_eq!(
653            results.len(),
654            2,
655            "should find 2 entities with matching ref_id"
656        );
657        assert!(results.iter().all(|e| e.ref_id == target_ref));
658    }
659
660    #[tokio::test]
661    async fn test_data_search_by_datetime_field() {
662        let service = InMemoryDataService::<ExtendedTestEntity>::new();
663        let entity = ExtendedTestEntity::new("Timed", Uuid::new_v4());
664        let created_rfc3339 = entity.created_at.to_rfc3339();
665
666        service.create(entity).await.expect("create should succeed");
667
668        let results = service
669            .search("created_at", &created_rfc3339)
670            .await
671            .expect("search by created_at should succeed");
672        assert_eq!(
673            results.len(),
674            1,
675            "should find entity by its created_at timestamp"
676        );
677        assert_eq!(results[0].entity_name, "Timed");
678    }
679
680    #[tokio::test]
681    async fn test_data_default_creates_empty_service() {
682        let service = InMemoryDataService::<TestDataEntity>::default();
683        let all = service
684            .list()
685            .await
686            .expect("list should succeed on default service");
687        assert!(all.is_empty(), "default service should start empty");
688    }
689
690    // -----------------------------------------------------------------------
691    // InMemoryLinkService tests (existing)
692    // -----------------------------------------------------------------------
693
694    #[tokio::test]
695    async fn test_create_link() {
696        let service = InMemoryLinkService::new();
697        let user_id = Uuid::new_v4();
698        let car_id = Uuid::new_v4();
699
700        let link = LinkEntity::new("owner", user_id, car_id, None);
701
702        let created = service.create(link.clone()).await.unwrap();
703
704        assert_eq!(created.link_type, "owner");
705        assert_eq!(created.source_id, user_id);
706        assert_eq!(created.target_id, car_id);
707    }
708
709    #[tokio::test]
710    async fn test_get_link() {
711        let service = InMemoryLinkService::new();
712        let link = LinkEntity::new("owner", Uuid::new_v4(), Uuid::new_v4(), None);
713
714        service.create(link.clone()).await.unwrap();
715
716        let retrieved = service.get(&link.id).await.unwrap();
717        assert!(retrieved.is_some());
718        assert_eq!(retrieved.unwrap().id, link.id);
719    }
720
721    #[tokio::test]
722    async fn test_list_links() {
723        let service = InMemoryLinkService::new();
724
725        let link1 = LinkEntity::new("owner", Uuid::new_v4(), Uuid::new_v4(), None);
726        let link2 = LinkEntity::new("driver", Uuid::new_v4(), Uuid::new_v4(), None);
727
728        service.create(link1).await.unwrap();
729        service.create(link2).await.unwrap();
730
731        let links = service.list().await.unwrap();
732        assert_eq!(links.len(), 2);
733    }
734
735    #[tokio::test]
736    async fn test_find_by_source() {
737        let service = InMemoryLinkService::new();
738        let user_id = Uuid::new_v4();
739        let car1_id = Uuid::new_v4();
740        let car2_id = Uuid::new_v4();
741
742        // User owns car1
743        service
744            .create(LinkEntity::new("owner", user_id, car1_id, None))
745            .await
746            .unwrap();
747
748        // User drives car2
749        service
750            .create(LinkEntity::new("driver", user_id, car2_id, None))
751            .await
752            .unwrap();
753
754        // Find all links from user
755        let links = service.find_by_source(&user_id, None, None).await.unwrap();
756        assert_eq!(links.len(), 2);
757
758        // Find only owner links
759        let owner_links = service
760            .find_by_source(&user_id, Some("owner"), None)
761            .await
762            .unwrap();
763        assert_eq!(owner_links.len(), 1);
764        assert_eq!(owner_links[0].link_type, "owner");
765    }
766
767    #[tokio::test]
768    async fn test_find_by_target() {
769        let service = InMemoryLinkService::new();
770        let user1_id = Uuid::new_v4();
771        let user2_id = Uuid::new_v4();
772        let car_id = Uuid::new_v4();
773
774        // User1 owns car
775        service
776            .create(LinkEntity::new("owner", user1_id, car_id, None))
777            .await
778            .unwrap();
779
780        // User2 drives car
781        service
782            .create(LinkEntity::new("driver", user2_id, car_id, None))
783            .await
784            .unwrap();
785
786        // Find all links to car
787        let links = service.find_by_target(&car_id, None, None).await.unwrap();
788        assert_eq!(links.len(), 2);
789
790        // Find only driver links
791        let driver_links = service
792            .find_by_target(&car_id, Some("driver"), None)
793            .await
794            .unwrap();
795        assert_eq!(driver_links.len(), 1);
796        assert_eq!(driver_links[0].link_type, "driver");
797    }
798
799    #[tokio::test]
800    async fn test_update_link() {
801        let service = InMemoryLinkService::new();
802        let user_id = Uuid::new_v4();
803        let company_id = Uuid::new_v4();
804
805        let mut link = LinkEntity::new(
806            "worker",
807            user_id,
808            company_id,
809            Some(serde_json::json!({"role": "Developer"})),
810        );
811
812        service.create(link.clone()).await.unwrap();
813
814        // Update metadata
815        link.metadata = Some(serde_json::json!({"role": "Senior Developer"}));
816        link.touch();
817
818        let updated = service.update(&link.id, link.clone()).await.unwrap();
819        assert_eq!(
820            updated.metadata,
821            Some(serde_json::json!({"role": "Senior Developer"}))
822        );
823    }
824
825    #[tokio::test]
826    async fn test_delete_link() {
827        let service = InMemoryLinkService::new();
828        let link = LinkEntity::new("owner", Uuid::new_v4(), Uuid::new_v4(), None);
829
830        service.create(link.clone()).await.unwrap();
831
832        let retrieved = service.get(&link.id).await.unwrap();
833        assert!(retrieved.is_some());
834
835        service.delete(&link.id).await.unwrap();
836
837        let retrieved = service.get(&link.id).await.unwrap();
838        assert!(retrieved.is_none());
839    }
840
841    #[tokio::test]
842    async fn test_delete_by_entity() {
843        let service = InMemoryLinkService::new();
844        let user_id = Uuid::new_v4();
845        let car1_id = Uuid::new_v4();
846        let car2_id = Uuid::new_v4();
847
848        service
849            .create(LinkEntity::new("owner", user_id, car1_id, None))
850            .await
851            .unwrap();
852        service
853            .create(LinkEntity::new("driver", user_id, car2_id, None))
854            .await
855            .unwrap();
856        service
857            .create(LinkEntity::new("owner", Uuid::new_v4(), car1_id, None))
858            .await
859            .unwrap();
860
861        let links = service.list().await.unwrap();
862        assert_eq!(links.len(), 3);
863
864        // Delete all links involving user_id
865        service.delete_by_entity(&user_id).await.unwrap();
866
867        let remaining = service.list().await.unwrap();
868        assert_eq!(remaining.len(), 1);
869        assert_ne!(remaining[0].source_id, user_id);
870        assert_ne!(remaining[0].target_id, user_id);
871    }
872}