use std::time::Duration;
use emdb::{EmdbBuilder, Ttl};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::temp_dir().join("emdb-smoke.emdb");
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_file(format!("{}.lock", path.display()));
let _ = std::fs::remove_file(format!("{}.compact.tmp", path.display()));
let _ = std::fs::remove_file(format!("{}.encbak", path.display()));
println!("=== emdb smoke test ===");
println!("path: {}", path.display());
println!("\n[session 1] open + write");
let db = EmdbBuilder::new()
.path(&path)
.enable_range_scans(true)
.default_ttl(Duration::from_secs(3600))
.build()?;
db.insert("hello", "world")?;
db.insert("user:001", "alice")?;
db.insert("user:002", "bob")?;
db.insert("user:003", "carol")?;
println!(" inserted 4 records, len() = {}", db.len()?);
db.insert_with_ttl("ephemeral", "ghost", Ttl::After(Duration::from_millis(50)))?;
println!(" inserted 'ephemeral' with 50ms TTL");
assert!(
db.get("ephemeral")?.is_some(),
"should be alive immediately"
);
std::thread::sleep(Duration::from_millis(80));
assert!(
db.get("ephemeral")?.is_none(),
"should be gone after 80ms wait"
);
println!(" ✓ TTL evict-on-read works");
let users = db.range(b"user:".to_vec()..b"user;".to_vec())?;
println!(" range scan 'user:..user;' → {} records:", users.len());
for (k, v) in &users {
println!(
" {} = {}",
String::from_utf8_lossy(k),
String::from_utf8_lossy(v)
);
}
assert_eq!(users.len(), 3);
let prefix = db.range_prefix(b"user:")?;
assert_eq!(prefix.len(), 3);
println!(" ✓ range_prefix matches range");
let sessions = db.namespace("sessions")?;
sessions.insert(b"sid-abc", b"token-xyz")?;
sessions.insert(b"sid-def", b"token-uvw")?;
println!(" namespace 'sessions' len = {}", sessions.len()?);
let focus = db.focus("config");
focus.set("theme", "dark")?;
focus.set("language", "en")?;
let theme = focus.get("theme")?.unwrap();
println!(
" focus('config').get('theme') = {}",
String::from_utf8_lossy(&theme)
);
assert_eq!(theme, b"dark");
db.transaction(|tx| {
tx.insert("tx-key-1", "tx-val-1")?;
tx.insert("tx-key-2", "tx-val-2")?;
Ok(())
})?;
println!(" ✓ transaction committed 2 keys");
db.flush()?;
let size_before_compact = std::fs::metadata(&path)?.len();
println!(" flushed; on-disk size = {} bytes", size_before_compact);
let _ = db.remove(b"hello")?;
let _ = db.remove(b"tx-key-1")?;
let _ = sessions.remove(b"sid-abc")?;
db.flush()?;
let size_after_removes = std::fs::metadata(&path)?.len();
println!(
" after removes (tombstones added), size = {} bytes",
size_after_removes
);
db.compact()?;
db.flush()?;
let size_after_compact = std::fs::metadata(&path)?.len();
println!(" after compact(), size = {} bytes", size_after_compact);
assert!(
size_after_compact < size_after_removes,
"compact should shrink"
);
drop(focus);
drop(sessions);
drop(db);
println!(" dropped all handles");
println!("\n[session 2] reopen + verify");
let db = EmdbBuilder::new()
.path(&path)
.enable_range_scans(true)
.default_ttl(Duration::from_secs(3600))
.build()?;
println!(" reopen successful; len() = {}", db.len()?);
assert_eq!(db.get(b"hello")?, None, "removed key stays removed");
assert_eq!(db.get(b"user:001")?.as_deref(), Some(b"alice".as_slice()));
assert_eq!(
db.get(b"tx-key-2")?.as_deref(),
Some(b"tx-val-2".as_slice())
);
println!(" ✓ default-namespace records intact");
let sessions = db.namespace("sessions")?;
assert_eq!(sessions.get(b"sid-abc")?, None, "removed session gone");
assert_eq!(
sessions.get(b"sid-def")?.as_deref(),
Some(b"token-uvw".as_slice())
);
println!(" ✓ named namespace 'sessions' rebound to same id, records intact");
let focus = db.focus("config");
let theme = focus.get("theme")?.unwrap();
assert_eq!(theme, b"dark");
println!(" ✓ focus('config').get('theme') = dark");
let users = db.range(b"user:".to_vec()..b"user;".to_vec())?;
assert_eq!(users.len(), 3, "range index rebuilt from records on reopen");
println!(" ✓ range index rebuilt: {} 'user:*' records", users.len());
let mut names = db.list_namespaces()?;
names.sort();
println!(" list_namespaces() = {:?}", names);
drop(focus);
drop(sessions);
drop(db);
println!("\n[session 3] encryption round-trip");
let enc_path = std::env::temp_dir().join("emdb-smoke-enc.emdb");
let _ = std::fs::remove_file(&enc_path);
let _ = std::fs::remove_file(format!("{}.lock", enc_path.display()));
let db = EmdbBuilder::new()
.path(&enc_path)
.encryption_passphrase("correct horse battery staple")
.build()?;
db.insert("secret-1", "shhh")?;
db.insert("secret-2", "more shhh")?;
db.flush()?;
let size = std::fs::metadata(&enc_path)?.len();
println!(" inserted 2 records, encrypted file size = {} bytes", size);
let raw = std::fs::read(&enc_path)?;
assert!(
!raw.windows(4).any(|w| w == b"shhh"),
"plaintext leaked into the encrypted file!"
);
println!(" ✓ raw on-disk scan: 'shhh' plaintext not present");
drop(db);
let reopened = EmdbBuilder::new()
.path(&enc_path)
.encryption_passphrase("correct horse battery staple")
.build()?;
let s1 = reopened.get(b"secret-1")?.unwrap();
assert_eq!(s1, b"shhh");
println!(
" ✓ reopen with same passphrase: secret-1 = {}",
String::from_utf8_lossy(&s1)
);
drop(reopened);
let wrong = EmdbBuilder::new()
.path(&enc_path)
.encryption_passphrase("definitely the wrong horse")
.build();
match wrong {
Err(emdb::Error::EncryptionKeyMismatch) => {
println!(" ✓ wrong passphrase correctly rejected with EncryptionKeyMismatch");
}
Err(other) => {
println!(" ✗ wrong passphrase rejected but with unexpected error: {other:?}");
}
Ok(_) => {
println!(" ✗ WRONG: bad passphrase opened the database!");
}
}
let _ = std::fs::remove_file(&enc_path);
let _ = std::fs::remove_file(format!("{}.lock", enc_path.display()));
println!("\n[cleanup] removing test files");
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_file(format!("{}.lock", path.display()));
println!("\n=== ALL CHECKS PASSED ===");
Ok(())
}