Merge pull request #6699 from dotCooCoo/fix/links-read-roots

MCP: links.list, links.check, links.info and document.open leave out paths outside the read roots
This commit is contained in:
BFlat
2026-11-12 11:41:52 +07:00
committed by GitHub
7 changed files with 149 additions and 20 deletions
+53
View File
@@ -296,6 +296,59 @@ fn link_info_stays_within_the_asking_callers_roots() {
assert_ne!(i["status"], "ok", "{i}");
}
/// A confined caller's `links.list`, `links.check` and `links.info` replies, and its
/// `document.open` reply, leave out the path and folder of a linked file outside its read roots
/// and keep the row with the file's name. A file found by name inside the roots shows its path.
#[test]
fn link_paths_outside_the_read_roots_are_left_out() {
let away = Folder::new("lrr-away");
let home = Folder::new("lrr-home");
let pic = away.file("photo.png");
let bytes = png(60, 30, RED);
write(&pic, &bytes);
let mut s = session();
let id = place(&mut s, &pic);
let roots = crate::file_access::AutomationRoots::new(&[&home.0], &[]).unwrap().unwrap();
let ask = |s: &mut Session, cmd: &str, p: Value| crate::file_access::confine(Some(&roots), || run(s, cmd, p));
let hidden = |r: &Value| assert!(!r.to_string().contains("lrr-away"), "{r}");
let list = ask(&mut s, "links.list", json!({}));
hidden(&list);
let row = &list["links"][0];
assert_eq!((row["id"].as_u64(), row["name"].as_str(), row["status"].as_str()), (Some(id.0), Some("photo.png"), Some("missing")), "{list}");
let check = ask(&mut s, "links.check", json!({}));
hidden(&check);
assert_eq!((check["links"][0]["name"].as_str(), check["missing"].as_u64()), (Some("photo.png"), Some(1)), "{check}");
let info = ask(&mut s, "links.info", json!({"id": id.0}));
hidden(&info);
assert_eq!(
(info["fileName"].as_str(), info["status"].as_str(), info["linked"].as_bool()),
(Some("photo.png"), Some("missing"), Some(true)),
"{info}"
);
for k in ["path", "link", "location", "fileSize", "modified", "created"] {
assert!(info.get(k).is_none(), "{k}: {info}");
}
// Unconfined, the same replies carry the paths.
assert_eq!(run(&mut s, "links.list", json!({}))["links"][0]["path"], json!(pic));
let i = run(&mut s, "links.info", json!({"id": id.0}));
assert_eq!((&i["path"], &i["link"], i["fileSize"].as_u64()), (&json!(pic), &json!(pic), Some(bytes.len() as u64)), "{i}");
// Saved inside the roots next to a copy of the file: the copy is found by name and shown.
save(&mut s, &home.file("poster.vectorcraft"));
let copy = home.file("photo.png");
write(&copy, &bytes);
let check = ask(&mut s, "links.check", json!({}));
hidden(&check);
assert_eq!((check["links"][0]["status"].as_str(), &check["links"][0]["found"]), (Some("ok"), &json!(copy)), "{check}");
let list = ask(&mut s, "links.list", json!({}));
hidden(&list);
assert_eq!(list["links"][0]["found"], json!(copy), "{list}");
// Opened again without the copy: the missing link's row has no path.
std::fs::remove_file(&copy).unwrap();
let opened = ask(&mut Session::new(), "document.open", json!({"path": home.file("poster.vectorcraft")}));
hidden(&opened);
assert_eq!(opened["missingLinks"][0]["name"], "photo.png", "{opened}");
}
/// A 600×300 pt image at the centre of the artboard, its placement options set to `options`,
/// relinked to a 300×100 px file → the image's id and session.
fn relinked(name: &str, options: Value) -> (Session, NodeId, Folder) {
@@ -1060,6 +1060,30 @@ fn link_info_gives_the_artboards_size_not_pixels() {
);
}
/// A confined caller's Link Info of a placed document whose file is outside its read roots has no
/// `link`, `path` or `location`, and keeps the file's name.
#[test]
fn link_info_leaves_out_a_placed_documents_path_outside_the_read_roots() {
let away = Folder::new("placed-lrr-away");
let home = Folder::new("placed-lrr-home");
let src = away.file("logo.vectorcraft");
source(&src, 100.0, 50.0, RED, 0);
let mut s = session();
let id = place(&mut s, &src);
let roots = crate::file_access::AutomationRoots::new(&[&home.0], &[]).unwrap().unwrap();
let i = crate::file_access::confine(Some(&roots), || s.execute("links.info", &json!({"id": id.0}))).unwrap();
assert!(!i.to_string().contains("placed-lrr-away"), "{i}");
assert_eq!(
(i["fileName"].as_str(), i["document"].as_bool(), i["status"].as_str()),
(Some("logo.vectorcraft"), Some(true), Some("missing")),
"{i}"
);
let l = crate::file_access::confine(Some(&roots), || s.execute("links.list", &json!({}))).unwrap();
assert!(!l.to_string().contains("placed-lrr-away") && l["links"][0]["name"] == "logo.vectorcraft", "{l}");
let i = s.execute("links.info", &json!({"id": id.0})).unwrap();
assert_eq!((&i["link"], &i["path"]), (&json!(src), &json!(src)), "unconfined: {i}");
}
/// Save As reads its defaults from the catalog; using them must retain current editable links.
#[test]
fn native_save_catalog_defaults_keep_placed_documents_editable() {