1use std::collections::HashMap;
9use std::sync::Arc;
10
11use crate::graph::prune_graph as core_prune_graph;
12use crate::graph::{StaticEdge, StaticGraph, StaticNode, StaticNodegroup, StaticTile};
13use crate::instance_wrapper_core::ModelAccess;
14use crate::permissions::PermissionRule;
15
16#[derive(Clone)]
27pub struct GraphModelAccess {
28 graph: Arc<StaticGraph>,
29
30 nodes: Option<Arc<HashMap<String, Arc<StaticNode>>>>,
32 nodes_by_alias: Option<Arc<HashMap<String, Arc<StaticNode>>>>,
33 edges: Option<Arc<HashMap<String, Vec<String>>>>,
34 reverse_edges: Option<Arc<HashMap<String, Vec<String>>>>,
35 nodes_by_nodegroup: Option<Arc<HashMap<String, Vec<Arc<StaticNode>>>>>,
36 nodegroups: Option<Arc<HashMap<String, Arc<StaticNodegroup>>>>,
37 root_node_id: Option<String>,
38
39 permitted_nodegroups: HashMap<String, PermissionRule>,
40 default_allow: bool,
41}
42
43impl GraphModelAccess {
44 pub fn new(graph: Arc<StaticGraph>, default_allow: bool) -> Self {
47 GraphModelAccess {
48 graph,
49 nodes: None,
50 nodes_by_alias: None,
51 edges: None,
52 reverse_edges: None,
53 nodes_by_nodegroup: None,
54 nodegroups: None,
55 root_node_id: None,
56 permitted_nodegroups: HashMap::new(),
57 default_allow,
58 }
59 }
60
61 pub fn new_eager(graph: Arc<StaticGraph>, default_allow: bool) -> Self {
63 let mut access = Self::new(graph, default_allow);
64 access
67 .build_indices()
68 .expect("Failed to build graph indices");
69 access
70 }
71
72 pub fn from_graph(graph: &StaticGraph) -> Self {
74 Self::new_eager(Arc::new(graph.clone()), true)
75 }
76
77 pub fn ensure_built(&mut self) -> Result<(), String> {
83 if self.nodes.is_none() {
84 self.build_indices()?;
85 }
86 Ok(())
87 }
88
89 pub fn is_built(&self) -> bool {
91 self.nodes.is_some()
92 }
93
94 pub fn invalidate_caches(&mut self) {
96 self.nodes = None;
97 self.nodes_by_alias = None;
98 self.edges = None;
99 self.reverse_edges = None;
100 self.nodes_by_nodegroup = None;
101 self.nodegroups = None;
102 self.root_node_id = None;
103 }
104
105 fn build_indices(&mut self) -> Result<(), String> {
107 let graph = &self.graph;
108
109 let mut nodes: HashMap<String, Arc<StaticNode>> = HashMap::new();
110 let mut nodes_by_alias: HashMap<String, Arc<StaticNode>> = HashMap::new();
111 let mut edges_map: HashMap<String, Vec<String>> = HashMap::new();
112 let mut reverse_edges_map: HashMap<String, Vec<String>> = HashMap::new();
113 let mut nodes_by_nodegroup: HashMap<String, Vec<Arc<StaticNode>>> = HashMap::new();
114 let mut nodegroups: HashMap<String, Arc<StaticNodegroup>> = HashMap::new();
115 let mut root_node_id = String::new();
116
117 for node in &graph.nodes {
119 let mut node_copy = node.clone();
120 if (node_copy.nodegroup_id.is_none()
122 || node_copy
123 .nodegroup_id
124 .as_ref()
125 .map(|s| s.is_empty())
126 .unwrap_or(false))
127 && node_copy.alias.is_none()
128 {
129 node_copy.alias = Some(String::new());
130 }
131 let arc_node = Arc::new(node_copy);
132 nodes.insert(arc_node.nodeid.clone(), Arc::clone(&arc_node));
133
134 if let Some(ref alias) = arc_node.alias {
136 if !alias.is_empty() {
137 nodes_by_alias.insert(alias.clone(), Arc::clone(&arc_node));
138 } else {
139 nodes_by_alias.insert(String::new(), Arc::clone(&arc_node));
140 }
141 } else {
142 nodes_by_alias.insert(String::new(), Arc::clone(&arc_node));
143 }
144
145 if arc_node.istopnode {
147 root_node_id = arc_node.nodeid.clone();
148 }
149
150 if let Some(ref ng_id) = arc_node.nodegroup_id {
151 nodes_by_nodegroup
152 .entry(ng_id.clone())
153 .or_default()
154 .push(Arc::clone(&arc_node));
155 }
156 }
157
158 if root_node_id.is_empty() {
160 for node in nodes.values() {
161 if node.nodegroup_id.is_none()
162 || node
163 .nodegroup_id
164 .as_ref()
165 .map(|s| s.is_empty())
166 .unwrap_or(true)
167 {
168 root_node_id = node.nodeid.clone();
169 break;
170 }
171 }
172 }
173
174 for edge in &graph.edges {
176 let parent_id = edge.domainnode_id.clone();
177 let child_id = edge.rangenode_id.clone();
178
179 edges_map
180 .entry(parent_id.clone())
181 .or_default()
182 .push(child_id.clone());
183
184 reverse_edges_map
185 .entry(child_id)
186 .or_default()
187 .push(parent_id);
188 }
189
190 for node in &graph.nodes {
192 if let Some(ref ng_id) = node.nodegroup_id {
193 if !ng_id.is_empty() && !nodegroups.contains_key(ng_id) {
194 nodegroups.insert(
195 ng_id.clone(),
196 Arc::new(StaticNodegroup {
197 cardinality: Some("n".to_string()),
198 legacygroupid: None,
199 nodegroupid: ng_id.clone(),
200 parentnodegroup_id: None,
201 grouping_node_id: None,
202 }),
203 );
204 }
205 }
206 }
207 for ng in &graph.nodegroups {
208 nodegroups.insert(ng.nodegroupid.clone(), Arc::new(ng.clone()));
209 }
210
211 self.nodes = Some(Arc::new(nodes));
212 self.nodes_by_alias = Some(Arc::new(nodes_by_alias));
213 self.edges = Some(Arc::new(edges_map));
214 self.reverse_edges = Some(Arc::new(reverse_edges_map));
215 self.nodes_by_nodegroup = Some(Arc::new(nodes_by_nodegroup));
216 self.nodegroups = Some(Arc::new(nodegroups));
217 self.root_node_id = Some(root_node_id);
218
219 if self.permitted_nodegroups.is_empty() {
223 for key in self.nodegroups.as_ref().unwrap().keys() {
224 self.permitted_nodegroups
225 .insert(key.clone(), PermissionRule::Boolean(self.default_allow));
226 }
227 self.permitted_nodegroups
228 .insert(String::new(), PermissionRule::Boolean(true));
229 }
230
231 Ok(())
232 }
233
234 pub fn get_nodes_internal(&self) -> Option<&HashMap<String, Arc<StaticNode>>> {
240 self.nodes.as_ref().map(|arc| arc.as_ref())
241 }
242
243 pub fn get_nodes_by_alias_internal(&self) -> Option<&HashMap<String, Arc<StaticNode>>> {
245 self.nodes_by_alias.as_ref().map(|arc| arc.as_ref())
246 }
247
248 pub fn get_edges_internal(&self) -> Option<&HashMap<String, Vec<String>>> {
250 self.edges.as_ref().map(|arc| arc.as_ref())
251 }
252
253 pub fn get_reverse_edges_internal(&self) -> Option<&HashMap<String, Vec<String>>> {
255 self.reverse_edges.as_ref().map(|arc| arc.as_ref())
256 }
257
258 pub fn get_nodes_by_nodegroup_internal(
260 &self,
261 ) -> Option<&HashMap<String, Vec<Arc<StaticNode>>>> {
262 self.nodes_by_nodegroup.as_ref().map(|arc| arc.as_ref())
263 }
264
265 pub fn get_nodegroups_internal(&self) -> Option<&HashMap<String, Arc<StaticNodegroup>>> {
267 self.nodegroups.as_ref().map(|arc| arc.as_ref())
268 }
269
270 pub fn get_nodes_arc(&self) -> Option<Arc<HashMap<String, Arc<StaticNode>>>> {
275 self.nodes.as_ref().map(Arc::clone)
276 }
277
278 pub fn get_nodes_by_alias_arc(&self) -> Option<Arc<HashMap<String, Arc<StaticNode>>>> {
279 self.nodes_by_alias.as_ref().map(Arc::clone)
280 }
281
282 pub fn get_edges_arc(&self) -> Option<Arc<HashMap<String, Vec<String>>>> {
283 self.edges.as_ref().map(Arc::clone)
284 }
285
286 pub fn get_reverse_edges_arc(&self) -> Option<Arc<HashMap<String, Vec<String>>>> {
287 self.reverse_edges.as_ref().map(Arc::clone)
288 }
289
290 pub fn get_nodes_by_nodegroup_arc(&self) -> Option<Arc<HashMap<String, Vec<Arc<StaticNode>>>>> {
291 self.nodes_by_nodegroup.as_ref().map(Arc::clone)
292 }
293
294 pub fn get_nodegroups_arc(&self) -> Option<Arc<HashMap<String, Arc<StaticNodegroup>>>> {
295 self.nodegroups.as_ref().map(Arc::clone)
296 }
297
298 pub fn get_node_objects(&mut self) -> Result<&HashMap<String, Arc<StaticNode>>, String> {
304 self.ensure_built()?;
305 self.nodes
306 .as_ref()
307 .map(|arc| arc.as_ref())
308 .ok_or_else(|| "Could not build nodes".to_string())
309 }
310
311 pub fn get_node_objects_by_alias(
313 &mut self,
314 ) -> Result<&HashMap<String, Arc<StaticNode>>, String> {
315 self.ensure_built()?;
316 self.nodes_by_alias
317 .as_ref()
318 .map(|arc| arc.as_ref())
319 .ok_or_else(|| "Could not build nodes".to_string())
320 }
321
322 pub fn get_edges(&mut self) -> Result<&HashMap<String, Vec<String>>, String> {
324 self.ensure_built()?;
325 self.edges
326 .as_ref()
327 .map(|arc| arc.as_ref())
328 .ok_or_else(|| "Could not build edges".to_string())
329 }
330
331 pub fn get_nodegroup_objects(
333 &mut self,
334 ) -> Result<&HashMap<String, Arc<StaticNodegroup>>, String> {
335 self.ensure_built()?;
336 self.nodegroups
337 .as_ref()
338 .map(|arc| arc.as_ref())
339 .ok_or_else(|| "Could not build nodegroups".to_string())
340 }
341
342 pub fn get_root_node_mut(&mut self) -> Result<Arc<StaticNode>, String> {
344 self.ensure_built()?;
345 let root_id = self
346 .root_node_id
347 .as_ref()
348 .ok_or_else(|| "Root node ID not set".to_string())?;
349 self.nodes
350 .as_ref()
351 .and_then(|n| n.get(root_id))
352 .cloned()
353 .ok_or_else(|| "Root node not found in nodes cache".to_string())
354 }
355
356 pub fn get_child_nodes_mut(
358 &mut self,
359 node_id: &str,
360 ) -> Result<HashMap<String, Arc<StaticNode>>, String> {
361 self.ensure_built()?;
362 ModelAccess::get_child_nodes(self, node_id)
364 }
365
366 pub fn get_child_nodes(&self, node_id: &str) -> HashMap<String, Arc<StaticNode>> {
372 ModelAccess::get_child_nodes(self, node_id).unwrap_or_default()
373 }
374
375 pub fn get_nodes_by_alias(&self) -> Option<&HashMap<String, Arc<StaticNode>>> {
381 self.nodes_by_alias.as_ref().map(|arc| arc.as_ref())
382 }
383
384 pub fn get_graph(&self) -> &StaticGraph {
390 &self.graph
391 }
392
393 pub fn get_graph_arc(&self) -> Arc<StaticGraph> {
395 Arc::clone(&self.graph)
396 }
397
398 pub fn get_default_allow(&self) -> bool {
400 self.default_allow
401 }
402
403 pub fn set_graph(&mut self, graph: Arc<StaticGraph>) {
409 self.graph = graph;
410 self.invalidate_caches();
411 }
412
413 pub fn set_graph_nodes(&mut self, nodes: Vec<StaticNode>) {
415 let mut graph = (*self.graph).clone();
416 graph.nodes = nodes;
417 self.graph = Arc::new(graph);
418 self.invalidate_caches();
419 }
420
421 pub fn set_graph_edges(&mut self, edges: Vec<StaticEdge>) {
423 let mut graph = (*self.graph).clone();
424 graph.edges = edges;
425 self.graph = Arc::new(graph);
426 self.invalidate_caches();
427 }
428
429 pub fn set_graph_nodegroups(&mut self, nodegroups: Vec<StaticNodegroup>) {
431 let mut graph = (*self.graph).clone();
432 graph.nodegroups = nodegroups;
433 self.graph = Arc::new(graph);
434 self.invalidate_caches();
435 }
436
437 pub fn rebuild_from_graph(&mut self, graph: &StaticGraph) {
439 self.graph = Arc::new(graph.clone());
440 self.invalidate_caches();
441 let _ = self.ensure_built();
444 }
445
446 pub fn set_permitted_nodegroups_rules(&mut self, permissions: HashMap<String, PermissionRule>) {
452 self.permitted_nodegroups = permissions;
453 }
454
455 pub fn set_permitted_nodegroups_bool(&mut self, permissions: HashMap<String, bool>) {
457 self.permitted_nodegroups = permissions
458 .into_iter()
459 .map(|(k, v)| (k, PermissionRule::Boolean(v)))
460 .collect();
461 }
462
463 pub fn set_default_allow(&mut self, default_allow: bool) {
465 self.default_allow = default_allow;
466 }
467
468 pub fn is_nodegroup_permitted(&self, nodegroup_id: &str) -> bool {
471 self.permitted_nodegroups
472 .get(nodegroup_id)
473 .map(|rule| rule.permits_nodegroup())
474 .unwrap_or(self.default_allow)
475 }
476
477 pub fn is_tile_permitted(&self, tile: &StaticTile) -> bool {
479 self.permitted_nodegroups
480 .get(&tile.nodegroup_id)
481 .map(|rule| rule.permits_tile(tile))
482 .unwrap_or(self.default_allow)
483 }
484
485 pub fn get_permission_rule(&self, nodegroup_id: &str) -> Option<&PermissionRule> {
487 self.permitted_nodegroups.get(nodegroup_id)
488 }
489
490 pub fn get_permitted_nodegroups_bool(&self) -> HashMap<String, bool> {
492 self.permitted_nodegroups
494 .iter()
495 .map(|(k, v)| (k.clone(), v.permits_nodegroup()))
496 .collect()
497 }
498
499 pub fn get_permitted_nodegroups_rules(&self) -> &HashMap<String, PermissionRule> {
501 &self.permitted_nodegroups
502 }
503
504 pub fn prune_graph(&mut self, keep_functions: Option<&[String]>) -> Result<(), String> {
511 let is_permitted = |ng_id: &str| self.is_nodegroup_permitted(ng_id);
512 let pruned = core_prune_graph(&self.graph, is_permitted, keep_functions)
513 .map_err(|e| e.to_string())?;
514 self.graph = Arc::new(pruned);
515 self.invalidate_caches();
516 self.ensure_built()?;
517 Ok(())
518 }
519}
520
521impl ModelAccess for GraphModelAccess {
522 fn get_nodes(&self) -> Option<&HashMap<String, Arc<StaticNode>>> {
523 self.nodes.as_ref().map(|arc| arc.as_ref())
524 }
525
526 fn get_edges(&self) -> Option<&HashMap<String, Vec<String>>> {
527 self.edges.as_ref().map(|arc| arc.as_ref())
528 }
529
530 fn get_reverse_edges(&self) -> Option<&HashMap<String, Vec<String>>> {
531 self.reverse_edges.as_ref().map(|arc| arc.as_ref())
532 }
533
534 fn get_nodes_by_nodegroup(&self) -> Option<&HashMap<String, Vec<Arc<StaticNode>>>> {
535 self.nodes_by_nodegroup.as_ref().map(|arc| arc.as_ref())
536 }
537
538 fn get_nodegroups(&self) -> Option<&HashMap<String, Arc<StaticNodegroup>>> {
539 self.nodegroups.as_ref().map(|arc| arc.as_ref())
540 }
541
542 fn get_root_node(&self) -> Result<Arc<StaticNode>, String> {
543 let root_id = self
544 .root_node_id
545 .as_ref()
546 .ok_or_else(|| "Caches not built".to_string())?;
547 self.nodes
548 .as_ref()
549 .and_then(|n| n.get(root_id))
550 .cloned()
551 .ok_or_else(|| "Root node not found".to_string())
552 }
553
554 fn get_permitted_nodegroups(&self) -> HashMap<String, PermissionRule> {
555 self.permitted_nodegroups.clone()
558 }
559}
560
561#[cfg(test)]
562mod tests {
563 use super::*;
564 use crate::graph::{StaticEdge, StaticNodegroup};
565 use crate::instance_wrapper_core::ModelAccess;
566 use serde_json::json;
567 use std::collections::HashSet;
568
569 fn create_test_graph() -> StaticGraph {
577 let root: StaticNode = serde_json::from_value(json!({
578 "nodeid": "root-id",
579 "name": "Root",
580 "alias": "root",
581 "datatype": "semantic",
582 "graph_id": "test-graph",
583 "is_collector": false,
584 "isrequired": false,
585 "issearchable": false,
586 "istopnode": true,
587 "sortorder": 0
588 }))
589 .unwrap();
590
591 let child_a: StaticNode = serde_json::from_value(json!({
592 "nodeid": "child-a-id",
593 "name": "Name",
594 "alias": "name",
595 "datatype": "string",
596 "nodegroup_id": "ng1",
597 "graph_id": "test-graph",
598 "is_collector": false,
599 "isrequired": false,
600 "issearchable": false,
601 "istopnode": false,
602 "sortorder": 0
603 }))
604 .unwrap();
605
606 let child_b: StaticNode = serde_json::from_value(json!({
607 "nodeid": "child-b-id",
608 "name": "Details",
609 "alias": "details",
610 "datatype": "semantic",
611 "nodegroup_id": "ng2",
612 "graph_id": "test-graph",
613 "is_collector": true,
614 "isrequired": false,
615 "issearchable": false,
616 "istopnode": false,
617 "sortorder": 1
618 }))
619 .unwrap();
620
621 let grandchild: StaticNode = serde_json::from_value(json!({
622 "nodeid": "grandchild-id",
623 "name": "Description",
624 "alias": "description",
625 "datatype": "string",
626 "nodegroup_id": "ng2",
627 "graph_id": "test-graph",
628 "is_collector": false,
629 "isrequired": false,
630 "issearchable": false,
631 "istopnode": false,
632 "sortorder": 0
633 }))
634 .unwrap();
635
636 let ng1 = StaticNodegroup {
637 cardinality: Some("n".to_string()),
638 legacygroupid: None,
639 nodegroupid: "ng1".to_string(),
640 parentnodegroup_id: None,
641 grouping_node_id: None,
642 };
643 let ng2 = StaticNodegroup {
644 cardinality: Some("1".to_string()),
645 legacygroupid: None,
646 nodegroupid: "ng2".to_string(),
647 parentnodegroup_id: None,
648 grouping_node_id: None,
649 };
650
651 let edge_root_a: StaticEdge = serde_json::from_value(json!({
652 "domainnode_id": "root-id",
653 "rangenode_id": "child-a-id",
654 "edgeid": "edge-1",
655 "graph_id": "test-graph"
656 }))
657 .unwrap();
658
659 let edge_root_b: StaticEdge = serde_json::from_value(json!({
660 "domainnode_id": "root-id",
661 "rangenode_id": "child-b-id",
662 "edgeid": "edge-2",
663 "graph_id": "test-graph"
664 }))
665 .unwrap();
666
667 let edge_b_gc: StaticEdge = serde_json::from_value(json!({
668 "domainnode_id": "child-b-id",
669 "rangenode_id": "grandchild-id",
670 "edgeid": "edge-3",
671 "graph_id": "test-graph"
672 }))
673 .unwrap();
674
675 serde_json::from_value(json!({
676 "graphid": "test-graph",
677 "name": {"en": "Test Graph"},
678 "root": root,
679 "nodes": [root.clone(), child_a, child_b, grandchild],
680 "edges": [edge_root_a, edge_root_b, edge_b_gc],
681 "nodegroups": [ng1, ng2]
682 }))
683 .unwrap()
684 }
685
686 #[test]
687 fn from_graph_builds_node_index() {
688 let graph = create_test_graph();
689 let access = GraphModelAccess::from_graph(&graph);
690 let nodes = access.nodes.as_ref().unwrap();
691 assert_eq!(nodes.len(), 4);
692 assert!(nodes.contains_key("root-id"));
693 assert!(nodes.contains_key("child-a-id"));
694 assert!(nodes.contains_key("child-b-id"));
695 assert!(nodes.contains_key("grandchild-id"));
696 }
697
698 #[test]
699 fn from_graph_builds_alias_index() {
700 let graph = create_test_graph();
701 let access = GraphModelAccess::from_graph(&graph);
702 let aliases = access.nodes_by_alias.as_ref().unwrap();
703 assert!(aliases.contains_key("name"));
704 assert!(aliases.contains_key("details"));
705 assert!(aliases.contains_key("description"));
706 assert_eq!(aliases.get("name").unwrap().nodeid, "child-a-id");
707 }
708
709 #[test]
710 fn from_graph_builds_edges() {
711 let graph = create_test_graph();
712 let access = GraphModelAccess::from_graph(&graph);
713 let edges = access.edges.as_ref().unwrap();
714 let root_children = edges.get("root-id").unwrap();
715 assert_eq!(root_children.len(), 2);
716 assert!(root_children.contains(&"child-a-id".to_string()));
717 assert!(root_children.contains(&"child-b-id".to_string()));
718 let b_children = edges.get("child-b-id").unwrap();
719 assert_eq!(b_children, &vec!["grandchild-id".to_string()]);
720 }
721
722 #[test]
723 fn from_graph_builds_reverse_edges() {
724 let graph = create_test_graph();
725 let access = GraphModelAccess::from_graph(&graph);
726 let rev = access.reverse_edges.as_ref().unwrap();
727 let gc_parents = rev.get("grandchild-id").unwrap();
728 assert_eq!(gc_parents, &vec!["child-b-id".to_string()]);
729 let a_parents = rev.get("child-a-id").unwrap();
730 assert_eq!(a_parents, &vec!["root-id".to_string()]);
731 }
732
733 #[test]
734 fn from_graph_builds_nodegroups() {
735 let graph = create_test_graph();
736 let access = GraphModelAccess::from_graph(&graph);
737 let ngs = access.nodegroups.as_ref().unwrap();
738 assert!(ngs.contains_key("ng1"));
739 assert!(ngs.contains_key("ng2"));
740 assert_eq!(ngs.get("ng2").unwrap().cardinality, Some("1".to_string()));
742 }
743
744 #[test]
745 fn from_graph_identifies_root() {
746 let graph = create_test_graph();
747 let access = GraphModelAccess::from_graph(&graph);
748 assert_eq!(access.root_node_id.as_deref(), Some("root-id"));
749 }
750
751 #[test]
752 fn from_graph_sets_root_alias_when_missing() {
753 let mut graph = create_test_graph();
754 graph.nodes[0].alias = None;
756 graph.nodes[0].nodegroup_id = None;
757 let access = GraphModelAccess::from_graph(&graph);
758 let aliases = access.nodes_by_alias.as_ref().unwrap();
759 assert!(aliases.contains_key(""));
761 assert_eq!(aliases.get("").unwrap().nodeid, "root-id");
762 }
763
764 #[test]
765 fn get_child_nodes_returns_children_by_alias() {
766 let graph = create_test_graph();
767 let access = GraphModelAccess::from_graph(&graph);
768 let children = access.get_child_nodes("root-id");
769 assert_eq!(children.len(), 2);
770 assert!(children.contains_key("name"));
771 assert!(children.contains_key("details"));
772 assert_eq!(children.get("name").unwrap().nodeid, "child-a-id");
773 }
774
775 #[test]
776 fn get_child_nodes_empty_for_leaf() {
777 let graph = create_test_graph();
778 let access = GraphModelAccess::from_graph(&graph);
779 let children = access.get_child_nodes("grandchild-id");
780 assert!(children.is_empty());
781 }
782
783 #[test]
784 fn is_nodegroup_permitted_default_allow() {
785 let graph = create_test_graph();
786 let access = GraphModelAccess::from_graph(&graph);
787 assert!(access.is_nodegroup_permitted("ng1"));
789 let access2 = GraphModelAccess::new_eager(Arc::new(graph), false);
791 assert!(!access2.is_nodegroup_permitted("ng1"));
792 }
793
794 #[test]
795 fn is_nodegroup_permitted_explicit_deny() {
796 let graph = create_test_graph();
797 let mut access = GraphModelAccess::from_graph(&graph);
798 let mut perms = HashMap::new();
799 perms.insert("ng1".to_string(), PermissionRule::Boolean(true));
800 perms.insert("ng2".to_string(), PermissionRule::Boolean(false));
801 access.set_permitted_nodegroups_rules(perms);
802 assert!(access.is_nodegroup_permitted("ng1"));
803 assert!(!access.is_nodegroup_permitted("ng2"));
804 }
805
806 #[test]
807 fn is_nodegroup_permitted_conditional() {
808 let graph = create_test_graph();
809 let mut access = GraphModelAccess::from_graph(&graph);
810 let mut perms = HashMap::new();
811 perms.insert(
812 "ng1".to_string(),
813 PermissionRule::Conditional {
814 path: ".data.field".to_string(),
815 allowed: HashSet::from(["value1".to_string()]),
816 },
817 );
818 access.set_permitted_nodegroups_rules(perms);
819 assert!(access.is_nodegroup_permitted("ng1"));
821 }
822
823 #[test]
824 fn get_permitted_nodegroups_bool_returns_all_when_empty() {
825 let graph = create_test_graph();
826 let access = GraphModelAccess::from_graph(&graph);
827 let perms = access.get_permitted_nodegroups_bool();
828 assert!(perms.contains_key("ng1"));
830 assert!(perms.contains_key("ng2"));
831 assert!(perms.contains_key(""));
832 assert!(perms.values().all(|&v| v));
833 }
834
835 #[test]
836 fn rebuild_from_graph_preserves_permissions() {
837 let graph = create_test_graph();
838 let mut access = GraphModelAccess::from_graph(&graph);
839 let mut perms = HashMap::new();
840 perms.insert("ng1".to_string(), PermissionRule::Boolean(false));
841 access.set_permitted_nodegroups_rules(perms);
842
843 access.rebuild_from_graph(&graph);
845
846 assert!(!access.is_nodegroup_permitted("ng1"));
848 }
849
850 #[test]
851 fn trait_get_root_node_returns_correct_node() {
852 let graph = create_test_graph();
853 let access = GraphModelAccess::from_graph(&graph);
854 let root = access.get_root_node().unwrap();
855 assert_eq!(root.nodeid, "root-id");
856 assert!(root.istopnode);
857 }
858
859 #[test]
860 fn nodes_by_nodegroup_indexed_correctly() {
861 let graph = create_test_graph();
862 let access = GraphModelAccess::from_graph(&graph);
863 let nbn = access.nodes_by_nodegroup.as_ref().unwrap();
864 let ng2_nodes = nbn.get("ng2").unwrap();
865 assert_eq!(ng2_nodes.len(), 2);
867 let node_ids: Vec<&str> = ng2_nodes.iter().map(|n| n.nodeid.as_str()).collect();
868 assert!(node_ids.contains(&"child-b-id"));
869 assert!(node_ids.contains(&"grandchild-id"));
870 }
871
872 #[test]
875 fn lazy_new_does_not_build_caches() {
876 let graph = create_test_graph();
877 let access = GraphModelAccess::new(Arc::new(graph), true);
878 assert!(!access.is_built());
879 assert!(access.get_nodes_internal().is_none());
880 assert!(access.get_edges_internal().is_none());
881 }
882
883 #[test]
884 fn ensure_built_populates_caches() {
885 let graph = create_test_graph();
886 let mut access = GraphModelAccess::new(Arc::new(graph), true);
887 assert!(!access.is_built());
888 access.ensure_built().unwrap();
889 assert!(access.is_built());
890 assert_eq!(access.get_nodes_internal().unwrap().len(), 4);
891 }
892
893 #[test]
894 fn ensure_built_is_idempotent() {
895 let graph = create_test_graph();
896 let mut access = GraphModelAccess::new(Arc::new(graph), true);
897 access.ensure_built().unwrap();
898 let ptr1 = Arc::as_ptr(access.nodes.as_ref().unwrap());
899 access.ensure_built().unwrap();
900 let ptr2 = Arc::as_ptr(access.nodes.as_ref().unwrap());
901 assert_eq!(ptr1, ptr2);
903 }
904
905 #[test]
906 fn invalidate_caches_clears_indices() {
907 let graph = create_test_graph();
908 let mut access = GraphModelAccess::from_graph(&graph);
909 assert!(access.is_built());
910 access.invalidate_caches();
911 assert!(!access.is_built());
912 assert!(access.get_nodes_internal().is_none());
913 }
914
915 #[test]
916 fn set_graph_nodes_invalidates_caches() {
917 let graph = create_test_graph();
918 let mut access = GraphModelAccess::from_graph(&graph);
919 assert_eq!(access.get_nodes_internal().unwrap().len(), 4);
920
921 let root_only: Vec<StaticNode> = graph
923 .nodes
924 .iter()
925 .filter(|n| n.istopnode)
926 .cloned()
927 .collect();
928 access.set_graph_nodes(root_only);
929 assert!(!access.is_built());
931 access.ensure_built().unwrap();
933 assert_eq!(access.get_nodes_internal().unwrap().len(), 1);
934 }
935
936 #[test]
937 fn arc_sharing_returns_same_allocation() {
938 let graph = create_test_graph();
939 let access = GraphModelAccess::from_graph(&graph);
940 let arc1 = access.get_nodes_arc().unwrap();
941 let arc2 = access.get_nodes_arc().unwrap();
942 assert!(Arc::ptr_eq(&arc1, &arc2));
943 }
944
945 #[test]
946 fn mutable_accessor_triggers_lazy_build() {
947 let graph = create_test_graph();
948 let mut access = GraphModelAccess::new(Arc::new(graph), true);
949 assert!(!access.is_built());
950 let nodes = access.get_node_objects().unwrap();
951 assert_eq!(nodes.len(), 4);
952 assert!(access.is_built());
953 }
954}