use fsqlite_core::connection::Connection;
async fn build_clean_db(dir: &std::path::Path, name: &str) -> String {
let db = dir.join(name).to_string_lossy().into_owned();
let conn = Connection::open(&db).await.expect("open");
conn.execute("PRAGMA journal_mode=DELETE;")
.await
.expect("journal_mode");
conn.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, v TEXT);")
.await
.expect("create");
conn.execute("INSERT INTO t VALUES (1,'x'),(2,'y'),(3,'z');")
.await
.expect("insert");
conn.close().await.expect("close");
db
}
fn set_byte(db: &str, offset: usize, value: u8) {
let mut bytes = std::fs::read(db).expect("read image");
assert!(offset < bytes.len());
bytes[offset] = value;
std::fs::write(db, &bytes).expect("write corrupted image");
}
async fn fsqlite_rejects(db: &str) -> bool {
let Ok(conn) = Connection::open(db).await else {
return true; };
let rejected = conn.query("SELECT count(*) FROM t;").await.is_err()
|| conn
.query("PRAGMA integrity_check;")
.await
.map(|rows| {
!matches!(rows.first().map(|r| &r.values()[0]),
Some(fsqlite_types::value::SqliteValue::Text(s)) if s.as_ref() == "ok")
})
.unwrap_or(true);
conn.close().await.ok();
rejected
}
fn stock_rejects(db: &str) -> bool {
let Ok(conn) = rusqlite::Connection::open(db) else {
return true;
};
conn.query_row("SELECT count(*) FROM t;", [], |r| r.get::<_, i64>(0))
.is_err()
}
async fn assert_header_corruption_rejected(name: &str, offset: usize, value: u8) {
let dir = tempfile::tempdir().expect("tempdir");
let db = build_clean_db(dir.path(), &format!("{name}.db")).await;
assert!(
!stock_rejects(&db),
"{name}: pristine fixture must be readable before corruption"
);
set_byte(&db, offset, value);
assert!(
stock_rejects(&db),
"{name}: fixture invalid — stock accepted header corruption at offset {offset} \
(value {value:#x}); choose a critical header field"
);
assert!(
fsqlite_rejects(&db).await,
"{name}: fsqlite SILENTLY ACCEPTED header corruption at offset {offset} \
(value {value:#x}) that stock rejects — a broken header must fail loudly, not \
open and serve wrong data"
);
}
#[test]
fn header_magic_byte_0() {
asupersync::test_utils::run_test(|| async {
assert_header_corruption_rejected("magic0", 0, 0xFF).await;
});
}
#[test]
fn header_magic_byte_8() {
asupersync::test_utils::run_test(|| async {
assert_header_corruption_rejected("magic8", 8, 0x00).await;
});
}
#[test]
fn header_magic_byte_15() {
asupersync::test_utils::run_test(|| async {
assert_header_corruption_rejected("magic15", 15, 0x01).await;
});
}
#[test]
fn header_page_size_high_invalid() {
asupersync::test_utils::run_test(|| async {
assert_header_corruption_rejected("pagesize_hi", 16, 0xFF).await;
});
}
#[test]
fn header_page_size_low_not_pow2() {
asupersync::test_utils::run_test(|| async {
assert_header_corruption_rejected("pagesize_lo", 17, 0x01).await;
});
}