use std::fs::OpenOptions;
use std::io::{Seek, SeekFrom, Write};
use assert_cmd::Command;
use obj::{Db, Document};
use obj_core::pager::page::PAGE_SIZE;
use predicates::str::contains;
use serde::{Deserialize, Serialize};
use tempfile::TempDir;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SmokeUser {
email: String,
handle: String,
}
impl Document for SmokeUser {
const COLLECTION: &'static str = "smoke_users";
const VERSION: u32 = 1;
}
fn seed(path: &std::path::Path) {
let db = Db::open(path).expect("open source");
for i in 0..8u32 {
db.insert(SmokeUser {
email: format!("user{i}@example.com"),
handle: format!("u{i}"),
})
.expect("insert");
}
}
#[test]
fn cli_smoke_clean_then_corrupted() {
let dir = TempDir::new().expect("tmp");
let db_path = dir.path().join("smoke.obj");
let backup_path = dir.path().join("backup.obj");
seed(&db_path);
Command::cargo_bin("obj")
.expect("binary")
.arg("stat")
.arg(&db_path)
.assert()
.success()
.stdout(contains("## smoke_users"))
.stdout(contains("doc_count: 8"));
Command::cargo_bin("obj")
.expect("binary")
.arg("check")
.arg(&db_path)
.assert()
.success()
.stdout(contains("ok:"));
Command::cargo_bin("obj")
.expect("binary")
.arg("backup")
.arg(&db_path)
.arg(&backup_path)
.assert()
.success();
Command::cargo_bin("obj")
.expect("binary")
.arg("check")
.arg(&backup_path)
.assert()
.success()
.stdout(contains("ok:"));
corrupt_one_byte(&db_path);
Command::cargo_bin("obj")
.expect("binary")
.arg("check")
.arg(&db_path)
.assert()
.code(1)
.stderr(contains("ChecksumMismatch"));
}
fn corrupt_one_byte(path: &std::path::Path) {
checkpoint_and_close(path).expect("checkpoint");
let target_pid = locate_primary_root(path).expect("locate primary root");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.expect("open for corruption");
let target_off = target_pid
.saturating_mul(PAGE_SIZE as u64)
.saturating_add(64);
file.seek(SeekFrom::Start(target_off)).expect("seek");
file.write_all(&[0xFFu8]).expect("flip");
}
fn checkpoint_and_close(path: &std::path::Path) -> obj::Result<()> {
use obj_core::pager::{Config, Pager};
use obj_core::platform::FileHandle;
let pager = Pager::<FileHandle>::open(path, Config::default())?;
pager.close()
}
fn locate_primary_root(path: &std::path::Path) -> obj::Result<u64> {
use obj_core::pager::{Config, Pager};
use obj_core::platform::FileHandle;
use obj_core::Catalog;
let mut pager = Pager::<FileHandle>::open(path, Config::default())?;
pager.begin_txn();
let catalog = Catalog::<FileHandle>::open_or_init(&mut pager)?;
let descriptor = catalog
.get(&mut pager, "smoke_users")?
.expect("smoke_users present");
pager.end_txn();
Ok(descriptor.primary_root)
}