use topodb::*;
struct Fx {
db: Db,
_dir: tempfile::TempDir,
}
fn fx() -> Fx {
let dir = tempfile::tempdir().unwrap();
let spec = IndexSpec {
equality: vec![],
text: vec![PropIndex {
label: "Note".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(dir.path().join("t.redb"), spec).unwrap();
Fx { db, _dir: dir }
}
fn note(id: NodeId, scope: Scope, content: &str) -> Op {
let mut props = Props::new();
props.insert("content".into(), PropValue::Str(content.into()));
Op::CreateNode {
id,
scope,
label: "Note".into(),
props,
}
}
#[test]
fn a_project_read_never_sees_another_projects_nodes() {
let f = fx();
let (a, b) = (ScopeId::new(), ScopeId::new());
let (na, nb) = (NodeId::new(), NodeId::new());
f.db.submit(vec![
note(na, Scope::Id(a), "alpha note"),
note(nb, Scope::Id(b), "bravo note"),
])
.unwrap();
let in_a: Vec<_> =
f.db.nodes_by_label(&ScopeSet::of(&[a]), "Note")
.into_iter()
.map(|r| r.id)
.collect();
assert_eq!(in_a, vec![na], "scope A must see only A's node");
let in_b: Vec<_> =
f.db.nodes_by_label(&ScopeSet::of(&[b]), "Note")
.into_iter()
.map(|r| r.id)
.collect();
assert_eq!(in_b, vec![nb], "scope B must see only B's node");
assert!(
f.db.node(&ScopeSet::of(&[a]), nb).is_none(),
"fetching B's node id under scope A must return None (no cross-scope id leak)"
);
assert!(
f.db.node(&ScopeSet::of(&[a]), na).is_some(),
"A's own node resolves under scope A"
);
}
#[test]
fn full_text_search_is_scoped() {
let f = fx();
let (a, b) = (ScopeId::new(), ScopeId::new());
f.db.submit(vec![
note(NodeId::new(), Scope::Id(a), "shared secret alpha"),
note(NodeId::new(), Scope::Id(b), "shared secret bravo"),
])
.unwrap();
let hits_a = f.db.search_text(&ScopeSet::of(&[a]), "secret", 10).unwrap();
assert_eq!(hits_a.len(), 1, "search in A matches only A's note");
let hits_b = f.db.search_text(&ScopeSet::of(&[b]), "secret", 10).unwrap();
assert_eq!(hits_b.len(), 1, "search in B matches only B's note");
let hits_ab =
f.db.search_text(&ScopeSet::of(&[a, b]), "secret", 10)
.unwrap();
assert_eq!(hits_ab.len(), 2, "search over {{A,B}} matches both");
}
#[test]
fn the_shared_layer_is_visible_only_when_opted_in() {
let f = fx();
let a = ScopeId::new();
let (shared_node, proj_node) = (NodeId::new(), NodeId::new());
f.db.submit(vec![
note(shared_node, Scope::Shared, "shared knowledge"),
note(proj_node, Scope::Id(a), "project knowledge"),
])
.unwrap();
let only_proj: Vec<_> =
f.db.nodes_by_label(&ScopeSet::of(&[a]), "Note")
.into_iter()
.map(|r| r.id)
.collect();
assert_eq!(
only_proj,
vec![proj_node],
"without with_shared(), the shared node must NOT appear"
);
let with_shared: std::collections::HashSet<_> =
f.db.nodes_by_label(&ScopeSet::of(&[a]).with_shared(), "Note")
.into_iter()
.map(|r| r.id)
.collect();
assert!(
with_shared.contains(&shared_node) && with_shared.contains(&proj_node),
"with_shared() must expose the shared node alongside the project's own"
);
}
#[test]
fn a_shared_node_is_visible_from_any_project_that_opts_in() {
let f = fx();
let (a, b) = (ScopeId::new(), ScopeId::new());
let shared_node = NodeId::new();
f.db.submit(vec![note(shared_node, Scope::Shared, "common")])
.unwrap();
for (name, s) in [("A", a), ("B", b)] {
let seen = f.db.node(&ScopeSet::of(&[s]).with_shared(), shared_node);
assert!(
seen.is_some(),
"the shared node must be visible from project {name} with_shared()"
);
assert!(
f.db.node(&ScopeSet::of(&[s]), shared_node).is_none(),
"the shared node must be hidden from project {name} without with_shared()"
);
}
}