#[cfg(test)]
mod storage_abstraction_tests {
use crate::storage::traits::*;
use crate::storage::*;
#[tokio::test]
async fn test_sled_backend_basic_operations() {
let temp_dir = tempfile::tempdir().unwrap();
let store = SledNamespacedStore::open(temp_dir.path()).unwrap();
let kv = store.open_namespace("test").await.unwrap();
kv.put(b"key1", b"value1".to_vec()).await.unwrap();
let value = kv.get(b"key1").await.unwrap();
assert_eq!(value, Some(b"value1".to_vec()));
assert!(kv.exists(b"key1").await.unwrap());
assert!(!kv.exists(b"key2").await.unwrap());
assert!(kv.delete(b"key1").await.unwrap());
assert!(!kv.exists(b"key1").await.unwrap());
}
#[tokio::test]
async fn test_sled_backend_scan_prefix() {
let temp_dir = tempfile::tempdir().unwrap();
let store = SledNamespacedStore::open(temp_dir.path()).unwrap();
let kv = store.open_namespace("test").await.unwrap();
kv.put(b"prefix:key1", b"value1".to_vec()).await.unwrap();
kv.put(b"prefix:key2", b"value2".to_vec()).await.unwrap();
kv.put(b"other:key3", b"value3".to_vec()).await.unwrap();
let results = kv.scan_prefix(b"prefix:").await.unwrap();
assert_eq!(results.len(), 2);
}
#[tokio::test]
async fn test_sled_backend_batch_operations() {
let temp_dir = tempfile::tempdir().unwrap();
let store = SledNamespacedStore::open(temp_dir.path()).unwrap();
let kv = store.open_namespace("test").await.unwrap();
let items = vec![
(b"key1".to_vec(), b"value1".to_vec()),
(b"key2".to_vec(), b"value2".to_vec()),
];
kv.batch_put(items).await.unwrap();
assert!(kv.exists(b"key1").await.unwrap());
assert!(kv.exists(b"key2").await.unwrap());
kv.batch_delete(vec![b"key1".to_vec(), b"key2".to_vec()])
.await
.unwrap();
assert!(!kv.exists(b"key1").await.unwrap());
assert!(!kv.exists(b"key2").await.unwrap());
}
#[tokio::test]
async fn test_typed_store_operations() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct TestData {
name: String,
value: i32,
}
let temp_dir = tempfile::tempdir().unwrap();
let store = SledNamespacedStore::open(temp_dir.path()).unwrap();
let kv = store.open_namespace("test").await.unwrap();
let typed = TypedKvStore::new(kv);
let data = TestData {
name: "test".to_string(),
value: 42,
};
typed.put_item("key1", &data).await.unwrap();
let retrieved: Option<TestData> = typed.get_item("key1").await.unwrap();
assert_eq!(retrieved, Some(data.clone()));
typed.put_item("key2", &data).await.unwrap();
let keys = typed.list_keys_with_prefix("key").await.unwrap();
assert_eq!(keys.len(), 2);
let items: Vec<(String, TestData)> = typed.scan_items_with_prefix("key").await.unwrap();
assert_eq!(items.len(), 2);
assert!(typed.delete_item("key1").await.unwrap());
assert!(!typed.exists_item("key1").await.unwrap());
}
#[tokio::test]
async fn test_namespaced_store_isolation() {
let temp_dir = tempfile::tempdir().unwrap();
let store = SledNamespacedStore::open(temp_dir.path()).unwrap();
let ns1 = store.open_namespace("namespace1").await.unwrap();
let ns2 = store.open_namespace("namespace2").await.unwrap();
ns1.put(b"key", b"value1".to_vec()).await.unwrap();
ns2.put(b"key", b"value2".to_vec()).await.unwrap();
let val1 = ns1.get(b"key").await.unwrap();
let val2 = ns2.get(b"key").await.unwrap();
assert_eq!(val1, Some(b"value1".to_vec()));
assert_eq!(val2, Some(b"value2".to_vec()));
}
#[tokio::test]
async fn test_inmemory_backend_operations() {
let store = InMemoryNamespacedStore::new();
let kv = store.open_namespace("test").await.unwrap();
kv.put(b"key1", b"value1".to_vec()).await.unwrap();
let value = kv.get(b"key1").await.unwrap();
assert_eq!(value, Some(b"value1".to_vec()));
kv.put(b"prefix:a", b"a".to_vec()).await.unwrap();
kv.put(b"prefix:b", b"b".to_vec()).await.unwrap();
let results = kv.scan_prefix(b"prefix:").await.unwrap();
assert_eq!(results.len(), 2);
}
#[tokio::test]
async fn test_backend_name() {
let sled_dir = tempfile::tempdir().unwrap();
let sled_store = SledNamespacedStore::open(sled_dir.path()).unwrap();
let sled_kv = sled_store.open_namespace("test").await.unwrap();
assert_eq!(sled_kv.backend_name(), "sled");
let mem_store = InMemoryNamespacedStore::new();
let mem_kv = mem_store.open_namespace("test").await.unwrap();
assert_eq!(mem_kv.backend_name(), "in-memory");
}
#[tokio::test]
async fn test_execution_model_metadata() {
use crate::storage::traits::{ExecutionModel, FlushBehavior};
let sled_dir = tempfile::tempdir().unwrap();
let sled_store = SledNamespacedStore::open(sled_dir.path()).unwrap();
let sled_kv = sled_store.open_namespace("test").await.unwrap();
assert_eq!(sled_kv.execution_model(), ExecutionModel::SyncWrapped);
assert_eq!(sled_kv.flush_behavior(), FlushBehavior::Persists);
let mem_store = InMemoryNamespacedStore::new();
let mem_kv = mem_store.open_namespace("test").await.unwrap();
assert_eq!(mem_kv.execution_model(), ExecutionModel::SyncWrapped);
assert_eq!(mem_kv.flush_behavior(), FlushBehavior::NoOp);
#[cfg(feature = "aws-backend")]
{
use crate::storage::dynamodb_backend::DynamoDbNamespacedStore;
use aws_sdk_dynamodb::Client;
let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
.load()
.await;
let client = Client::new(&config);
let dynamodb_store = DynamoDbNamespacedStore::new_with_prefix(
client,
"test-table".to_string(),
"user_123".to_string(),
);
match dynamodb_store.open_namespace("test").await {
Ok(dynamodb_kv) => {
assert_eq!(dynamodb_kv.execution_model(), ExecutionModel::Async);
assert_eq!(dynamodb_kv.flush_behavior(), FlushBehavior::NoOp);
}
Err(_) => {
}
}
}
}
#[tokio::test]
async fn test_batch_put_items_typed() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Item {
id: u32,
}
let store = InMemoryNamespacedStore::new();
let kv = store.open_namespace("test").await.unwrap();
let typed = TypedKvStore::new(kv);
let items = vec![
("item1".to_string(), Item { id: 1 }),
("item2".to_string(), Item { id: 2 }),
("item3".to_string(), Item { id: 3 }),
];
typed.batch_put_items(items).await.unwrap();
let retrieved: Vec<(String, Item)> = typed.scan_items_with_prefix("item").await.unwrap();
assert_eq!(retrieved.len(), 3);
}
#[tokio::test]
#[cfg(feature = "aws-backend")]
async fn test_dynamodb_partition_key_logic() {
use crate::storage::dynamodb_backend::DynamoDbKvStore;
use aws_sdk_dynamodb::Client;
use std::sync::Arc;
let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
.load()
.await;
let client = Arc::new(Client::new(&config));
let store_with_user = DynamoDbKvStore::new(
client.clone(),
"test-table".to_string(),
"user_123".to_string(),
);
let pk = store_with_user.get_partition_key();
assert_eq!(pk, "user_123");
let test_key = b"atom:abc123";
let sort_key = store_with_user.make_sort_key(test_key);
assert_eq!(sort_key, "atom:abc123");
let store_without_user = DynamoDbKvStore::new(
client.clone(),
"test-table".to_string(),
"default".to_string(),
);
let pk_default = store_without_user.get_partition_key();
assert_eq!(pk_default, "default");
let sort_key_no_user = store_without_user.make_sort_key(test_key);
assert_eq!(sort_key_no_user, "atom:abc123");
}
#[tokio::test]
#[cfg(feature = "aws-backend")]
async fn test_dynamodb_namespaced_store_user_isolation() {
use crate::storage::dynamodb_backend::DynamoDbNamespacedStore;
use aws_sdk_dynamodb::Client;
let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
.load()
.await;
let client = Client::new(&config);
let store = DynamoDbNamespacedStore::new_with_prefix(
client,
"DataFoldStorage".to_string(),
"main_user".to_string(),
);
let table_name = store.get_table_name_for_namespace("main");
assert_eq!(table_name, "DataFoldStorage-main");
let store_with_user = DynamoDbNamespacedStore::new_with_prefix(
Client::new(&config),
"DataFoldStorage".to_string(),
"default".to_string(),
)
.with_user_id("user_456".to_string());
match store_with_user.open_namespace("test").await {
Ok(kv) => {
assert_eq!(kv.backend_name(), "dynamodb");
}
Err(e) => {
let error_msg = e.to_string();
assert!(
error_msg.contains("DynamoDbError")
|| error_msg.contains("dispatch failure")
|| error_msg.contains("credentials")
|| error_msg.contains("table"),
"Unexpected error: {}",
error_msg
);
}
}
}
}