From a1f2cd4430b34195a30c662370f4fab97c717875 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 7 Apr 2026 13:44:06 +0300 Subject: [PATCH] test(platform-value): improve coverage for pointer, bytes_36, path operations, and diff Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/inner_value_at_path.rs | 355 +++++++++++++++++ packages/rs-platform-value/src/patch/diff.rs | 333 ++++++++++++++++ packages/rs-platform-value/src/pointer.rs | 238 +++++++++++ .../rs-platform-value/src/types/bytes_36.rs | 370 ++++++++++++++++++ 4 files changed, 1296 insertions(+) diff --git a/packages/rs-platform-value/src/inner_value_at_path.rs b/packages/rs-platform-value/src/inner_value_at_path.rs index bb056d3232b..2c5508d0b24 100644 --- a/packages/rs-platform-value/src/inner_value_at_path.rs +++ b/packages/rs-platform-value/src/inner_value_at_path.rs @@ -486,5 +486,360 @@ mod tests { assert_eq!(field_name, "array"); assert_eq!(index, None); } + + #[test] + fn test_underscore_field_name() { + let result = is_array_path("my_field[5]").unwrap(); + assert!(result.is_some()); + let (field_name, index) = result.unwrap(); + assert_eq!(field_name, "my_field"); + assert_eq!(index, Some(5)); + } + + #[test] + fn test_zero_index() { + let result = is_array_path("field[0]").unwrap(); + assert!(result.is_some()); + let (field_name, index) = result.unwrap(); + assert_eq!(field_name, "field"); + assert_eq!(index, Some(0)); + } + + #[test] + fn test_only_brackets_no_field() { + // "[]" has empty field name before bracket + let result = is_array_path("[]").unwrap(); + assert!(result.is_none()); + } + + #[test] + fn test_dot_in_field_name() { + // dot is not alphanumeric or underscore, so returns None + let result = is_array_path("a.b[0]").unwrap(); + assert!(result.is_none()); + } + + #[test] + fn test_nested_brackets_returns_none() { + // rfind('[') finds the last '[' at position 4, field name becomes "a[1]" + // which contains non-alphanumeric/underscore chars, so returns None + let result = is_array_path("a[1][2]").unwrap(); + assert!(result.is_none()); + } + + #[test] + fn test_large_index() { + let result = is_array_path("items[999999]").unwrap(); + assert!(result.is_some()); + let (field_name, index) = result.unwrap(); + assert_eq!(field_name, "items"); + assert_eq!(index, Some(999999)); + } + } + + // --------------------------------------------------------------- + // remove_value_at_path + // --------------------------------------------------------------- + + mod remove_value_at_path { + use super::*; + + #[test] + fn remove_top_level_key() { + let mut doc = platform_value!({ + "a": 1, + "b": 2 + }); + let removed = doc.remove_value_at_path("a").unwrap(); + assert_eq!(removed, platform_value!(1)); + assert!(doc.get_optional_value_at_path("a").unwrap().is_none()); + } + + #[test] + fn remove_nested_key() { + let mut doc = platform_value!({ + "root": { + "child": { + "leaf": "value" + } + } + }); + let removed = doc.remove_value_at_path("root.child.leaf").unwrap(); + assert_eq!(removed, platform_value!("value")); + } + + #[test] + fn remove_missing_intermediate_key_errors() { + let mut doc = platform_value!({ + "a": 1 + }); + let result = doc.remove_value_at_path("nonexistent.child"); + assert!(result.is_err()); + } + + #[test] + fn remove_missing_leaf_key_errors() { + let mut doc = platform_value!({ + "a": { "b": 1 } + }); + let result = doc.remove_value_at_path("a.nonexistent"); + assert!(result.is_err()); + } + } + + // --------------------------------------------------------------- + // remove_optional_value_at_path + // --------------------------------------------------------------- + + mod remove_optional_value_at_path { + use super::*; + + #[test] + fn remove_existing_key_returns_some() { + let mut doc = platform_value!({ + "x": { "y": 42 } + }); + let result = doc.remove_optional_value_at_path("x.y").unwrap(); + assert_eq!(result, Some(platform_value!(42))); + } + + #[test] + fn remove_missing_intermediate_returns_none() { + let mut doc = platform_value!({ + "a": 1 + }); + let result = doc + .remove_optional_value_at_path("nonexistent.child") + .unwrap(); + assert_eq!(result, None); + } + + #[test] + fn remove_missing_leaf_returns_none() { + let mut doc = platform_value!({ + "a": { "b": 1 } + }); + let result = doc.remove_optional_value_at_path("a.nonexistent").unwrap(); + assert_eq!(result, None); + } + } + + // --------------------------------------------------------------- + // set_value_at_full_path + // --------------------------------------------------------------- + + mod set_value_at_full_path { + use super::*; + + #[test] + fn set_creates_nested_structures() { + let mut doc = platform_value!({}); + doc.set_value_at_full_path("a.b.c", platform_value!("deep")) + .unwrap(); + assert_eq!( + doc.get_value_at_path("a.b.c").unwrap(), + &platform_value!("deep") + ); + } + + #[test] + fn set_overwrites_existing_key() { + let mut doc = platform_value!({ + "a": { "b": 1 } + }); + doc.set_value_at_full_path("a.b", platform_value!(99)) + .unwrap(); + assert_eq!(doc.get_value_at_path("a.b").unwrap(), &platform_value!(99)); + } + + #[test] + fn set_with_array_index() { + let mut doc = platform_value!({ + "root": {} + }); + doc.set_value_at_full_path("root.items[0].name", platform_value!("first")) + .unwrap(); + assert_eq!( + doc.get_value_at_path("root.items[0].name").unwrap(), + &platform_value!("first") + ); + } + + #[test] + fn set_single_key() { + let mut doc = platform_value!({}); + doc.set_value_at_full_path("key", platform_value!(1)) + .unwrap(); + assert_eq!(doc.get_value_at_path("key").unwrap(), &platform_value!(1)); + } + } + + // --------------------------------------------------------------- + // get_value_at_path + // --------------------------------------------------------------- + + mod get_value_at_path { + use super::*; + + #[test] + fn get_nested_value() { + let doc = platform_value!({ + "a": { "b": { "c": 42 } } + }); + assert_eq!( + doc.get_value_at_path("a.b.c").unwrap(), + &platform_value!(42) + ); + } + + #[test] + fn get_missing_key_errors() { + let doc = platform_value!({ + "a": 1 + }); + assert!(doc.get_value_at_path("nonexistent").is_err()); + } + + #[test] + fn get_array_element() { + let doc = platform_value!({ + "arr": [10, 20, 30] + }); + assert_eq!( + doc.get_value_at_path("arr[1]").unwrap(), + &platform_value!(20) + ); + } + + #[test] + fn get_array_out_of_bounds_errors() { + let doc = platform_value!({ + "arr": [10] + }); + assert!(doc.get_value_at_path("arr[5]").is_err()); + } + } + + // --------------------------------------------------------------- + // get_optional_value_at_path + // --------------------------------------------------------------- + + mod get_optional_value_at_path { + use super::*; + + #[test] + fn get_existing_returns_some() { + let doc = platform_value!({ + "a": { "b": 1 } + }); + let result = doc.get_optional_value_at_path("a.b").unwrap(); + assert_eq!(result, Some(&platform_value!(1))); + } + + #[test] + fn get_missing_returns_none() { + let doc = platform_value!({ + "a": 1 + }); + let result = doc.get_optional_value_at_path("missing").unwrap(); + assert_eq!(result, None); + } + + #[test] + fn get_missing_nested_returns_none() { + let doc = platform_value!({ + "a": { "b": 1 } + }); + let result = doc.get_optional_value_at_path("a.nonexistent").unwrap(); + assert_eq!(result, None); + } + + #[test] + fn get_array_element_returns_some() { + let doc = platform_value!({ + "arr": [10, 20] + }); + let result = doc.get_optional_value_at_path("arr[0]").unwrap(); + assert_eq!(result, Some(&platform_value!(10))); + } + + #[test] + fn get_array_out_of_bounds_returns_none() { + let doc = platform_value!({ + "arr": [10] + }); + let result = doc.get_optional_value_at_path("arr[99]").unwrap(); + assert_eq!(result, None); + } + } + + // --------------------------------------------------------------- + // set_value_at_path (set key within existing path) + // --------------------------------------------------------------- + + mod set_value_at_path { + use super::*; + + #[test] + fn set_key_at_existing_path() { + let mut doc = platform_value!({ + "root": { + "inner": {} + } + }); + doc.set_value_at_path("root.inner", "new_key", platform_value!("new_value")) + .unwrap(); + assert_eq!( + doc.get_value_at_path("root.inner.new_key").unwrap(), + &platform_value!("new_value") + ); + } + + #[test] + fn set_key_at_nonexistent_path_errors() { + let mut doc = platform_value!({ + "a": 1 + }); + let result = doc.set_value_at_path("nonexistent", "key", platform_value!(1)); + assert!(result.is_err()); + } + } + + // --------------------------------------------------------------- + // remove_values_matching_path with arrays + // --------------------------------------------------------------- + + mod remove_values_matching_path { + use super::*; + + #[test] + fn remove_top_level_key() { + let mut doc = platform_value!({ + "a": 1, + "b": 2 + }); + let removed = doc.remove_values_matching_path("a").unwrap(); + assert_eq!(removed, vec![platform_value!(1)]); + } + + #[test] + fn remove_nested_key() { + let mut doc = platform_value!({ + "root": { + "child": 42 + } + }); + let removed = doc.remove_values_matching_path("root.child").unwrap(); + assert_eq!(removed, vec![platform_value!(42)]); + } + + #[test] + fn remove_from_null_value_returns_empty() { + let mut doc = platform_value!({ + "a": null + }); + let removed = doc.remove_values_matching_path("a.child").unwrap(); + assert!(removed.is_empty()); + } } } diff --git a/packages/rs-platform-value/src/patch/diff.rs b/packages/rs-platform-value/src/patch/diff.rs index 36c5d701db2..346ab7e2aa5 100644 --- a/packages/rs-platform-value/src/patch/diff.rs +++ b/packages/rs-platform-value/src/patch/diff.rs @@ -323,4 +323,337 @@ mod tests { crate::patch(&mut left, &patch).unwrap(); assert_eq!(left, right); } + + // --------------------------------------------------------------- + // append_path: tilde and slash escaping + // --------------------------------------------------------------- + + #[test] + fn append_path_escapes_tilde() { + let mut path = String::from("/"); + super::append_path(&mut path, "a~b"); + assert_eq!(path, "/a~0b"); + } + + #[test] + fn append_path_escapes_slash() { + let mut path = String::from("/"); + super::append_path(&mut path, "a/b"); + assert_eq!(path, "/a~1b"); + } + + #[test] + fn append_path_escapes_both() { + let mut path = String::from("/"); + super::append_path(&mut path, "~/"); + assert_eq!(path, "/~0~1"); + } + + #[test] + fn append_path_no_escaping_needed() { + let mut path = String::from("/"); + super::append_path(&mut path, "plain"); + assert_eq!(path, "/plain"); + } + + #[test] + fn append_path_empty_key() { + let mut path = String::from("/"); + super::append_path(&mut path, ""); + assert_eq!(path, "/"); + } + + // --------------------------------------------------------------- + // diff: identical values returns empty patch + // --------------------------------------------------------------- + + #[test] + fn diff_identical_scalars_returns_empty() { + let v = platform_value!("hello"); + let p = super::diff(&v, &v); + assert!(p.0.is_empty()); + } + + #[test] + fn diff_identical_maps_returns_empty() { + let v = platform_value!({"a": 1, "b": 2}); + let p = super::diff(&v, &v); + assert!(p.0.is_empty()); + } + + #[test] + fn diff_identical_arrays_returns_empty() { + let v = platform_value!([1, 2, 3]); + let p = super::diff(&v, &v); + assert!(p.0.is_empty()); + } + + #[test] + fn diff_identical_nested_returns_empty() { + let v = platform_value!({"a": {"b": [1, 2]}}); + let p = super::diff(&v, &v); + assert!(p.0.is_empty()); + } + + // --------------------------------------------------------------- + // diff: maps with added/removed/modified keys + // --------------------------------------------------------------- + + #[test] + fn diff_map_added_key() { + let left = platform_value!({"a": 1}); + let right = platform_value!({"a": 1, "b": 2}); + let p = super::diff(&left, &right); + assert_eq!( + p, + from_value(platform_value!([ + { "op": "add", "path": "/b", "value": 2 }, + ])) + .unwrap() + ); + } + + #[test] + fn diff_map_removed_key() { + let left = platform_value!({"a": 1, "b": 2}); + let right = platform_value!({"a": 1}); + let p = super::diff(&left, &right); + assert_eq!( + p, + from_value(platform_value!([ + { "op": "remove", "path": "/b" }, + ])) + .unwrap() + ); + } + + #[test] + fn diff_map_modified_key() { + let left = platform_value!({"a": 1}); + let right = platform_value!({"a": 2}); + let p = super::diff(&left, &right); + assert_eq!( + p, + from_value(platform_value!([ + { "op": "replace", "path": "/a", "value": 2 }, + ])) + .unwrap() + ); + } + + #[test] + fn diff_map_added_removed_modified() { + let left = platform_value!({"a": 1, "b": 2}); + let right = platform_value!({"a": 10, "c": 3}); + let mut p_left = left.clone(); + let patch = super::diff(&left, &right); + crate::patch(&mut p_left, &patch).unwrap(); + assert_eq!(p_left, right); + } + + // --------------------------------------------------------------- + // diff: arrays with insertions/deletions + // --------------------------------------------------------------- + + #[test] + fn diff_array_append() { + let left = platform_value!([1, 2]); + let right = platform_value!([1, 2, 3]); + let patch = super::diff(&left, &right); + let mut doc = left.clone(); + crate::patch(&mut doc, &patch).unwrap(); + assert_eq!(doc, right); + } + + #[test] + fn diff_array_removal_from_middle() { + let left = platform_value!([1, 2, 3]); + let right = platform_value!([1, 3]); + let patch = super::diff(&left, &right); + let mut doc = left.clone(); + crate::patch(&mut doc, &patch).unwrap(); + assert_eq!(doc, right); + } + + #[test] + fn diff_array_complete_replacement() { + let left = platform_value!([1, 2, 3]); + let right = platform_value!([4, 5]); + let patch = super::diff(&left, &right); + let mut doc = left.clone(); + crate::patch(&mut doc, &patch).unwrap(); + assert_eq!(doc, right); + } + + // --------------------------------------------------------------- + // diff: nested maps + // --------------------------------------------------------------- + + #[test] + fn diff_nested_map_modification() { + let left = platform_value!({ + "outer": { + "inner": 1, + "keep": "same" + } + }); + let right = platform_value!({ + "outer": { + "inner": 99, + "keep": "same" + } + }); + let p = super::diff(&left, &right); + assert_eq!( + p, + from_value(platform_value!([ + { "op": "replace", "path": "/outer/inner", "value": 99 }, + ])) + .unwrap() + ); + } + + #[test] + fn diff_nested_map_add_and_remove() { + let left = platform_value!({ + "a": { + "b": 1, + "c": 2 + } + }); + let right = platform_value!({ + "a": { + "c": 2, + "d": 3 + } + }); + let patch = super::diff(&left, &right); + let mut doc = left.clone(); + crate::patch(&mut doc, &patch).unwrap(); + assert_eq!(doc, right); + } + + #[test] + fn diff_deeply_nested() { + let left = platform_value!({ + "l1": { + "l2": { + "l3": "old" + } + } + }); + let right = platform_value!({ + "l1": { + "l2": { + "l3": "new" + } + } + }); + let p = super::diff(&left, &right); + assert_eq!( + p, + from_value(platform_value!([ + { "op": "replace", "path": "/l1/l2/l3", "value": "new" }, + ])) + .unwrap() + ); + } + + // --------------------------------------------------------------- + // diff: tilde-escaped keys round-trip through patch + // --------------------------------------------------------------- + + #[test] + fn diff_tilde_key_round_trip() { + let left = platform_value!({ + "a~b": 1 + }); + let right = platform_value!({ + "a~b": 2 + }); + let mut doc = left.clone(); + let patch = super::diff(&left, &right); + crate::patch(&mut doc, &patch).unwrap(); + assert_eq!(doc, right); + } + + #[test] + fn diff_slash_key_round_trip() { + let left = platform_value!({ + "x/y": 10 + }); + let right = platform_value!({ + "x/y": 20 + }); + let mut doc = left.clone(); + let patch = super::diff(&left, &right); + crate::patch(&mut doc, &patch).unwrap(); + assert_eq!(doc, right); + } + + // --------------------------------------------------------------- + // diff: null values + // --------------------------------------------------------------- + + #[test] + fn diff_both_null_returns_empty() { + let p = super::diff(&Value::Null, &Value::Null); + assert!(p.0.is_empty()); + } + + #[test] + fn diff_null_to_value() { + let left = Value::Null; + let right = platform_value!(42); + let p = super::diff(&left, &right); + assert_eq!( + p, + from_value(platform_value!([ + { "op": "replace", "path": "/", "value": 42 }, + ])) + .unwrap() + ); + } + + #[test] + fn diff_value_to_null() { + let left = platform_value!(42); + let right = Value::Null; + let p = super::diff(&left, &right); + assert_eq!( + p, + from_value(platform_value!([ + { "op": "replace", "path": "/", "value": null }, + ])) + .unwrap() + ); + } + + // --------------------------------------------------------------- + // diff: mixed types + // --------------------------------------------------------------- + + #[test] + fn diff_scalar_to_map() { + let left = platform_value!(42); + let right = platform_value!({"a": 1}); + let p = super::diff(&left, &right); + assert_eq!( + p, + from_value(platform_value!([ + { "op": "replace", "path": "/", "value": {"a": 1} }, + ])) + .unwrap() + ); + } + + #[test] + fn diff_array_with_nested_maps() { + let left = platform_value!([{"name": "alice"}, {"name": "bob"}]); + let right = platform_value!([{"name": "alice"}, {"name": "charlie"}]); + let patch = super::diff(&left, &right); + let mut doc = left.clone(); + crate::patch(&mut doc, &patch).unwrap(); + assert_eq!(doc, right); + } } diff --git a/packages/rs-platform-value/src/pointer.rs b/packages/rs-platform-value/src/pointer.rs index fa42add829e..f9c481c015c 100644 --- a/packages/rs-platform-value/src/pointer.rs +++ b/packages/rs-platform-value/src/pointer.rs @@ -119,3 +119,241 @@ impl Value { mem::replace(self, Value::Null) } } + +#[cfg(test)] +mod tests { + use crate::{platform_value, Value}; + + // --------------------------------------------------------------- + // parse_index (module-private helper) + // --------------------------------------------------------------- + + #[test] + fn parse_index_valid_number() { + assert_eq!(super::parse_index("0"), Some(0)); + assert_eq!(super::parse_index("1"), Some(1)); + assert_eq!(super::parse_index("42"), Some(42)); + } + + #[test] + fn parse_index_leading_plus_returns_none() { + assert_eq!(super::parse_index("+1"), None); + } + + #[test] + fn parse_index_leading_zero_returns_none() { + assert_eq!(super::parse_index("01"), None); + assert_eq!(super::parse_index("007"), None); + } + + #[test] + fn parse_index_single_zero_is_valid() { + assert_eq!(super::parse_index("0"), Some(0)); + } + + #[test] + fn parse_index_non_numeric_returns_none() { + assert_eq!(super::parse_index("abc"), None); + assert_eq!(super::parse_index(""), None); + } + + // --------------------------------------------------------------- + // pointer() — read-only access + // --------------------------------------------------------------- + + #[test] + fn pointer_empty_string_returns_self() { + let data = platform_value!({"a": 1}); + assert_eq!(data.pointer(""), Some(&data)); + } + + #[test] + fn pointer_no_leading_slash_returns_none() { + let data = platform_value!({"a": 1}); + assert_eq!(data.pointer("a"), None); + assert_eq!(data.pointer("a/b"), None); + } + + #[test] + fn pointer_simple_key_lookup() { + let data = platform_value!({"a": 1, "b": 2}); + assert_eq!(data.pointer("/a"), Some(&platform_value!(1))); + assert_eq!(data.pointer("/b"), Some(&platform_value!(2))); + } + + #[test] + fn pointer_nested_key_lookup() { + let data = platform_value!({"x": {"y": {"z": 42}}}); + assert_eq!(data.pointer("/x/y/z"), Some(&platform_value!(42))); + } + + #[test] + fn pointer_missing_path_returns_none() { + let data = platform_value!({"a": 1}); + assert_eq!(data.pointer("/b"), None); + assert_eq!(data.pointer("/a/b/c"), None); + } + + #[test] + fn pointer_array_index() { + let data = platform_value!({"arr": [10, 20, 30]}); + assert_eq!(data.pointer("/arr/0"), Some(&platform_value!(10))); + assert_eq!(data.pointer("/arr/1"), Some(&platform_value!(20))); + assert_eq!(data.pointer("/arr/2"), Some(&platform_value!(30))); + } + + #[test] + fn pointer_array_out_of_bounds_returns_none() { + let data = platform_value!({"arr": [10]}); + assert_eq!(data.pointer("/arr/5"), None); + } + + #[test] + fn pointer_tilde_escape_tilde_zero_becomes_tilde() { + // Key contains a literal ~ character, encoded as ~0 + let data = platform_value!({"a~b": 1}); + assert_eq!(data.pointer("/a~0b"), Some(&platform_value!(1))); + } + + #[test] + fn pointer_tilde_escape_tilde_one_becomes_slash() { + // Key contains a literal / character, encoded as ~1 + let data = platform_value!({"a/b": 1}); + assert_eq!(data.pointer("/a~1b"), Some(&platform_value!(1))); + } + + #[test] + fn pointer_combined_tilde_escapes() { + let data = platform_value!({"~/key": 99}); + // ~ is ~0, / is ~1, so "~/key" is encoded as "~0~1key" + assert_eq!(data.pointer("/~0~1key"), Some(&platform_value!(99))); + } + + #[test] + fn pointer_scalar_value_returns_none_for_child() { + let data = platform_value!(42); + assert_eq!(data.pointer("/anything"), None); + } + + #[test] + fn pointer_nested_array_in_map() { + let data = platform_value!({ + "x": { + "y": ["z", "zz"] + } + }); + assert_eq!(data.pointer("/x/y/0"), Some(&platform_value!("z"))); + assert_eq!(data.pointer("/x/y/1"), Some(&platform_value!("zz"))); + } + + #[test] + fn pointer_root_is_array() { + let data = platform_value!(["a", "b", "c"]); + assert_eq!(data.pointer("/0"), Some(&platform_value!("a"))); + assert_eq!(data.pointer("/2"), Some(&platform_value!("c"))); + } + + #[test] + fn pointer_leading_zero_index_rejected() { + let data = platform_value!({"arr": [10, 20, 30]}); + // "01" has a leading zero (and len > 1), so parse_index returns None + assert_eq!(data.pointer("/arr/01"), None); + } + + // --------------------------------------------------------------- + // pointer_mut() — mutable access + // --------------------------------------------------------------- + + #[test] + fn pointer_mut_empty_string_returns_self() { + let mut data = platform_value!({"a": 1}); + let reference = data.pointer_mut(""); + assert!(reference.is_some()); + } + + #[test] + fn pointer_mut_no_leading_slash_returns_none() { + let mut data = platform_value!({"a": 1}); + assert!(data.pointer_mut("a").is_none()); + } + + #[test] + fn pointer_mut_modify_nested_value() { + let mut data = platform_value!({"x": {"y": 1}}); + *data.pointer_mut("/x/y").unwrap() = platform_value!(99); + assert_eq!(data.pointer("/x/y"), Some(&platform_value!(99))); + } + + #[test] + fn pointer_mut_modify_array_element() { + let mut data = platform_value!({"arr": [10, 20, 30]}); + *data.pointer_mut("/arr/1").unwrap() = platform_value!(999); + assert_eq!(data.pointer("/arr/1"), Some(&platform_value!(999))); + } + + #[test] + fn pointer_mut_missing_path_returns_none() { + let mut data = platform_value!({"a": 1}); + assert!(data.pointer_mut("/nonexistent").is_none()); + assert!(data.pointer_mut("/a/b/c").is_none()); + } + + #[test] + fn pointer_mut_tilde_escapes() { + let mut data = platform_value!({"a/b": 1, "c~d": 2}); + *data.pointer_mut("/a~1b").unwrap() = platform_value!(10); + *data.pointer_mut("/c~0d").unwrap() = platform_value!(20); + assert_eq!(data.pointer("/a~1b"), Some(&platform_value!(10))); + assert_eq!(data.pointer("/c~0d"), Some(&platform_value!(20))); + } + + #[test] + fn pointer_mut_scalar_returns_none() { + let mut data = platform_value!("hello"); + assert!(data.pointer_mut("/anything").is_none()); + } + + // --------------------------------------------------------------- + // take() — replace with Null and return old value + // --------------------------------------------------------------- + + #[test] + fn take_replaces_with_null() { + let mut data = platform_value!({"x": "y"}); + let taken = data.pointer_mut("/x").unwrap().take(); + assert_eq!(taken, platform_value!("y")); + assert_eq!(data.pointer("/x"), Some(&Value::Null)); + } + + #[test] + fn take_on_integer() { + let mut val: Value = platform_value!(42); + let taken = val.take(); + assert_eq!(taken, platform_value!(42)); + assert_eq!(val, Value::Null); + } + + #[test] + fn take_on_null_returns_null() { + let mut val = Value::Null; + let taken = val.take(); + assert_eq!(taken, Value::Null); + assert_eq!(val, Value::Null); + } + + #[test] + fn take_on_array() { + let mut val = platform_value!([1, 2, 3]); + let taken = val.take(); + assert_eq!(taken, platform_value!([1, 2, 3])); + assert_eq!(val, Value::Null); + } + + #[test] + fn take_nested_via_pointer_mut() { + let mut data = platform_value!({"a": {"b": "deep"}}); + let taken = data.pointer_mut("/a/b").map(Value::take).unwrap(); + assert_eq!(taken, platform_value!("deep")); + assert_eq!(data.pointer("/a/b"), Some(&Value::Null)); + } +} diff --git a/packages/rs-platform-value/src/types/bytes_36.rs b/packages/rs-platform-value/src/types/bytes_36.rs index c0faea925cb..b65e4056d5d 100644 --- a/packages/rs-platform-value/src/types/bytes_36.rs +++ b/packages/rs-platform-value/src/types/bytes_36.rs @@ -199,3 +199,373 @@ impl From<&Bytes36> for String { val.to_string(Encoding::Base64) } } + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + fn compute_hash(value: &T) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() + } + + // --------------------------------------------------------------- + // From<[u8; 36]> and Into<[u8; 36]> round-trip + // --------------------------------------------------------------- + + #[test] + fn from_array_round_trip() { + let arr = [0xABu8; 36]; + let b = Bytes36::from(arr); + assert_eq!(b.0, arr); + let back: [u8; 36] = b.to_buffer(); + assert_eq!(back, arr); + } + + #[test] + fn from_ref_array() { + let arr = [7u8; 36]; + let b = Bytes36::from(&arr); + assert_eq!(b.0, arr); + } + + // --------------------------------------------------------------- + // from_vec — correct and wrong sizes + // --------------------------------------------------------------- + + #[test] + fn from_vec_correct_size() { + let v = vec![1u8; 36]; + let b = Bytes36::from_vec(v).unwrap(); + assert_eq!(b.0, [1u8; 36]); + } + + #[test] + fn from_vec_too_short() { + let v = vec![1u8; 35]; + let err = Bytes36::from_vec(v).unwrap_err(); + match err { + Error::ByteLengthNot36BytesError(_) => {} + other => panic!("expected ByteLengthNot36BytesError, got {:?}", other), + } + } + + #[test] + fn from_vec_too_long() { + let v = vec![1u8; 37]; + let err = Bytes36::from_vec(v).unwrap_err(); + match err { + Error::ByteLengthNot36BytesError(_) => {} + other => panic!("expected ByteLengthNot36BytesError, got {:?}", other), + } + } + + #[test] + fn from_vec_empty() { + let v = vec![]; + assert!(Bytes36::from_vec(v).is_err()); + } + + // --------------------------------------------------------------- + // TryFrom for owned Value + // --------------------------------------------------------------- + + #[test] + fn try_from_value_bytes36() { + let arr = [9u8; 36]; + let val = Value::Bytes36(arr); + let b = Bytes36::try_from(val).unwrap(); + assert_eq!(b.0, arr); + } + + #[test] + fn try_from_value_bytes_correct_len() { + let v = vec![3u8; 36]; + let val = Value::Bytes(v); + let b = Bytes36::try_from(val).unwrap(); + assert_eq!(b.0, [3u8; 36]); + } + + #[test] + fn try_from_value_bytes_wrong_len() { + let val = Value::Bytes(vec![1, 2, 3]); + assert!(Bytes36::try_from(val).is_err()); + } + + #[test] + fn try_from_value_unsupported_variant() { + let val = Value::Bool(true); + assert!(Bytes36::try_from(val).is_err()); + } + + #[test] + fn try_from_value_null_errors() { + let val = Value::Null; + assert!(Bytes36::try_from(val).is_err()); + } + + // --------------------------------------------------------------- + // TryFrom<&Value> for borrowed Value + // --------------------------------------------------------------- + + #[test] + fn try_from_ref_value_bytes36() { + let arr = [10u8; 36]; + let val = Value::Bytes36(arr); + let b = Bytes36::try_from(&val).unwrap(); + assert_eq!(b.0, arr); + } + + #[test] + fn try_from_ref_value_bytes_correct_len() { + let val = Value::Bytes(vec![4u8; 36]); + let b = Bytes36::try_from(&val).unwrap(); + assert_eq!(b.0, [4u8; 36]); + } + + #[test] + fn try_from_ref_value_bytes_wrong_len() { + let val = Value::Bytes(vec![1, 2]); + assert!(Bytes36::try_from(&val).is_err()); + } + + #[test] + fn try_from_ref_value_unsupported() { + let val = Value::Float(3.14); + assert!(Bytes36::try_from(&val).is_err()); + } + + // --------------------------------------------------------------- + // as_slice(), to_vec() + // --------------------------------------------------------------- + + #[test] + fn as_slice_returns_inner_bytes() { + let arr = [0xFFu8; 36]; + let b = Bytes36::new(arr); + assert_eq!(b.as_slice(), &arr[..]); + assert_eq!(b.as_slice().len(), 36); + } + + #[test] + fn to_vec_returns_copy() { + let arr = [5u8; 36]; + let b = Bytes36::new(arr); + let v = b.to_vec(); + assert_eq!(v.len(), 36); + assert_eq!(v, arr.to_vec()); + } + + // --------------------------------------------------------------- + // Hash impl: equal values hash equally, different values differ + // --------------------------------------------------------------- + + #[test] + fn hash_equal_values() { + let a = Bytes36::new([1u8; 36]); + let b = Bytes36::new([1u8; 36]); + assert_eq!(compute_hash(&a), compute_hash(&b)); + } + + #[test] + fn hash_different_values() { + let a = Bytes36::new([1u8; 36]); + let b = Bytes36::new([2u8; 36]); + // Highly unlikely to collide + assert_ne!(compute_hash(&a), compute_hash(&b)); + } + + // --------------------------------------------------------------- + // PartialOrd / Ord: ordering matches byte ordering + // --------------------------------------------------------------- + + #[test] + fn ordering_matches_byte_ordering() { + let mut low = [0u8; 36]; + low[0] = 1; + let mut high = [0u8; 36]; + high[0] = 2; + let a = Bytes36::new(low); + let b = Bytes36::new(high); + assert!(a < b); + assert!(b > a); + } + + #[test] + fn ordering_equal() { + let a = Bytes36::new([5u8; 36]); + let b = Bytes36::new([5u8; 36]); + assert_eq!(a.cmp(&b), std::cmp::Ordering::Equal); + } + + #[test] + fn ordering_last_byte_differs() { + let mut low = [0u8; 36]; + low[35] = 1; + let mut high = [0u8; 36]; + high[35] = 2; + assert!(Bytes36::new(low) < Bytes36::new(high)); + } + + // --------------------------------------------------------------- + // Default is all zeros + // --------------------------------------------------------------- + + #[test] + fn default_is_all_zeros() { + let b = Bytes36::default(); + assert_eq!(b.0, [0u8; 36]); + } + + // --------------------------------------------------------------- + // Value round-trips + // --------------------------------------------------------------- + + #[test] + fn into_value_and_back() { + let arr = [42u8; 36]; + let b = Bytes36::new(arr); + let val: Value = b.into(); + assert_eq!(val, Value::Bytes36(arr)); + let back = Bytes36::try_from(val).unwrap(); + assert_eq!(back, b); + } + + #[test] + fn ref_into_value() { + let b = Bytes36::new([7u8; 36]); + let val: Value = (&b).into(); + assert_eq!(val, Value::Bytes36([7u8; 36])); + } + + // --------------------------------------------------------------- + // String conversions (Base64 round-trip) + // --------------------------------------------------------------- + + #[test] + fn try_from_string_base64_round_trip() { + let arr = [99u8; 36]; + let b = Bytes36::new(arr); + let s: String = b.into(); + let recovered = Bytes36::try_from(s).unwrap(); + assert_eq!(recovered, Bytes36::new(arr)); + } + + #[test] + fn try_from_string_invalid_base64() { + let s = "not-valid-base64!!!".to_string(); + assert!(Bytes36::try_from(s).is_err()); + } + + #[test] + fn ref_to_string() { + let b = Bytes36::new([0u8; 36]); + let s: String = (&b).into(); + // Verify it's valid base64 + let decoded = BASE64_STANDARD.decode(&s).unwrap(); + assert_eq!(decoded.len(), 36); + } + + // --------------------------------------------------------------- + // from_string with different encodings + // --------------------------------------------------------------- + + #[test] + fn from_string_base58_round_trip() { + let arr = [0xABu8; 36]; + let b = Bytes36::new(arr); + let encoded = b.to_string(Encoding::Base58); + let recovered = Bytes36::from_string(&encoded, Encoding::Base58).unwrap(); + assert_eq!(recovered, b); + } + + #[test] + fn from_string_hex_round_trip() { + let arr = [0xCDu8; 36]; + let b = Bytes36::new(arr); + let encoded = b.to_string(Encoding::Hex); + let recovered = Bytes36::from_string(&encoded, Encoding::Hex).unwrap(); + assert_eq!(recovered, b); + } + + #[test] + fn from_string_with_encoding_string_none_defaults_to_base58() { + let arr = [0x01u8; 36]; + let b = Bytes36::new(arr); + let encoded = b.to_string_with_encoding_string(None); + let recovered = Bytes36::from_string_with_encoding_string(&encoded, None).unwrap(); + assert_eq!(recovered, b); + } + + // --------------------------------------------------------------- + // Serde round-trips + // --------------------------------------------------------------- + + #[test] + #[cfg(feature = "json")] + fn serde_json_round_trip() { + let arr = [0x12u8; 36]; + let b = Bytes36::new(arr); + let json = serde_json::to_string(&b).unwrap(); + let recovered: Bytes36 = serde_json::from_str(&json).unwrap(); + assert_eq!(recovered, b); + } + + #[test] + fn serde_bincode_round_trip() { + let arr = [0x34u8; 36]; + let b = Bytes36::new(arr); + let config = bincode::config::standard(); + let encoded = bincode::encode_to_vec(&b, config).unwrap(); + let (decoded, _): (Bytes36, _) = bincode::decode_from_slice(&encoded, config).unwrap(); + assert_eq!(decoded, b); + } + + // --------------------------------------------------------------- + // Clone and Copy semantics + // --------------------------------------------------------------- + + #[test] + fn clone_is_equal() { + let b = Bytes36::new([77u8; 36]); + let c = b.clone(); + assert_eq!(b, c); + } + + #[test] + fn copy_semantics() { + let b = Bytes36::new([88u8; 36]); + let c = b; // Copy + assert_eq!(b, c); // b is still valid because Bytes36 is Copy + } + + // --------------------------------------------------------------- + // Equality + // --------------------------------------------------------------- + + #[test] + fn equality_same_bytes() { + let a = Bytes36::new([1u8; 36]); + let b = Bytes36::new([1u8; 36]); + assert_eq!(a, b); + } + + #[test] + fn inequality_different_bytes() { + let a = Bytes36::new([1u8; 36]); + let b = Bytes36::new([2u8; 36]); + assert_ne!(a, b); + } + + #[test] + fn inequality_single_byte_diff() { + let mut arr = [0u8; 36]; + let a = Bytes36::new(arr); + arr[18] = 1; + let b = Bytes36::new(arr); + assert_ne!(a, b); + } +}