Files
guara/crates/guara-core/tests/recovery.rs
T
Lucas RayanandClaude Sonnet 5.5 45d535bdb7
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s
feat: explorador de arquivos, miniaturas, metadados e recursos periciais (0.3.0)
- Explorador com grade/lista virtualizada, navegação por tipo, data EXIF, pasta e
  câmera, seleção estilo gerenciador de arquivos e visualização rápida.
- Miniaturas no núcleo, metadados (EXIF, PDF, Office, MP4), datas MACB do NTFS/FAT/exFAT.
- Hashes MD5/SHA-1/SHA-256, busca de texto no disco, exportação CSV, relatório HTML,
  detecção de duplicadas, tema, correções de interface e textos sem travessões.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 16:16:20 -03:00

153 lines
4.4 KiB
Rust

//! Recuperação de ponta a ponta sobre imagens reais (FAT32, NTFS, ext4).
mod common;
use common::*;
use guara_core::*;
fn check(fs: &str, only_carvable: bool) {
let Some(dir) = fixtures() else { return };
let img = dir.join(format!("{fs}.img"));
let before = sha256_file(&img);
let res = scan_path(&img, &ScanOptions::default());
let out = tempfile::tempdir().unwrap();
let rep = recover_all(&img, &res.found, out.path());
assert!(rep.failed.is_empty(), "falhas: {:?}", rep.failed);
let got = hashes_in(out.path());
for (hash, path) in expected(&dir, fs) {
let carvable = [".jpg", ".png", ".pdf", ".docx", ".db"]
.iter()
.any(|e| path.ends_with(e));
if only_carvable && !carvable {
continue;
}
assert!(
got.contains_key(&hash),
"[{fs}] não recuperado bit a bit: {path}"
);
}
// Os dois métodos juntos não podem listar o mesmo conteúdo duas vezes.
let mut seen = std::collections::HashSet::new();
for f in &res.found {
if let Some(o) = f.first_offset() {
assert!(seen.insert(o), "[{fs}] achado duplicado no offset {o}");
}
}
if fs != "ext4" {
for f in res.found.iter().filter(|f| f.origin != Origin::Carved) {
assert!(
f.modified.is_some() && f.created.is_some(),
"[{fs}] {} sem datas de modificação/criação",
f.name
);
}
}
assert_eq!(before, sha256_file(&img), "a origem foi modificada!");
}
#[test]
fn fat32_recupera_apagados_com_nome_e_pasta() {
check("fat32", false);
let Some(dir) = fixtures() else { return };
let res = scan_path(&dir.join("fat32.img"), &ScanOptions::default());
assert!(res
.found
.iter()
.any(|f| f.dir.as_deref() == Some("Documentos/Trabalho") && f.name == "relatorio.pdf"));
assert!(
res.found.iter().all(|f| f.name != "mantida.jpg"),
"arquivo existente não deve ser listado"
);
}
#[test]
fn ntfs_recupera_apagados() {
check("ntfs", false);
}
#[test]
fn ext4_recupera_por_assinatura() {
check("ext4", true);
}
#[test]
fn varredura_rapida_nao_faz_carving() {
let Some(dir) = fixtures() else { return };
let res = scan_path(
&dir.join("ext4.img"),
&ScanOptions {
mode: ScanMode::Quick,
..Default::default()
},
);
assert!(res.found.is_empty());
let res = scan_path(
&dir.join("ntfs.img"),
&ScanOptions {
mode: ScanMode::Quick,
..Default::default()
},
);
assert_eq!(
res.found
.iter()
.filter(|f| f.origin == Origin::Ntfs)
.count(),
7
);
}
/// Regressão: mkfs.vfat -F 32 em mídia pequena (< 65525 clusters) é FAT32 pelo BPB.
#[test]
fn fat32_pequeno_com_poucos_clusters_e_reconhecido() {
use std::process::Command;
let dir = tempfile::tempdir().unwrap();
let img = dir.path().join("p.img");
let ok = Command::new("truncate")
.args(["-s", "32M"])
.arg(&img)
.status()
.map(|s| s.success())
.unwrap_or(false)
&& Command::new("mkfs.vfat")
.args(["-F", "32"])
.arg(&img)
.output()
.map(|o| o.status.success())
.unwrap_or(false);
let file = dir.path().join("arquivo.txt");
std::fs::write(&file, b"conteudo de teste\n".repeat(500)).unwrap();
let ok = ok
&& Command::new("mcopy")
.arg("-i")
.arg(&img)
.arg(&file)
.arg("::/")
.status()
.map(|s| s.success())
.unwrap_or(false)
&& Command::new("mdel")
.arg("-i")
.arg(&img)
.arg("::/arquivo.txt")
.status()
.map(|s| s.success())
.unwrap_or(false);
if !ok {
eprintln!("SKIP: mtools/dosfstools ausentes");
return;
}
let res = scan_path(
&img,
&ScanOptions {
mode: ScanMode::Quick,
..Default::default()
},
);
let layout = res.layout.unwrap();
assert_eq!(layout.partitions[0].fs, FsKind::Fat32);
assert_eq!(res.found.len(), 1);
let out = tempfile::tempdir().unwrap();
recover_all(&img, &res.found, out.path());
assert!(hashes_in(out.path()).contains_key(&sha256(&b"conteudo de teste\n".repeat(500))));
}