hephaestus/crates/heph-core/tests/wikilinks.rs
Erich Blume b112b0d7c1
Some checks failed
Build / validate (pull_request) Failing after 11s
feat: heph migrate-links — rewrite legacy [[Name]] links to [[id]] (§8.4)
`wikilink::to_ids` rewrites name-addressed links to the canonical id
(id-first resolve: an already-id target is left alone, a name → its id
with any label preserved). `Store::migrate_wikilinks_to_ids` runs it over
every body and re-saves through update_node (which collapses + materializes
by id); idempotent. Surfaced as the `migrate.wikilinks` RPC + RemoteStore
forward + the `heph migrate-links` CLI command (not auto-run — the owner
runs it once per store).

Name-resolution + the canonical-context hack stay for now so legacy links
keep resolving pre-migration; retiring them is a later tidy. Tests:
to_ids unit + a heph-core migrate integration (rewrite + materialize +
idempotency).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:40:57 -07:00

64 lines
2.5 KiB
Rust

//! Wiki-link display projection (tech-spec §8.4): a body's links are stored as
//! canonical bare `[[NODEID]]`. `update_node` collapses a name-matching
//! `[[NODEID|Name]]` label back to bare (the daemon expands it again on read);
//! a custom label is preserved. `get_node` here returns the **raw stored** body
//! (expansion is a daemon-read concern), so it shows the collapsed form.
use heph_core::{FixedClock, LinkType, LocalStore, NewNode, Store};
fn store() -> LocalStore {
LocalStore::open_in_memory(Box::new(FixedClock(1_700_000_000_000))).unwrap()
}
#[test]
fn update_collapses_name_matching_labels_and_materializes_by_id() {
let mut s = store();
let target = s.create_node(NewNode::doc("Roof", "")).unwrap();
let src = s.create_node(NewNode::doc("Daily", "")).unwrap();
// The buffer the user saves carries the expanded label `[[id|Roof]]`.
let updated = s
.update_node(&src.id, None, Some(format!("see [[{}|Roof]] here", target.id)))
.unwrap();
// Stored body collapsed the auto-label back to the canonical bare id.
assert_eq!(
updated.body.as_deref(),
Some(format!("see [[{}]] here", target.id).as_str())
);
// And the `[[id]]` materialized as a wiki link by id.
assert!(s
.outgoing_links(&src.id)
.unwrap()
.iter()
.any(|l| l.link_type == LinkType::Wiki && l.dst_id == target.id));
// A custom label (≠ the target's current name) is a real override — kept.
let custom = format!("[[{}|my roof]]", target.id);
let u2 = s.update_node(&src.id, None, Some(custom.clone())).unwrap();
assert_eq!(u2.body.as_deref(), Some(custom.as_str()));
}
#[test]
fn migrate_rewrites_legacy_name_links_to_ids() {
let mut s = store();
let target = s.create_node(NewNode::doc("Roof", "")).unwrap();
// A legacy body authored with a name-addressed link (pre-§8.4).
let src = s
.create_node(NewNode::doc("Daily", "fix the [[Roof]] soon"))
.unwrap();
let changed = s.migrate_wikilinks_to_ids().unwrap();
assert_eq!(changed, 1);
// The name was rewritten to the canonical bare id, and the wiki link is by id.
let body = s.get_node(&src.id).unwrap().unwrap().body.unwrap();
assert_eq!(body, format!("fix the [[{}]] soon", target.id));
assert!(s
.outgoing_links(&src.id)
.unwrap()
.iter()
.any(|l| l.link_type == LinkType::Wiki && l.dst_id == target.id));
// Idempotent: a second run finds nothing to change.
assert_eq!(s.migrate_wikilinks_to_ids().unwrap(), 0);
}