KeiSeiKit-1.0/_primitives/_rust/kei-diff/tests/round_trip.rs
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

278 lines
7.3 KiB
Rust

//! Integration tests for kei-diff.
//!
//! Core property: `apply(old, diff(old, new)) == new` for every fixture.
//! Plus edge cases on pointer escaping, array edits, apply errors, and
//! the RFC 6902 wire format.
use kei_diff::{apply, diff, ApplyError, Op, Patch};
use serde_json::{json, Value};
fn rt(old: Value, new: Value) {
let patch = diff(&old, &new);
let applied = apply(&old, &patch).expect("apply failed");
assert_eq!(
applied, new,
"round-trip failed\n old = {old}\n new = {new}\n patch = {}",
serde_json::to_string(&patch).unwrap()
);
}
#[test]
fn equal_values_produce_empty_patch() {
let patch = diff(&json!({"a": 1}), &json!({"a": 1}));
assert!(patch.is_empty());
rt(json!({"a": 1}), json!({"a": 1}));
}
#[test]
fn scalar_replace() {
rt(json!(1), json!(2));
rt(json!("a"), json!("b"));
rt(json!(true), json!(false));
}
#[test]
fn type_change_emits_replace() {
let old = json!("hello");
let new = json!(42);
let patch = diff(&old, &new);
assert_eq!(patch.len(), 1);
assert!(matches!(patch.0[0], Op::Replace { .. }));
rt(old, new);
}
#[test]
fn object_add_key() {
let old = json!({"a": 1});
let new = json!({"a": 1, "b": 2});
let patch = diff(&old, &new);
assert_eq!(patch.len(), 1);
assert!(matches!(&patch.0[0], Op::Add { path, .. } if path == "/b"));
rt(old, new);
}
#[test]
fn object_remove_key() {
let old = json!({"a": 1, "b": 2});
let new = json!({"a": 1});
let patch = diff(&old, &new);
assert_eq!(patch.len(), 1);
assert!(matches!(&patch.0[0], Op::Remove { path } if path == "/b"));
rt(old, new);
}
#[test]
fn object_replace_value() {
let old = json!({"a": 1});
let new = json!({"a": 2});
let patch = diff(&old, &new);
assert_eq!(patch.len(), 1);
assert!(matches!(&patch.0[0], Op::Replace { path, .. } if path == "/a"));
rt(old, new);
}
#[test]
fn nested_object_replace() {
let old = json!({"a": {"b": {"c": 1}}});
let new = json!({"a": {"b": {"c": 2}}});
let patch = diff(&old, &new);
assert_eq!(patch.len(), 1);
assert!(matches!(&patch.0[0], Op::Replace { path, .. } if path == "/a/b/c"));
rt(old, new);
}
#[test]
fn array_append() {
let old = json!([1, 2]);
let new = json!([1, 2, 3, 4]);
let patch = diff(&old, &new);
assert_eq!(patch.len(), 2);
assert!(matches!(&patch.0[0], Op::Add { path, .. } if path == "/2"));
assert!(matches!(&patch.0[1], Op::Add { path, .. } if path == "/3"));
rt(old, new);
}
#[test]
fn array_truncate() {
let old = json!([1, 2, 3, 4]);
let new = json!([1, 2]);
let patch = diff(&old, &new);
// Expect removals highest-first (/3 then /2) so indices stay valid.
assert_eq!(patch.len(), 2);
assert!(matches!(&patch.0[0], Op::Remove { path } if path == "/3"));
assert!(matches!(&patch.0[1], Op::Remove { path } if path == "/2"));
rt(old, new);
}
#[test]
fn array_element_replace() {
let old = json!([1, 2, 3]);
let new = json!([1, 99, 3]);
let patch = diff(&old, &new);
assert_eq!(patch.len(), 1);
assert!(matches!(&patch.0[0], Op::Replace { path, .. } if path == "/1"));
rt(old, new);
}
#[test]
fn nested_array_inside_object() {
let old = json!({"xs": [1, 2, 3], "y": "z"});
let new = json!({"xs": [1, 7, 3, 4], "y": "z"});
rt(old, new);
}
#[test]
fn deeply_nested_mixed() {
let old = json!({
"meta": {"ts": 100, "tags": ["a", "b"]},
"items": [{"id": 1}, {"id": 2}],
});
let new = json!({
"meta": {"ts": 200, "tags": ["a", "b", "c"]},
"items": [{"id": 1}, {"id": 3}, {"id": 4}],
"extra": true,
});
rt(old, new);
}
#[test]
fn pointer_escapes_slash_and_tilde() {
let old = json!({"a/b": 1, "c~d": 2});
let new = json!({"a/b": 9, "c~d": 2});
let patch = diff(&old, &new);
assert_eq!(patch.len(), 1);
let expected_path = "/a~1b";
match &patch.0[0] {
Op::Replace { path, .. } => assert_eq!(path, expected_path),
other => panic!("expected Replace, got {other:?}"),
}
rt(old, new);
let old2 = json!({"c~d": 1});
let new2 = json!({"c~d": 2});
let p2 = diff(&old2, &new2);
match &p2.0[0] {
Op::Replace { path, .. } => assert_eq!(path, "/c~0d"),
other => panic!("expected Replace, got {other:?}"),
}
rt(old2, new2);
}
#[test]
fn apply_missing_path_errors() {
let doc = json!({"a": 1});
let patch = Patch(vec![Op::Replace {
path: "/nope".into(),
value: json!(9),
}]);
let err = apply(&doc, &patch).unwrap_err();
assert!(matches!(err, ApplyError::MissingTarget(_)));
}
#[test]
fn apply_remove_missing_errors() {
let doc = json!({"a": 1});
let patch = Patch(vec![Op::Remove { path: "/ghost".into() }]);
assert!(matches!(
apply(&doc, &patch).unwrap_err(),
ApplyError::MissingTarget(_)
));
}
#[test]
fn apply_replace_root() {
let doc = json!({"a": 1});
let patch = Patch(vec![Op::Replace {
path: "".into(),
value: json!([1, 2, 3]),
}]);
let out = apply(&doc, &patch).unwrap();
assert_eq!(out, json!([1, 2, 3]));
}
#[test]
fn empty_patch_is_identity() {
let doc = json!({"a": [1, 2, {"b": true}]});
let out = apply(&doc, &Patch::new()).unwrap();
assert_eq!(out, doc);
}
#[test]
fn apply_add_on_root_errors() {
let doc = json!({"a": 1});
let patch = Patch(vec![Op::Add {
path: "".into(),
value: json!(2),
}]);
assert!(matches!(
apply(&doc, &patch).unwrap_err(),
ApplyError::CannotAddToRoot
));
}
#[test]
fn apply_remove_root_errors() {
let doc = json!([1, 2]);
let patch = Patch(vec![Op::Remove { path: "".into() }]);
assert!(matches!(
apply(&doc, &patch).unwrap_err(),
ApplyError::CannotRemoveRoot
));
}
#[test]
fn wire_format_matches_rfc_6902() {
let patch = Patch(vec![
Op::Add {
path: "/x".into(),
value: json!(1),
},
Op::Remove { path: "/y".into() },
Op::Replace {
path: "/z".into(),
value: json!("hi"),
},
]);
let txt = serde_json::to_string(&patch).unwrap();
let parsed: Value = serde_json::from_str(&txt).unwrap();
assert_eq!(
parsed,
json!([
{"op": "add", "path": "/x", "value": 1},
{"op": "remove", "path": "/y"},
{"op": "replace", "path": "/z", "value": "hi"},
])
);
}
#[test]
fn patch_roundtrip_through_serde() {
let p1 = Patch(vec![
Op::Add {
path: "/a".into(),
value: json!({"nested": [1, 2]}),
},
Op::Remove { path: "/b/0".into() },
]);
let txt = serde_json::to_string(&p1).unwrap();
let p2: Patch = serde_json::from_str(&txt).unwrap();
assert_eq!(p1, p2);
}
#[test]
fn array_of_objects_element_replace() {
let old = json!([{"id": 1, "v": "a"}, {"id": 2, "v": "b"}]);
let new = json!([{"id": 1, "v": "a"}, {"id": 2, "v": "z"}]);
let patch = diff(&old, &new);
// Should be a single deep replace at /1/v
assert_eq!(patch.len(), 1);
assert!(matches!(&patch.0[0], Op::Replace { path, .. } if path == "/1/v"));
rt(old, new);
}
#[test]
fn null_to_value_is_replace() {
rt(json!(null), json!({"x": 1}));
rt(json!({"x": null}), json!({"x": 1}));
rt(json!({"x": 1}), json!({"x": null}));
}