use std::path::Path;
use dbmd_core::index::Index;
use dbmd_core::store::Store;
use dbmd_core::validate::{codes, validate_all, validate_working_set, Issue};
fn write(root: &Path, rel: &str, contents: &str) {
let abs = root.join(rel);
std::fs::create_dir_all(abs.parent().unwrap()).unwrap();
std::fs::write(abs, contents).unwrap();
}
fn fresh_store(dir: &Path) {
std::fs::write(
dir.join("DB.md"),
"---\ntype: db-md\nscope: company\nowner: Test\n---\n",
)
.unwrap();
for layer in ["sources", "records", "wiki"] {
std::fs::create_dir_all(dir.join(layer)).unwrap();
}
}
fn open(dir: &Path) -> Store {
Store::open_strict(dir).expect("tempdir has a valid DB.md")
}
fn contact(summary: &str) -> String {
format!(
"---\ntype: contact\ncreated: 2026-05-22T10:00:00-07:00\nupdated: 2026-05-22T10:00:00-07:00\nsummary: \"{summary}\"\nname: A\n---\n\n# A\n"
)
}
fn has(issues: &[Issue], code: &str) -> bool {
issues.iter().any(|i| i.code == code)
}
#[test]
fn regression_index_summary_with_middle_dot_does_not_false_positive() {
let tmp = tempfile::TempDir::new().unwrap();
let root = tmp.path();
fresh_store(root);
write(
root,
"records/companies/acme.md",
&contact("Acme · Q2 renewal"),
);
let store = open(root);
Index::rebuild_all(&store).unwrap();
let issues = validate_all(&store).unwrap();
assert!(
!has(&issues, codes::INDEX_SUMMARY_MISMATCH),
"a middle-dot summary on a freshly-rebuilt store must not desync: {issues:#?}"
);
assert!(
!issues.iter().any(Issue::is_error),
"clean store with a middle-dot summary should have no errors: {issues:#?}"
);
}
#[test]
fn regression_index_summary_strips_real_double_spaced_tag_suffix() {
let tmp = tempfile::TempDir::new().unwrap();
let root = tmp.path();
fresh_store(root);
write(
root,
"records/contacts/a.md",
"---\ntype: contact\ncreated: 2026-05-22T10:00:00-07:00\nupdated: 2026-05-22T10:00:00-07:00\nsummary: \"clean summary\"\nname: A\ntags:\n - vip\n---\n\n# A\n",
);
let store = open(root);
Index::rebuild_all(&store).unwrap();
let issues = validate_all(&store).unwrap();
assert!(
!has(&issues, codes::INDEX_SUMMARY_MISMATCH),
"the renderer's ` · #tag` suffix must be stripped before compare: {issues:#?}"
);
}
#[test]
fn regression_working_set_validates_archived_changed_file() {
let tmp = tempfile::TempDir::new().unwrap();
let root = tmp.path();
fresh_store(root);
write(
root,
"records/contacts/sarah-chen.md",
"---\ntype: contact\ncreated: 2026-05-22T10:00:00-07:00\nupdated: 2026-05-31T14:00:00-07:00\nsummary: \"changed in May, never validated\"\nname: Sarah\n---\n\nSee [[tom]].\n",
);
write(root, "records/contacts/tom.md", &contact("created in June"));
write(
root,
"log.md",
"---\ntype: log\n---\n\n## [2026-06-01 08:00] create | records/contacts/tom\n",
);
write(
root,
"log/2026-05.md",
"## [2026-05-30 09:00] validate\nPASS\n\n## [2026-05-31 14:00] update | records/contacts/sarah-chen\nedited\n",
);
let store = open(root);
let issues = validate_working_set(&store, None).unwrap();
assert!(
issues.iter().any(|i| i.code == codes::WIKI_LINK_SHORT_FORM
&& i.file == Path::new("records/contacts/sarah-chen.md")),
"the archived May change must be validated, surfacing its short-form link: {issues:#?}"
);
}
#[test]
fn regression_working_set_cutoff_reads_archived_validate_entry() {
let tmp = tempfile::TempDir::new().unwrap();
let root = tmp.path();
fresh_store(root);
write(
root,
"records/contacts/before.md",
"---\ntype: contact\ncreated: 2026-05-20T10:00:00-07:00\nupdated: 2026-05-20T10:00:00-07:00\nsummary: \"changed before validate\"\nname: B\n---\n\nSee [[ghost]].\n",
);
write(
root,
"records/contacts/after.md",
"---\ntype: contact\ncreated: 2026-06-02T10:00:00-07:00\nupdated: 2026-06-02T10:00:00-07:00\nsummary: \"changed after validate\"\nname: A\n---\n\nSee [[phantom]].\n",
);
write(
root,
"log.md",
"---\ntype: log\n---\n\n## [2026-06-02 10:00] update | records/contacts/after\n",
);
write(
root,
"log/2026-05.md",
"## [2026-05-20 10:00] update | records/contacts/before\nx\n\n## [2026-05-30 09:00] validate\nPASS\n",
);
let store = open(root);
let issues = validate_working_set(&store, None).unwrap();
assert!(
issues
.iter()
.any(|i| i.file == Path::new("records/contacts/after.md")),
"post-validate change must be in the working set: {issues:#?}"
);
assert!(
!issues
.iter()
.any(|i| i.file == Path::new("records/contacts/before.md")),
"pre-validate change (before the archived cutoff) must be excluded: {issues:#?}"
);
}