e210b23a25
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
169 lines
8.3 KiB
Rust
169 lines
8.3 KiB
Rust
//! Bookmark commands share the registry and atomic private vault state.
|
|
|
|
use notescraft_engine::{Error, Registry, Session, register_core};
|
|
use serde_json::{Value, json};
|
|
|
|
mod common;
|
|
|
|
fn registry() -> Registry<Session> {
|
|
let mut registry = Registry::new();
|
|
register_core(&mut registry).expect("register commands");
|
|
registry
|
|
}
|
|
|
|
#[test]
|
|
fn add_list_remove_and_dedupe_by_path_and_heading() {
|
|
let vault = common::temp_vault("bookmark-commands");
|
|
common::seed(vault.path(), &[("folder/note.md", "# Heading\n"), ("map.canvas", "{}")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let registry = registry();
|
|
let list = |session: &mut Session| registry.run("bookmarks.list", session, &Value::Null).expect("list");
|
|
assert_eq!(list(&mut session), json!({"bookmarks": []}));
|
|
let note = json!({"path": "folder/note.md", "title": "My note"});
|
|
assert_eq!(registry.run("bookmarks.add", &mut session, ¬e).expect("add")["added"], true);
|
|
assert_eq!(registry.run("bookmarks.add", &mut session, ¬e).expect("dedupe")["added"], false);
|
|
let heading = json!({"path": "folder/note.md", "heading": "Heading", "title": "Jump here"});
|
|
registry.run("bookmarks.add", &mut session, &heading).expect("heading");
|
|
registry.run("bookmarks.add", &mut session, &json!({"path":"map.canvas"})).expect("canvas");
|
|
assert_eq!(
|
|
list(&mut session),
|
|
json!({"bookmarks": [
|
|
{"path":"folder/note.md", "heading":null, "title":"My note"},
|
|
{"path":"folder/note.md", "heading":"Heading", "title":"Jump here"},
|
|
{"path":"map.canvas", "heading":null, "title":null}
|
|
]})
|
|
);
|
|
let remove = json!({"path":"folder/note.md", "heading":"Heading"});
|
|
assert_eq!(registry.run("bookmarks.remove", &mut session, &remove).expect("remove")["removed"], true);
|
|
assert_eq!(registry.run("bookmarks.remove", &mut session, &remove).expect("absent")["removed"], false);
|
|
assert_eq!(list(&mut session)["bookmarks"].as_array().expect("array").len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn bookmarks_persist_across_session_reopen_and_removal() {
|
|
let vault = common::temp_vault("bookmark-reopen");
|
|
common::seed(vault.path(), &[("note.md", "# Heading\n")]);
|
|
let registry = registry();
|
|
let params = json!({"path":"note.md", "heading":"Heading", "title":"Saved"});
|
|
let expected;
|
|
{
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
registry.run("bookmarks.add", &mut session, ¶ms).expect("add");
|
|
expected = registry.run("bookmarks.list", &mut session, &Value::Null).expect("list");
|
|
assert!(vault.path().join(".notescraft/bookmarks.json").is_file());
|
|
assert!(!vault.path().join(".obsidian").exists());
|
|
}
|
|
{
|
|
let mut session = Session::open(vault.path()).expect("reopen");
|
|
assert_eq!(registry.run("bookmarks.list", &mut session, &Value::Null).expect("list"), expected);
|
|
registry.run("bookmarks.remove", &mut session, &json!({"path":"note.md", "heading":"Heading"})).expect("remove");
|
|
}
|
|
let mut session = Session::open(vault.path()).expect("reopen removal");
|
|
assert_eq!(registry.run("bookmarks.list", &mut session, &Value::Null).expect("list"), json!({"bookmarks":[]}));
|
|
}
|
|
|
|
#[test]
|
|
fn read_only_sessions_list_but_cannot_change_bookmarks() {
|
|
let vault = common::temp_vault("bookmark-read-only");
|
|
common::seed(vault.path(), &[("note.md", "# Note\n")]);
|
|
let registry = registry();
|
|
{
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
registry.run("bookmarks.add", &mut session, &json!({"path":"note.md"})).expect("add");
|
|
}
|
|
let mut session = Session::open_read_only(vault.path()).expect("read only");
|
|
assert_eq!(registry.run("bookmarks.list", &mut session, &Value::Null).expect("list")["bookmarks"].as_array().expect("array").len(), 1);
|
|
for id in ["bookmarks.add", "bookmarks.remove"] {
|
|
assert!(matches!(registry.run(id, &mut session, &json!({"path":"note.md"})), Err(Error::Disabled(_))));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_targets_and_fields_never_write_state() {
|
|
let vault = common::temp_vault("bookmark-invalid");
|
|
common::seed(vault.path(), &[("note.md", "# Note\n")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let registry = registry();
|
|
for params in [
|
|
json!({"path":"../note.md"}),
|
|
json!({"path":"/note.md"}),
|
|
json!({"path":".obsidian/note.md"}),
|
|
json!({"path":".notescraft/note.md"}),
|
|
json!({"path":"missing.md"}),
|
|
json!({"path":"./note.md"}),
|
|
json!({"path":"note.md", "heading":""}),
|
|
json!({"path":"note.md", "title":""}),
|
|
json!({"path":"note.md", "heading":42}),
|
|
json!({"path":"note.md", "title":"x".repeat(1025)}),
|
|
] {
|
|
assert!(registry.run("bookmarks.add", &mut session, ¶ms).is_err(), "{params}");
|
|
}
|
|
assert!(!vault.path().join(".notescraft/bookmarks.json").exists());
|
|
}
|
|
|
|
#[test]
|
|
fn damaged_private_state_is_reported_and_preserved() {
|
|
let vault = common::temp_vault("bookmark-corrupt");
|
|
common::seed(vault.path(), &[("note.md", "# Note\n")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
session.vault().write_private(".notescraft/bookmarks.json", b"broken json").expect("seed corrupt state");
|
|
let registry = registry();
|
|
for id in ["bookmarks.list", "bookmarks.add", "bookmarks.remove"] {
|
|
let params = if id == "bookmarks.list" { Value::Null } else { json!({"path":"note.md"}) };
|
|
assert!(registry.run(id, &mut session, ¶ms).is_err());
|
|
}
|
|
assert_eq!(session.vault().read(".notescraft/bookmarks.json").expect("preserved"), b"broken json");
|
|
}
|
|
|
|
#[test]
|
|
fn note_rename_moves_bookmark_targets_and_preserves_heading_title() {
|
|
let vault = common::temp_vault("bookmark-rename");
|
|
common::seed(vault.path(), &[("note.md", "# Heading\n"), ("taken.md", "taken")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let registry = registry();
|
|
registry.run("bookmarks.add", &mut session, &json!({"path":"note.md", "heading":"Heading", "title":"Saved"})).expect("add");
|
|
assert!(registry.run("note.rename", &mut session, &json!({"from":"note.md", "to":"taken.md"})).is_err());
|
|
assert_eq!(registry.run("bookmarks.list", &mut session, &Value::Null).expect("list")["bookmarks"][0]["path"], "note.md");
|
|
registry.run("note.rename", &mut session, &json!({"from":"note.md", "to":"folder/moved.md"})).expect("move");
|
|
assert_eq!(
|
|
registry.run("bookmarks.list", &mut session, &Value::Null).expect("list"),
|
|
json!({"bookmarks":[
|
|
{"path":"folder/moved.md", "heading":"Heading", "title":"Saved"}
|
|
]})
|
|
);
|
|
drop(session);
|
|
let mut reopened = Session::open(vault.path()).expect("reopen");
|
|
assert_eq!(registry.run("bookmarks.list", &mut reopened, &Value::Null).expect("list")["bookmarks"][0]["path"], "folder/moved.md");
|
|
}
|
|
|
|
#[test]
|
|
fn deleted_file_bookmark_can_still_be_removed() {
|
|
let vault = common::temp_vault("bookmark-deleted");
|
|
common::seed(vault.path(), &[("note.md", "# Note\n")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let registry = registry();
|
|
registry.run("bookmarks.add", &mut session, &json!({"path":"note.md"})).expect("add");
|
|
session.delete_note("note.md").expect("delete note");
|
|
assert_eq!(registry.run("bookmarks.remove", &mut session, &json!({"path":"note.md"})).expect("remove")["removed"], true);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_stored_bookmarks_never_get_repaired_or_overwritten() {
|
|
let vault = common::temp_vault("bookmark-state-invalid");
|
|
common::seed(vault.path(), &[("note.md", "# Note\n")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let registry = registry();
|
|
for state in [
|
|
json!([{"path":"../note.md"}]),
|
|
json!([{"path":"note.md"}, {"path":"note.md"}]),
|
|
json!([{"path":"note.md", "title":" "}]),
|
|
json!(vec![json!({"path":"note.md"}); 1025]),
|
|
] {
|
|
let bytes = serde_json::to_vec(&state).expect("json");
|
|
session.vault().write_private(".notescraft/bookmarks.json", &bytes).expect("seed state");
|
|
assert!(registry.run("bookmarks.list", &mut session, &Value::Null).is_err(), "{state}");
|
|
assert!(registry.run("bookmarks.add", &mut session, &json!({"path":"note.md"})).is_err());
|
|
assert_eq!(session.vault().read(".notescraft/bookmarks.json").expect("preserved"), bytes);
|
|
}
|
|
}
|