#[cfg(test)]
pub mod tests {
use std::path::Path;
use crate::local_db_model::LocalDbModel;
use crate::local_db_state::AppDbState;
use std::time::{SystemTime, UNIX_EPOCH};
use std::ffi::CString;
use std::thread;
use log::{info, warn};
fn create_test_model(id: &str, data: Option<serde_json::Value>) -> LocalDbModel {
LocalDbModel {
id: id.to_string(),
hash: format!("hash_{}", id),
data: data.unwrap_or(serde_json::json!({"test": "data"})),
}
}
fn cleanup_test_databases() {
info!("Starting comprehensive test database cleanup...");
for pass in 1..=3 {
info!("Cleanup pass {}/3", pass);
if let Ok(entries) = std::fs::read_dir(".") {
let mut cleaned_count = 0;
for entry_result in entries {
let entry = match entry_result {
Ok(e) => e,
Err(e) => {
warn!("Error reading directory entry: {e}");
continue;
}
};
let file_name = match entry.file_name().into_string() {
Ok(name) => name,
Err(_) => {
warn!("Error: filename contains invalid characters");
continue;
}
};
let should_clean = (file_name.starts_with("database_tested_")
|| file_name.starts_with("ffi_test_")
|| file_name.starts_with("edge_case_")
|| file_name.starts_with("unicode_test_")
|| file_name.starts_with("size_limit_")
|| file_name.starts_with("boundary_test_")
|| file_name.starts_with("concurrent_")
|| file_name.starts_with("memory_test_")
|| file_name.starts_with("memory_stability_")
|| file_name.starts_with("stress_test_")
|| file_name.starts_with("bulk_perf_")
|| file_name.starts_with("size_growth_")
|| file_name.starts_with("relative_path_")
|| file_name.starts_with("space test")
|| file_name.starts_with("测试数据库")
|| file_name.starts_with("very_long_")
|| file_name.starts_with("case_test_")
|| file_name.starts_with("hot_restart_")
|| file_name.starts_with("cleanup_test_")
|| file_name.starts_with("multi_db_test_")
|| file_name.starts_with("invalid_db_")
|| file_name.ends_with(".lmdb")
|| file_name.ends_with(".lock")
|| file_name.ends_with(".tmp")) && {
match entry.metadata() {
Ok(metadata) => {
if let Ok(modified) = metadata.modified() {
if let Ok(elapsed) = modified.elapsed() {
elapsed.as_secs() > 30
} else {
false
}
} else {
false
}
},
Err(_) => false
}
};
if should_clean {
let path = entry.path();
let removal_result = if path.is_dir() {
std::fs::remove_dir_all(&path)
} else {
std::fs::remove_file(&path)
};
match removal_result {
Ok(_) => {
cleaned_count += 1;
info!("Cleaned test artifact: {}", file_name);
},
Err(e) => {
if path.is_dir() {
if let Ok(dir_entries) = std::fs::read_dir(&path) {
for dir_entry in dir_entries.flatten() {
let _ = std::fs::remove_file(dir_entry.path());
}
let _ = std::fs::remove_dir(&path);
}
}
warn!("Error removing {}: {e}", file_name);
}
}
}
}
if cleaned_count > 0 {
info!("✅ Pass {}: {} test artifacts removed", pass, cleaned_count);
} else {
info!("✅ Pass {}: No test artifacts found to clean", pass);
}
} else {
warn!("Could not read current directory for cleanup in pass {}", pass);
}
if pass < 3 {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}
fn final_cleanup_all() {
info!("🧹 Starting FINAL comprehensive cleanup...");
for pass in 1..=3 {
info!("Cleanup pass {}/3", pass);
cleanup_test_databases();
thread::sleep(std::time::Duration::from_millis(100));
}
if let Ok(entries) = std::fs::read_dir(".") {
for entry in entries.flatten() {
let file_name = entry.file_name().to_string_lossy().to_string();
if file_name.contains("lmdb") || file_name.contains("lock") ||
file_name.contains("test") && (file_name.ends_with(".db") || file_name.ends_with(".tmp")) {
let path = entry.path();
let removal_result = if path.is_dir() {
std::fs::remove_dir_all(&path)
} else {
std::fs::remove_file(&path)
};
if removal_result.is_ok() {
info!("🗑️ Final cleanup removed: {}", file_name);
}
}
}
}
info!("🎉 FINAL CLEANUP COMPLETE - Workspace is now clean!");
}
#[test]
fn test_zzz_final_cleanup() {
final_cleanup_all();
let mut remaining_artifacts = Vec::new();
if let Ok(entries) = std::fs::read_dir(".") {
for entry in entries.flatten() {
let file_name = entry.file_name().to_string_lossy().to_string();
if file_name.starts_with("ffi_test_") ||
file_name.starts_with("concurrent_") ||
file_name.starts_with("memory_") ||
file_name.starts_with("stress_") ||
file_name.starts_with("bulk_") ||
file_name.contains("test") && file_name.ends_with(".lmdb") {
remaining_artifacts.push(file_name);
}
}
}
if remaining_artifacts.is_empty() {
info!("✅ SUCCESS: All test artifacts successfully cleaned!");
} else {
warn!("⚠️ Some artifacts remain: {:?}", remaining_artifacts);
for artifact in &remaining_artifacts {
let _ = std::fs::remove_dir_all(artifact);
let _ = std::fs::remove_file(artifact);
}
}
assert!(true, "Final cleanup test completed");
}
fn generate_unique_db_name(prefix: &str) -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(); let counter = COUNTER.fetch_add(1, Ordering::SeqCst);
format!("database_tested_{}_{}_{}",
prefix,
timestamp,
counter
)
}
#[test]
fn test_push() {
let state = AppDbState::init(generate_unique_db_name("push"));
let model = create_test_model("1", None);
match state {
Ok(results) => {
let result = results.push(model.clone()).unwrap();
assert_eq!(result.id, model.id);
assert_eq!(result.hash, model.hash);
let stored = results.get_by_id("1").unwrap().unwrap();
assert_eq!(stored.id, model.id);
}
Err(_) => {
warn!("Error on testing")
}
}
}
#[test]
fn test_db_already_open() {
let db_name = "database_tested_already_open";
let db_dir = format!("{}.lmdb", db_name);
let path = Path::new(&db_dir);
if path.exists() {
std::fs::remove_dir_all(path).expect("Failed to remove existing test database");
}
let first_instance = AppDbState::init(db_name.to_string());
assert!(first_instance.is_ok(), "First instance should be created successfully");
let first_db = first_instance.unwrap();
info!("First instance opened successfully");
let second_instance = AppDbState::init(db_name.to_string());
if second_instance.is_ok() {
info!("Second instance opened successfully - database supports multiple connections");
let model_1 = create_test_model("test1", None);
let result_1 = first_db.push(model_1.clone());
info!("Write to first instance: {}", result_1.is_ok());
let second_db = second_instance.as_ref().unwrap();
let model_2 = create_test_model("test2", None);
let result_2 = second_db.push(model_2.clone());
info!("Write to second instance: {}", result_2.is_ok());
if result_1.is_ok() && result_2.is_ok() {
let read_1 = first_db.get_by_id("test2");
info!("First instance can read data from second: {}",
read_1.is_ok() && read_1.unwrap().is_some());
let read_2 = second_db.get_by_id("test1");
info!("Second instance can read data from first: {}",
read_2.is_ok() && read_2.unwrap().is_some());
}
} else {
info!("Second instance failed to open the same database");
match second_instance.err().unwrap() {
error => {
info!("LMDB error: {:?}", error);
}
}
let model = create_test_model("test1", None);
let result = first_db.push(model);
info!("First instance still functioning: {}", result.is_ok());
}
if path.exists() {
std::fs::remove_dir_all(path).expect("Failed to clean up test database");
}
}
#[test]
fn test_get_by_id() {
let state = AppDbState::init(generate_unique_db_name("get"));
match state {
Ok(response) => {
let no_result = response.get_by_id("nonexistent").unwrap();
assert!(no_result.is_none());
let model = create_test_model("1", None);
response.push(model.clone()).unwrap();
let result = response.get_by_id("1").unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().id, "1");
}
Err(_) => {
warn!("Error on get data")
}
}
}
#[test]
fn test_get_all() {
let db_name = generate_unique_db_name("get_all");
match AppDbState::init(db_name.clone()) {
Ok(mut state) => {
state.reset_database(&db_name).unwrap();
let empty_results = state.get().unwrap();
assert!(empty_results.is_empty(), "Database should be initially empty");
let model1 = create_test_model("1", None);
state.push(model1).unwrap();
let results = state.get().unwrap();
assert_eq!(results.len(), 1, "Should have exactly 1 record");
let model2 = create_test_model("2", None);
state.push(model2).unwrap();
let results = state.get().unwrap();
assert_eq!(results.len(), 2, "Should have exactly 2 records");
let model3 = create_test_model("3", None);
state.push(model3).unwrap();
let results = state.get().unwrap();
assert_eq!(results.len(), 3, "Should have exactly 3 records");
assert!(state.get_by_id("1").unwrap().is_some());
assert!(state.get_by_id("2").unwrap().is_some());
assert!(state.get_by_id("3").unwrap().is_some());
},
Err(_) => {
panic!("Error initializing database for test_get_all");
}
}
}
#[test]
fn test_update() {
match AppDbState::init(generate_unique_db_name("update")) {
Ok(state) => {
let non_existent = create_test_model("999", None);
let update_result = state.update(non_existent).unwrap();
assert!(update_result.is_none());
let model = create_test_model("1", Some(serde_json::json!({"original": true})));
state.push(model).unwrap();
let updated_model = create_test_model("1", Some(serde_json::json!({"updated": true})));
let result = state.update(updated_model.clone()).unwrap();
assert!(result.is_some());
let updated = state.get_by_id("1").unwrap().unwrap();
assert_eq!(updated.data, updated_model.data);
},
Err(_) => {
panic!("Error initializing database for test_update");
}
}
}
#[test]
fn test_delete() {
match AppDbState::init(generate_unique_db_name("delete")) {
Ok(state) => {
let delete_result = state.delete_by_id("nonexistent").unwrap();
assert!(!delete_result);
let model = create_test_model("1", None);
state.push(model).unwrap();
let delete_result = state.delete_by_id("1").unwrap();
assert!(delete_result);
let not_found = state.get_by_id("1").unwrap();
assert!(not_found.is_none());
},
Err(e) => {
panic!("Error initializing database for test_delete: {:?}", e);
}
}
}
#[test]
fn test_clear_all_records() {
match AppDbState::init(generate_unique_db_name("clear")) {
Ok(state) => {
let count = state.clear_all_records().unwrap();
assert_eq!(count, 0);
for i in 1..=3 {
state.push(create_test_model(&i.to_string(), None)).unwrap();
}
let count = state.clear_all_records().unwrap();
assert_eq!(count, 3);
let remaining = state.get().unwrap();
assert!(remaining.is_empty());
},
Err(_) => {
panic!("Error initializing database for test_clear_all_records");
}
}
}
#[test]
fn test_reset_database() {
match AppDbState::init(generate_unique_db_name("reset")) {
Ok(mut state) => {
for i in 1..=3 {
state.push(create_test_model(&i.to_string(), None)).unwrap();
}
let new_name = generate_unique_db_name("hard_reset");
let reset = state.reset_database(&new_name).unwrap();
assert!(reset);
let empty = state.get().unwrap();
assert!(empty.is_empty());
},
Err(_) => {
panic!("Error initializing database for test_reset_database");
}
}
}
#[test]
fn test_basic_operations() {
match AppDbState::init(generate_unique_db_name("basic")) {
Ok(state) => {
for i in 1..=5 {
let model = create_test_model(&i.to_string(), None);
let result = state.push(model).unwrap();
assert_eq!(result.id, i.to_string());
}
let all_records = state.get().unwrap();
assert_eq!(all_records.len(), 5, "Should have inserted 5 records");
for i in 1..=5 {
let record = state.get_by_id(&i.to_string()).unwrap();
assert!(record.is_some(), "Record {} should exist", i);
let record = record.unwrap();
assert_eq!(record.hash, format!("hash_{}", i));
}
},
Err(_) => {
panic!("Error initializing database for test_basic_operations");
}
}
}
#[test]
fn test_large_dataset() {
match AppDbState::init(generate_unique_db_name("large_data")) {
Ok(state) => {
for i in 1..=100 {
let model = create_test_model(
&i.to_string(),
Some(serde_json::json!({
"name": format!("test_{}", i),
"value": i,
"data": vec![1, 2, 3, 4, 5]
}))
);
state.push(model).unwrap();
}
let all_records = state.get().unwrap();
assert_eq!(all_records.len(), 100);
let start = std::time::Instant::now();
let _result = state.get_by_id("50").unwrap();
let duration = start.elapsed();
assert!(duration.as_millis() < 100, "La búsqueda tardó demasiado");
},
Err(_) => {
panic!("Error initializing database for test_large_dataset");
}
}
}
#[test]
fn test_data_integrity() {
match AppDbState::init(generate_unique_db_name("integrity")) {
Ok(state) => {
let complex_data = serde_json::json!({
"nested": {
"array": [1, 2, 3],
"object": {
"key": "value",
"number": 42,
"boolean": true,
"null": null
}
},
"special_chars": "!@#$%^&*()_+-=[]{}|;:'\",.<>?/\\",
"unicode": "Hello, 世界! 🌍"
});
let model = create_test_model("complex", Some(complex_data.clone()));
state.push(model).unwrap();
let retrieved = state.get_by_id("complex").unwrap().unwrap();
assert_eq!(retrieved.data, complex_data);
},
Err(_) => {
panic!("Error initializing database for test_data_integrity");
}
}
}
#[test]
fn test_edge_cases() {
match AppDbState::init(generate_unique_db_name("edge_cases")) {
Ok(state) => {
let empty_id_model = create_test_model("", None);
match state.push(empty_id_model) {
Ok(_) => {
assert!(state.get_by_id("").unwrap().is_some());
info!("Empty ID stored successfully");
},
Err(e) => {
info!("Empty ID not allowed in LMDB: {:?}", e);
}
}
let large_data = serde_json::json!({
"large_array": vec![0; 1000], "large_string": "a".repeat(1000) });
let large_model = create_test_model("large", Some(large_data));
match state.push(large_model) {
Ok(_) => info!("Large data stored successfully"),
Err(e) => info!("Large data too big for LMDB: {:?}", e),
}
let updated_model = create_test_model("large", Some(serde_json::json!({"small": "data"})));
state.update(updated_model).unwrap();
},
Err(_) => {
panic!("Error initializing database for test_edge_cases");
}
}
}
#[test]
fn test_edge_cases_extended() {
match AppDbState::init(generate_unique_db_name("edge_cases_extended")) {
Ok(state) => {
let special_id_model = create_test_model("!@#$%^&*()", None);
state.push(special_id_model).unwrap();
assert!(state.get_by_id("!@#$%^&*()").unwrap().is_some());
let null_model = create_test_model("null_data", Some(serde_json::json!(null)));
state.push(null_model).unwrap();
let empty_model = create_test_model("empty_data", Some(serde_json::json!({})));
state.push(empty_model).unwrap();
let extreme_values = create_test_model("extreme", Some(serde_json::json!({
"max_i64": i64::MAX,
"min_i64": i64::MIN,
"max_f64": f64::MAX,
"min_f64": f64::MIN
})));
state.push(extreme_values).unwrap();
let unicode_model = create_test_model("unicode", Some(serde_json::json!({
"text": "Hello 世界 🌍 👋 🤖"
})));
state.push(unicode_model).unwrap();
let nested_array = create_test_model("nested", Some(serde_json::json!([
[[[[[1,2,3]]]]]
])));
state.push(nested_array).unwrap();
let repeated_model = create_test_model("repeated", None);
state.push(repeated_model.clone()).unwrap();
for i in 1..100 {
let updated = create_test_model("repeated", Some(serde_json::json!({
"update_number": i
})));
state.update(updated).unwrap();
}
let long_id_model = create_test_model(&"a".repeat(250), None); match state.push(long_id_model) {
Ok(_) => info!("Long ID stored successfully"),
Err(e) => info!("Long ID too big for LMDB: {:?}", e),
}
for i in 1..100 {
let quick_model = create_test_model(&format!("quick_{}", i), None);
state.push(quick_model).unwrap();
state.get_by_id(&format!("quick_{}", i)).unwrap();
state.delete_by_id(&format!("quick_{}", i)).unwrap();
}
},
Err(_) => {
panic!("Error initializing database for test_edge_cases_extended");
}
}
}
#[test]
fn test_full_workflow() {
match AppDbState::init(generate_unique_db_name("workflow")) {
Ok(mut state) => {
let test_model = create_test_model("1", Some(serde_json::json!({"test": "data"})));
state.push(test_model).unwrap();
std::thread::sleep(std::time::Duration::from_millis(100));
let get_all_data = state.get().unwrap();
assert!(!get_all_data.is_empty(), "Database should not be empty");
assert_eq!(get_all_data.len(), 1, "Should have exactly one record");
let result = state.get_by_id("1").unwrap();
assert!(result.is_some(), "Should find record with id 1");
assert_eq!(result.unwrap().id, "1");
let updated_model = create_test_model("1", Some(serde_json::json!({"test": "updated_data"})));
let update_result = state.update(updated_model).unwrap();
assert!(update_result.is_some());
std::thread::sleep(std::time::Duration::from_millis(100));
let updated = state.get_by_id("1").unwrap().unwrap();
assert_eq!(updated.data, serde_json::json!({"test": "updated_data"}));
assert!(state.delete_by_id("1").unwrap());
std::thread::sleep(std::time::Duration::from_millis(100));
assert!(state.get_by_id("1").unwrap().is_none());
for i in 1..=3 {
let model = create_test_model(&i.to_string(), None);
state.push(model).unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
assert!(state.get_by_id(&i.to_string()).unwrap().is_some());
}
let cleared = state.clear_all_records().unwrap();
assert_eq!(cleared, 3);
std::thread::sleep(std::time::Duration::from_millis(100));
let after_clear = state.get().unwrap();
assert!(after_clear.is_empty(), "Database should be empty after clear");
let new_db_name = format!(
"database_tested_{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
);
let reset_result = state.reset_database(&new_db_name);
assert!(reset_result.is_ok());
std::thread::sleep(std::time::Duration::from_millis(100));
let final_check = state.get().unwrap();
assert!(final_check.is_empty(), "Database should be empty after reset");
cleanup_test_databases();
},
Err(_) => {
panic!("Error initializing database for test_full_workflow");
}
}
}
#[test]
fn test_error_handling() {
match AppDbState::init(generate_unique_db_name("handling")) {
Ok(state) => {
assert!(state.get_by_id("nonexistent").unwrap().is_none());
assert!(!state.delete_by_id("nonexistent").unwrap());
assert!(state.update(create_test_model("nonexistent", None)).unwrap().is_none());
state.push(create_test_model("1", None)).unwrap();
state.clear_all_records().unwrap();
assert!(state.get_by_id("1").unwrap().is_none());
},
Err(_) => {
panic!("Error initializing database for test_error_handling");
}
}
}
#[test]
fn test_interrupted_operations() {
match AppDbState::init(generate_unique_db_name("interrupted")) {
Ok(state) => {
let model = create_test_model("1", None);
state.push(model).unwrap();
let updated_model = create_test_model("1", Some(serde_json::json!({"updated": true})));
state.update(updated_model).unwrap();
state.delete_by_id("1").unwrap();
assert!(state.get_by_id("1").unwrap().is_none());
},
Err(_) => {
panic!("Error initializing database for test_interrupted_operations");
}
}
}
#[test]
fn test_recovery_after_errors() {
match AppDbState::init(generate_unique_db_name("recovery")) {
Ok(state) => {
let model = create_test_model("1", None);
state.push(model).unwrap();
let result = state.get_by_id("nonexistent");
assert!(result.is_ok());
let model2 = create_test_model("2", None);
assert!(state.push(model2).is_ok());
},
Err(_) => {
panic!("Error initializing database for test_recovery_after_errors");
}
}
}
#[test]
fn test_data_validation() {
match AppDbState::init(generate_unique_db_name("validation")) {
Ok(state) => {
let models = vec![
create_test_model("bool", Some(serde_json::json!(true))),
create_test_model("number", Some(serde_json::json!(42.5))),
create_test_model("array", Some(serde_json::json!([1,2,3]))),
create_test_model("nested", Some(serde_json::json!({
"a": {"b": {"c": 1}}
}))),
];
for model in models {
state.push(model).unwrap();
}
let retrieved = state.get_by_id("number").unwrap().unwrap();
assert!(retrieved.data.is_number());
},
Err(_) => {
panic!("Error initializing database for test_data_validation");
}
}
}
#[test]
fn test_batch_operations() {
cleanup_test_databases();
let db_name = generate_unique_db_name("batch");
info!("Attempting to initialize database: {}", db_name);
match AppDbState::init(db_name) {
Ok(state) => {
let models: Vec<_> = (1..100)
.map(|i| create_test_model(&i.to_string(), None))
.collect();
for model in models {
state.push(model).unwrap();
}
for i in 1..50 {
state.delete_by_id(&i.to_string()).unwrap();
}
let remaining = state.get().unwrap();
assert_eq!(remaining.len(), 50);
},
Err(e) => {
eprintln!("Error initializing database for test_batch_operations: {:?}", e);
panic!("Error initializing database for test_batch_operations: {:?}", e);
}
}
}
#[test]
fn test_data_consistency() {
match AppDbState::init(generate_unique_db_name("consistency")) {
Ok(state) => {
let original = create_test_model("1", Some(serde_json::json!({
"count": 0,
"timestamp": SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
})));
state.push(original).unwrap();
for i in 1..10 {
let updated = create_test_model("1", Some(serde_json::json!({
"count": i,
"timestamp": SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
})));
state.update(updated).unwrap();
}
let final_state = state.get_by_id("1").unwrap().unwrap();
assert_eq!(final_state.data["count"], 9);
},
Err(_) => {
panic!("Error initializing database for test_data_consistency");
}
}
}
#[test]
fn test_ffi_create_db_success() {
use std::ffi::CString;
use crate::{create_db};
cleanup_test_databases();
let db_name = CString::new("ffi_test_create").unwrap();
let db_ptr = create_db(db_name.as_ptr());
assert!(!db_ptr.is_null(), "Database pointer should not be null");
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_create_db_null_pointer() {
use crate::{create_db};
let db_ptr = create_db(std::ptr::null());
assert!(db_ptr.is_null(), "Should return null for null input");
}
#[test]
fn test_ffi_create_db_invalid_utf8() {
use crate::{create_db};
let invalid_bytes = [0xFF, 0xFE, 0xFD, 0x00]; let db_ptr = create_db(invalid_bytes.as_ptr() as *const i8);
assert!(db_ptr.is_null(), "Should return null for invalid UTF-8");
}
#[test]
fn test_ffi_push_data_success() {
use std::ffi::CString;
use crate::{create_db, push_data};
cleanup_test_databases();
let db_name = CString::new("ffi_test_push").unwrap();
let db_ptr = create_db(db_name.as_ptr());
assert!(!db_ptr.is_null());
let json_data = CString::new(r#"{"id":"test1","hash":"hash1","data":{"key":"value"}}"#).unwrap();
let result_ptr = push_data(db_ptr, json_data.as_ptr());
assert!(!result_ptr.is_null(), "Result should not be null");
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("Ok"), "Should contain success response");
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_push_data_null_pointers() {
use std::ffi::CString;
use crate::{create_db, push_data};
cleanup_test_databases();
let db_name = CString::new("ffi_test_push_null").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let json_data = CString::new(r#"{"id":"test1","hash":"hash1","data":{}}"#).unwrap();
let result_ptr = push_data(std::ptr::null_mut(), json_data.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
let result_ptr = push_data(db_ptr, std::ptr::null());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_push_data_invalid_json() {
use std::ffi::CString;
use crate::{create_db, push_data};
cleanup_test_databases();
let db_name = CString::new("ffi_test_push_invalid").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let invalid_json = CString::new(r#"{"invalid": json structure"#).unwrap();
let result_ptr = push_data(db_ptr, invalid_json.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("SerializationError"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_get_by_id_success() {
use std::ffi::CString;
use crate::{create_db, push_data, get_by_id};
cleanup_test_databases();
let db_name = CString::new("ffi_test_get").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let json_data = CString::new(r#"{"id":"test1","hash":"hash1","data":{"key":"value"}}"#).unwrap();
let _push_result = push_data(db_ptr, json_data.as_ptr());
let id = CString::new("test1").unwrap();
let result_ptr = get_by_id(db_ptr, id.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"Ok\":"));
assert!(result_json.contains("test1"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_get_by_id_not_found() {
use std::ffi::CString;
use crate::{create_db, get_by_id};
cleanup_test_databases();
let db_name = CString::new("ffi_test_get_notfound").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let id = CString::new("nonexistent").unwrap();
let result_ptr = get_by_id(db_ptr, id.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"NotFound\":"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_get_by_id_null_pointers() {
use std::ffi::CString;
use crate::{create_db, get_by_id};
cleanup_test_databases();
let db_name = CString::new("ffi_test_get_null").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let id = CString::new("test1").unwrap();
let result_ptr = get_by_id(std::ptr::null_mut(), id.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
let result_ptr = get_by_id(db_ptr, std::ptr::null());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_get_all_success() {
use std::ffi::CString;
use crate::{create_db, push_data, get_all};
cleanup_test_databases();
let db_name = CString::new("ffi_test_get_all").unwrap();
let db_ptr = create_db(db_name.as_ptr());
for i in 1..=3 {
let json_data = CString::new(format!(
r#"{{"id":"test{}","hash":"hash{}","data":{{"number":{}}}}}"#,
i, i, i
)).unwrap();
let _result = push_data(db_ptr, json_data.as_ptr());
}
let result_ptr = get_all(db_ptr);
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"Ok\":"));
assert!(result_json.contains("test1"));
assert!(result_json.contains("test2"));
assert!(result_json.contains("test3"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_get_all_null_pointer() {
use crate::{get_all};
let result_ptr = get_all(std::ptr::null_mut());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
}
#[test]
fn test_ffi_update_data_success() {
use std::ffi::CString;
use crate::{create_db, push_data, update_data};
cleanup_test_databases();
let db_name = CString::new("ffi_test_update").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let json_data = CString::new(r#"{"id":"test1","hash":"hash1","data":{"value":1}}"#).unwrap();
let _push_result = push_data(db_ptr, json_data.as_ptr());
let updated_json = CString::new(r#"{"id":"test1","hash":"hash2","data":{"value":2}}"#).unwrap();
let result_ptr = update_data(db_ptr, updated_json.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"Ok\":"));
assert!(result_json.contains("hash2"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_update_data_not_found() {
use std::ffi::CString;
use crate::{create_db, update_data};
cleanup_test_databases();
let db_name = CString::new("ffi_test_update_notfound").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let json_data = CString::new(r#"{"id":"nonexistent","hash":"hash1","data":{"value":1}}"#).unwrap();
let result_ptr = update_data(db_ptr, json_data.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"NotFound\":"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_update_data_null_pointers() {
use std::ffi::CString;
use crate::{create_db, update_data};
cleanup_test_databases();
let db_name = CString::new("ffi_test_update_null").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let json_data = CString::new(r#"{"id":"test1","hash":"hash1","data":{}}"#).unwrap();
let result_ptr = update_data(std::ptr::null_mut(), json_data.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
let result_ptr = update_data(db_ptr, std::ptr::null());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_delete_by_id_success() {
use std::ffi::CString;
use crate::{create_db, push_data, delete_by_id};
cleanup_test_databases();
let db_name = CString::new("ffi_test_delete").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let json_data = CString::new(r#"{"id":"test1","hash":"hash1","data":{"key":"value"}}"#).unwrap();
let _push_result = push_data(db_ptr, json_data.as_ptr());
let id = CString::new("test1").unwrap();
let result_ptr = delete_by_id(db_ptr, id.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"Ok\":"));
assert!(result_json.contains("successfully"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_delete_by_id_not_found() {
use std::ffi::CString;
use crate::{create_db, delete_by_id};
cleanup_test_databases();
let db_name = CString::new("ffi_test_delete_notfound").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let id = CString::new("nonexistent").unwrap();
let result_ptr = delete_by_id(db_ptr, id.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"NotFound\":"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_delete_by_id_null_pointers() {
use std::ffi::CString;
use crate::{create_db, delete_by_id};
cleanup_test_databases();
let db_name = CString::new("ffi_test_delete_null").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let id = CString::new("test1").unwrap();
let result_ptr = delete_by_id(std::ptr::null_mut(), id.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
let result_ptr = delete_by_id(db_ptr, std::ptr::null());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_clear_all_records_success() {
use std::ffi::CString;
use crate::{create_db, push_data, clear_all_records};
cleanup_test_databases();
let db_name = CString::new("ffi_test_clear").unwrap();
let db_ptr = create_db(db_name.as_ptr());
for i in 1..=3 {
let json_data = CString::new(format!(
r#"{{"id":"test{}","hash":"hash{}","data":{{"number":{}}}}}"#,
i, i, i
)).unwrap();
let _result = push_data(db_ptr, json_data.as_ptr());
}
let result_ptr = clear_all_records(db_ptr);
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"Ok\":"));
assert!(result_json.contains("cleared"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_clear_all_records_null_pointer() {
use crate::{clear_all_records};
let result_ptr = clear_all_records(std::ptr::null_mut());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
}
#[test]
fn test_ffi_reset_database_success() {
use std::ffi::CString;
use crate::{create_db, push_data, reset_database};
cleanup_test_databases();
let db_name = CString::new("ffi_test_reset").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let json_data = CString::new(r#"{"id":"test1","hash":"hash1","data":{"key":"value"}}"#).unwrap();
let _push_result = push_data(db_ptr, json_data.as_ptr());
let new_name = CString::new("ffi_test_reset_new").unwrap();
let result_ptr = reset_database(db_ptr, new_name.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"Ok\":"));
assert!(result_json.contains("reset successfully"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_reset_database_null_pointers() {
use std::ffi::CString;
use crate::{create_db, reset_database};
cleanup_test_databases();
let db_name = CString::new("ffi_test_reset_null").unwrap();
let db_ptr = create_db(db_name.as_ptr());
let new_name = CString::new("new_db").unwrap();
let result_ptr = reset_database(std::ptr::null_mut(), new_name.as_ptr());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
let result_ptr = reset_database(db_ptr, std::ptr::null());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_close_database_success() {
use std::ffi::CString;
use crate::{create_db, close_database};
cleanup_test_databases();
let db_name = CString::new("ffi_test_close").unwrap();
let db_ptr = create_db(db_name.as_ptr());
assert!(!db_ptr.is_null());
let result_ptr = close_database(db_ptr);
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("\"Ok\":"));
assert!(result_json.contains("closed successfully"));
unsafe {
let _db = Box::from_raw(db_ptr);
}
}
#[test]
fn test_ffi_close_database_null_pointer() {
use crate::{close_database};
let result_ptr = close_database(std::ptr::null_mut());
assert!(!result_ptr.is_null());
let result_str = unsafe { CString::from_raw(result_ptr as *mut i8) };
let result_json = result_str.to_str().unwrap();
assert!(result_json.contains("BadRequest"));
}
#[test]
fn test_database_creation_invalid_names() {
let long_name = "a".repeat(256);
let invalid_names = vec![
"", "/", "\\", "CON", "PRN", "AUX", "NUL", &long_name, "db\0name", "db\x01name", ];
for invalid_name in invalid_names {
cleanup_test_databases();
match AppDbState::init(format!("invalid_db_{}", invalid_name.len())) {
Ok(_) => {
info!("Database creation succeeded with potentially invalid name");
}
Err(_) => {
info!("Database creation properly failed for invalid name");
}
}
}
}
#[test]
fn test_json_edge_cases() {
cleanup_test_databases();
match AppDbState::init("edge_case_json_test".to_string()) {
Ok(state) => {
let deep_json = (0..100).fold("\"value\"".to_string(), |acc, i| {
format!(r#"{{"level{}": {}}}"#, i, acc)
});
let deep_model = LocalDbModel {
id: "deep_test".to_string(),
hash: "deep_hash".to_string(),
data: serde_json::from_str(&deep_json).unwrap_or(serde_json::json!({})),
};
let _result = state.push(deep_model);
let large_array = serde_json::json!((0..1000).collect::<Vec<i32>>());
let large_model = LocalDbModel {
id: "large_array".to_string(),
hash: "large_hash".to_string(),
data: large_array,
};
let _result = state.push(large_model);
let empty_model = LocalDbModel {
id: "empty_test".to_string(),
hash: "".to_string(),
data: serde_json::json!(null),
};
let _result = state.push(empty_model);
}
Err(_) => panic!("Failed to initialize database for JSON edge case tests")
}
}
#[test]
fn test_unicode_and_special_characters() {
cleanup_test_databases();
match AppDbState::init("unicode_test_db".to_string()) {
Ok(state) => {
let unicode_tests = vec![
("emoji_test", "🦀🔥🚀", serde_json::json!({"emoji": "🎉🎊"})),
("chinese_test", "测试哈希", serde_json::json!({"text": "你好世界"})),
("arabic_test", "اختبار", serde_json::json!({"text": "مرحبا"})),
("russian_test", "тест", serde_json::json!({"text": "привет"})),
("special_chars", "!@#$%^&*()", serde_json::json!({"chars": "<>&\"'"})),
];
for (id, hash, data) in unicode_tests {
let model = LocalDbModel {
id: id.to_string(),
hash: hash.to_string(),
data,
};
match state.push(model.clone()) {
Ok(_) => {
match state.get_by_id(id) {
Ok(Some(retrieved)) => {
assert_eq!(retrieved.id, model.id);
assert_eq!(retrieved.hash, model.hash);
assert_eq!(retrieved.data, model.data);
}
Ok(None) => panic!("Unicode data not found after insertion"),
Err(e) => panic!("Error retrieving unicode data: {:?}", e),
}
}
Err(_) => {
info!("Unicode test failed for: {}", id);
}
}
}
}
Err(_) => panic!("Failed to initialize database for Unicode tests")
}
}
#[test]
fn test_size_limits() {
cleanup_test_databases();
match AppDbState::init("size_limit_test".to_string()) {
Ok(state) => {
let long_id = "a".repeat(500);
let model = LocalDbModel {
id: long_id.clone(),
hash: "test_hash".to_string(),
data: serde_json::json!({"test": "data"}),
};
match state.push(model) {
Ok(_) => {
let result = state.get_by_id(&long_id);
assert!(result.is_ok());
}
Err(_) => {
info!("Long key test failed as expected");
}
}
let large_string = "x".repeat(1024 * 1024); let large_data = serde_json::json!({"large_field": large_string});
let large_model = LocalDbModel {
id: "large_value_test".to_string(),
hash: "large_hash".to_string(),
data: large_data,
};
let _result = state.push(large_model);
let huge_string = "x".repeat(10 * 1024 * 1024); let huge_data = serde_json::json!({"huge_field": huge_string});
let huge_model = LocalDbModel {
id: "huge_value_test".to_string(),
hash: "huge_hash".to_string(),
data: huge_data,
};
let result = state.push(huge_model);
if result.is_err() {
info!("Huge value test properly failed");
}
}
Err(_) => panic!("Failed to initialize database for size limit tests")
}
}
#[test]
fn test_empty_and_boundary_values() {
cleanup_test_databases();
match AppDbState::init("boundary_test_db".to_string()) {
Ok(state) => {
let single_char_model = LocalDbModel {
id: "a".to_string(),
hash: "h".to_string(),
data: serde_json::json!({"key": "value"}),
};
assert!(state.push(single_char_model).is_ok());
let whitespace_model = LocalDbModel {
id: "whitespace_test".to_string(),
hash: " ".to_string(),
data: serde_json::json!({"spaces": " "}),
};
assert!(state.push(whitespace_model).is_ok());
let numeric_model = LocalDbModel {
id: "12345".to_string(),
hash: "67890".to_string(),
data: serde_json::json!({"number": 42}),
};
assert!(state.push(numeric_model).is_ok());
let zero_model = LocalDbModel {
id: "zero_test".to_string(),
hash: "zero_hash".to_string(),
data: serde_json::json!({"zero": 0, "false": false, "null": null}),
};
assert!(state.push(zero_model).is_ok());
}
Err(_) => panic!("Failed to initialize database for boundary tests")
}
}
#[test]
fn test_concurrent_reads() {
use std::sync::Arc;
use std::thread;
cleanup_test_databases();
let state = Arc::new(AppDbState::init("concurrent_read_test".to_string()).unwrap());
for i in 1..=10 {
let model = create_test_model(&format!("concurrent_{}", i), None);
state.push(model).unwrap();
}
let mut handles = vec![];
for thread_id in 0..5 {
let state_clone = Arc::clone(&state);
let handle = thread::spawn(move || {
for i in 1..=10 {
let result = state_clone.get_by_id(&format!("concurrent_{}", i));
assert!(result.is_ok(), "Thread {} failed to read record {}", thread_id, i);
if let Ok(Some(model)) = result {
assert_eq!(model.id, format!("concurrent_{}", i));
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_concurrent_read_during_write() {
use std::sync::Arc;
use std::thread;
use std::time::Duration;
cleanup_test_databases();
let state = Arc::new(AppDbState::init("concurrent_rw_test".to_string()).unwrap());
for i in 1..=5 {
let model = create_test_model(&format!("initial_{}", i), None);
state.push(model).unwrap();
}
let state_reader = Arc::clone(&state);
let state_writer = Arc::clone(&state);
let reader_handle = thread::spawn(move || {
for _ in 0..20 {
let result = state_reader.get_by_id("initial_1");
assert!(result.is_ok(), "Reader failed");
thread::sleep(Duration::from_millis(10));
}
});
let writer_handle = thread::spawn(move || {
for i in 6..=15 {
let model = create_test_model(&format!("concurrent_write_{}", i), None);
let result = state_writer.push(model);
assert!(result.is_ok(), "Writer failed for record {}", i);
thread::sleep(Duration::from_millis(15));
}
});
reader_handle.join().unwrap();
writer_handle.join().unwrap();
let all_records = state.get().unwrap();
assert!(all_records.len() >= 15, "Expected at least 15 records, got {}", all_records.len());
}
#[test]
fn test_multiple_database_instances() {
cleanup_test_databases();
let db1 = AppDbState::init(generate_unique_db_name("multi_db_test_1")).unwrap();
let db2 = AppDbState::init(generate_unique_db_name("multi_db_test_2")).unwrap();
let db3 = AppDbState::init(generate_unique_db_name("multi_db_test_3")).unwrap();
for i in 1..=3 {
let model1 = create_test_model(&format!("db1_record_{}", i), Some(serde_json::json!({"db": 1, "id": i})));
let model2 = create_test_model(&format!("db2_record_{}", i), Some(serde_json::json!({"db": 2, "id": i})));
let model3 = create_test_model(&format!("db3_record_{}", i), Some(serde_json::json!({"db": 3, "id": i})));
assert!(db1.push(model1).is_ok());
assert!(db2.push(model2).is_ok());
assert!(db3.push(model3).is_ok());
}
assert_eq!(db1.get().unwrap().len(), 3);
assert_eq!(db2.get().unwrap().len(), 3);
assert_eq!(db3.get().unwrap().len(), 3);
assert!(db1.get_by_id("db2_record_1").unwrap().is_none());
assert!(db2.get_by_id("db3_record_1").unwrap().is_none());
assert!(db3.get_by_id("db1_record_1").unwrap().is_none());
}
#[test]
fn test_memory_usage_large_dataset() {
cleanup_test_databases();
match AppDbState::init("memory_test_db".to_string()) {
Ok(state) => {
let initial_memory = get_memory_usage();
for i in 0..1000 {
let large_data = serde_json::json!({
"id": i,
"data": "x".repeat(1024), "nested": {
"array": (0..100).collect::<Vec<i32>>(),
"string": format!("test_string_{}", i)
}
});
let model = LocalDbModel {
id: format!("memory_test_{}", i),
hash: format!("hash_{}", i),
data: large_data,
};
if let Err(e) = state.push(model) {
info!("Memory test stopped at record {} due to: {:?}", i, e);
break;
}
if i % 100 == 0 {
let current_memory = get_memory_usage();
let memory_increase = current_memory.saturating_sub(initial_memory);
info!("Memory usage after {} records: {} KB increase", i, memory_increase / 1024);
if memory_increase > 100 * 1024 * 1024 { info!("Memory usage limit reached, stopping test");
break;
}
}
}
for i in 0..10 {
let result = state.get_by_id(&format!("memory_test_{}", i));
if let Ok(Some(model)) = result {
assert_eq!(model.id, format!("memory_test_{}", i));
}
}
info!("Memory test completed successfully");
}
Err(_) => panic!("Failed to initialize database for memory tests")
}
}
#[test]
fn test_repeated_operations_memory_stability() {
cleanup_test_databases();
match AppDbState::init("memory_stability_test".to_string()) {
Ok(state) => {
let initial_memory = get_memory_usage();
for cycle in 0..10 {
for i in 0..50 {
let model = create_test_model(&format!("cycle_{}_record_{}", cycle, i), None);
let _ = state.push(model);
}
for i in 0..50 {
let _ = state.get_by_id(&format!("cycle_{}_record_{}", cycle, i));
}
for i in 0..25 {
let mut model = create_test_model(&format!("cycle_{}_record_{}", cycle, i), None);
model.data = serde_json::json!({"updated": true, "cycle": cycle});
let _ = state.update(model);
}
for i in 25..50 {
let _ = state.delete_by_id(&format!("cycle_{}_record_{}", cycle, i));
}
if cycle % 3 == 0 {
let current_memory = get_memory_usage();
let memory_increase = current_memory.saturating_sub(initial_memory);
info!("Memory usage after cycle {}: {} KB increase", cycle, memory_increase / 1024);
}
}
info!("Memory stability test completed");
}
Err(_) => panic!("Failed to initialize database for memory stability tests")
}
}
#[test]
fn test_rapid_insert_delete_cycles() {
cleanup_test_databases();
match AppDbState::init("stress_test_db".to_string()) {
Ok(state) => {
let start_time = SystemTime::now();
for cycle in 0..100 {
for i in 0..10 {
let model = create_test_model(&format!("stress_{}_{}", cycle, i), None);
if state.push(model).is_err() {
info!("Insert failed at cycle {} item {}", cycle, i);
}
}
for i in 0..10 {
if state.delete_by_id(&format!("stress_{}_{}", cycle, i)).is_err() {
info!("Delete failed at cycle {} item {}", cycle, i);
}
}
if cycle % 20 == 0 {
let records = state.get().unwrap_or_default();
info!("After {} cycles: {} records remaining", cycle, records.len());
}
}
let duration = start_time.elapsed().unwrap();
info!("Rapid cycles completed in {:?}", duration);
let final_records = state.get().unwrap_or_default();
info!("Final record count: {}", final_records.len());
}
Err(_) => panic!("Failed to initialize database for stress tests")
}
}
#[test]
fn test_bulk_operations_performance() {
cleanup_test_databases();
match AppDbState::init("bulk_perf_test".to_string()) {
Ok(state) => {
let start_time = SystemTime::now();
info!("Starting bulk insert test");
for i in 0..1000 {
let model = create_test_model(&format!("bulk_{}", i), Some(serde_json::json!({
"index": i,
"data": format!("bulk_data_{}", i),
"timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()
})));
if state.push(model).is_err() {
info!("Bulk insert failed at record {}", i);
break;
}
if i % 100 == 0 {
let elapsed = start_time.elapsed().unwrap();
info!("Inserted {} records in {:?}", i, elapsed);
}
}
let insert_time = start_time.elapsed().unwrap();
info!("Bulk insert completed in {:?}", insert_time);
let read_start = SystemTime::now();
let all_records = state.get().unwrap_or_default();
let read_time = read_start.elapsed().unwrap();
info!("Bulk read of {} records completed in {:?}", all_records.len(), read_time);
let random_start = SystemTime::now();
for i in (0..all_records.len()).step_by(13) { let _ = state.get_by_id(&format!("bulk_{}", i));
}
let random_time = random_start.elapsed().unwrap();
info!("Random access test completed in {:?}", random_time);
let update_start = SystemTime::now();
for i in (0..all_records.len()).step_by(10) { let mut model = create_test_model(&format!("bulk_{}", i), None);
model.data = serde_json::json!({"updated": true, "original_index": i});
let _ = state.update(model);
}
let update_time = update_start.elapsed().unwrap();
info!("Bulk update test completed in {:?}", update_time);
}
Err(_) => panic!("Failed to initialize database for performance tests")
}
}
#[test]
fn test_database_size_growth() {
cleanup_test_databases();
match AppDbState::init("size_growth_test".to_string()) {
Ok(state) => {
let mut sizes = Vec::new();
for batch in 0..10 {
for i in 0..100 {
let model = create_test_model(
&format!("size_test_{}_{}", batch, i),
Some(serde_json::json!({
"batch": batch,
"index": i,
"payload": "x".repeat(100) }))
);
let _ = state.push(model);
}
let db_size = get_database_size("size_growth_test.lmdb");
sizes.push(db_size);
info!("After batch {}: {} records, {} KB database size",
batch, (batch + 1) * 100, db_size / 1024);
}
for i in 1..sizes.len() {
assert!(sizes[i] >= sizes[i-1], "Database size should not decrease");
}
info!("Database size growth test completed");
}
Err(_) => panic!("Failed to initialize database for size growth tests")
}
}
#[test]
fn test_special_filesystem_paths() {
cleanup_test_databases();
let relative_result = AppDbState::init("./relative_path_test".to_string());
assert!(relative_result.is_ok() || relative_result.is_err());
let space_result = AppDbState::init("space test db".to_string());
assert!(space_result.is_ok() || space_result.is_err());
let unicode_result = AppDbState::init("测试数据库".to_string());
assert!(unicode_result.is_ok() || unicode_result.is_err());
let long_name = "very_long_database_name_".repeat(10);
let long_result = AppDbState::init(long_name);
assert!(long_result.is_ok() || long_result.is_err());
info!("Filesystem path tests completed");
}
#[test]
fn test_case_sensitivity() {
cleanup_test_databases();
match AppDbState::init("case_test_db".to_string()) {
Ok(state) => {
let lower_model = create_test_model("lowercase_id", None);
let upper_model = create_test_model("UPPERCASE_ID", None);
let mixed_model = create_test_model("MixedCase_ID", None);
assert!(state.push(lower_model).is_ok());
assert!(state.push(upper_model).is_ok());
assert!(state.push(mixed_model).is_ok());
assert!(state.get_by_id("lowercase_id").unwrap().is_some());
assert!(state.get_by_id("LOWERCASE_ID").unwrap().is_none()); assert!(state.get_by_id("UPPERCASE_ID").unwrap().is_some());
assert!(state.get_by_id("uppercase_id").unwrap().is_none()); assert!(state.get_by_id("MixedCase_ID").unwrap().is_some());
assert!(state.get_by_id("mixedcase_id").unwrap().is_none());
info!("Case sensitivity test completed");
}
Err(_) => panic!("Failed to initialize database for case sensitivity tests")
}
}
#[test]
fn test_hot_restart_simulation() {
cleanup_test_databases();
let db_name = generate_unique_db_name("hot_restart_test");
{
let state = AppDbState::init(db_name.clone()).unwrap();
for i in 1..=5 {
let model = create_test_model(&format!("persistent_data_{}", i), None);
state.push(model).unwrap();
info!("Inserted persistent_data_{}", i);
}
let all_records = state.get().unwrap();
assert_eq!(all_records.len(), 5, "Data should be present before closing");
info!("Verified {} records before close", all_records.len());
let mut state_mut = state;
let _ = state_mut.close_database();
std::thread::sleep(std::time::Duration::from_millis(500));
drop(state_mut);
}
std::thread::sleep(std::time::Duration::from_millis(200));
let db_path = format!("{}.lmdb", db_name);
info!("Checking if database path exists: {}", db_path);
if std::path::Path::new(&db_path).exists() {
info!("Database path exists");
} else {
panic!("Database path does not exist: {}", db_path);
}
{
info!("Reopening database: {}", db_name);
let state = AppDbState::init(db_name.clone()).unwrap();
let all_records = state.get().unwrap();
info!("After restart, found {} records", all_records.len());
for record in &all_records {
info!("Found record: {}", record.id);
}
assert_eq!(all_records.len(), 5, "Data should persist through hot restart");
for i in 1..=5 {
let record = state.get_by_id(&format!("persistent_data_{}", i)).unwrap();
assert!(record.is_some(), "Record {} should persist", i);
}
for i in 6..=10 {
let model = create_test_model(&format!("post_restart_data_{}", i), None);
state.push(model).unwrap();
}
let final_records = state.get().unwrap();
assert_eq!(final_records.len(), 10, "Should have 10 records after restart");
}
info!("Hot restart simulation completed successfully");
}
#[test]
fn test_multiple_instance_cleanup() {
cleanup_test_databases();
let db_names: Vec<String> = (0..5).map(|i| generate_unique_db_name(&format!("cleanup_test_{}", i))).collect();
let instances = db_names.iter().enumerate().map(|(i, db_name)| {
let state = AppDbState::init(db_name.clone()).unwrap();
let model = create_test_model(&format!("data_{}", i), None);
state.push(model).unwrap();
let records = state.get().unwrap();
assert_eq!(records.len(), 1, "Should have 1 record in instance {}", i);
state
}).collect::<Vec<_>>();
for (i, instance) in instances.iter().enumerate() {
let record = instance.get_by_id(&format!("data_{}", i)).unwrap();
assert!(record.is_some());
}
drop(instances);
std::thread::sleep(std::time::Duration::from_millis(300));
for (i, db_name) in db_names.iter().enumerate() {
let state = AppDbState::init(db_name.clone()).unwrap();
let records = state.get().unwrap();
info!("Instance {} has {} records after restart", i, records.len());
assert_eq!(records.len(), 1, "Data should persist for instance {}", i);
}
info!("Multiple instance cleanup test completed");
}
fn get_memory_usage() -> usize {
0
}
fn get_database_size(db_path: &str) -> u64 {
use std::fs;
match fs::metadata(db_path) {
Ok(metadata) => {
if metadata.is_dir() {
fs::read_dir(db_path)
.map(|entries| {
entries
.filter_map(Result::ok)
.filter_map(|entry| entry.metadata().ok())
.map(|metadata| metadata.len())
.sum()
})
.unwrap_or(0)
} else {
metadata.len()
}
}
Err(_) => 0,
}
}
}