1use parking_lot::RwLock;
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet, VecDeque};
9use std::sync::Arc;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct PersonaNode {
14 pub persona_id: String,
16 pub entity_type: String,
18 pub relationships: HashMap<String, Vec<String>>,
22 #[serde(default)]
24 pub metadata: HashMap<String, serde_json::Value>,
25}
26
27impl PersonaNode {
28 pub fn new(persona_id: String, entity_type: String) -> Self {
30 Self {
31 persona_id,
32 entity_type,
33 relationships: HashMap::new(),
34 metadata: HashMap::new(),
35 }
36 }
37
38 pub fn add_relationship(&mut self, relationship_type: String, related_persona_id: String) {
40 self.relationships
41 .entry(relationship_type)
42 .or_default()
43 .push(related_persona_id);
44 }
45
46 pub fn get_related(&self, relationship_type: &str) -> Vec<String> {
48 self.relationships.get(relationship_type).cloned().unwrap_or_default()
49 }
50
51 pub fn get_relationship_types(&self) -> Vec<String> {
53 self.relationships.keys().cloned().collect()
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct Edge {
60 pub from: String,
62 pub to: String,
64 pub relationship_type: String,
66 #[serde(default = "default_edge_weight")]
68 pub weight: f64,
69}
70
71fn default_edge_weight() -> f64 {
72 1.0
73}
74
75#[derive(Debug, Clone)]
80pub struct PersonaGraph {
81 nodes: Arc<RwLock<HashMap<String, PersonaNode>>>,
83 edges: Arc<RwLock<HashMap<String, Vec<Edge>>>>,
85 reverse_edges: Arc<RwLock<HashMap<String, Vec<Edge>>>>,
87}
88
89impl PersonaGraph {
90 pub fn new() -> Self {
92 Self {
93 nodes: Arc::new(RwLock::new(HashMap::new())),
94 edges: Arc::new(RwLock::new(HashMap::new())),
95 reverse_edges: Arc::new(RwLock::new(HashMap::new())),
96 }
97 }
98
99 pub fn add_node(&self, node: PersonaNode) {
101 let mut nodes = self.nodes.write();
102 nodes.insert(node.persona_id.clone(), node);
103 }
104
105 pub fn get_node(&self, persona_id: &str) -> Option<PersonaNode> {
107 let nodes = self.nodes.read();
108 nodes.get(persona_id).cloned()
109 }
110
111 pub fn add_edge(&self, from: String, to: String, relationship_type: String) {
113 let to_clone = to.clone();
114 let edge = Edge {
115 from: from.clone(),
116 to: to_clone.clone(),
117 relationship_type: relationship_type.clone(),
118 weight: 1.0,
119 };
120
121 let mut edges = self.edges.write();
123 edges.entry(from.clone()).or_default().push(edge.clone());
124
125 let mut reverse_edges = self.reverse_edges.write();
127 reverse_edges.entry(to_clone.clone()).or_default().push(edge);
128
129 if let Some(node) = self.get_node(&from) {
131 let mut updated_node = node;
132 updated_node.add_relationship(relationship_type, to_clone);
133 self.add_node(updated_node);
134 }
135 }
136
137 pub fn get_edges_from(&self, persona_id: &str) -> Vec<Edge> {
139 let edges = self.edges.read();
140 edges.get(persona_id).cloned().unwrap_or_default()
141 }
142
143 pub fn get_edges_to(&self, persona_id: &str) -> Vec<Edge> {
145 let reverse_edges = self.reverse_edges.read();
146 reverse_edges.get(persona_id).cloned().unwrap_or_default()
147 }
148
149 pub fn find_related_bfs(
162 &self,
163 start_persona_id: &str,
164 relationship_types: Option<&[String]>,
165 max_depth: Option<usize>,
166 ) -> Vec<String> {
167 let mut visited = HashSet::new();
168 let mut queue = VecDeque::new();
169 let mut result = Vec::new();
170
171 queue.push_back((start_persona_id.to_string(), 0));
172 visited.insert(start_persona_id.to_string());
173
174 while let Some((current_id, depth)) = queue.pop_front() {
175 if let Some(max) = max_depth {
176 if depth >= max {
177 continue;
178 }
179 }
180
181 let edges = self.get_edges_from(¤t_id);
182 for edge in edges {
183 if let Some(types) = relationship_types {
185 if !types.contains(&edge.relationship_type) {
186 continue;
187 }
188 }
189
190 if !visited.contains(&edge.to) {
191 visited.insert(edge.to.clone());
192 result.push(edge.to.clone());
193 queue.push_back((edge.to.clone(), depth + 1));
194 }
195 }
196 }
197
198 result
199 }
200
201 pub fn find_related_dfs(
214 &self,
215 start_persona_id: &str,
216 relationship_types: Option<&[String]>,
217 max_depth: Option<usize>,
218 ) -> Vec<String> {
219 let mut visited = HashSet::new();
220 let mut result = Vec::new();
221
222 self.dfs_recursive(
223 start_persona_id,
224 relationship_types,
225 max_depth,
226 0,
227 &mut visited,
228 &mut result,
229 );
230
231 result
232 }
233
234 fn dfs_recursive(
236 &self,
237 current_id: &str,
238 relationship_types: Option<&[String]>,
239 max_depth: Option<usize>,
240 current_depth: usize,
241 visited: &mut HashSet<String>,
242 result: &mut Vec<String>,
243 ) {
244 if visited.contains(current_id) {
245 return;
246 }
247
248 if let Some(max) = max_depth {
249 if current_depth >= max {
250 return;
251 }
252 }
253
254 visited.insert(current_id.to_string());
255 if current_depth > 0 {
256 result.push(current_id.to_string());
258 }
259
260 let edges = self.get_edges_from(current_id);
261 for edge in edges {
262 if let Some(types) = relationship_types {
264 if !types.contains(&edge.relationship_type) {
265 continue;
266 }
267 }
268
269 self.dfs_recursive(
270 &edge.to,
271 relationship_types,
272 max_depth,
273 current_depth + 1,
274 visited,
275 result,
276 );
277 }
278 }
279
280 pub fn get_subgraph(&self, start_persona_id: &str) -> (Vec<PersonaNode>, Vec<Edge>) {
284 let related_ids = self.find_related_bfs(start_persona_id, None, None);
285 let mut all_ids = vec![start_persona_id.to_string()];
286 all_ids.extend(related_ids);
287
288 let nodes = self.nodes.read();
289 let edges = self.edges.read();
290
291 let subgraph_nodes: Vec<PersonaNode> =
292 all_ids.iter().filter_map(|id| nodes.get(id).cloned()).collect();
293
294 let subgraph_edges: Vec<Edge> = all_ids
295 .iter()
296 .flat_map(|id| edges.get(id).cloned().unwrap_or_default())
297 .filter(|edge| all_ids.contains(&edge.to))
298 .collect();
299
300 (subgraph_nodes, subgraph_edges)
301 }
302
303 pub fn get_all_nodes(&self) -> Vec<PersonaNode> {
305 let nodes = self.nodes.read();
306 nodes.values().cloned().collect()
307 }
308
309 pub fn remove_node(&self, persona_id: &str) {
311 let mut nodes = self.nodes.write();
312 nodes.remove(persona_id);
313
314 let mut edges = self.edges.write();
316 edges.remove(persona_id);
317
318 let mut reverse_edges = self.reverse_edges.write();
320 reverse_edges.remove(persona_id);
321
322 for edges_list in edges.values_mut() {
324 edges_list.retain(|e| e.to != persona_id);
325 }
326 for edges_list in reverse_edges.values_mut() {
327 edges_list.retain(|e| e.from != persona_id);
328 }
329 }
330
331 pub fn clear(&self) {
333 let mut nodes = self.nodes.write();
334 nodes.clear();
335
336 let mut edges = self.edges.write();
337 edges.clear();
338
339 let mut reverse_edges = self.reverse_edges.write();
340 reverse_edges.clear();
341 }
342
343 pub fn get_stats(&self) -> GraphStats {
345 let nodes = self.nodes.read();
346 let edges = self.edges.read();
347
348 let mut relationship_type_counts = HashMap::new();
349 for edges_list in edges.values() {
350 for edge in edges_list {
351 *relationship_type_counts.entry(edge.relationship_type.clone()).or_insert(0) += 1;
352 }
353 }
354
355 GraphStats {
356 node_count: nodes.len(),
357 edge_count: edges.values().map(|e| e.len()).sum(),
358 relationship_types: relationship_type_counts,
359 }
360 }
361
362 pub fn link_entity_types(
377 &self,
378 from_persona_id: &str,
379 from_entity_type: &str,
380 to_persona_id: &str,
381 to_entity_type: &str,
382 ) {
383 let relationship_type: String = match (from_entity_type, to_entity_type) {
385 ("user", "order") | ("user", "orders") => "has_orders".to_string(),
386 ("user", "account") | ("user", "accounts") => "has_accounts".to_string(),
387 ("user", "webhook") | ("user", "webhooks") => "has_webhooks".to_string(),
388 ("user", "tcp_message") | ("user", "tcp_messages") => "has_tcp_messages".to_string(),
389 ("order", "payment") | ("order", "payments") => "has_payments".to_string(),
390 ("account", "order") | ("account", "orders") => "has_orders".to_string(),
391 ("account", "payment") | ("account", "payments") => "has_payments".to_string(),
392 _ => {
393 format!("has_{}", to_entity_type.to_lowercase().trim_end_matches('s'))
395 }
396 };
397
398 if self.get_node(from_persona_id).is_none() {
400 let node = PersonaNode::new(from_persona_id.to_string(), from_entity_type.to_string());
401 self.add_node(node);
402 }
403
404 if self.get_node(to_persona_id).is_none() {
405 let node = PersonaNode::new(to_persona_id.to_string(), to_entity_type.to_string());
406 self.add_node(node);
407 }
408
409 self.add_edge(
411 from_persona_id.to_string(),
412 to_persona_id.to_string(),
413 relationship_type.to_string(),
414 );
415 }
416
417 pub fn find_related_by_entity_type(
430 &self,
431 start_persona_id: &str,
432 target_entity_type: &str,
433 relationship_type: Option<&str>,
434 ) -> Vec<String> {
435 let related_ids = if let Some(rel_type) = relationship_type {
436 let rel_types = vec![rel_type.to_string()];
437 self.find_related_bfs(start_persona_id, Some(&rel_types), Some(2))
438 } else {
439 self.find_related_bfs(start_persona_id, None, Some(2))
440 };
441
442 related_ids
444 .into_iter()
445 .filter_map(|persona_id| {
446 if let Some(node) = self.get_node(&persona_id) {
447 if node.entity_type.to_lowercase() == target_entity_type.to_lowercase() {
448 Some(persona_id)
449 } else {
450 None
451 }
452 } else {
453 None
454 }
455 })
456 .collect()
457 }
458
459 pub fn get_or_create_node_with_links(
470 &self,
471 persona_id: &str,
472 entity_type: &str,
473 related_entity_id: Option<&str>,
474 related_entity_type: Option<&str>,
475 ) -> PersonaNode {
476 let node = if let Some(existing) = self.get_node(persona_id) {
478 existing
479 } else {
480 let new_node = PersonaNode::new(persona_id.to_string(), entity_type.to_string());
481 self.add_node(new_node.clone());
482 new_node
483 };
484
485 if let (Some(related_id), Some(related_type)) = (related_entity_id, related_entity_type) {
487 self.link_entity_types(persona_id, entity_type, related_id, related_type);
488 }
489
490 node
491 }
492}
493
494impl Default for PersonaGraph {
495 fn default() -> Self {
496 Self::new()
497 }
498}
499
500#[derive(Debug, Clone, Serialize, Deserialize)]
502pub struct GraphStats {
503 pub node_count: usize,
505 pub edge_count: usize,
507 pub relationship_types: HashMap<String, usize>,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct GraphVisualization {
514 pub nodes: Vec<VisualizationNode>,
516 pub edges: Vec<VisualizationEdge>,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct VisualizationNode {
523 pub id: String,
525 pub entity_type: String,
527 pub label: String,
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub position: Option<(f64, f64)>,
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct VisualizationEdge {
537 pub from: String,
539 pub to: String,
541 pub relationship_type: String,
543 pub label: String,
545}
546
547impl PersonaGraph {
548 pub fn to_visualization(&self) -> GraphVisualization {
550 let nodes = self.get_all_nodes();
551 let edges = self.edges.read();
552
553 let vis_nodes: Vec<VisualizationNode> = nodes
554 .iter()
555 .map(|node| VisualizationNode {
556 id: node.persona_id.clone(),
557 entity_type: node.entity_type.clone(),
558 label: format!("{} ({})", node.persona_id, node.entity_type),
559 position: None,
560 })
561 .collect();
562
563 let vis_edges: Vec<VisualizationEdge> = edges
564 .values()
565 .flatten()
566 .map(|edge| VisualizationEdge {
567 from: edge.from.clone(),
568 to: edge.to.clone(),
569 relationship_type: edge.relationship_type.clone(),
570 label: edge.relationship_type.clone(),
571 })
572 .collect();
573
574 GraphVisualization {
575 nodes: vis_nodes,
576 edges: vis_edges,
577 }
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584
585 #[test]
590 fn test_persona_graph_lock_not_poisoned_after_panic() {
591 let graph = PersonaGraph::new();
592 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
593
594 let graph_clone = graph.clone();
595 let handle = std::thread::spawn(move || {
596 let _guard = graph_clone.nodes.write();
598 panic!("intentional panic while holding the write lock");
599 });
600 assert!(handle.join().is_err(), "spawned thread should have panicked");
601
602 assert!(graph.get_node("user-1").is_some());
604 graph.add_node(PersonaNode::new("user-2".to_string(), "user".to_string()));
605 assert!(graph.get_node("user-2").is_some());
606 }
607
608 #[test]
613 fn test_persona_node_new() {
614 let node = PersonaNode::new("user-123".to_string(), "user".to_string());
615 assert_eq!(node.persona_id, "user-123");
616 assert_eq!(node.entity_type, "user");
617 assert!(node.relationships.is_empty());
618 assert!(node.metadata.is_empty());
619 }
620
621 #[test]
622 fn test_persona_node_add_relationship() {
623 let mut node = PersonaNode::new("user-123".to_string(), "user".to_string());
624 node.add_relationship("has_orders".to_string(), "order-1".to_string());
625 node.add_relationship("has_orders".to_string(), "order-2".to_string());
626
627 let related = node.get_related("has_orders");
628 assert_eq!(related.len(), 2);
629 assert!(related.contains(&"order-1".to_string()));
630 assert!(related.contains(&"order-2".to_string()));
631 }
632
633 #[test]
634 fn test_persona_node_get_related_empty() {
635 let node = PersonaNode::new("user-123".to_string(), "user".to_string());
636 let related = node.get_related("has_orders");
637 assert!(related.is_empty());
638 }
639
640 #[test]
641 fn test_persona_node_get_relationship_types() {
642 let mut node = PersonaNode::new("user-123".to_string(), "user".to_string());
643 node.add_relationship("has_orders".to_string(), "order-1".to_string());
644 node.add_relationship("has_payments".to_string(), "payment-1".to_string());
645
646 let types = node.get_relationship_types();
647 assert_eq!(types.len(), 2);
648 assert!(types.contains(&"has_orders".to_string()));
649 assert!(types.contains(&"has_payments".to_string()));
650 }
651
652 #[test]
653 fn test_persona_node_clone() {
654 let mut node = PersonaNode::new("user-123".to_string(), "user".to_string());
655 node.add_relationship("has_orders".to_string(), "order-1".to_string());
656
657 let cloned = node.clone();
658 assert_eq!(cloned.persona_id, node.persona_id);
659 assert_eq!(cloned.entity_type, node.entity_type);
660 assert_eq!(cloned.relationships, node.relationships);
661 }
662
663 #[test]
664 fn test_persona_node_debug() {
665 let node = PersonaNode::new("user-123".to_string(), "user".to_string());
666 let debug_str = format!("{:?}", node);
667 assert!(debug_str.contains("user-123"));
668 assert!(debug_str.contains("user"));
669 }
670
671 #[test]
672 fn test_persona_node_serialize_deserialize() {
673 let mut node = PersonaNode::new("user-123".to_string(), "user".to_string());
674 node.add_relationship("has_orders".to_string(), "order-1".to_string());
675
676 let json = serde_json::to_string(&node).unwrap();
677 let deserialized: PersonaNode = serde_json::from_str(&json).unwrap();
678
679 assert_eq!(deserialized.persona_id, "user-123");
680 assert_eq!(deserialized.entity_type, "user");
681 }
682
683 #[test]
688 fn test_edge_creation() {
689 let edge = Edge {
690 from: "user-123".to_string(),
691 to: "order-456".to_string(),
692 relationship_type: "has_orders".to_string(),
693 weight: 1.0,
694 };
695 assert_eq!(edge.from, "user-123");
696 assert_eq!(edge.to, "order-456");
697 assert_eq!(edge.relationship_type, "has_orders");
698 assert!((edge.weight - 1.0).abs() < f64::EPSILON);
699 }
700
701 #[test]
702 fn test_edge_clone() {
703 let edge = Edge {
704 from: "a".to_string(),
705 to: "b".to_string(),
706 relationship_type: "rel".to_string(),
707 weight: 2.5,
708 };
709 let cloned = edge.clone();
710 assert_eq!(cloned.from, edge.from);
711 assert!((cloned.weight - 2.5).abs() < f64::EPSILON);
712 }
713
714 #[test]
715 fn test_edge_debug() {
716 let edge = Edge {
717 from: "a".to_string(),
718 to: "b".to_string(),
719 relationship_type: "rel".to_string(),
720 weight: 1.0,
721 };
722 let debug_str = format!("{:?}", edge);
723 assert!(debug_str.contains("from"));
724 assert!(debug_str.contains("to"));
725 }
726
727 #[test]
728 fn test_edge_serialize_default_weight() {
729 let edge = Edge {
730 from: "a".to_string(),
731 to: "b".to_string(),
732 relationship_type: "rel".to_string(),
733 weight: 1.0,
734 };
735 let json = serde_json::to_string(&edge).unwrap();
736 let deserialized: Edge = serde_json::from_str(&json).unwrap();
737 assert!((deserialized.weight - 1.0).abs() < f64::EPSILON);
738 }
739
740 #[test]
745 fn test_persona_graph_new() {
746 let graph = PersonaGraph::new();
747 let stats = graph.get_stats();
748 assert_eq!(stats.node_count, 0);
749 assert_eq!(stats.edge_count, 0);
750 }
751
752 #[test]
753 fn test_persona_graph_default() {
754 let graph = PersonaGraph::default();
755 assert_eq!(graph.get_stats().node_count, 0);
756 }
757
758 #[test]
759 fn test_persona_graph_add_node() {
760 let graph = PersonaGraph::new();
761 let node = PersonaNode::new("user-123".to_string(), "user".to_string());
762 graph.add_node(node);
763
764 let stats = graph.get_stats();
765 assert_eq!(stats.node_count, 1);
766 }
767
768 #[test]
769 fn test_persona_graph_get_node() {
770 let graph = PersonaGraph::new();
771 let node = PersonaNode::new("user-123".to_string(), "user".to_string());
772 graph.add_node(node);
773
774 let retrieved = graph.get_node("user-123");
775 assert!(retrieved.is_some());
776 assert_eq!(retrieved.unwrap().persona_id, "user-123");
777 }
778
779 #[test]
780 fn test_persona_graph_get_node_not_found() {
781 let graph = PersonaGraph::new();
782 assert!(graph.get_node("nonexistent").is_none());
783 }
784
785 #[test]
786 fn test_persona_graph_add_edge() {
787 let graph = PersonaGraph::new();
788 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
789 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
790
791 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
792
793 let stats = graph.get_stats();
794 assert_eq!(stats.edge_count, 1);
795 }
796
797 #[test]
798 fn test_persona_graph_get_edges_from() {
799 let graph = PersonaGraph::new();
800 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
801 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
802 graph.add_node(PersonaNode::new("order-2".to_string(), "order".to_string()));
803
804 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
805 graph.add_edge("user-1".to_string(), "order-2".to_string(), "has_orders".to_string());
806
807 let edges = graph.get_edges_from("user-1");
808 assert_eq!(edges.len(), 2);
809 }
810
811 #[test]
812 fn test_persona_graph_get_edges_to() {
813 let graph = PersonaGraph::new();
814 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
815 graph.add_node(PersonaNode::new("user-2".to_string(), "user".to_string()));
816 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
817
818 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
819 graph.add_edge("user-2".to_string(), "order-1".to_string(), "has_orders".to_string());
820
821 let edges = graph.get_edges_to("order-1");
822 assert_eq!(edges.len(), 2);
823 }
824
825 #[test]
826 fn test_persona_graph_find_related_bfs() {
827 let graph = PersonaGraph::new();
828 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
829 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
830 graph.add_node(PersonaNode::new("payment-1".to_string(), "payment".to_string()));
831
832 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
833 graph.add_edge("order-1".to_string(), "payment-1".to_string(), "has_payments".to_string());
834
835 let related = graph.find_related_bfs("user-1", None, None);
836 assert_eq!(related.len(), 2);
837 assert!(related.contains(&"order-1".to_string()));
838 assert!(related.contains(&"payment-1".to_string()));
839 }
840
841 #[test]
842 fn test_persona_graph_find_related_bfs_with_depth_limit() {
843 let graph = PersonaGraph::new();
844 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
845 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
846 graph.add_node(PersonaNode::new("payment-1".to_string(), "payment".to_string()));
847
848 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
849 graph.add_edge("order-1".to_string(), "payment-1".to_string(), "has_payments".to_string());
850
851 let related = graph.find_related_bfs("user-1", None, Some(1));
852 assert_eq!(related.len(), 1);
853 assert!(related.contains(&"order-1".to_string()));
854 }
855
856 #[test]
857 fn test_persona_graph_find_related_bfs_with_type_filter() {
858 let graph = PersonaGraph::new();
859 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
860 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
861 graph.add_node(PersonaNode::new("account-1".to_string(), "account".to_string()));
862
863 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
864 graph.add_edge("user-1".to_string(), "account-1".to_string(), "has_accounts".to_string());
865
866 let filter = vec!["has_orders".to_string()];
867 let related = graph.find_related_bfs("user-1", Some(&filter), None);
868 assert_eq!(related.len(), 1);
869 assert!(related.contains(&"order-1".to_string()));
870 }
871
872 #[test]
873 fn test_persona_graph_find_related_dfs() {
874 let graph = PersonaGraph::new();
875 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
876 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
877 graph.add_node(PersonaNode::new("payment-1".to_string(), "payment".to_string()));
878
879 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
880 graph.add_edge("order-1".to_string(), "payment-1".to_string(), "has_payments".to_string());
881
882 let related = graph.find_related_dfs("user-1", None, None);
883 assert_eq!(related.len(), 2);
884 }
885
886 #[test]
887 fn test_persona_graph_find_related_dfs_with_depth_limit() {
888 let graph = PersonaGraph::new();
889 graph.add_node(PersonaNode::new("a".to_string(), "node".to_string()));
890 graph.add_node(PersonaNode::new("b".to_string(), "node".to_string()));
891 graph.add_node(PersonaNode::new("c".to_string(), "node".to_string()));
892 graph.add_node(PersonaNode::new("d".to_string(), "node".to_string()));
893
894 graph.add_edge("a".to_string(), "b".to_string(), "linked".to_string());
895 graph.add_edge("b".to_string(), "c".to_string(), "linked".to_string());
896 graph.add_edge("c".to_string(), "d".to_string(), "linked".to_string());
897
898 let related = graph.find_related_dfs("a", None, Some(2));
901 assert_eq!(related.len(), 1); assert!(related.contains(&"b".to_string()));
903 }
904
905 #[test]
906 fn test_persona_graph_get_subgraph() {
907 let graph = PersonaGraph::new();
908 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
909 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
910 graph.add_node(PersonaNode::new("isolated".to_string(), "node".to_string()));
911
912 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
913
914 let (nodes, edges) = graph.get_subgraph("user-1");
915 assert_eq!(nodes.len(), 2); assert_eq!(edges.len(), 1);
917 }
918
919 #[test]
920 fn test_persona_graph_get_all_nodes() {
921 let graph = PersonaGraph::new();
922 graph.add_node(PersonaNode::new("a".to_string(), "node".to_string()));
923 graph.add_node(PersonaNode::new("b".to_string(), "node".to_string()));
924 graph.add_node(PersonaNode::new("c".to_string(), "node".to_string()));
925
926 let nodes = graph.get_all_nodes();
927 assert_eq!(nodes.len(), 3);
928 }
929
930 #[test]
931 fn test_persona_graph_remove_node() {
932 let graph = PersonaGraph::new();
933 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
934 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
935
936 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
937
938 graph.remove_node("order-1");
939
940 assert!(graph.get_node("order-1").is_none());
941 assert_eq!(graph.get_edges_from("user-1").len(), 0);
942 }
943
944 #[test]
945 fn test_persona_graph_clear() {
946 let graph = PersonaGraph::new();
947 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
948 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
949 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
950
951 graph.clear();
952
953 let stats = graph.get_stats();
954 assert_eq!(stats.node_count, 0);
955 assert_eq!(stats.edge_count, 0);
956 }
957
958 #[test]
959 fn test_persona_graph_get_stats() {
960 let graph = PersonaGraph::new();
961 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
962 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
963 graph.add_node(PersonaNode::new("order-2".to_string(), "order".to_string()));
964
965 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
966 graph.add_edge("user-1".to_string(), "order-2".to_string(), "has_orders".to_string());
967
968 let stats = graph.get_stats();
969 assert_eq!(stats.node_count, 3);
970 assert_eq!(stats.edge_count, 2);
971 assert_eq!(*stats.relationship_types.get("has_orders").unwrap(), 2);
972 }
973
974 #[test]
979 fn test_persona_graph_link_entity_types_user_order() {
980 let graph = PersonaGraph::new();
981 graph.link_entity_types("user-1", "user", "order-1", "order");
982
983 let node = graph.get_node("user-1").unwrap();
984 assert_eq!(node.entity_type, "user");
985
986 let related = node.get_related("has_orders");
987 assert!(related.contains(&"order-1".to_string()));
988 }
989
990 #[test]
991 fn test_persona_graph_link_entity_types_order_payment() {
992 let graph = PersonaGraph::new();
993 graph.link_entity_types("order-1", "order", "payment-1", "payment");
994
995 let related = graph.get_node("order-1").unwrap().get_related("has_payments");
996 assert!(related.contains(&"payment-1".to_string()));
997 }
998
999 #[test]
1000 fn test_persona_graph_link_entity_types_generic() {
1001 let graph = PersonaGraph::new();
1002 graph.link_entity_types("foo-1", "foo", "bar-1", "bars");
1003
1004 let node = graph.get_node("foo-1").unwrap();
1005 let related = node.get_related("has_bar");
1007 assert!(related.contains(&"bar-1".to_string()));
1008 }
1009
1010 #[test]
1011 fn test_persona_graph_link_entity_types_creates_nodes() {
1012 let graph = PersonaGraph::new();
1013 graph.link_entity_types("new-user", "user", "new-order", "order");
1014
1015 assert!(graph.get_node("new-user").is_some());
1016 assert!(graph.get_node("new-order").is_some());
1017 }
1018
1019 #[test]
1024 fn test_persona_graph_find_related_by_entity_type() {
1025 let graph = PersonaGraph::new();
1026 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
1027 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
1028 graph.add_node(PersonaNode::new("order-2".to_string(), "order".to_string()));
1029 graph.add_node(PersonaNode::new("payment-1".to_string(), "payment".to_string()));
1030
1031 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
1032 graph.add_edge("user-1".to_string(), "order-2".to_string(), "has_orders".to_string());
1033 graph.add_edge("user-1".to_string(), "payment-1".to_string(), "has_payments".to_string());
1034
1035 let orders = graph.find_related_by_entity_type("user-1", "order", None);
1036 assert_eq!(orders.len(), 2);
1037 }
1038
1039 #[test]
1040 fn test_persona_graph_find_related_by_entity_type_with_filter() {
1041 let graph = PersonaGraph::new();
1042 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
1043 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
1044
1045 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
1046
1047 let orders = graph.find_related_by_entity_type("user-1", "order", Some("has_orders"));
1048 assert_eq!(orders.len(), 1);
1049 }
1050
1051 #[test]
1056 fn test_persona_graph_get_or_create_node_new() {
1057 let graph = PersonaGraph::new();
1058 let node = graph.get_or_create_node_with_links("user-new", "user", None, None);
1059 assert_eq!(node.persona_id, "user-new");
1060 assert!(graph.get_node("user-new").is_some());
1061 }
1062
1063 #[test]
1064 fn test_persona_graph_get_or_create_node_existing() {
1065 let graph = PersonaGraph::new();
1066 let node1 = PersonaNode::new("user-existing".to_string(), "user".to_string());
1067 graph.add_node(node1);
1068
1069 let node2 = graph.get_or_create_node_with_links("user-existing", "user", None, None);
1070 assert_eq!(node2.persona_id, "user-existing");
1071 }
1072
1073 #[test]
1074 fn test_persona_graph_get_or_create_node_with_link() {
1075 let graph = PersonaGraph::new();
1076 let _node = graph.get_or_create_node_with_links(
1077 "user-link",
1078 "user",
1079 Some("order-link"),
1080 Some("order"),
1081 );
1082
1083 assert!(graph.get_node("user-link").is_some());
1084 assert!(graph.get_node("order-link").is_some());
1085 assert_eq!(graph.get_edges_from("user-link").len(), 1);
1086 }
1087
1088 #[test]
1093 fn test_graph_stats_clone() {
1094 let stats = GraphStats {
1095 node_count: 5,
1096 edge_count: 10,
1097 relationship_types: {
1098 let mut map = HashMap::new();
1099 map.insert("has_orders".to_string(), 5);
1100 map
1101 },
1102 };
1103 let cloned = stats.clone();
1104 assert_eq!(cloned.node_count, 5);
1105 assert_eq!(cloned.edge_count, 10);
1106 }
1107
1108 #[test]
1109 fn test_graph_stats_debug() {
1110 let stats = GraphStats {
1111 node_count: 3,
1112 edge_count: 2,
1113 relationship_types: HashMap::new(),
1114 };
1115 let debug_str = format!("{:?}", stats);
1116 assert!(debug_str.contains("node_count"));
1117 assert!(debug_str.contains("edge_count"));
1118 }
1119
1120 #[test]
1121 fn test_graph_stats_serialize() {
1122 let stats = GraphStats {
1123 node_count: 1,
1124 edge_count: 2,
1125 relationship_types: HashMap::new(),
1126 };
1127 let json = serde_json::to_string(&stats).unwrap();
1128 assert!(json.contains("node_count"));
1129 }
1130
1131 #[test]
1136 fn test_visualization_node_creation() {
1137 let node = VisualizationNode {
1138 id: "user-1".to_string(),
1139 entity_type: "user".to_string(),
1140 label: "User 1".to_string(),
1141 position: Some((0.0, 0.0)),
1142 };
1143 assert_eq!(node.id, "user-1");
1144 }
1145
1146 #[test]
1147 fn test_visualization_edge_creation() {
1148 let edge = VisualizationEdge {
1149 from: "a".to_string(),
1150 to: "b".to_string(),
1151 relationship_type: "linked".to_string(),
1152 label: "Linked".to_string(),
1153 };
1154 assert_eq!(edge.from, "a");
1155 }
1156
1157 #[test]
1158 fn test_persona_graph_to_visualization() {
1159 let graph = PersonaGraph::new();
1160 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
1161 graph.add_node(PersonaNode::new("order-1".to_string(), "order".to_string()));
1162 graph.add_edge("user-1".to_string(), "order-1".to_string(), "has_orders".to_string());
1163
1164 let viz = graph.to_visualization();
1165 assert_eq!(viz.nodes.len(), 2);
1166 assert_eq!(viz.edges.len(), 1);
1167 }
1168
1169 #[test]
1170 fn test_visualization_serialize() {
1171 let viz = GraphVisualization {
1172 nodes: vec![VisualizationNode {
1173 id: "test".to_string(),
1174 entity_type: "node".to_string(),
1175 label: "Test".to_string(),
1176 position: None,
1177 }],
1178 edges: vec![],
1179 };
1180 let json = serde_json::to_string(&viz).unwrap();
1181 assert!(json.contains("test"));
1182 }
1183
1184 #[test]
1189 fn test_persona_graph_handles_cycles() {
1190 let graph = PersonaGraph::new();
1191 graph.add_node(PersonaNode::new("a".to_string(), "node".to_string()));
1192 graph.add_node(PersonaNode::new("b".to_string(), "node".to_string()));
1193 graph.add_node(PersonaNode::new("c".to_string(), "node".to_string()));
1194
1195 graph.add_edge("a".to_string(), "b".to_string(), "linked".to_string());
1197 graph.add_edge("b".to_string(), "c".to_string(), "linked".to_string());
1198 graph.add_edge("c".to_string(), "a".to_string(), "linked".to_string());
1199
1200 let related = graph.find_related_bfs("a", None, None);
1202 assert_eq!(related.len(), 2); let related_dfs = graph.find_related_dfs("a", None, None);
1206 assert_eq!(related_dfs.len(), 2);
1207 }
1208
1209 #[test]
1214 fn test_persona_graph_clone() {
1215 let graph = PersonaGraph::new();
1216 graph.add_node(PersonaNode::new("user-1".to_string(), "user".to_string()));
1217
1218 let cloned = graph.clone();
1219 assert!(cloned.get_node("user-1").is_some());
1221 }
1222}