#![allow(clippy::unwrap_used, clippy::expect_used, clippy::pedantic)]
use std::path::PathBuf;
use sqlite_core::{Database, Value};
use sqlite_forensic::carve_all_deleted_records;
fn corpus_root() -> Option<PathBuf> {
match std::env::var("SQLITE_FORENSIC_UNHIDE_CORPUS") {
Ok(p) if !p.is_empty() => {
let path = PathBuf::from(p);
if path.is_dir() {
Some(path)
} else {
eprintln!(
"SKIP sqlite_unhide_corpus: SQLITE_FORENSIC_UNHIDE_CORPUS={} is not a directory",
path.display()
);
None
}
}
_ => {
eprintln!(
"SKIP sqlite_unhide_corpus: set SQLITE_FORENSIC_UNHIDE_CORPUS to a local copy of \
little-brother/sqlite-unhide tests/extra (home-use, never committed)"
);
None
}
}
}
fn open(root: &std::path::Path, stem: &str) -> Option<Database> {
let path = root.join(format!("{stem}.db"));
let bytes = std::fs::read(&path).ok()?;
Database::open(bytes).ok()
}
#[test]
fn no_full_record_is_structural_noise() {
let Some(root) = corpus_root() else {
return;
};
let mut checked = 0usize;
for i in 1..=9 {
let stem = format!("{i:02}");
let Some(db) = open(&root, &stem) else {
continue;
};
checked += 1;
for rec in carve_all_deleted_records(&db) {
let content_free = rec.values.len() >= 2
&& rec.values.iter().skip(1).all(|v| matches!(v, Value::Null));
assert!(
!content_free,
"{stem}.db: recovered a content-free record (rowid echo + all-NULL tail): {:?}",
rec.values
);
}
}
assert!(
checked > 0,
"SQLITE_FORENSIC_UNHIDE_CORPUS contained no 01.db..09.db"
);
eprintln!("sqlite_unhide_corpus: structural-noise invariant held across {checked} database(s)");
}
#[test]
fn recovers_utf8_high_rowid_rows_from_09() {
let Some(root) = corpus_root() else {
return;
};
let Some(db) = open(&root, "09") else {
eprintln!("SKIP: 09.db absent from corpus");
return;
};
let texts: Vec<String> = carve_all_deleted_records(&db)
.into_iter()
.flat_map(|r| r.values)
.filter_map(|v| match v {
Value::Text(t) => Some(t),
_ => None,
})
.collect();
assert!(
texts.iter().any(|t| t.contains("Столица")),
"09.db: deleted UTF-8 row (id 5014) not recovered; got {texts:?}"
);
assert!(
texts.iter().any(|t| t.contains("Весь мир")),
"09.db: deleted UTF-8 row (id 5002) not recovered; got {texts:?}"
);
}