use entidb_codec::CanonicalEncoder;
use entidb_core::{CollectionId, CoreError, Database, EntityId};
use entidb_storage::StorageBackend;
use std::collections::HashMap;
pub struct IntegrationHarness {
pub db: Database,
entities: HashMap<(CollectionId, EntityId), Vec<u8>>,
}
impl IntegrationHarness {
pub fn new() -> Self {
Self {
db: Database::open_in_memory().expect("Failed to open database"),
entities: HashMap::new(),
}
}
pub fn put(&mut self, collection: CollectionId, id: EntityId, data: Vec<u8>) {
self.db
.transaction(|txn| {
txn.put(collection, id, data.clone())?;
Ok(())
})
.expect("Failed to put entity");
self.entities.insert((collection, id), data);
}
pub fn get_and_verify(&self, collection: CollectionId, id: EntityId) -> Option<Vec<u8>> {
let actual = self.db.get(collection, id).expect("Failed to get entity");
if let Some(expected) = self.entities.get(&(collection, id)) {
assert_eq!(
actual.as_ref(),
Some(expected),
"Entity data mismatch for {:?}",
id
);
}
actual
}
pub fn delete(&mut self, collection: CollectionId, id: EntityId) {
self.db
.transaction(|txn| {
txn.delete(collection, id)?;
Ok(())
})
.expect("Failed to delete entity");
self.entities.remove(&(collection, id));
}
pub fn verify_all(&self) {
for ((collection, id), expected) in &self.entities {
let actual = self.db.get(*collection, *id).expect("Failed to get entity");
assert_eq!(
actual.as_ref(),
Some(expected),
"Entity data mismatch for {:?}",
id
);
}
}
pub fn tracked_count(&self) -> usize {
self.entities.len()
}
}
impl Default for IntegrationHarness {
fn default() -> Self {
Self::new()
}
}
pub mod codec_storage {
use super::*;
use entidb_codec::Value;
pub fn test_encode_store_retrieve(db: &Database, collection: CollectionId, value: Value) {
let mut encoder = CanonicalEncoder::new();
encoder.encode(&value).expect("Failed to encode");
let encoded = encoder.into_bytes();
let id = EntityId::new();
db.transaction(|txn| {
txn.put(collection, id, encoded.clone())?;
Ok(())
})
.expect("Failed to put");
let retrieved = db.get(collection, id).expect("Failed to get");
assert!(retrieved.is_some(), "Entity should exist");
assert_eq!(
encoded,
retrieved.unwrap(),
"Retrieved bytes should match encoded"
);
}
pub fn test_storage_persistence(backend: &mut dyn StorageBackend, data: &[u8]) {
let offset = backend.append(data).expect("Failed to append");
backend.flush().expect("Failed to flush");
let retrieved = backend.read_at(offset, data.len()).expect("Failed to read");
assert_eq!(data, &retrieved[..], "Retrieved data should match");
}
}
pub mod transaction {
use super::*;
pub fn test_transaction_isolation(db: &Database) {
let collection = db.collection("isolation_test");
let id = EntityId::new();
let data1 = b"version1".to_vec();
let data2 = b"version2".to_vec();
db.transaction(|txn| {
txn.put(collection, id, data1.clone())?;
Ok(())
})
.expect("Failed to put initial data");
let snapshot_data = db.get(collection, id).expect("Failed to get");
assert_eq!(snapshot_data, Some(data1.clone()));
db.transaction(|txn| {
txn.put(collection, id, data2.clone())?;
Ok(())
})
.expect("Failed to update");
let new_data = db.get(collection, id).expect("Failed to get");
assert_eq!(new_data, Some(data2));
}
pub fn test_transaction_abort(db: &Database) {
let collection = db.collection("abort_test");
let id = EntityId::new();
let original = b"original".to_vec();
let modified = b"modified".to_vec();
db.transaction(|txn| {
txn.put(collection, id, original.clone())?;
Ok(())
})
.expect("Failed to put initial data");
let result: Result<(), CoreError> = db.transaction(|txn| {
txn.put(collection, id, modified)?;
Err(CoreError::InvalidOperation {
message: "Simulated abort".into(),
})
});
assert!(result.is_err());
let data = db.get(collection, id).expect("Failed to get");
assert_eq!(data, Some(original));
}
}
pub mod index {
use super::*;
pub fn test_index_consistency(db: &Database) {
let collection = db.collection("index_test");
let mut ids = Vec::new();
for i in 0..10 {
let id = EntityId::new();
let data = format!(r#"{{"value":{}}}"#, i).into_bytes();
db.transaction(|txn| {
txn.put(collection, id, data)?;
Ok(())
})
.expect("Failed to put");
ids.push(id);
}
for id in ids.iter().take(5) {
db.transaction(|txn| {
txn.delete(collection, *id)?;
Ok(())
})
.expect("Failed to delete");
}
for id in ids.iter().skip(5) {
let data = db.get(collection, *id).expect("Failed to get");
assert!(data.is_some(), "Entity should exist");
}
for id in ids.iter().take(5) {
let data = db.get(collection, *id).expect("Failed to get");
assert!(data.is_none(), "Entity should not exist");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_integration_harness() {
let mut harness = IntegrationHarness::new();
let collection = harness.db.collection("test");
let id = EntityId::new();
let data = b"test data".to_vec();
harness.put(collection, id, data.clone());
assert_eq!(harness.tracked_count(), 1);
let retrieved = harness.get_and_verify(collection, id);
assert_eq!(retrieved, Some(data));
harness.verify_all();
}
#[test]
fn test_transaction_isolation() {
let db = Database::open_in_memory().expect("Failed to open database");
transaction::test_transaction_isolation(&db);
}
#[test]
fn test_transaction_abort() {
let db = Database::open_in_memory().expect("Failed to open database");
transaction::test_transaction_abort(&db);
}
#[test]
fn test_index_consistency() {
let db = Database::open_in_memory().expect("Failed to open database");
index::test_index_consistency(&db);
}
}