#![cfg(test)]
use std::collections::HashMap;
use std::path::Path;
use code_repo_wiki::incremental::state::GenerationState;
fn fixture(tag: &str, content: &str) -> (std::path::PathBuf, String) {
let dir = std::env::temp_dir().join(format!("code_repo_wiki_sync_{tag}_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let path = dir.join("wiki").join("zh").join("foo.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, content).unwrap();
(dir, path.to_string_lossy().to_string())
}
fn save_state(dir: &Path, doc_fps: HashMap<String, String>, protected: Vec<String>) {
let state = GenerationState {
last_commit_hash: None,
file_fingerprints: HashMap::new(),
doc_fingerprints: doc_fps,
doc_modules: HashMap::new(),
protected_docs: protected,
generated_at: String::new(),
tool_version: None,
failed_modules: vec![],
};
state.save(&dir.join(".state")).unwrap();
}
fn load_state(dir: &Path) -> GenerationState {
GenerationState::load(&dir.join(".state")).unwrap()
}
#[test]
fn test_sync_updates_fingerprint() {
let (dir, path_str) = fixture("update", "生成时内容");
let mut fps = HashMap::new();
fps.insert(
path_str.clone(),
GenerationState::compute_file_fingerprint(Path::new(&path_str)).unwrap(),
);
save_state(&dir, fps, vec![]);
std::fs::write(&path_str, "人工编辑后的内容").unwrap();
code_repo_wiki::commands::sync_from_git(&dir).unwrap();
let state = load_state(&dir);
assert_eq!(
state.doc_fingerprints.get(&path_str).unwrap(),
&GenerationState::compute_file_fingerprint(Path::new(&path_str)).unwrap(),
"sync 后指纹应更新为工作区当前内容"
);
assert_eq!(
std::fs::read_to_string(&path_str).unwrap(),
"人工编辑后的内容",
"sync 不应改写工作区内容"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_sync_skips_protected() {
let (dir, path_str) = fixture("protected", "生成时内容");
let mut fps = HashMap::new();
fps.insert(
path_str.clone(),
GenerationState::compute_file_fingerprint(Path::new(&path_str)).unwrap(),
);
let old_fp = fps.get(&path_str).unwrap().clone();
save_state(&dir, fps, vec![path_str.clone()]);
std::fs::write(&path_str, "人工修改后的受保护内容").unwrap();
code_repo_wiki::commands::sync_from_git(&dir).unwrap();
let state = load_state(&dir);
assert_eq!(
state.doc_fingerprints.get(&path_str).unwrap(),
&old_fp,
"受保护页面的指纹不应被 sync 更新"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_sync_without_state_records_new_fingerprints() {
let (dir, path_str) = fixture("fresh", "全新内容");
code_repo_wiki::commands::sync_from_git(&dir).unwrap();
let state = load_state(&dir);
assert_eq!(
state.doc_fingerprints.get(&path_str).unwrap(),
&GenerationState::compute_file_fingerprint(Path::new(&path_str)).unwrap(),
"无状态时 sync 应记录产物指纹"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_sync_corrupted_state_errors_explicitly() {
let (dir, _) = fixture("corrupt", "内容");
let state_dir = dir.join(".state");
std::fs::create_dir_all(&state_dir).unwrap();
std::fs::write(state_dir.join("generation_state.json"), "{ not valid json !").unwrap();
let err = code_repo_wiki::commands::sync_from_git(&dir).unwrap_err();
assert!(
err.to_string().contains("状态文件损坏"),
"损坏状态应显式报错而非静默重置: {}",
err
);
let _ = std::fs::remove_dir_all(&dir);
}