#![forbid(unsafe_code)]
#[cfg(feature = "kv-redb")]
#[test]
fn detect_backend_redb() {
let path = std::env::temp_dir().join(format!(
"detect_redb_{}_{:?}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
));
let store = oxistore::open(&path).expect("open redb");
store.put(b"k", b"v").expect("put");
drop(store);
let detected = oxistore::detect_backend(&path).expect("detect");
assert_eq!(detected, oxistore::StoreKind::Redb);
let _ = std::fs::remove_file(&path);
}
#[cfg(feature = "kv-sled")]
#[test]
fn detect_backend_sled() {
let path = std::env::temp_dir().join(format!(
"detect_sled_{}_{:?}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
));
let store = oxistore::open_with(oxistore::StoreKind::Sled, &path).expect("open sled");
store.put(b"k", b"v").expect("put");
drop(store);
let detected = oxistore::detect_backend(&path).expect("detect");
assert_eq!(detected, oxistore::StoreKind::Sled);
let _ = std::fs::remove_dir_all(&path);
}
#[test]
fn detect_backend_missing_path_returns_error() {
let path = std::env::temp_dir().join(format!(
"detect_missing_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
));
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_dir_all(&path);
let result = oxistore::detect_backend(&path);
assert!(result.is_err(), "expected error for non-existent path");
}
#[cfg(feature = "kv-redb")]
#[test]
fn destroy_removes_redb_store() {
let path = std::env::temp_dir().join(format!(
"destroy_redb_{}_{:?}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
));
let store = oxistore::open(&path).expect("open");
store.put(b"k", b"v").expect("put");
drop(store);
assert!(path.exists(), "store file should exist before destroy");
oxistore::destroy(oxistore::StoreKind::Redb, &path).expect("destroy");
assert!(!path.exists(), "store file should be removed after destroy");
}
#[cfg(feature = "kv-sled")]
#[test]
fn destroy_removes_sled_store() {
let path = std::env::temp_dir().join(format!(
"destroy_sled_{}_{:?}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
));
let store = oxistore::open_with(oxistore::StoreKind::Sled, &path).expect("open sled");
store.put(b"k", b"v").expect("put");
drop(store);
assert!(path.exists(), "store dir should exist before destroy");
oxistore::destroy(oxistore::StoreKind::Sled, &path).expect("destroy");
assert!(!path.exists(), "store dir should be removed after destroy");
}
#[test]
fn destroy_noop_when_path_absent() {
let path = std::env::temp_dir().join(format!(
"destroy_absent_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
));
oxistore::destroy(oxistore::StoreKind::Redb, &path)
.expect("destroy on absent path should be a no-op");
}
#[cfg(feature = "kv-redb")]
#[test]
fn backup_store_redb_produces_file() {
let src = std::env::temp_dir().join(format!(
"backup_src_{}_{:?}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
));
let dst = std::env::temp_dir().join(format!(
"backup_dst_{}_{:?}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
));
let store = oxistore::open(&src).expect("open src");
store.put(b"backup-key", b"backup-val").expect("put");
drop(store);
let result = oxistore::backup_store(oxistore::StoreKind::Redb, &src, &dst);
drop(result);
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&dst);
}
#[test]
fn prelude_imports_compile() {
use oxistore::prelude::*;
let _: Option<StoreError> = None;
let _: Option<StoreKind> = None;
}
#[cfg(feature = "encrypt")]
#[test]
fn encrypt_key_rotation_error_display() {
use oxistore_encrypt::EncryptError;
let err = EncryptError::KeyRotation {
old_version: 1,
new_version: 2,
reason: "test reason".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("1"), "should mention old version");
assert!(msg.contains("2"), "should mention new version");
assert!(msg.contains("test reason"), "should contain reason text");
}
#[cfg(feature = "encrypt")]
#[test]
fn encrypt_encrypted_kv_debug_redacts_key() {
use oxistore_core::{KvSnapshot, KvStore, KvTxn, RangeIter, StoreError};
use oxistore_encrypt::{EncryptedKv, StaticKey};
use std::collections::HashMap;
use std::sync::Mutex;
#[derive(Default)]
struct MemStore(Mutex<HashMap<Vec<u8>, Vec<u8>>>);
impl KvStore for MemStore {
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError> {
Ok(self.0.lock().expect("lock").get(key).cloned())
}
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), StoreError> {
self.0
.lock()
.expect("lock")
.insert(key.to_vec(), value.to_vec());
Ok(())
}
fn delete(&self, key: &[u8]) -> Result<(), StoreError> {
self.0.lock().expect("lock").remove(key);
Ok(())
}
fn range<'a>(&'a self, lo: &[u8], hi: &[u8]) -> Result<RangeIter<'a>, StoreError> {
let guard = self.0.lock().expect("lock");
let lo = lo.to_vec();
let hi = hi.to_vec();
let items: Vec<_> = guard
.iter()
.filter(|(k, _)| k.as_slice() >= lo.as_slice() && k.as_slice() < hi.as_slice())
.map(|(k, v)| Ok((k.clone(), v.clone())))
.collect();
Ok(Box::new(items.into_iter()))
}
fn iter<'a>(&'a self) -> Result<RangeIter<'a>, StoreError> {
let guard = self.0.lock().expect("lock");
let items: Vec<_> = guard
.iter()
.map(|(k, v)| Ok((k.clone(), v.clone())))
.collect();
Ok(Box::new(items.into_iter()))
}
fn transaction(&self) -> Result<Box<dyn KvTxn + '_>, StoreError> {
Err(StoreError::Unsupported("no txn in MemStore".to_string()))
}
fn snapshot(&self) -> Result<Box<dyn KvSnapshot + '_>, StoreError> {
Err(StoreError::Unsupported(
"no snapshot in MemStore".to_string(),
))
}
fn flush(&self) -> Result<(), StoreError> {
Ok(())
}
}
let inner = MemStore::default();
let key_bytes = [0u8; 32];
let key_provider = StaticKey::new(key_bytes.to_vec());
let encrypted = EncryptedKv::new(inner, key_provider);
let debug_str = format!("{encrypted:?}");
assert!(
debug_str.contains("EncryptedKv"),
"debug output should include struct name"
);
assert!(
debug_str.contains("REDACTED"),
"debug output should redact key material: got {debug_str}"
);
}
#[cfg(feature = "blob")]
#[test]
fn open_blob_returns_local_blob_store() {
use oxistore_blob::LocalBlobStore;
let path = std::env::temp_dir().join(format!(
"open_blob_test_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos()
));
let store: LocalBlobStore = oxistore::open_blob(&path).expect("open_blob");
let _ = store;
}