use std::collections::HashMap; use std::collections::HashSet; use std::sync::Arc; use rand::random_range; use crate::{Graph, Reference, random_object}; fn get_random_candidate(graph: &Graph) -> Arc { // Pick a random leaf node to update let refs: Vec> =graph.refs.values().filter(|r| r.name != graph.root.name).map(|r| r.clone()).collect(); let random_index = random_range(0..refs.len()); refs[random_index].clone() } /// Tests that all dependencies are kept updated when new nodes are added #[test] fn test_dependencies_updated_when_nodes_added() { // Create a simple graph let mut graph = create_test_graph(); // Get the initial content address of the root let initial_root_id = graph.root.id.clone(); let candidate = get_random_candidate(&graph); // Update the leaf node graph.update_object_reference(&candidate.name, random_object().1).unwrap(); // Verify that the leaf node's ID has changed let updated_leaf = dbg!(graph.get_reference(&candidate.name).unwrap()); assert_ne!(updated_leaf.id, candidate.id, "Leaf node ID should change when content is updated"); // Verify that the root's ID has changed assert_ne!(graph.root.id, initial_root_id, "Root ID should change when a dependent node is updated"); } /// Tests that the root of the graph is not itself a dependency of any other node #[test] fn test_root_not_a_dependency() { let graph = create_test_graph(); let root_name = graph.root.name.clone(); // Check all references to ensure none have the root as a dependent for (_, reference) in graph.refs.as_ref() { for dep in &reference.dependents { assert_ne!(dep.name, root_name, "Root should not be a dependency of any other node"); } } } /// Tests that all nodes are dependents or transitive dependents of the root #[test] fn test_all_nodes_connected_to_root() { let graph = create_test_graph(); // Collect all nodes reachable from the root let mut reachable = HashSet::new(); fn collect_reachable(node: &Arc, reachable: &mut HashSet) { reachable.insert(node.name.clone()); for dep in &node.dependents { if !reachable.contains(&dep.name) { collect_reachable(dep, reachable); } } } collect_reachable(&graph.root, &mut reachable); // Check that all nodes in the graph are reachable from the root for (name, _) in graph.refs.as_ref() { assert!(reachable.contains(name), "All nodes should be reachable from the root: {}", name); } } /// Helper function to create a test graph with a known structure fn create_test_graph() -> Graph { let root_name = String::from("/root"); let mut objects = HashMap::new(); let mut refs = HashMap::new(); // Create the root reference let mut root_ref = Reference::new( Some(String::from("root_content")), root_name.clone(), ); // Create 3 item references for i in 1..=3 { let item_name = format!("/item/{}", i); let mut item_ref = Reference::new( Some(format!("item_content_{}", i)), item_name.clone(), ); // Create 3 subitems for each item for j in 1..=3 { let (address, content) = random_object(); let subitem_name = format!("/item/{}/subitem/{}", i, j); // Create a leaf reference let leaf_ref = Reference::new( Some(address.clone()), subitem_name, ).to_arc(); // Add the leaf reference as a dependent to the item reference item_ref = item_ref.add_dep(leaf_ref.clone()); // Store the content in the objects map objects.insert(address.clone(), content); // Store the leaf reference in the refs map refs.insert(leaf_ref.name.clone(), leaf_ref); } // Convert the item reference to Arc and add it to the root reference let arc_item_ref = item_ref.to_arc(); root_ref = root_ref.add_dep(arc_item_ref.clone()); // Store the item reference in the refs map refs.insert(arc_item_ref.name.clone(), arc_item_ref); } // Convert the root reference to Arc let arc_root_ref = root_ref.to_arc(); // Store the root reference in the refs map refs.insert(arc_root_ref.name.clone(), arc_root_ref.clone()); Graph { root: arc_root_ref, refs: Arc::new(refs), objects: Arc::new(objects), } } /// Tests that the graph correctly handles content-addressable properties #[test] fn test_content_addressable_properties() { let mut graph = create_test_graph(); // Update a leaf node with the same content let leaf_path = "/item/1/subitem/1".to_string(); let initial_leaf = graph.get_reference(&leaf_path).unwrap(); if let Some(content_address) = initial_leaf.content_address.clone() { // Get the content for this address let content = graph.get_object(&content_address).unwrap().clone(); // Update with the same content graph.update_object_reference(&leaf_path, content).unwrap(); } // Verify that nothing changed since the content is the same let updated_leaf = graph.get_reference(&leaf_path).unwrap(); assert_eq!(updated_leaf.content_address, initial_leaf.content_address, "Content address should not change when content remains the same"); } /// Tests that the graph correctly handles ID calculation #[test] fn test_id_calculation() { let mut graph = create_test_graph(); // Update a leaf node let leaf_path = "/item/1/subitem/1".to_string(); let initial_leaf = graph.get_reference(&leaf_path).unwrap(); graph.update_object_reference(&leaf_path, "new content".as_bytes().to_vec()).unwrap(); // Verify that the ID changed let updated_leaf = graph.get_reference(&leaf_path).unwrap(); assert_ne!(updated_leaf.id, initial_leaf.id, "Reference ID should change when content changes"); // Verify that parent ID changed let parent_path = "/item/1".to_string(); let parent = graph.get_reference(&parent_path).unwrap(); // Create a reference with the same properties to calculate expected ID let mut test_ref = Reference::new( parent.content_address.clone(), parent.name.clone(), ); // Add the same dependents for dep in &parent.dependents { test_ref = test_ref.add_dep(dep.clone()); } // Verify the ID calculation is consistent assert_eq!(parent.id, test_ref.id, "ID calculation should be consistent for the same reference properties"); }