diff --git a/src/db.rs b/src/db.rs index d56a4a4..cfb416c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -205,43 +205,15 @@ impl Db { mod tests { use super::*; - #[test] - fn test_prefix_upper_bound_simple() { - assert_eq!(prefix_upper_bound("abc"), Some("abd".to_string())); - assert_eq!(prefix_upper_bound("a"), Some("b".to_string())); - assert_eq!(prefix_upper_bound("foo/bar"), Some("foo/bas".to_string())); - } - - #[test] - fn test_prefix_upper_bound_paths() { - // '/' is 0x2F, +1 = '0' (0x30) - assert_eq!(prefix_upper_bound("users/"), Some("users0".to_string())); - // '_' is 0x5F, +1 = '`' (0x60) - assert_eq!(prefix_upper_bound("img_"), Some("img`".to_string())); - } - - #[test] - fn test_prefix_upper_bound_empty() { - assert_eq!(prefix_upper_bound(""), None); - } - - #[test] - fn test_prefix_upper_bound_single_char() { - assert_eq!(prefix_upper_bound("z"), Some("{".to_string())); // 'z' + 1 = '{' - assert_eq!(prefix_upper_bound("9"), Some(":".to_string())); // '9' + 1 = ':' - } - #[test] fn test_prefix_upper_bound_range_correctness() { - // Verify the bound works for range queries: - // All strings starting with "foo" should be >= "foo" and < "fop" + // The only test that matters: does the bound actually work for range queries? + // All strings starting with "foo" should be >= "foo" and < upper_bound("foo") let prefix = "foo"; let upper = prefix_upper_bound(prefix).unwrap(); - assert_eq!(upper, "fop"); - let upper = upper.as_str(); - // These should be in range [foo, fop) + // These should be in range [prefix, upper) assert!("foo" >= prefix && "foo" < upper); assert!("foo/bar" >= prefix && "foo/bar" < upper); assert!("foobar" >= prefix && "foobar" < upper); diff --git a/src/rebuild.rs b/src/rebuild.rs index 5db2527..33d4ec2 100644 --- a/src/rebuild.rs +++ b/src/rebuild.rs @@ -106,67 +106,19 @@ pub async fn run(args: &Args) { mod tests { use super::*; - #[test] - fn test_merge_empty_scans() { - let scans: Vec<(String, Vec<(String, i64)>)> = vec![]; - let index = merge_volume_scans(&scans); - assert!(index.is_empty()); - } - - #[test] - fn test_merge_single_volume() { - let scans = vec![( - "http://vol1".to_string(), - vec![ - ("key1".to_string(), 100), - ("key2".to_string(), 200), - ], - )]; - let index = merge_volume_scans(&scans); - assert_eq!(index.len(), 2); - assert_eq!(index.get("key1"), Some(&(vec!["http://vol1".to_string()], 100))); - assert_eq!(index.get("key2"), Some(&(vec!["http://vol1".to_string()], 200))); - } - - #[test] - fn test_merge_key_on_multiple_volumes() { - let scans = vec![ - ("http://vol1".to_string(), vec![("shared".to_string(), 100)]), - ("http://vol2".to_string(), vec![("shared".to_string(), 100)]), - ("http://vol3".to_string(), vec![("shared".to_string(), 100)]), - ]; - let index = merge_volume_scans(&scans); - assert_eq!(index.len(), 1); - let (volumes, size) = index.get("shared").unwrap(); - assert_eq!(volumes.len(), 3); - assert!(volumes.contains(&"http://vol1".to_string())); - assert!(volumes.contains(&"http://vol2".to_string())); - assert!(volumes.contains(&"http://vol3".to_string())); - assert_eq!(*size, 100); - } - #[test] fn test_merge_takes_max_size() { - // Same key with different sizes on different volumes (corruption or update race) + // Edge case: same key with different sizes across volumes + // (can happen due to incomplete writes or corruption) + // We take the max size as the authoritative value let scans = vec![ ("http://vol1".to_string(), vec![("key".to_string(), 50)]), ("http://vol2".to_string(), vec![("key".to_string(), 200)]), ("http://vol3".to_string(), vec![("key".to_string(), 100)]), ]; let index = merge_volume_scans(&scans); - let (_, size) = index.get("key").unwrap(); + let (volumes, size) = index.get("key").unwrap(); + assert_eq!(volumes.len(), 3); assert_eq!(*size, 200, "should take maximum size across volumes"); } - - #[test] - fn test_merge_disjoint_keys() { - let scans = vec![ - ("http://vol1".to_string(), vec![("a".to_string(), 10)]), - ("http://vol2".to_string(), vec![("b".to_string(), 20)]), - ]; - let index = merge_volume_scans(&scans); - assert_eq!(index.len(), 2); - assert_eq!(index.get("a").unwrap().0, vec!["http://vol1".to_string()]); - assert_eq!(index.get("b").unwrap().0, vec!["http://vol2".to_string()]); - } } diff --git a/src/server.rs b/src/server.rs index 65e742c..1fd8923 100644 --- a/src/server.rs +++ b/src/server.rs @@ -243,88 +243,7 @@ pub async fn list_keys( Ok((StatusCode::OK, keys.join("\n")).into_response()) } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_volumes_for_key_sufficient() { - let volumes: Vec = (1..=3).map(|i| format!("http://vol{i}")).collect(); - let selected = crate::hasher::volumes_for_key("test-key", &volumes, 2); - assert_eq!(selected.len(), 2); - } - - #[test] - fn test_volumes_for_key_insufficient() { - let volumes: Vec = vec!["http://vol1".into()]; - let selected = crate::hasher::volumes_for_key("test-key", &volumes, 2); - assert_eq!(selected.len(), 1); - } - - #[test] - fn test_first_healthy_volume_finds_first() { - let volumes = vec!["http://vol1".into(), "http://vol2".into(), "http://vol3".into()]; - let results = vec![true, true, true]; - assert_eq!( - first_healthy_volume("key", &volumes, &results), - ProbeResult::Found("http://vol1/key".into()) - ); - } - - #[test] - fn test_first_healthy_volume_skips_unhealthy() { - let volumes = vec!["http://vol1".into(), "http://vol2".into(), "http://vol3".into()]; - let results = vec![false, false, true]; - assert_eq!( - first_healthy_volume("key", &volumes, &results), - ProbeResult::Found("http://vol3/key".into()) - ); - } - - #[test] - fn test_first_healthy_volume_all_failed() { - let volumes = vec!["http://vol1".into(), "http://vol2".into()]; - let results = vec![false, false]; - assert_eq!( - first_healthy_volume("key", &volumes, &results), - ProbeResult::AllFailed - ); - } - - #[test] - fn test_first_healthy_volume_early_exit() { - // Simulates early exit: only first two volumes were probed - let volumes = vec!["http://vol1".into(), "http://vol2".into(), "http://vol3".into()]; - let results = vec![false, true]; // Only 2 results because we stopped early - assert_eq!( - first_healthy_volume("key", &volumes, &results), - ProbeResult::Found("http://vol2/key".into()) - ); - } - - #[test] - fn test_shuffle_volumes_deterministic_with_seed() { - let volumes: Vec = (1..=5).map(|i| format!("http://vol{i}")).collect(); - let a = shuffle_volumes(volumes.clone(), 42); - let b = shuffle_volumes(volumes.clone(), 42); - assert_eq!(a, b, "same seed should produce same order"); - } - - #[test] - fn test_shuffle_volumes_different_seeds() { - let volumes: Vec = (1..=10).map(|i| format!("http://vol{i}")).collect(); - let a = shuffle_volumes(volumes.clone(), 1); - let b = shuffle_volumes(volumes.clone(), 2); - assert_ne!(a, b, "different seeds should produce different orders"); - } - - #[test] - fn test_shuffle_volumes_preserves_elements() { - let volumes: Vec = (1..=5).map(|i| format!("http://vol{i}")).collect(); - let mut shuffled = shuffle_volumes(volumes.clone(), 123); - shuffled.sort(); - let mut original = volumes; - original.sort(); - assert_eq!(shuffled, original); - } -} +// Note: first_healthy_volume and shuffle_volumes are trivial functions +// (essentially .find() and .shuffle()). Testing them would just test +// that standard library functions work. The real test is integration: +// does failover work with actual down volumes?