mod common;
use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
use common::{copy_store_to_temp, corpus_a, dbmd};
fn index_artifacts(dir: &std::path::Path) -> BTreeSet<String> {
fn walk(root: &std::path::Path, dir: &std::path::Path, out: &mut BTreeSet<String>) {
for entry in std::fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let p = entry.path();
if p.is_dir() {
walk(root, &p, out);
} else if matches!(
p.file_name().and_then(|n| n.to_str()),
Some("index.md") | Some("index.jsonl")
) {
out.insert(
p.strip_prefix(root)
.unwrap()
.to_string_lossy()
.replace('\\', "/"),
);
}
}
}
let mut out = BTreeSet::new();
walk(dir, dir, &mut out);
out
}
#[test]
fn rebuild_full_is_byte_identical_to_the_committed_corpus() {
let (_tmp, store) = copy_store_to_temp(&corpus_a());
dbmd()
.current_dir(&store)
.args(["index", "rebuild"])
.assert()
.success();
let committed = corpus_a();
let artifacts = index_artifacts(&store);
assert!(!artifacts.is_empty(), "corpus-a has index artifacts");
for rel in &artifacts {
let rebuilt = std::fs::read(store.join(rel)).unwrap();
let original = std::fs::read(committed.join(rel)).unwrap();
assert_eq!(
rebuilt, original,
"rebuilt {rel} must be byte-identical to the committed corpus"
);
}
let committed_artifacts = index_artifacts(&committed);
assert_eq!(
artifacts, committed_artifacts,
"rebuild must produce exactly the committed artifact set (no orphans/extras)"
);
}
fn live_index_artifacts(store: &Path) -> BTreeMap<String, Vec<u8>> {
fn walk(root: &Path, dir: &Path, out: &mut BTreeMap<String, Vec<u8>>) {
for entry in std::fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let p = entry.path();
if p.is_dir() {
walk(root, &p, out);
} else if matches!(
p.file_name().and_then(|n| n.to_str()),
Some("index.md") | Some("index.jsonl")
) {
let rel = p
.strip_prefix(root)
.unwrap()
.to_string_lossy()
.replace('\\', "/");
out.insert(rel, std::fs::read(&p).unwrap());
}
}
}
let mut out = BTreeMap::new();
let root_index = store.join("index.md");
if root_index.is_file() {
out.insert("index.md".to_string(), std::fs::read(&root_index).unwrap());
}
for layer in ["sources", "records", "wiki"] {
let dir = store.join(layer);
if dir.is_dir() {
walk(store, &dir, &mut out);
}
}
out
}
#[test]
fn writethrough_sequence_equals_rebuild_byte_for_byte() {
let (_tmp, store) = copy_store_to_temp(&corpus_a());
std::fs::remove_dir_all(store.join("EXPECTED")).ok();
for entry in std::fs::read_dir(&store).unwrap() {
let entry = entry.unwrap();
let name = entry.file_name();
let name = name.to_string_lossy();
if name.starts_with(".gen-") && name.ends_with(".py") {
std::fs::remove_file(entry.path()).ok();
}
}
dbmd()
.current_dir(&store)
.args([
"write",
"records/contacts/jordan-li.md",
"--type",
"contact",
"--summary",
"VP Eng at Globex; technical sponsor on the platform eval",
"--fm",
"created=2026-05-25T10:00:00-07:00",
"--fm",
"updated=2026-05-25T10:00:00-07:00",
"--fm",
"name=Jordan Li",
])
.assert()
.success();
dbmd()
.current_dir(&store)
.args([
"fm",
"set",
"records/contacts/marcus-okafor.md",
"status=inactive",
])
.assert()
.success();
dbmd()
.current_dir(&store)
.args([
"fm",
"set",
"records/contacts/marcus-okafor.md",
"updated=2026-05-28T12:00:00-07:00",
])
.assert()
.success();
dbmd()
.current_dir(&store)
.args([
"rename",
"records/contacts/david-kim.md",
"records/contacts/david-kim-ae.md",
])
.assert()
.success();
let meeting = std::fs::read_to_string(
store.join("records/meetings/2026/05/2026-05-22-northstar-renewal-call.md"),
)
.unwrap();
assert!(
meeting.contains("[[records/contacts/david-kim-ae]]")
&& !meeting.contains("[[records/contacts/david-kim]]"),
"precondition: rename must have rewritten the meeting's attendees link"
);
let write_through = live_index_artifacts(&store);
assert!(
write_through.contains_key("index.md")
&& write_through.contains_key("records/contacts/index.md")
&& write_through.contains_key("records/contacts/index.jsonl")
&& write_through.contains_key("records/meetings/index.jsonl"),
"the sequence must have produced the catalog artifacts under test (not a vacuous compare): {:?}",
write_through.keys().collect::<Vec<_>>()
);
dbmd()
.current_dir(&store)
.args(["index", "rebuild"])
.assert()
.success();
let rebuilt = live_index_artifacts(&store);
assert_eq!(
write_through.keys().collect::<Vec<_>>(),
rebuilt.keys().collect::<Vec<_>>(),
"write-through and rebuild must produce the SAME set of index artifacts\
\n write-through: {:?}\n rebuild: {:?}",
write_through.keys().collect::<Vec<_>>(),
rebuilt.keys().collect::<Vec<_>>(),
);
for (rel, wt_bytes) in &write_through {
let rb_bytes = &rebuilt[rel];
assert_eq!(
wt_bytes,
rb_bytes,
"INVARIANT VIOLATED: `{rel}` differs between write-through and rebuild\
\n--- write-through ---\n{}\n--- rebuild ---\n{}",
String::from_utf8_lossy(wt_bytes),
String::from_utf8_lossy(rb_bytes),
);
}
}
#[test]
fn rebuild_scoped_to_a_folder_matches_committed() {
let (_tmp, store) = copy_store_to_temp(&corpus_a());
dbmd()
.current_dir(&store)
.args(["index", "rebuild", "--folder", "records/contacts"])
.assert()
.success();
for rel in ["records/contacts/index.md", "records/contacts/index.jsonl"] {
let rebuilt = std::fs::read(store.join(rel)).unwrap();
let original = std::fs::read(corpus_a().join(rel)).unwrap();
assert_eq!(
rebuilt, original,
"{rel} identical after folder-scoped rebuild"
);
}
}
#[test]
fn rebuild_dry_run_previews_without_writing() {
let (_tmp, store) = copy_store_to_temp(&corpus_a());
let target = store.join("records/contacts/index.md");
let before = std::fs::read(&target).unwrap();
std::fs::write(&target, "STALE PLACEHOLDER\n").unwrap();
let out = dbmd()
.current_dir(&store)
.args([
"index",
"rebuild",
"--dry-run",
"--folder",
"records/contacts",
])
.assert()
.success();
let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
assert!(
stdout.contains("--- records/contacts/index.md ---"),
"dry-run prints the md separator:\n{stdout}"
);
assert!(
stdout.contains("--- records/contacts/index.jsonl ---"),
"dry-run prints the jsonl separator:\n{stdout}"
);
let after = std::fs::read(&target).unwrap();
assert_eq!(after, b"STALE PLACEHOLDER\n", "dry-run must not write");
assert_ne!(after, before);
}
#[test]
fn rebuild_rejects_both_layer_and_folder() {
let (_tmp, store) = copy_store_to_temp(&corpus_a());
dbmd()
.current_dir(&store)
.args([
"index",
"rebuild",
"--layer",
"records",
"--folder",
"records/contacts",
])
.assert()
.failure()
.code(1);
}
#[test]
fn show_root_prints_the_root_index() {
let out = dbmd()
.args(["index", "show"])
.arg("--dir")
.arg(corpus_a())
.assert()
.success();
let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
assert!(
stdout.contains("scope: root"),
"root index frontmatter:\n{stdout}"
);
assert!(stdout.contains("# Knowledge base index"));
}
#[test]
fn show_scoped_prints_a_type_folder_index() {
let out = dbmd()
.args(["index", "show", "wiki/people"])
.arg("--dir")
.arg(corpus_a())
.assert()
.success();
let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
assert!(stdout.contains("folder: wiki/people"));
assert!(stdout.contains("[[wiki/people/sarah-chen]]"));
}
#[test]
fn show_byte_matches_the_committed_index() {
let out = dbmd()
.args(["index", "show", "records/contacts"])
.arg("--dir")
.arg(corpus_a())
.assert()
.success();
let printed = out.get_output().stdout.clone();
let on_disk = std::fs::read(corpus_a().join("records/contacts/index.md")).unwrap();
assert_eq!(printed, on_disk, "show prints the index.md verbatim");
}
#[test]
fn show_missing_index_exits_1_with_empty_stdout_and_a_hint() {
let out = dbmd()
.args(["index", "show", "records/nonexistent"])
.arg("--dir")
.arg(corpus_a())
.assert()
.failure()
.code(1);
let o = out.get_output();
assert!(
o.stdout.is_empty(),
"stdout stays empty so pipelines don't break"
);
let stderr = String::from_utf8(o.stderr.clone()).unwrap();
assert!(
stderr.contains("no index.md") && stderr.contains("rebuild"),
"stderr carries the rebuild hint; got: {stderr}"
);
}
fn query_paths(args: &[&str]) -> Vec<String> {
let out = dbmd()
.arg("index")
.arg("query")
.args(args)
.arg("--dir")
.arg(corpus_a())
.assert()
.success();
let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
stdout.lines().map(str::to_string).collect()
}
#[test]
fn query_by_type_returns_the_folder_records() {
let got: BTreeSet<String> = query_paths(&["--type", "invoice", "--in", "records"])
.into_iter()
.collect();
let expected: BTreeSet<String> = [
"records/invoices/2026/04/2026-04-18-figma-annual.md",
"records/invoices/2026/04/2026-04-30-aws-april.md",
"records/invoices/2026/05/2026-05-31-aws-may.md",
]
.iter()
.map(|s| s.to_string())
.collect();
assert_eq!(got, expected);
}
#[test]
fn query_where_narrows_within_type() {
let got: BTreeSet<String> = query_paths(&["--type", "invoice", "--where", "status=paid"])
.into_iter()
.collect();
assert!(got.contains("records/invoices/2026/04/2026-04-18-figma-annual.md"));
assert!(got.contains("records/invoices/2026/04/2026-04-30-aws-april.md"));
assert!(!got.contains("records/invoices/2026/05/2026-05-31-aws-may.md"));
}
#[test]
fn query_updated_after_window_filters_by_recency() {
let got: BTreeSet<String> =
query_paths(&["--type", "invoice", "--updated-after", "2026-05-01"])
.into_iter()
.collect();
assert!(got.contains("records/invoices/2026/04/2026-04-30-aws-april.md"));
assert!(got.contains("records/invoices/2026/05/2026-05-31-aws-may.md"));
assert!(
!got.contains("records/invoices/2026/04/2026-04-18-figma-annual.md"),
"the figma invoice (updated 2026-04-18) is outside the window: {got:?}"
);
}
#[test]
fn query_limit_caps_results() {
let capped = query_paths(&["--type", "expense", "--limit", "3"]);
assert_eq!(capped.len(), 3, "limit caps the complete result set");
}
#[test]
fn query_json_returns_full_records() {
let out = dbmd()
.args([
"index",
"query",
"--type",
"invoice",
"--where",
"status=paid",
])
.arg("--json")
.arg("--dir")
.arg(corpus_a())
.assert()
.success();
let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let arr = v.as_array().expect("array of records");
assert_eq!(arr.len(), 2);
for rec in arr {
assert_eq!(rec["status"], serde_json::json!("paid"));
assert!(rec["path"]
.as_str()
.unwrap()
.starts_with("records/invoices/"));
assert!(rec.get("summary").is_some());
}
}