Skip to main content
Glama
tests.rs54.8 kB
use std::collections::HashMap; use si_events::{ ContentHash, ulid::Ulid, }; use crate::{ EdgeWeight, EdgeWeightKind, PropKind, workspace_snapshot::{ graph::WorkspaceSnapshotGraphVCurrent, node_weight::NodeWeight, }, }; mod detect_changes; mod detect_updates; mod exclusive_outgoing_edges; mod rebase; #[allow(dead_code)] fn add_prop_nodes_to_graph<'a, 'b>( graph: &'a mut WorkspaceSnapshotGraphVCurrent, nodes: &'a [&'b str], ordered: bool, ) -> HashMap<&'b str, Ulid> { let mut node_id_map = HashMap::new(); for node in nodes { // "props" here are just nodes that are easy to create and render the name on the dot // output. there is no domain modeling in this test. let node_id = graph.generate_ulid().expect("Unable to generate Ulid"); let node_lineage_id = graph.generate_ulid().expect("Unable to generate Ulid"); let prop_node_weight = NodeWeight::new_prop( node_id, node_lineage_id, PropKind::Object, node, ContentHash::new(node.as_bytes()), ); if ordered { graph .add_ordered_node(prop_node_weight) .expect("Unable to add prop"); } else { graph .add_or_replace_node(prop_node_weight) .expect("Unable to add prop"); } node_id_map.insert(*node, node_id); } node_id_map } #[allow(dead_code)] fn add_edges( graph: &mut WorkspaceSnapshotGraphVCurrent, node_id_map: &HashMap<&str, Ulid>, edges: &[(Option<&str>, &str)], ) { for (source, target) in edges { let source = match source { None => graph.root(), Some(node) => graph .get_node_index_by_id( node_id_map .get(node) .copied() .expect("source node should have an id"), ) .expect("get node index by id"), }; let target = graph .get_node_index_by_id( node_id_map .get(target) .copied() .expect("target node should have an id"), ) .expect("get node index by id"); graph .add_edge(source, EdgeWeight::new(EdgeWeightKind::new_use()), target) .expect("add edge"); } } #[allow(clippy::panic)] #[cfg(test)] mod test { use std::{ collections::HashSet, str::FromStr, }; use petgraph::{ Outgoing, graph::NodeIndex, visit::EdgeRef, }; use pretty_assertions_sorted::assert_eq; use si_events::{ ContentHash, merkle_tree_hash::MerkleTreeHash, ulid::Ulid, }; use super::{ add_edges, add_prop_nodes_to_graph, }; use crate::{ ComponentId, FuncId, PropId, SchemaId, SchemaVariantId, workspace_snapshot::{ content_address::ContentAddress, edge_weight::{ EdgeWeight, EdgeWeightKind, EdgeWeightKindDiscriminants, }, graph::{ WorkspaceSnapshotGraphVCurrent, detector::Update, }, node_weight::NodeWeight, }, }; #[test] fn new() { let graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); assert!(graph.is_acyclic_directed()); } // Previously, WorkspaceSnapshotGraph::new would not populate its node_index_by_id, so this test // would fail, in addition to any functionality that depended on getting the root node index // on a fresh graph (like add_ordered_node) #[test] fn get_root_index_by_root_id_on_fresh_graph() { let graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let root_id = graph .get_node_weight(graph.root()) .expect("get root weight") .id(); let root_node_idx = graph .get_node_index_by_id(root_id) .expect("get root node index from ULID"); assert_eq!(graph.root(), root_node_idx); } #[test] fn multiply_parented_nodes() { // All edges are outgoing from top to bottom except e to u // // root node---->t--->u--->v // | ^ // | | // r ------ | // / \ | | // a b | | // \ / \ | | // c | | | // / | | | | // | d <- | | // | | | | // ->e<------ | // | | // ---------------- // // Edge from e to u mimics a function edge from a prop through a prototype to a function // There are a few other edges to "u" that are not represented in the drawing above. // let nodes = ["r", "t", "u", "v", "a", "b", "c", "d", "e"]; let edges = [ (None, "r"), (None, "t"), (Some("t"), "u"), (Some("u"), "v"), (Some("r"), "a"), (Some("r"), "b"), (Some("r"), "e"), (Some("a"), "c"), (Some("b"), "c"), (Some("c"), "d"), (Some("b"), "d"), (Some("d"), "e"), (Some("c"), "e"), (Some("e"), "u"), (Some("c"), "u"), (Some("a"), "u"), (Some("a"), "b"), ]; let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let node_id_map = add_prop_nodes_to_graph(&mut graph, &nodes, false); add_edges(&mut graph, &node_id_map, &edges); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); for (source, target) in edges { let source_idx = match source { None => graph.root(), Some(node) => graph .get_node_index_by_id( node_id_map .get(node) .copied() .expect("source node should have an id"), ) .expect("get node index by id"), }; let target_idx = graph .get_node_index_by_id( node_id_map .get(target) .copied() .expect("target node should have an id"), ) .expect("get node index by id"); assert!( graph .edges_directed(source_idx, Outgoing) .any(|edge_ref| edge_ref.target() == target_idx), "An edge from {} to {} should exist", source.unwrap_or("root"), target ); } assert!(graph.is_acyclic_directed()); for (_, id) in node_id_map.iter() { let idx_for_node = graph .get_node_index_by_id(*id) .expect("able to get idx by id"); graph .get_node_weight(idx_for_node) .expect("node with weight in graph"); } } #[test] fn add_nodes_and_edges() { let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let schema_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_index = graph .add_or_replace_node(NodeWeight::new_content( schema_id, Ulid::new(), ContentAddress::Schema(ContentHash::new( SchemaId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema"); let schema_variant_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_variant_index = graph .add_or_replace_node(NodeWeight::new_content( schema_variant_id, Ulid::new(), ContentAddress::SchemaVariant(ContentHash::new( SchemaVariantId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema variant"); let component_id = graph.generate_ulid().expect("Unable to generate Ulid"); let component_index = graph .add_or_replace_node(NodeWeight::new_content( component_id, Ulid::new(), ContentAddress::Component(ContentHash::new( ComponentId::generate().to_string().as_bytes(), )), )) .expect("Unable to add component"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), component_index, ) .expect("Unable to add root -> component edge"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), schema_index, ) .expect("Unable to add root -> schema edge"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), schema_variant_index, ) .expect("Unable to add schema -> schema variant edge"); graph .add_edge( graph .get_node_index_by_id(component_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), graph .get_node_index_by_id(schema_variant_id) .expect("Unable to get NodeIndex"), ) .expect("Unable to add component -> schema variant edge"); let func_id = graph.generate_ulid().expect("Unable to generate Ulid"); let func_index = graph .add_or_replace_node(NodeWeight::new_content( func_id, Ulid::new(), ContentAddress::Func(ContentHash::new(FuncId::generate().to_string().as_bytes())), )) .expect("Unable to add func"); let prop_id = graph.generate_ulid().expect("Unable to generate Ulid"); let prop_index = graph .add_or_replace_node(NodeWeight::new_content( prop_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(PropId::generate().to_string().as_bytes())), )) .expect("Unable to add prop"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), func_index, ) .expect("Unable to add root -> func edge"); graph .add_edge( graph .get_node_index_by_id(schema_variant_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), prop_index, ) .expect("Unable to add schema variant -> prop edge"); graph .add_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), graph .get_node_index_by_id(func_id) .expect("Unable to get NodeIndex"), ) .expect("Unable to add prop -> func edge"); assert!(graph.is_acyclic_directed()); } #[test] fn cyclic_failure() { let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let schema_id = graph.generate_ulid().expect("Unable to generate Ulid"); let initial_schema_node_index = graph .add_or_replace_node(NodeWeight::new_content( schema_id, Ulid::new(), ContentAddress::Schema(ContentHash::new( SchemaId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema"); let schema_variant_id = graph.generate_ulid().expect("Unable to generate Ulid"); let initial_schema_variant_node_index = graph .add_or_replace_node(NodeWeight::new_content( schema_variant_id, Ulid::new(), ContentAddress::SchemaVariant(ContentHash::new( SchemaVariantId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema variant"); let component_id = graph.generate_ulid().expect("Unable to generate Ulid"); let initial_component_node_index = graph .add_or_replace_node(NodeWeight::new_content( component_id, Ulid::new(), ContentAddress::Component(ContentHash::new( ComponentId::generate().to_string().as_bytes(), )), )) .expect("Unable to add component"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), initial_component_node_index, ) .expect("Unable to add root -> component edge"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), initial_schema_node_index, ) .expect("Unable to add root -> schema edge"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to find NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), initial_schema_variant_node_index, ) .expect("Unable to add schema -> schema variant edge"); graph .add_edge( graph .get_node_index_by_id(component_id) .expect("Unable to find NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), graph .get_node_index_by_id(schema_variant_id) .expect("Unable to find NodeIndex"), ) .expect("Unable to add component -> schema variant edge"); let pre_cycle_root_index = graph.root(); // This should cause a cycle. graph .add_edge( graph .get_node_index_by_id(schema_variant_id) .expect("Unable to find NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), graph .get_node_index_by_id(component_id) .expect("Unable to find NodeIndex"), ) .expect_err("Created a cycle"); assert_eq!(pre_cycle_root_index, graph.root(),); } #[test] fn update_content() { let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let schema_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_index = graph .add_or_replace_node(NodeWeight::new_content( schema_id, Ulid::new(), ContentAddress::Schema(ContentHash::from("Constellation")), )) .expect("Unable to add schema"); let schema_variant_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_variant_index = graph .add_or_replace_node(NodeWeight::new_content( schema_variant_id, Ulid::new(), ContentAddress::SchemaVariant(ContentHash::new("Freestar Collective".as_bytes())), )) .expect("Unable to add schema variant"); let component_id = graph.generate_ulid().expect("Unable to generate Ulid"); let component_index = graph .add_or_replace_node(NodeWeight::new_content( component_id, Ulid::new(), ContentAddress::Component(ContentHash::from("Crimson Fleet")), )) .expect("Unable to add component"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), component_index, ) .expect("Unable to add root -> component edge"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), schema_index, ) .expect("Unable to add root -> schema edge"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), schema_variant_index, ) .expect("Unable to add schema -> schema variant edge"); graph .add_edge( graph .get_node_index_by_id(component_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), graph .get_node_index_by_id(schema_variant_id) .expect("Unable to get NodeIndex"), ) .expect("Unable to add component -> schema variant edge"); // Ensure that the root node merkle tree hash looks as we expect before the update. graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); let pre_update_root_node_merkle_tree_hash: MerkleTreeHash = MerkleTreeHash::from_str("3206cdbe645cbc3bfde3d64c5712abf9") .expect("able to create hash from hex string"); assert_eq!( pre_update_root_node_merkle_tree_hash, // expected graph .get_node_weight(graph.root()) .expect("could not get node weight") .merkle_tree_hash(), // actual ); let updated_content_hash = ContentHash::from("new_content"); graph .update_content(component_id, updated_content_hash) .expect("Unable to update Component content hash"); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); let post_update_root_node_merkle_tree_hash: MerkleTreeHash = MerkleTreeHash::from_str("fcf780cbc0e2efa2a04c30cef2880cfe") .expect("able to create hash from hex string"); assert_eq!( post_update_root_node_merkle_tree_hash, // expected graph .get_node_weight(graph.root()) .expect("could not get node weight") .merkle_tree_hash(), // actual ); assert_eq!( updated_content_hash, // expected graph .get_node_weight( graph .get_node_index_by_id(component_id) .expect("could not get node index by id") ) .expect("could not get node weight") .content_hash(), // actual ); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); // Ensure that there are not more nodes than the ones that should be in use, and the // initial ones (categories and default views) assert_eq!(17, graph.node_count()); // The hashes must not change upon cleanup. assert_eq!( post_update_root_node_merkle_tree_hash, // expected graph .get_node_weight(graph.root()) .expect("could not get node weight") .merkle_tree_hash(), // actual ); assert_eq!( updated_content_hash, // expected graph .get_node_weight( graph .get_node_index_by_id(component_id) .expect("could not get node index by id") ) .expect("could not get node weight") .content_hash(), // actual ); } #[test] fn add_ordered_node() { let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let schema_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_index = graph .add_or_replace_node(NodeWeight::new_content( schema_id, Ulid::new(), ContentAddress::Schema(ContentHash::new( SchemaId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema"); let schema_variant_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_variant_index = graph .add_or_replace_node(NodeWeight::new_content( schema_variant_id, Ulid::new(), ContentAddress::SchemaVariant(ContentHash::new( SchemaVariantId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema variant"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), schema_index, ) .expect("Unable to add root -> schema edge"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), schema_variant_index, ) .expect("Unable to add schema -> schema variant edge"); let func_id = graph.generate_ulid().expect("Unable to generate Ulid"); let func_index = graph .add_or_replace_node(NodeWeight::new_content( func_id, Ulid::new(), ContentAddress::Func(ContentHash::new(FuncId::generate().to_string().as_bytes())), )) .expect("Unable to add func"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), func_index, ) .expect("Unable to add root -> func edge"); let prop_id = graph.generate_ulid().expect("Unable to generate Ulid"); let prop_index = graph .add_ordered_node(NodeWeight::new_content( prop_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(PropId::generate().to_string().as_bytes())), )) .expect("Unable to add prop"); graph .add_edge( graph .get_node_index_by_id(schema_variant_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), prop_index, ) .expect("Unable to add schema variant -> prop edge"); graph .add_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), graph .get_node_index_by_id(func_id) .expect("Unable to get NodeIndex"), ) .expect("Unable to add prop -> func edge"); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); graph.dot(); let ordered_prop_1_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_1_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_1_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_1_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_1_index, ) .expect("Unable to add prop -> ordered_prop_1 edge"); let ordered_prop_2_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_2_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_2_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_2_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_2_index, ) .expect("Unable to add prop -> ordered_prop_2 edge"); let ordered_prop_3_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_3_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_3_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_3_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_3_index, ) .expect("Unable to add prop -> ordered_prop_3 edge"); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); graph.dot(); assert_eq!( vec![ ordered_prop_1_index, ordered_prop_2_index, ordered_prop_3_index, ], graph .ordered_children_for_node( graph .get_node_index_by_id(prop_id) .expect("Unable to get prop NodeIndex") ) .expect("Unable to find ordered children for node") .expect("Node is not an ordered node") ); } #[test] fn add_ordered_node_below_root() { let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let prop_id = graph.generate_ulid().expect("Unable to generate Ulid"); let prop_index = graph .add_ordered_node(NodeWeight::new_content( prop_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(prop_id.to_string().as_bytes())), )) .expect("Unable to add prop"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), prop_index, ) .expect("Unable to add root -> prop edge"); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); assert_eq!( Vec::<NodeIndex>::new(), graph .ordered_children_for_node( graph .get_node_index_by_id(prop_id) .expect("Unable to get prop NodeIndex") ) .expect("Unable to find ordered children for node") .expect("Node is not an ordered node") ); } #[test] fn reorder_ordered_node() { let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let schema_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_index = graph .add_or_replace_node(NodeWeight::new_content( schema_id, Ulid::new(), ContentAddress::Schema(ContentHash::new( SchemaId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema"); let schema_variant_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_variant_index = graph .add_or_replace_node(NodeWeight::new_content( schema_variant_id, Ulid::new(), ContentAddress::SchemaVariant(ContentHash::new( SchemaVariantId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema variant"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), schema_index, ) .expect("Unable to add root -> schema edge"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), schema_variant_index, ) .expect("Unable to add schema -> schema variant edge"); let func_id = graph.generate_ulid().expect("Unable to generate Ulid"); let func_index = graph .add_or_replace_node(NodeWeight::new_content( func_id, Ulid::new(), ContentAddress::Func(ContentHash::new(FuncId::generate().to_string().as_bytes())), )) .expect("Unable to add func"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), func_index, ) .expect("Unable to add root -> func edge"); let prop_id = graph.generate_ulid().expect("Unable to generate Ulid"); let prop_index = graph .add_ordered_node(NodeWeight::new_content( prop_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(PropId::generate().to_string().as_bytes())), )) .expect("Unable to add prop"); graph .add_edge( graph .get_node_index_by_id(schema_variant_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), prop_index, ) .expect("Unable to add schema variant -> prop edge"); graph .add_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), graph .get_node_index_by_id(func_id) .expect("Unable to get NodeIndex"), ) .expect("Unable to add prop -> func edge"); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); graph.dot(); let ordered_prop_1_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_1_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_1_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_1_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_1_index, ) .expect("Unable to add prop -> ordered_prop_1 edge"); let ordered_prop_2_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_2_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_2_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_2_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_2_index, ) .expect("Unable to add prop -> ordered_prop_2 edge"); let ordered_prop_3_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_3_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_3_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_3_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_3_index, ) .expect("Unable to add prop -> ordered_prop_3 edge"); let ordered_prop_4_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_4_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_4_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_4_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_4_index, ) .expect("Unable to add prop -> ordered_prop_4 edge"); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); graph.dot(); assert_eq!( vec![ ordered_prop_1_index, ordered_prop_2_index, ordered_prop_3_index, ordered_prop_4_index, ], graph .ordered_children_for_node( graph .get_node_index_by_id(prop_id) .expect("Unable to get prop NodeIndex") ) .expect("Unable to find ordered children for node") .expect("Node is not an ordered node") ); let new_order = vec![ ordered_prop_2_id, ordered_prop_1_id, ordered_prop_4_id, ordered_prop_3_id, ]; graph .update_order(prop_id, new_order) .expect("Unable to update order of prop's children"); assert_eq!( vec![ ordered_prop_2_index, ordered_prop_1_index, ordered_prop_4_index, ordered_prop_3_index, ], graph .ordered_children_for_node( graph .get_node_index_by_id(prop_id) .expect("Unable to get prop NodeIndex") ) .expect("Unable to find ordered children for node") .expect("Node is not an ordered node") ); } #[test] fn remove_unordered_node_and_detect_edge_removal() { let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let schema_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_index = graph .add_or_replace_node(NodeWeight::new_content( schema_id, Ulid::new(), ContentAddress::Schema(ContentHash::new( SchemaId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema"); let schema_variant_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_variant_index = graph .add_or_replace_node(NodeWeight::new_content( schema_variant_id, Ulid::new(), ContentAddress::SchemaVariant(ContentHash::new( SchemaVariantId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema variant"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), schema_index, ) .expect("Unable to add root -> schema edge"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), schema_variant_index, ) .expect("Unable to add schema -> schema variant edge"); let schema_variant_2_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_variant_2_index = graph .add_or_replace_node(NodeWeight::new_content( schema_variant_2_id, Ulid::new(), ContentAddress::SchemaVariant(ContentHash::new( SchemaVariantId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema variant"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), schema_variant_2_index, ) .expect("Unable to add schema -> schema variant edge"); let expected_edges = HashSet::from([schema_variant_2_index, schema_variant_index]); let existing_edges: HashSet<NodeIndex> = graph .edges_directed( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex for schema"), Outgoing, ) .map(|edge_ref| edge_ref.target()) .collect(); assert_eq!( expected_edges, existing_edges, "confirm edges are there before deleting" ); let mut graph_with_deleted_edge = graph.clone(); graph_with_deleted_edge.dot(); graph_with_deleted_edge .remove_edge( graph_with_deleted_edge .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex for schema"), schema_variant_2_index, EdgeWeightKindDiscriminants::Use, ) .expect("Edge removal failed"); graph_with_deleted_edge.dot(); let existing_edges: Vec<NodeIndex> = graph_with_deleted_edge .edges_directed( graph_with_deleted_edge .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex for schema"), Outgoing, ) .map(|edge_ref| edge_ref.target()) .collect(); assert_eq!( vec![schema_variant_index], existing_edges, "confirm edges after deletion" ); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); let updates = graph.detect_updates(&graph_with_deleted_edge); assert_eq!(1, updates.len()); assert!(matches!( updates.first().expect("should be there"), Update::RemoveEdge { .. } )); } #[test] fn remove_unordered_node() { let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let schema_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_index = graph .add_or_replace_node(NodeWeight::new_content( schema_id, Ulid::new(), ContentAddress::Schema(ContentHash::new( SchemaId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema"); let schema_variant_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_variant_index = graph .add_or_replace_node(NodeWeight::new_content( Ulid::new(), schema_variant_id, ContentAddress::SchemaVariant(ContentHash::new( SchemaVariantId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema variant"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), schema_index, ) .expect("Unable to add root -> schema edge"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), schema_variant_index, ) .expect("Unable to add schema -> schema variant edge"); let schema_variant_2_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_variant_2_index = graph .add_or_replace_node(NodeWeight::new_content( schema_variant_2_id, Ulid::new(), ContentAddress::SchemaVariant(ContentHash::new( SchemaVariantId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema variant"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), schema_variant_2_index, ) .expect("Unable to add schema -> schema variant edge"); let expected_edges = HashSet::from([schema_variant_2_index, schema_variant_index]); let existing_edges: HashSet<NodeIndex> = graph .edges_directed( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex for schema"), Outgoing, ) .map(|edge_ref| edge_ref.target()) .collect(); assert_eq!( expected_edges, existing_edges, "confirm edges are there before deleting" ); graph .remove_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex for schema"), schema_variant_2_index, EdgeWeightKindDiscriminants::Use, ) .expect("Edge removal failed"); let existing_edges: Vec<NodeIndex> = graph .edges_directed( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex for schema"), Outgoing, ) .map(|edge_ref| edge_ref.target()) .collect(); assert_eq!( vec![schema_variant_index], existing_edges, "confirm edges after deletion" ); } #[test] fn remove_ordered_node() { let mut graph = WorkspaceSnapshotGraphVCurrent::new_for_unit_tests() .expect("Unable to create WorkspaceSnapshotGraph"); let schema_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_index = graph .add_or_replace_node(NodeWeight::new_content( schema_id, Ulid::new(), ContentAddress::Schema(ContentHash::new( SchemaId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema"); let schema_variant_id = graph.generate_ulid().expect("Unable to generate Ulid"); let schema_variant_index = graph .add_or_replace_node(NodeWeight::new_content( schema_variant_id, Ulid::new(), ContentAddress::SchemaVariant(ContentHash::new( SchemaVariantId::generate().to_string().as_bytes(), )), )) .expect("Unable to add schema variant"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), schema_index, ) .expect("Unable to add root -> schema edge"); graph .add_edge( graph .get_node_index_by_id(schema_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), schema_variant_index, ) .expect("Unable to add schema -> schema variant edge"); let func_id = graph.generate_ulid().expect("Unable to generate Ulid"); let func_index = graph .add_or_replace_node(NodeWeight::new_content( func_id, Ulid::new(), ContentAddress::Func(ContentHash::new(FuncId::generate().to_string().as_bytes())), )) .expect("Unable to add func"); graph .add_edge( graph.root(), EdgeWeight::new(EdgeWeightKind::new_use()), func_index, ) .expect("Unable to add root -> func edge"); let root_prop_id = graph.generate_ulid().expect("Unable to generate Ulid"); let root_prop_index = graph .add_ordered_node(NodeWeight::new_content( root_prop_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(PropId::generate().to_string().as_bytes())), )) .expect("Unable to add prop"); graph .add_edge( graph .get_node_index_by_id(schema_variant_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), root_prop_index, ) .expect("Unable to add schema variant -> prop edge"); graph .add_edge( graph .get_node_index_by_id(root_prop_id) .expect("Unable to get NodeIndex"), EdgeWeight::new(EdgeWeightKind::new_use()), graph .get_node_index_by_id(func_id) .expect("Unable to get NodeIndex"), ) .expect("Unable to add prop -> func edge"); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); graph.dot(); let ordered_prop_1_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_1_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_1_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_1_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(root_prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_1_index, ) .expect("Unable to add prop -> ordered_prop_1 edge"); let ordered_prop_2_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_2_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_2_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_2_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(root_prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_2_index, ) .expect("Unable to add prop -> ordered_prop_2 edge"); let ordered_prop_3_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_3_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_3_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_3_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(root_prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_3_index, ) .expect("Unable to add prop -> ordered_prop_3 edge"); let ordered_prop_4_id = graph.generate_ulid().expect("Unable to generate Ulid"); let ordered_prop_4_index = graph .add_or_replace_node(NodeWeight::new_content( ordered_prop_4_id, Ulid::new(), ContentAddress::Prop(ContentHash::new(ordered_prop_4_id.to_string().as_bytes())), )) .expect("Unable to add ordered prop"); graph .add_ordered_edge( graph .get_node_index_by_id(root_prop_id) .expect("Unable to get NodeWeight for prop"), EdgeWeight::new(EdgeWeightKind::new_use()), ordered_prop_4_index, ) .expect("Unable to add prop -> ordered_prop_4 edge"); graph .cleanup_and_merkle_tree_hash() .expect("cleanup and merkle"); graph.dot(); assert_eq!( vec![ ordered_prop_1_index, ordered_prop_2_index, ordered_prop_3_index, ordered_prop_4_index, ], graph .ordered_children_for_node( graph .get_node_index_by_id(root_prop_id) .expect("Unable to get prop NodeIndex") ) .expect("Unable to find ordered children for node") .expect("Node is not an ordered node") ); graph .remove_edge( graph .get_node_index_by_id(root_prop_id) .expect("Unable to get NodeIndex for prop"), ordered_prop_2_index, EdgeWeightKindDiscriminants::Use, ) .expect("Unable to remove prop -> ordered_prop_2 edge"); assert_eq!( vec![ ordered_prop_1_index, ordered_prop_3_index, ordered_prop_4_index, ], graph .ordered_children_for_node( graph .get_node_index_by_id(root_prop_id) .expect("Unable to get prop NodeIndex") ) .expect("Unable to find ordered children for node") .expect("Node is not an ordered node") ); if let NodeWeight::Ordering(ordering_weight) = graph .get_node_weight( graph .ordering_node_index_for_container( graph .get_node_index_by_id(root_prop_id) .expect("Unable to find ordering node for prop"), ) .expect("Error getting ordering NodeIndex for prop") .expect("Unable to find ordering NodeIndex"), ) .expect("Unable to get ordering NodeWeight for ordering node") { assert_eq!( &vec![ordered_prop_1_id, ordered_prop_3_id, ordered_prop_4_id], ordering_weight.order() ); } else { panic!("Unable to destructure ordering node weight"); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server