wip: the beginnings of some unit tests
This commit is contained in:
parent
eec0dab6f0
commit
0c369864d0
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
result/
|
||||
result
|
||||
target/
|
||||
*.avanterules
|
||||
|
@ -21,7 +21,7 @@ there is one, and a list of any dependent resources.
|
||||
{
|
||||
"objectId": <merkle-hash>,
|
||||
"content_address": <content-hash>,
|
||||
"path": "/path/name0",
|
||||
"name": "/path/name0",
|
||||
"dependents": [
|
||||
{
|
||||
"path": "path/name1",
|
||||
|
@ -17,7 +17,7 @@ async fn ref_path(refs: Arc<HashMap<String, Arc<Reference>>>, Path(path): Path<S
|
||||
}
|
||||
}
|
||||
|
||||
async fn object_path(objects: Arc<HashMap<String, String>>, Path(addr): Path<String>) -> String {
|
||||
async fn object_path(objects: Arc<HashMap<String, Vec<u8>>>, Path(addr): Path<String>) -> Vec<u8> {
|
||||
dbg!(&addr);
|
||||
match objects.get(&addr) {
|
||||
Some(o) => o.clone(),
|
||||
|
@ -21,7 +21,7 @@ async fn get_client_js() -> impl IntoResponse {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum ServerMsg {
|
||||
Reference(Reference),
|
||||
Object(String),
|
||||
Object(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
/// Each reference contains:
|
||||
/// - An id that uniquely identifies the referenced object
|
||||
/// - A content address hash that represents the content
|
||||
/// - A path that provides a human-readable location for the reference
|
||||
/// - A name that provides a human-readable name for the reference
|
||||
/// - A list of dependent references that this reference depends on
|
||||
///
|
||||
/// References form a directed acyclic graph where each node can have multiple dependents.
|
||||
@ -102,7 +102,7 @@ impl Reference {
|
||||
pub struct Graph {
|
||||
pub root: Arc<Reference>,
|
||||
pub refs: Arc<HashMap<String, Arc<Reference>>>,
|
||||
pub objects: Arc<HashMap<String, String>>,
|
||||
pub objects: Arc<HashMap<String, Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Graph {
|
||||
@ -112,23 +112,23 @@ impl Graph {
|
||||
}
|
||||
|
||||
/// Gets an object by its content address
|
||||
pub fn get_object(&self, content_address: &str) -> Option<&String> {
|
||||
pub fn get_object(&self, content_address: &str) -> Option<&Vec<u8>> {
|
||||
self.objects.get(content_address)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_object() -> (String, String) {
|
||||
pub fn random_object() -> (String, Vec<u8>) {
|
||||
let mut rng = rand::rng();
|
||||
let random_size = rng.random_range(50..=4096);
|
||||
let random_string: String = (0..random_size)
|
||||
.map(|_| rng.sample(rand::distr::Alphanumeric) as char)
|
||||
let random_bytes: Vec<u8> = (0..random_size)
|
||||
.map(|_| rng.random::<u8>())
|
||||
.collect();
|
||||
|
||||
let mut hasher = Blake2b512::new();
|
||||
hasher.update(&random_string);
|
||||
hasher.update(&random_bytes);
|
||||
let hash = format!("{:x}", hasher.finalize());
|
||||
|
||||
(hash, random_string)
|
||||
(hash, random_bytes)
|
||||
}
|
||||
|
||||
impl Graph {
|
||||
@ -137,7 +137,7 @@ impl Graph {
|
||||
///
|
||||
/// The reference ID is calculated from the content address, name, and any dependents,
|
||||
/// ensuring that it's truly content-addressable.
|
||||
pub fn update_reference(&mut self, name: &String, new_content: String) -> Result<(), String> {
|
||||
pub fn update_reference(&mut self, name: &String, new_content: Vec<u8>) -> Result<(), String> {
|
||||
// Create a mutable copy of our maps
|
||||
let mut refs = HashMap::new();
|
||||
for (k, v) in self.refs.as_ref() {
|
||||
@ -296,3 +296,6 @@ impl Graph {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
202
offline-web-model/src/test.rs
Normal file
202
offline-web-model/src/test.rs
Normal file
@ -0,0 +1,202 @@
|
||||
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<Reference> {
|
||||
// Pick a random leaf node to update
|
||||
let refs: Vec<Arc<Reference>> =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_reference(&candidate.name, random_object().1).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<Reference>, reachable: &mut HashSet<String>) {
|
||||
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(
|
||||
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(
|
||||
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(
|
||||
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();
|
||||
let 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_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_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");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user