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