use super::support::*;
use super::*;
#[test]
fn codewiki_verbose_progress_captures_generation_order() {
let project = tempfile::tempdir().expect("project tempdir");
std::fs::create_dir_all(project.path().join("src")).expect("source dir");
std::fs::write(project.path().join("src/api.rs"), "pub fn api() {}\n").expect("write api");
std::fs::write(
project.path().join("src/lib.rs"),
"pub struct Client;\npub fn connect() {}\npub fn render() {}\n",
)
.expect("write lib");
let input = CodewikiInput {
leading_chunks: std::collections::BTreeMap::new(),
files: vec!["src/api.rs".to_string(), "src/lib.rs".to_string()],
graph_edges: Vec::new(),
graph_availability: CodewikiGraphAvailability::Available,
symbols: vec![
test_symbol("src/lib.rs", "Client", "class", 1, "pub struct Client;"),
test_symbol("src/lib.rs", "connect", "function", 2, "pub fn connect()"),
test_symbol("src/lib.rs", "render", "function", 3, "pub fn render()"),
],
};
let mut progress = CodewikiProgress::capture();
let mut generator = |prompt: &str, _system: &str, _tier: PromptTier| {
if prompt.contains("src/api.rs") {
Some("Documents API entry points [src/api.rs:1].".to_string())
} else {
Some("Documents library behavior [src/lib.rs:1].".to_string())
}
};
let docs = generate_hierarchical_docs_with_progress(
&input,
Some(&mut generator),
AiDepth::Symbols,
&mut progress,
);
let changed = write_incremental_doc_set_with_snapshot(
project.path(),
&project.path().join("codewiki"),
&docs,
None,
"symbols",
DocPruneScope::unscoped(),
)
.expect("write docs");
assert!(!changed.is_empty());
let lines = progress.into_lines();
assert!(lines.iter().all(|line| line.starts_with("codewiki: ")));
assert_before(
&lines,
"generating file docs for 2 files",
"file 1/2 src/api.rs",
);
assert_before(&lines, "file 1/2 src/api.rs", "file 2/2 src/lib.rs");
assert_before(&lines, "file 2/2 src/lib.rs", "symbol 1/3 Client");
assert_before(&lines, "symbol 1/3 Client", "symbol 3/3 render");
assert_before(&lines, "generating module docs", "module 1/1 src");
assert_before(&lines, "module 1/1 src", "generating repo overview");
assert_before(
&lines,
"generating repo overview",
"generating architecture docs",
);
assert_before(&lines, "generating architecture docs", "subsystem 1/1 src");
assert_before(&lines, "subsystem 1/1 src", "generating onboarding docs");
assert_before(
&lines,
"generating onboarding docs",
"generating hotspots docs",
);
assert!(
lines
.iter()
.any(|line| line.contains("generating hotspots docs")),
"hotspots generation closes the build order: {lines:#?}"
);
}
#[test]
fn codewiki_verbose_progress_silent_sink_emits_no_lines() {
let mut progress = CodewikiProgress::silent();
progress.emit("loading indexed files");
assert!(progress.into_lines().is_empty());
let docs = generate_hierarchical_docs(&progress_input(), None);
assert!(
docs.iter()
.all(|(_, content)| !content.contains("codewiki:"))
);
}
#[test]
fn codewiki_verbose_progress_not_serialized_in_summary_json() {
let summary = CodewikiRunSummary {
command: "codewiki",
project_id: "project-1".to_string(),
project_root: "/repo".to_string(),
out_dir: "/repo/codewiki".to_string(),
generated_pages: 3,
changed_paths: vec!["code/repo.md".to_string()],
skipped: 2,
files: 1,
modules: 1,
symbols: 3,
ai_enabled: true,
degraded_pages: Vec::new(),
};
let value = serde_json::to_value(summary).expect("summary json");
assert!(value.get("progress").is_none());
assert!(value.get("stderr").is_none());
assert_eq!(value["changed_paths"][0], "code/repo.md");
assert_eq!(value["ai_enabled"], true);
}
#[test]
fn codewiki_verbose_progress_reflects_ai_depth_gating() {
let mut progress = CodewikiProgress::capture();
let mut generator = |_prompt: &str, _system: &str, _tier: PromptTier| None;
let docs = generate_hierarchical_docs_with_progress(
&progress_input(),
Some(&mut generator),
AiDepth::Sections,
&mut progress,
);
assert!(!docs.is_empty());
let lines = progress.into_lines();
assert!(
lines.iter().any(|line| line.contains("building file doc")),
"sections depth should report structural file assembly: {lines:#?}"
);
assert!(
lines
.iter()
.all(|line| !line.contains("generating file doc")),
"sections depth must not claim file generation: {lines:#?}"
);
assert!(
lines
.iter()
.all(|line| !line.contains("generating symbol doc")),
"sections depth must not claim symbol generation: {lines:#?}"
);
let mut progress = CodewikiProgress::capture();
let mut generator = |_prompt: &str, _system: &str, _tier: PromptTier| None;
let docs = generate_hierarchical_docs_with_progress(
&progress_input(),
Some(&mut generator),
AiDepth::Files,
&mut progress,
);
assert!(!docs.is_empty());
let lines = progress.into_lines();
assert!(
lines
.iter()
.any(|line| line.contains("generating file doc")),
"files depth should claim file generation: {lines:#?}"
);
assert!(
lines
.iter()
.all(|line| !line.contains("generating symbol doc")),
"files depth must not claim symbol generation: {lines:#?}"
);
}
#[test]
fn codewiki_symbol_loading_batches_files_once() {
let files = vec![
"src/a.rs".to_string(),
"src/b.rs".to_string(),
"src/c.rs".to_string(),
];
let mut calls = 0;
let mut progress = CodewikiProgress::capture();
let symbols = load_symbols_for_codewiki(&files, &mut progress, |paths| {
calls += 1;
assert_eq!(paths, files.as_slice());
Ok(vec![test_symbol(
"src/a.rs",
"load",
"function",
1,
"pub fn load() {}",
)])
})
.expect("symbol batch loads");
assert_eq!(calls, 1);
assert_eq!(symbols.len(), 1);
assert_eq!(
progress.into_lines(),
vec!["codewiki: loading symbols for 3 files".to_string()]
);
}
fn progress_input() -> CodewikiInput {
CodewikiInput {
leading_chunks: std::collections::BTreeMap::new(),
files: vec!["src/lib.rs".to_string()],
graph_edges: Vec::new(),
graph_availability: CodewikiGraphAvailability::Available,
symbols: vec![test_symbol(
"src/lib.rs",
"Client",
"class",
1,
"pub struct Client;",
)],
}
}
fn assert_before(lines: &[String], first: &str, second: &str) {
let first_index = line_index(lines, first);
let second_index = line_index(lines, second);
assert!(
first_index < second_index,
"expected {first:?} before {second:?}, got {lines:#?}"
);
}
fn line_index(lines: &[String], needle: &str) -> usize {
lines
.iter()
.position(|line| line.contains(needle))
.unwrap_or_else(|| panic!("missing progress line containing {needle:?}: {lines:#?}"))
}