blazehash 0.2.5

Forensic file hasher — hashdeep for the modern era, BLAKE3 by default
Documentation
use assert_cmd::Command;
use std::io::Write;
use tempfile::NamedTempFile;

const MANIFEST: &str = "\
## case: EXCLUDE-TEST
sha256  aaaa1111111111111111111111111111111111111111111111111111111111111111  /evidence/file1.exe
sha256  bbbb2222222222222222222222222222222222222222222222222222222222222222  /docs/readme.txt
blake3  cccc3333333333333333333333333333333333333333333333333333333333333333  /tmp/scratch.tmp
sha256  dddd4444444444444444444444444444444444444444444444444444444444444444  /evidence/report.pdf
";

fn write_manifest() -> NamedTempFile {
    let mut f = NamedTempFile::new().unwrap();
    f.write_all(MANIFEST.as_bytes()).unwrap();
    f.flush().unwrap();
    f
}

#[test]
fn test_exclude_drops_matching_entries() {
    let manifest = write_manifest();
    let output = Command::cargo_bin("blazehash")
        .unwrap()
        .args(["exclude", manifest.path().to_str().unwrap(), "--exclude-pattern", "*.tmp"])
        .output()
        .unwrap();

    assert!(output.status.success(), "stderr: {}", String::from_utf8_lossy(&output.stderr));
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(!stdout.contains("scratch.tmp"), "scratch.tmp should be excluded");
    assert!(stdout.contains("file1.exe"), "file1.exe should be kept");
    assert!(stdout.contains("readme.txt"), "readme.txt should be kept");
}

#[test]
fn test_exclude_preserves_headers() {
    let manifest = write_manifest();
    let output = Command::cargo_bin("blazehash")
        .unwrap()
        .args(["exclude", manifest.path().to_str().unwrap(), "--exclude-pattern", "*.tmp"])
        .output()
        .unwrap();

    assert!(output.status.success(), "stderr: {}", String::from_utf8_lossy(&output.stderr));
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("## case:"), "header should be preserved in output");
}

#[test]
fn test_exclude_glob_with_directory_prefix() {
    let manifest = write_manifest();
    let output = Command::cargo_bin("blazehash")
        .unwrap()
        .args(["exclude", manifest.path().to_str().unwrap(), "--exclude-pattern", "/evidence/*"])
        .output()
        .unwrap();

    assert!(output.status.success(), "stderr: {}", String::from_utf8_lossy(&output.stderr));
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(!stdout.contains("file1.exe"), "file1.exe should be excluded");
    assert!(!stdout.contains("report.pdf"), "report.pdf should be excluded");
    assert!(stdout.contains("readme.txt"), "readme.txt should be kept");
}

#[test]
fn test_exclude_no_matches_passes_all_through() {
    let manifest = write_manifest();
    let output = Command::cargo_bin("blazehash")
        .unwrap()
        .args(["exclude", manifest.path().to_str().unwrap(), "--exclude-pattern", "*.xyz"])
        .output()
        .unwrap();

    assert!(output.status.success(), "stderr: {}", String::from_utf8_lossy(&output.stderr));
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("file1.exe"), "file1.exe should be kept");
    assert!(stdout.contains("readme.txt"), "readme.txt should be kept");
    assert!(stdout.contains("scratch.tmp"), "scratch.tmp should be kept");
    assert!(stdout.contains("report.pdf"), "report.pdf should be kept");
}

#[test]
fn test_exclude_output_to_file() {
    let manifest = write_manifest();
    let out_file = NamedTempFile::new().unwrap();

    Command::cargo_bin("blazehash")
        .unwrap()
        .args([
            "exclude",
            manifest.path().to_str().unwrap(),
            "--exclude-pattern",
            "*.tmp",
            "-o",
            out_file.path().to_str().unwrap(),
        ])
        .assert()
        .success();
    let contents = std::fs::read_to_string(out_file.path()).unwrap();
    assert!(!contents.contains("scratch.tmp"), "excluded entry should not be in output file");
    assert!(contents.contains("file1.exe"), "kept entry should be in output file");
}