use crate::graph::storage::GraphRead;
use crate::okf::build::build;
use crate::okf::build::tests_support::{
copy_tree, labels_by_id, provisional_count, vault_build_with, write,
};
use crate::okf::model::{
BuildOptions, ATTACHMENT_LABEL, CONTAINS_CONN_TYPE, DEFAULT_LABEL, EMBEDS_CONN_TYPE,
FOLDER_LABEL, FOLDER_NOTE_CONN_TYPE, HAS_ATTACHMENT_CONN_TYPE, HAS_IMAGE_CONN_TYPE,
IMAGE_LABEL, TAGGED_CONN_TYPE, TAG_LABEL,
};
use std::collections::BTreeMap;
use std::path::Path;
use tempfile::tempdir;
#[test]
fn builds_nodes_edges_and_dangling_stub() {
let dir = tempdir().unwrap();
write(
dir.path(),
"a.md",
"---\ntype: Note\n---\nSee [b](b.md) and [gone](missing.md).",
);
write(dir.path(), "b.md", "---\ntype: Note\n---\nleaf");
let g = build(dir.path(), &BuildOptions::default()).unwrap().graph;
assert_eq!(g.graph.node_indices().count(), 3);
assert_eq!(g.graph.edge_count(), 2);
assert_eq!(provisional_count(&g), 1, "missing.md is a provisional stub");
}
#[test]
fn vault_report_carries_the_collision_findings() {
let dir = tempdir().unwrap();
write(dir.path(), "projects/alpha.md", "prose");
write(dir.path(), "archive/alpha.md", "prose");
write(dir.path(), "notes/Roadmap.md", "prose");
write(dir.path(), "plans/roadmap.md", "prose");
let opts = BuildOptions::for_dialect(crate::okf::Dialect::Obsidian);
let r = build(dir.path(), &opts).unwrap().report;
assert_eq!(r.errors.len(), 1, "the stem collision: {:?}", r.errors);
assert_eq!(r.warnings.len(), 1, "the case clash: {:?}", r.warnings);
}
#[test]
fn golden_vault_bundle_report() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/fixtures/okf/golden/vault");
let opts = BuildOptions::for_dialect(crate::okf::Dialect::Obsidian);
let r = build(&root, &opts).unwrap().report;
assert_eq!(r.files_scanned, 13, "`.kglite/` is never walked");
assert_eq!(r.concepts, 13, "a note needs no frontmatter in a vault");
assert_eq!(r.dangling, 1, "`[[Missing]]`, named by a `depends_on:`");
assert_eq!(r.folder_notes, 1, "`projects.md` stands in for `projects/`");
assert_eq!(
r.nodes_by_label,
BTreeMap::from([
("Article".to_string(), 11),
("Initiative".to_string(), 2), (FOLDER_LABEL.to_string(), 3),
(TAG_LABEL.to_string(), 3), ("Keyword".to_string(), 2),
(DEFAULT_LABEL.to_string(), 1), (IMAGE_LABEL.to_string(), 2),
(ATTACHMENT_LABEL.to_string(), 2),
])
);
assert_eq!(
r.edges_by_type,
BTreeMap::from([
(CONTAINS_CONN_TYPE.to_string(), 8),
("LINKS_TO".to_string(), 7),
("RELATED_TO".to_string(), 1),
(EMBEDS_CONN_TYPE.to_string(), 1), (FOLDER_NOTE_CONN_TYPE.to_string(), 5),
("DEPENDS_ON".to_string(), 2), (TAGGED_CONN_TYPE.to_string(), 4),
("HAS_KEYWORD".to_string(), 4),
(HAS_IMAGE_CONN_TYPE.to_string(), 4),
(HAS_ATTACHMENT_CONN_TYPE.to_string(), 3),
])
);
assert_eq!(r.missing_attachments, 1, "`img/appendix.pdf`");
assert_eq!(r.ambiguous_attachments, 0);
assert_eq!(
r.indexes_declared, 2,
"concept_id, and toc_depth as a range"
);
assert_eq!(r.text_indexes_built, 1, "Initiative.body");
assert_eq!(
r.embed_targets,
vec![("Article".to_string(), "body".to_string())]
);
assert_eq!(r.skills_imported, 1);
assert_eq!(r.recipes_imported, 1);
assert_eq!(r.errors.len(), 1, "{:?}", r.errors);
assert!(
r.errors[0].contains("`alpha`")
&& r.errors[0].contains("notes/alpha.md")
&& r.errors[0].contains("projects/alpha.md"),
"{}",
r.errors[0]
);
assert_eq!(r.warnings.len(), 3, "{:?}", r.warnings);
assert!(
r.warnings[0].contains("`Roadmap` (projects/Roadmap.md)")
&& r.warnings[0].contains("`roadmap` (notes/roadmap.md)"),
"{}",
r.warnings[0]
);
assert_eq!(r.warnings[1], "missing attachment: `img/appendix.pdf`");
assert_eq!(r.warnings[2], "dangling link: `Missing`");
}
#[test]
fn golden_vault_bundle_without_its_config() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/fixtures/okf/golden/vault");
let dir = tempdir().unwrap();
copy_tree(&root, dir.path(), false);
let opts = BuildOptions::for_dialect(crate::okf::Dialect::Obsidian);
let r = build(dir.path(), &opts).unwrap().report;
assert_eq!(
r.nodes_by_label,
BTreeMap::from([
("Note".to_string(), 2),
("Initiative".to_string(), 2),
("projects".to_string(), 2),
("notes".to_string(), 6),
("archive".to_string(), 1),
(FOLDER_LABEL.to_string(), 3),
(TAG_LABEL.to_string(), 3),
(DEFAULT_LABEL.to_string(), 1),
(IMAGE_LABEL.to_string(), 2),
(ATTACHMENT_LABEL.to_string(), 2),
]),
"no `default_label`, no `keywords` hub"
);
assert_eq!(
r.edges_by_type.get("RELATED"),
Some(&1),
"the built-in ladder types the `## Related topics` links"
);
assert_eq!(r.edges_by_type.get("HAS_KEYWORD"), None);
assert_eq!(r.indexes_declared, 0);
assert_eq!(r.text_indexes_built, 0);
assert!(r.embed_targets.is_empty());
assert_eq!(r.skills_imported, 0, "no `.kglite/skills/` was copied");
assert_eq!(r.recipes_imported, 0);
}
#[test]
fn report_counts_every_node_and_edge_the_build_made() {
let dir = tempdir().unwrap();
write(
dir.path(),
"a.md",
"---\ntype: Note\ntags:\n- alpha\n---\nSee [gone](missing.md).\n# Citations\n[src](https://example.com)",
);
write(dir.path(), "sub/b.md", "---\ntype: Note\n---\nleaf");
write(dir.path(), "plain.md", "no frontmatter");
let out = build(dir.path(), &BuildOptions::default()).unwrap();
let r = &out.report;
assert_eq!(r.files_scanned, 3);
assert_eq!(r.concepts, 2, "plain.md has no frontmatter");
assert_eq!(r.dangling, 1, "missing.md");
assert_eq!(
r.nodes_by_label,
BTreeMap::from([
("Note".to_string(), 2),
("Tag".to_string(), 1),
("Source".to_string(), 1),
("Folder".to_string(), 1),
(DEFAULT_LABEL.to_string(), 1),
])
);
assert_eq!(
r.edges_by_type,
BTreeMap::from([
("LINKS_TO".to_string(), 1),
("CITES".to_string(), 1),
("TAGGED".to_string(), 1),
("CONTAINS".to_string(), 1),
])
);
assert!(r.errors.is_empty(), "{:?}", r.errors);
assert_eq!(
r.warnings,
vec!["dangling link: `missing`".to_string()],
"the stub is reported as the warning VAULT.md §9 classifies it as"
);
assert_eq!(
out.graph.graph.node_indices().count(),
r.nodes_by_label.values().sum::<usize>()
);
assert_eq!(
out.graph.graph.edge_count(),
r.edges_by_type.values().sum::<usize>()
);
}
#[test]
fn the_profile_prunes_directories_too() {
let dir = tempdir().unwrap();
write(dir.path(), "keep/a.md", "kept");
write(dir.path(), "drafts/b.md", "pruned");
let out = vault_build_with(dir.path(), |p| {
p.skip_dirs.push("drafts".to_string());
});
assert_eq!(out.report.files_scanned, 1);
assert_eq!(
labels_by_id(&out.graph).keys().cloned().collect::<Vec<_>>(),
vec!["a".to_string(), "keep".to_string()],
"the note and its Folder, and nothing from `drafts/`"
);
}
#[test]
fn empty_bundle_is_empty_graph() {
let dir = tempdir().unwrap();
let g = build(dir.path(), &BuildOptions::default()).unwrap().graph;
assert_eq!(g.graph.node_indices().count(), 0);
}