use std::path::{Path, PathBuf};
use std::process::Command;
fn corpus_a() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/corpora/corpus-a-canonical")
.canonicalize()
.expect("corpus-a-canonical must exist")
}
fn run_in(dir: &Path, args: &[&str]) -> (i32, String, String) {
let output = Command::new(env!("CARGO_BIN_EXE_dbmd"))
.current_dir(dir)
.args(args)
.output()
.expect("failed to spawn dbmd");
(
output.status.code().unwrap_or(-1),
String::from_utf8_lossy(&output.stdout).into_owned(),
String::from_utf8_lossy(&output.stderr).into_owned(),
)
}
#[test]
fn neighborhood_default_cap_does_not_truncate_small_neighborhood() {
let dir = corpus_a();
let (code, out, err) = run_in(
&dir,
&[
"--json",
"graph",
"neighborhood",
"records/contacts/sarah-chen.md",
"--hops",
"1",
],
);
assert_eq!(code, 0, "neighborhood must succeed; stderr: {err}");
let v: serde_json::Value = serde_json::from_str(out.trim()).expect("neighborhood json");
assert_eq!(
v["nodes"].as_array().expect("nodes array").len(),
5,
"the default traversal cap must not truncate a 5-node neighborhood"
);
}
#[test]
fn neighborhood_limit_bounds_result_through_capped_path() {
let dir = corpus_a();
let (code, out, err) = run_in(
&dir,
&[
"--json",
"graph",
"neighborhood",
"records/contacts/sarah-chen.md",
"--hops",
"1",
"--limit",
"2",
],
);
assert_eq!(code, 0, "neighborhood --limit must succeed; stderr: {err}");
let v: serde_json::Value = serde_json::from_str(out.trim()).expect("neighborhood json");
let nodes = v["nodes"].as_array().expect("nodes array");
assert_eq!(nodes.len(), 2, "--limit 2 must bound the result to 2 nodes");
for n in nodes {
assert_eq!(n["hops"], 1, "each kept node is a one-hop neighbor: {n}");
assert_eq!(
n["via"], "records/contacts/sarah-chen",
"the kept edge must originate at the seed: {n}"
);
}
}
#[test]
fn search_tolerates_invalid_utf8_on_a_matched_line() {
let store = tempfile::tempdir().expect("tempdir");
let root = store.path();
std::fs::write(
root.join("DB.md"),
"---\ntype: db-md\nscope: company\nowner: Tester\ncomputer_id: t\n---\n# test store\n",
)
.expect("write DB.md");
let notes = root.join("records").join("notes");
std::fs::create_dir_all(¬es).expect("mkdir records/notes");
let mut bytes: Vec<u8> = Vec::new();
bytes.extend_from_slice(
b"---\ntype: note\ncreated: 2026-01-01T00:00:00Z\nupdated: 2026-01-01T00:00:00Z\nsummary: a note\n---\n",
);
bytes.extend_from_slice(b"the term MSAuniqueterm sits next to a bad byte ");
bytes.push(0xFF);
bytes.extend_from_slice(b" on this line\n");
std::fs::write(notes.join("bad.md"), &bytes).expect("write bad.md");
let (code, out, err) = run_in(root, &["search", "MSAuniqueterm"]);
assert_eq!(
code, 0,
"search must not fail on a single invalid UTF-8 byte on a matched line; stderr: {err}"
);
assert!(
out.contains("MSAuniqueterm") && out.contains("bad.md"),
"the match must still be returned (lossily decoded), got: {out:?}"
);
}