#![cfg(all(feature = "render-wry", feature = "ocr-tesseract"))]
use std::path::PathBuf;
use chk_defaced::finding::Verdict;
use chk_defaced::ocr::{OcrHint, TesseractOcr};
fn find_tessdata() -> Option<PathBuf> {
let mut cands: Vec<PathBuf> = Vec::new();
if let Ok(p) = std::env::var("TESSDATA_PREFIX") {
cands.push(PathBuf::from(p));
}
if let Ok(appdata) = std::env::var("APPDATA") {
cands.push(PathBuf::from(&appdata).join("tesseract-rs/aarch64/dynamic/tessdata"));
cands.push(PathBuf::from(&appdata).join("tesseract-rs/x86_64/dynamic/tessdata"));
cands.push(PathBuf::from(&appdata).join("tesseract-rs/tessdata"));
}
cands.push(PathBuf::from("C:/Program Files/Tesseract-OCR/tessdata"));
cands.into_iter().find(|d| d.join("eng.traineddata").is_file())
}
fn find_fixture() -> Option<PathBuf> {
if let Ok(p) = std::env::var("CHK_DEFACED_TAMPERED_DOCX") {
let pb = PathBuf::from(p);
return pb.is_file().then_some(pb);
}
let here = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let guess = here.join("../defaced-test/replaced.docx");
guess.is_file().then_some(guess)
}
#[test]
fn docx_render_ocr_confirms_positional_substitution() {
let Some(docx) = find_fixture() else {
eprintln!("skip: no tampered DOCX fixture (set CHK_DEFACED_TAMPERED_DOCX)");
return;
};
let Some(tessdata) = find_tessdata() else {
eprintln!("skip: eng.traineddata not found");
return;
};
let mut report = chk_defaced::scan::scan_path(&docx, None).expect("scan");
let has_collision =
report.findings.iter().any(|f| f.rule.contains("GLYPH_SEMANTIC_REPLACEMENT"));
assert!(has_collision, "deterministic scan should find a glyph-semantic-replacement collision");
let ocr = match TesseractOcr::new(Some(tessdata), "eng", OcrHint::Block) {
Ok(e) => e,
Err(e) => {
eprintln!("skip: could not init Tesseract ({e:#})");
return;
}
};
chk_defaced::render::verify_docx_with_render(&mut report, &docx, &ocr)
.expect("render-OCR verify");
assert_eq!(report.verdict, Some(Verdict::Confirmed), "render-OCR should confirm the divergence");
let confirmed = report
.findings
.iter()
.find(|f| f.rule == "DOCX.RENDER_DIVERGENCE_CONFIRMED")
.expect("a DOCX.RENDER_DIVERGENCE_CONFIRMED finding");
let msg = confirmed.message.to_lowercase();
assert!(
msg.contains("maryland") && msg.contains("delaware"),
"confirmed finding should name the delaware→maryland substitution, got: {}",
confirmed.message
);
}
#[test]
fn docx_render_ocr_confirms_hidden_text() {
let here = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let docx = here.join("../defaced-test/hidden-text.docx");
if !docx.is_file() {
eprintln!("skip: hidden-text.docx fixture absent");
return;
}
let Some(tessdata) = find_tessdata() else {
eprintln!("skip: eng.traineddata not found");
return;
};
let mut report = chk_defaced::scan::scan_path(&docx, None).expect("scan");
assert!(
report.findings.iter().any(|f| f.rule == "DOCX.INVISIBLE_TEXT_COLOR"),
"deterministic scan should flag the near-white run"
);
let ocr = match TesseractOcr::new(Some(tessdata), "eng", OcrHint::Block) {
Ok(e) => e,
Err(e) => {
eprintln!("skip: could not init Tesseract ({e:#})");
return;
}
};
chk_defaced::render::verify_docx_with_render(&mut report, &docx, &ocr).expect("render-OCR verify");
let confirmed = report
.findings
.iter()
.find(|f| f.rule == "DOCX.HIDDEN_TEXT_CONFIRMED")
.expect("a DOCX.HIDDEN_TEXT_CONFIRMED finding");
let msg = confirmed.message.to_lowercase();
assert!(
msg.contains("ignore") || msg.contains("instructions"),
"confirmed hidden text should recover the injected words, got: {}",
confirmed.message
);
}