#![allow(clippy::unwrap_used, clippy::expect_used)]
use forensicnomicon::report::{Category, Severity, Source};
use sqlite_core::Database;
use sqlite_forensic::{audit, audit_carved_findings, AnomalyKind};
const DELETED: &[u8] = include_bytes!("../../tests/data/deleted_places.db");
const WAL_MAIN: &[u8] = include_bytes!("../../tests/data/wal_places.db");
const WAL_SIDE: &[u8] = include_bytes!("../../tests/data/wal_places.db-wal");
const CLEAN: &[u8] = include_bytes!("../../tests/data/places.db");
fn src() -> Source {
Source {
analyzer: "sqlite-forensic".to_string(),
scope: "moz_places".to_string(),
version: None,
}
}
#[test]
fn deleted_db_flags_nonempty_freelist() {
let db = Database::open(DELETED.to_vec()).expect("open");
let kinds: Vec<_> = audit(&db).into_iter().map(|a| a.kind).collect();
assert!(
kinds.contains(&AnomalyKind::NonEmptyFreelist { free_pages: 5 }),
"expected NonEmptyFreelist {{ free_pages: 5 }}, got {kinds:?}"
);
}
#[test]
fn clean_db_has_no_freelist_finding() {
let db = Database::open(CLEAN.to_vec()).expect("open clean");
let anomalies = audit(&db);
assert!(
anomalies
.iter()
.all(|a| !matches!(a.kind, AnomalyKind::NonEmptyFreelist { .. })),
"a never-deleted DB must not raise a freelist finding"
);
}
#[test]
fn wal_overlay_flags_uncheckpointed_state() {
let db = Database::open_with_wal(WAL_MAIN.to_vec(), WAL_SIDE).expect("open with wal");
let anomalies = audit(&db);
let wal = anomalies
.iter()
.find(|a| matches!(a.kind, AnomalyKind::WalUncheckpointedState { .. }))
.expect("an active WAL overlay must raise an uncheckpointed-state finding");
assert!(
wal.note.contains("acquire") && wal.note.contains("checkpoint"),
"WAL-uncheckpointed note must warn to acquire the live -wal before a checkpoint discards it, got: {}",
wal.note
);
let main_only = Database::open(WAL_MAIN.to_vec()).expect("open main");
assert!(
audit(&main_only)
.iter()
.all(|a| !matches!(a.kind, AnomalyKind::WalUncheckpointedState { .. })),
"main-only open must not claim uncheckpointed state"
);
}
#[test]
fn carved_findings_are_residue_and_confidence_graded() {
let db = Database::open(DELETED.to_vec()).expect("open");
let findings = audit_carved_findings(&db, 6, &src());
assert!(!findings.is_empty(), "carving must yield findings");
let f = &findings[0];
assert_eq!(f.code, "SQLITE-DELETED-RECORD-RECOVERED");
assert_eq!(f.category, Category::Residue);
assert_eq!(f.severity, Some(Severity::Medium));
assert!(
f.context.confidence.is_some(),
"carved finding must carry a confidence"
);
let fields: Vec<&str> = f.evidence.iter().map(|e| e.field.as_str()).collect();
assert!(fields.contains(&"rowid"));
assert!(fields.contains(&"source_page"));
assert!(fields.contains(&"cell_offset"));
}