use loro::json::redact;
use loro::{LoroDoc, LoroList, LoroMovableList, LoroTree, LoroValue};
use loro_internal::version::VersionRange;
#[test]
fn redact_text_doc() {
let doc = LoroDoc::new();
doc.set_peer_id(1).unwrap();
let text = doc.get_text("text");
text.insert(0, "Hello, world! This is a secret message.")
.unwrap();
let mut json = doc.export_json_updates(&Default::default(), &doc.oplog_vv());
let mut range = VersionRange::new();
range.insert(1, 24, 30);
redact(&mut json, range).unwrap();
let redacted_doc = LoroDoc::new();
redacted_doc.import_json_updates(json).unwrap();
let redacted_text = redacted_doc.get_text("text");
assert_eq!(
redacted_text.to_string(),
"Hello, world! This is a ������ message."
);
assert_ne!(text.to_string(), redacted_text.to_string());
}
#[test]
fn redact_map_list_insertions() {
let doc = LoroDoc::new();
doc.set_peer_id(1).unwrap();
let map = doc.get_map("map");
let list = doc.get_list("list");
map.insert("key1", "sensitive data").unwrap();
map.insert("key2", 42).unwrap();
list.insert(0, "secret info").unwrap();
list.insert(1, true).unwrap();
let mut json = doc.export_json_updates(&Default::default(), &doc.oplog_vv());
let mut range = VersionRange::new();
range.insert(1, 0, 5); redact(&mut json, range).unwrap();
let s = serde_json::to_string_pretty(&json).unwrap();
let expected = r#"{
"schema_version": 1,
"start_version": {},
"peers": [
"1"
],
"changes": [
{
"id": "0@0",
"timestamp": 0,
"deps": [],
"lamport": 0,
"msg": null,
"ops": [
{
"container": "cid:root-map:Map",
"content": {
"type": "insert",
"key": "key1",
"value": null
},
"counter": 0
},
{
"container": "cid:root-map:Map",
"content": {
"type": "insert",
"key": "key2",
"value": null
},
"counter": 1
},
{
"container": "cid:root-list:List",
"content": {
"type": "insert",
"pos": 0,
"value": [
null,
null
]
},
"counter": 2
}
]
}
]
}"#;
assert_eq!(s, expected);
let redacted_doc = LoroDoc::new();
redacted_doc.import_json_updates(json).unwrap();
let redacted_map = redacted_doc.get_map("map");
let redacted_list = redacted_doc.get_list("list");
assert_eq!(
redacted_map.get("key1").unwrap().into_value().unwrap(),
LoroValue::Null
);
assert_eq!(
redacted_map.get("key2").unwrap().into_value().unwrap(),
LoroValue::Null
);
assert_eq!(
redacted_list.get(0).unwrap().into_value().unwrap(),
LoroValue::Null
);
assert_eq!(
redacted_list.get(1).unwrap().into_value().unwrap(),
LoroValue::Null
);
}
#[test]
fn redact_movable_list() {
let doc = LoroDoc::new();
doc.set_peer_id(1).unwrap();
let list = doc.get_movable_list("movable_list");
list.insert(0, "sensitive data 1").unwrap();
list.insert(1, "sensitive data 2").unwrap();
list.set(0, "updated sensitive data").unwrap();
let mut json = doc.export_json_updates(&Default::default(), &doc.oplog_vv());
let mut range = VersionRange::new();
range.insert(1, 0, 3);
redact(&mut json, range).unwrap();
let redacted_json = serde_json::to_string_pretty(&json).unwrap();
assert_eq!(
redacted_json,
r#"{
"schema_version": 1,
"start_version": {},
"peers": [
"1"
],
"changes": [
{
"id": "0@0",
"timestamp": 0,
"deps": [],
"lamport": 0,
"msg": null,
"ops": [
{
"container": "cid:root-movable_list:MovableList",
"content": {
"type": "insert",
"pos": 0,
"value": [
null,
null
]
},
"counter": 0
},
{
"container": "cid:root-movable_list:MovableList",
"content": {
"type": "set",
"elem_id": "L0@0",
"value": null
},
"counter": 2
}
]
}
]
}"#
);
let redacted_doc = LoroDoc::new();
redacted_doc.import_json_updates(&redacted_json).unwrap();
let redacted_list = redacted_doc.get_movable_list("movable_list");
assert_eq!(
redacted_list.get(0).unwrap().into_value().unwrap(),
LoroValue::Null
);
assert_eq!(
redacted_list.get(1).unwrap().into_value().unwrap(),
LoroValue::Null
);
assert_eq!(
redacted_list.get(0).unwrap().into_value().unwrap(),
LoroValue::Null
);
assert_eq!(redacted_list.len(), 2);
}
#[test]
fn redact_should_keep_parent_child_relationship() {
let doc = LoroDoc::new();
doc.set_peer_id(1).unwrap();
let map = doc.get_map("map");
let list = map.insert_container("list", LoroList::new()).unwrap();
let tree = list.insert_container(0, LoroTree::new()).unwrap();
let node = tree.create(None).unwrap();
let sub_map = tree.get_meta(node).unwrap();
let _m = sub_map
.insert_container("ll", LoroMovableList::new())
.unwrap();
let mut json = doc.export_json_updates(&Default::default(), &doc.oplog_vv());
let mut range = VersionRange::new();
range.insert(1, 0, 100);
redact(&mut json, range).unwrap();
let redacted_json = serde_json::to_string_pretty(&json).unwrap();
pretty_assertions::assert_eq!(
redacted_json,
r#"{
"schema_version": 1,
"start_version": {},
"peers": [
"1"
],
"changes": [
{
"id": "0@0",
"timestamp": 0,
"deps": [],
"lamport": 0,
"msg": null,
"ops": [
{
"container": "cid:root-map:Map",
"content": {
"type": "insert",
"key": "list",
"value": "🦜:cid:0@0:List"
},
"counter": 0
},
{
"container": "cid:0@0:List",
"content": {
"type": "insert",
"pos": 0,
"value": [
"🦜:cid:1@0:Tree"
]
},
"counter": 1
},
{
"container": "cid:1@0:Tree",
"content": {
"type": "create",
"target": "2@0",
"parent": null,
"fractional_index": "80"
},
"counter": 2
},
{
"container": "cid:2@0:Map",
"content": {
"type": "insert",
"key": "ll",
"value": "🦜:cid:3@0:MovableList"
},
"counter": 3
}
]
}
]
}"#
);
let new_doc = LoroDoc::new();
new_doc.import_json_updates(&redacted_json).unwrap();
assert_eq!(new_doc.get_deep_value(), doc.get_deep_value());
}