use std::collections::HashMap; use std::collections::HashSet; use std::sync::Arc; use crate::{Graph, Reference, random_object}; fn get_deterministic_candidate(graph: &Graph) -> Arc { // Pick a deterministic leaf node to update (first lexicographically) let mut refs: Vec> = graph.refs.values() .filter(|r| r.name != graph.root.name && r.is_leaf()) .map(|r| r.clone()) .collect(); // Sort by name to ensure deterministic ordering refs.sort_by(|a, b| a.name.cmp(&b.name)); refs[0].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_deterministic_candidate(&graph); // Update the leaf node with deterministic content let new_content = b"deterministic_test_content".to_vec(); graph.update_object_reference(&candidate.name, new_content).unwrap(); // Verify that the leaf node's ID has changed let updated_leaf = 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"); } /// Tests that the root ID always changes when any reference is updated #[test] fn test_root_id_changes_for_any_reference_update() { let mut graph = create_test_graph(); // Get all non-root references sorted by name for deterministic iteration let mut all_refs: Vec<(String, String)> = graph.refs.as_ref() .iter() .filter(|(name, _)| **name != graph.root.name) .map(|(name, ref_arc)| (name.clone(), ref_arc.id.clone())) .collect(); all_refs.sort_by(|a, b| a.0.cmp(&b.0)); // Test each reference update for (ref_name, original_ref_id) in all_refs { // Record the current root ID let initial_root_id = graph.root.id.clone(); // Update the reference with new content let new_content = format!("updated_content_for_{}", ref_name).into_bytes(); graph.update_object_reference(&ref_name, new_content).unwrap(); // Verify the reference itself changed let updated_ref = graph.get_reference(&ref_name).unwrap(); assert_ne!(updated_ref.id, original_ref_id, "Reference {} should have changed ID after update", ref_name); // Verify the root ID changed assert_ne!(graph.root.id, initial_root_id, "Root ID should change when reference {} is updated", ref_name); } } /// Tests that Reference IDs are stable regardless of dependency add order #[test] fn test_reference_ids_stable_regardless_of_dependency_order() { // Create test dependencies let dep_a = Reference::new( Some(String::from("content_a")), String::from("/dep_a"), ).to_arc(); let dep_b = Reference::new( Some(String::from("content_b")), String::from("/dep_b"), ).to_arc(); let dep_c = Reference::new( Some(String::from("content_c")), String::from("/dep_c"), ).to_arc(); let dep_d = Reference::new( Some(String::from("content_d")), String::from("/dep_d"), ).to_arc(); // Create base reference let base_ref = Reference::new( Some(String::from("base_content")), String::from("/base"), ); // Test multiple different orders of adding the same dependencies let orders = vec![ vec![dep_a.clone(), dep_b.clone(), dep_c.clone(), dep_d.clone()], // alphabetical vec![dep_d.clone(), dep_c.clone(), dep_b.clone(), dep_a.clone()], // reverse alphabetical vec![dep_b.clone(), dep_d.clone(), dep_a.clone(), dep_c.clone()], // random order 1 vec![dep_c.clone(), dep_a.clone(), dep_d.clone(), dep_b.clone()], // random order 2 vec![dep_d.clone(), dep_a.clone(), dep_b.clone(), dep_c.clone()], // random order 3 ]; let mut all_ids = Vec::new(); for (i, order) in orders.iter().enumerate() { let mut test_ref = base_ref.clone(); // Add dependencies in this specific order for dep in order { test_ref = test_ref.add_dep(dep.clone()); } all_ids.push(test_ref.id.clone()); // Verify that dependencies are always sorted lexicographically regardless of add order for j in 0..test_ref.dependents.len() - 1 { let current = &test_ref.dependents[j]; let next = &test_ref.dependents[j + 1]; assert!(current.name < next.name, "Dependencies should be lexicographically ordered. Order {}: Found '{}' before '{}'", i, current.name, next.name); } } // Verify all IDs are identical let first_id = &all_ids[0]; for (i, id) in all_ids.iter().enumerate() { assert_eq!(id, first_id, "Reference ID should be stable regardless of dependency add order. Order {} produced different ID", i); } } /// Tests that dependencies of a Reference are always lexicographically ordered by name #[test] fn test_dependencies_lexicographically_ordered() { let graph = create_test_graph(); // Check all references to ensure their dependents are lexicographically ordered for (_, reference) in graph.refs.as_ref() { if reference.dependents.len() > 1 { // Check that dependents are ordered by name for i in 0..reference.dependents.len() - 1 { let current = &reference.dependents[i]; let next = &reference.dependents[i + 1]; assert!(current.name < next.name, "Dependencies should be lexicographically ordered by name. Found '{}' before '{}'", current.name, next.name); } } } // Also verify that when we add dependencies, they maintain the correct order let mut test_ref = Reference::new( Some(String::from("test_content")), String::from("/test_ordering"), ); // Add dependencies in non-lexicographical order let dep_c = Reference::new( Some(String::from("c_content")), String::from("/c"), ).to_arc(); let dep_a = Reference::new( Some(String::from("a_content")), String::from("/a"), ).to_arc(); let dep_b = Reference::new( Some(String::from("b_content")), String::from("/b"), ).to_arc(); // Add in non-lexicographical order test_ref = test_ref.add_dep(dep_c.clone()); test_ref = test_ref.add_dep(dep_a.clone()); test_ref = test_ref.add_dep(dep_b.clone()); // Verify they are stored in lexicographical order assert_eq!(test_ref.dependents[0].name, "/a", "First dependent should be '/a'"); assert_eq!(test_ref.dependents[1].name, "/b", "Second dependent should be '/b'"); assert_eq!(test_ref.dependents[2].name, "/c", "Third dependent should be '/c'"); }