blazehash 0.2.5

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

fn write_manifest(dir: &TempDir) -> std::path::PathBuf {
    let p = dir.path().join("evidence.hash");
    fs::write(&p, concat!(
        "## case: CASE-001\n",
        "sha256  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  unchanged.txt\n",
        "sha256  bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb  modified.txt\n",
        "sha256  cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc  removed.txt\n",
    )).unwrap();
    p
}

fn write_patch(dir: &TempDir) -> std::path::PathBuf {
    let p = dir.path().join("changes.diff");
    fs::write(&p, concat!(
        "--- evidence.hash\n",
        "+++ evidence.hash\n",
        "-sha256  bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb  modified.txt\n",
        "+sha256  9999999999999999999999999999999999999999999999999999999999999999  modified.txt\n",
        "-sha256  cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc  removed.txt\n",
        "+sha256  dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd  added.txt\n",
    )).unwrap();
    p
}

#[test]
fn test_apply_patch_removes_minus_lines() {
    let dir = TempDir::new().unwrap();
    let manifest = write_manifest(&dir);
    let patch = write_patch(&dir);
    let out = Command::cargo_bin("blazehash").unwrap()
        .args(["apply-patch", manifest.to_str().unwrap(), patch.to_str().unwrap()])
        .output().unwrap();
    assert!(out.status.success(), "apply-patch failed: {}", String::from_utf8_lossy(&out.stderr));
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(!stdout.contains("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
        "removed entry must be gone");
    assert!(!stdout.contains("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
        "modified old hash must be gone");
}

#[test]
fn test_apply_patch_adds_plus_lines() {
    let dir = TempDir::new().unwrap();
    let manifest = write_manifest(&dir);
    let patch = write_patch(&dir);
    let out = Command::cargo_bin("blazehash").unwrap()
        .args(["apply-patch", manifest.to_str().unwrap(), patch.to_str().unwrap()])
        .output().unwrap();
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("added.txt"), "added entry must appear");
    assert!(stdout.contains("9999999999999999999999999999999999999999999999999999999999999999"),
        "modified new hash must appear");
}

#[test]
fn test_apply_patch_preserves_unchanged_entries() {
    let dir = TempDir::new().unwrap();
    let manifest = write_manifest(&dir);
    let patch = write_patch(&dir);
    let out = Command::cargo_bin("blazehash").unwrap()
        .args(["apply-patch", manifest.to_str().unwrap(), patch.to_str().unwrap()])
        .output().unwrap();
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("unchanged.txt"), "unchanged entry must be preserved");
    assert!(stdout.contains("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
        "unchanged hash must be preserved");
}

#[test]
fn test_apply_patch_preserves_headers() {
    let dir = TempDir::new().unwrap();
    let manifest = write_manifest(&dir);
    let patch = write_patch(&dir);
    let out = Command::cargo_bin("blazehash").unwrap()
        .args(["apply-patch", manifest.to_str().unwrap(), patch.to_str().unwrap()])
        .output().unwrap();
    assert!(out.status.success());
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("## case: CASE-001"), "headers must be preserved");
}

#[test]
fn test_apply_patch_output_to_file() {
    let dir = TempDir::new().unwrap();
    let manifest = write_manifest(&dir);
    let patch = write_patch(&dir);
    let out_path = dir.path().join("updated.hash");
    Command::cargo_bin("blazehash").unwrap()
        .args(["apply-patch", manifest.to_str().unwrap(), patch.to_str().unwrap(),
               "-o", out_path.to_str().unwrap()])
        .assert().success();
    let content = fs::read_to_string(&out_path).unwrap();
    assert!(content.contains("unchanged.txt"));
    assert!(content.contains("added.txt"));
}