This commit is contained in:
despiegk 2025-04-22 06:26:05 +04:00
parent f993d74ea1
commit 6443c6b647
6 changed files with 1 additions and 837 deletions

View File

@ -1,297 +0,0 @@
//! Integration tests for the zaz database module
use crate::core::{DB, DBBuilder, SledDBResult, Storable, SledModel, SledDB};
use crate::zaz::models::user::User;
use crate::zaz::models::company::{Company, BusinessType, CompanyStatus};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use tempfile::tempdir;
use std::fmt::{Display, Formatter};
/// Test the basic database functionality
#[test]
fn test_basic_database_operations() {
match run_comprehensive_test() {
Ok(_) => println!("All tests passed successfully!"),
Err(e) => panic!("Error running tests: {}", e),
}
}
fn run_comprehensive_test() -> Result<(), Box<dyn std::error::Error>> {
// Create a temporary directory for testing
let temp_dir = tempdir()?;
println!("Using temporary directory: {:?}", temp_dir.path());
println!("\n--- Testing User operations ---");
test_user_operations(temp_dir.path())?;
println!("\n--- Testing Company operations ---");
test_company_operations(temp_dir.path())?;
println!("\n--- Testing Transaction Simulation ---");
test_transaction_simulation(temp_dir.path())?;
// Clean up
drop(temp_dir);
println!("All comprehensive tests completed successfully!");
Ok(())
}
fn test_user_operations(base_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
// Open the user database
let db = SledDB::<User>::open(base_path.join("users"))?;
println!("Opened user database at: {:?}", base_path.join("users"));
// Create a test user
let user = User::new(
100,
"Test User".to_string(),
"test@example.com".to_string(),
"password123".to_string(),
"Test Company".to_string(),
"Admin".to_string(),
);
// Insert the user
db.insert(&user)?;
println!("Inserted user: {}", user.name);
// Retrieve the user
let retrieved_user = db.get(&user.id.to_string())?;
println!("Retrieved user: {}", retrieved_user.name);
assert_eq!(user.name, retrieved_user.name);
assert_eq!(user.email, retrieved_user.email);
// Update the user
let updated_user = User::new(
100,
"Updated User".to_string(),
"updated@example.com".to_string(),
"newpassword".to_string(),
"New Company".to_string(),
"SuperAdmin".to_string(),
);
db.insert(&updated_user)?;
println!("Updated user: {}", updated_user.name);
// Retrieve the updated user
let retrieved_user = db.get(&user.id.to_string())?;
println!("Retrieved updated user: {}", retrieved_user.name);
assert_eq!(updated_user.name, retrieved_user.name);
assert_eq!(updated_user.email, retrieved_user.email);
// Delete the user
db.delete(&user.id.to_string())?;
println!("Deleted user: {}", user.name);
// Try to retrieve the deleted user (should fail)
let result = db.get(&user.id.to_string());
assert!(result.is_err(), "User should be deleted");
println!("Verified user was deleted");
Ok(())
}
fn test_company_operations(base_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
// Open the company database
let db = SledDB::<Company>::open(base_path.join("companies"))?;
println!("Opened company database at: {:?}", base_path.join("companies"));
// Create a test company
let company = Company::new(
100,
"Test Corp".to_string(),
"TEST123".to_string(),
Utc::now(),
"12-31".to_string(),
"test@corp.com".to_string(),
"123-456-7890".to_string(),
"www.testcorp.com".to_string(),
"123 Test St".to_string(),
BusinessType::new(BusinessType::GLOBAL.to_string())
.unwrap_or_else(|e| {
eprintln!("Warning: {}", e);
BusinessType::new_unchecked(BusinessType::GLOBAL.to_string())
}),
"Technology".to_string(),
"A test company".to_string(),
CompanyStatus::Active,
);
// Insert the company
db.insert(&company)?;
println!("Inserted company: {}", company.name);
// Retrieve the company
let retrieved_company = db.get(&company.id.to_string())?;
println!("Retrieved company: {}", retrieved_company.name);
assert_eq!(company.name, retrieved_company.name);
// List all companies
let companies = db.list()?;
println!("Found {} companies", companies.len());
assert_eq!(companies.len(), 1);
// Delete the company
db.delete(&company.id.to_string())?;
println!("Deleted company: {}", company.name);
// List companies again (should be empty)
let companies = db.list()?;
assert_eq!(companies.len(), 0);
println!("Verified company was deleted");
Ok(())
}
fn test_transaction_simulation(base_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
// Create a DB instance with User model registered
let mut db = DB::new(base_path.join("transaction"))?;
db.register::<User>()?;
println!("Created DB with User model registered at: {:?}", base_path.join("transaction"));
// Add a user outside of transaction
let user = User::new(
200,
"Transaction Test".to_string(),
"tx@example.com".to_string(),
"password".to_string(),
"TX Corp".to_string(),
"User".to_string(),
);
// Add the user without a transaction
db.set(&user)?;
println!("Added initial user: {}", user.name);
// Begin a transaction
db.begin_transaction()?;
println!("Transaction started");
// Update user in transaction
let updated_user = User::new(
200,
"Updated in TX".to_string(),
"updated@example.com".to_string(),
"newpass".to_string(),
"New Corp".to_string(),
"Admin".to_string(),
);
db.set(&updated_user)?;
println!("Updated user in transaction");
// Add new user in transaction
let new_user = User::new(
201,
"New in TX".to_string(),
"new@example.com".to_string(),
"password".to_string(),
"New Corp".to_string(),
"User".to_string(),
);
db.set(&new_user)?;
println!("Added new user in transaction");
// Verify transaction changes are visible within transaction
let tx_user: User = db.get(&user.id.to_string())?;
assert_eq!(tx_user.name, "Updated in TX");
println!("Verified transaction changes are visible");
// Commit the transaction
db.commit_transaction()?;
println!("Transaction committed");
// Verify changes persisted after commit
let committed_user: User = db.get(&user.id.to_string())?;
assert_eq!(committed_user.name, "Updated in TX");
println!("Verified changes persisted after commit");
// Test transaction rollback
// Begin another transaction
db.begin_transaction()?;
println!("New transaction started");
// Make changes that will be rolled back
let rollback_user = User::new(
200,
"Will Be Rolled Back".to_string(),
"rollback@example.com".to_string(),
"temppass".to_string(),
"Temp Corp".to_string(),
"TempAdmin".to_string(),
);
db.set(&rollback_user)?;
println!("Updated user in transaction that will be rolled back");
// Rollback the transaction
db.rollback_transaction()?;
println!("Transaction rolled back");
// Verify original data is intact
let after_rollback: User = db.get(&user.id.to_string())?;
assert_eq!(after_rollback.name, "Updated in TX");
println!("Verified data is intact after rollback: {}", after_rollback.name);
Ok(())
}
#[test]
fn test_simple_db() {
// A simpler test that uses a basic DB setup
// Test model
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
id: u32,
name: String,
email: String,
}
impl User {
fn new(id: u32, name: String, email: String) -> Self {
Self { id, name, email }
}
}
impl Storable for User {}
impl SledModel for User {
fn get_id(&self) -> String {
self.id.to_string()
}
fn db_prefix() -> &'static str {
"test_simple_user"
}
}
// Create a temporary directory for the test
let dir = tempdir().expect("Failed to create temp dir");
// Create a DB with the builder
let db = DBBuilder::new(dir.path())
.register_model::<User>()
.build()
.expect("Failed to build DB");
// Create a test user
let user = User::new(1, "Simple Test User".to_string(), "simple@example.com".to_string());
// Set the user
db.set(&user).expect("Failed to set user");
// Get the user
let retrieved: User = db.get(&user.id.to_string()).expect("Failed to get user");
// Check that it matches
assert_eq!(user.name, retrieved.name);
assert_eq!(user.email, retrieved.email);
println!("Simple DB test passed!");
}

View File

@ -1,6 +0,0 @@
//! Tests for the Zaz module
// Re-export the test modules
pub mod model_db_test;
pub mod db_integration_test;
pub mod transaction_test;

View File

@ -1,194 +0,0 @@
//! Tests for the new model-DB architecture
use crate::core::{DB, DBBuilder, SledDBResult, Storable, SledModel};
use crate::zaz::factory::create_zaz_db;
use crate::zaz::models::user::User;
use crate::zaz::models::company::{Company, BusinessType, CompanyStatus};
use crate::zaz::models::product::{Product, ProductStatus, ProductType, Currency};
use chrono::Utc;
use std::path::Path;
use tempfile::tempdir;
#[test]
fn test_zaz_db_factory() {
// Create a temporary directory for testing
let temp_dir = tempdir().expect("Failed to create temp directory");
println!("Created temporary directory at: {:?}", temp_dir.path());
// Create a DB with all zaz models registered using the factory
let db = create_zaz_db(temp_dir.path()).expect("Failed to create zaz DB");
// Test with a user model
let user = User::new(
1,
"Factory Test User".to_string(),
"factory@example.com".to_string(),
"password".to_string(),
"Test Company".to_string(),
"User".to_string(),
);
// Insert the user
db.set(&user).expect("Failed to insert user");
// Retrieve the user
let retrieved: User = db.get(&user.id.to_string()).expect("Failed to retrieve user");
// Verify the user is correct
assert_eq!(user.name, retrieved.name);
assert_eq!(user.email, retrieved.email);
// Test with a company model
let company = Company::new(
1,
"Factory Test Corp".to_string(),
"FTC-123".to_string(),
Utc::now(),
"12-31".to_string(),
"info@ftc.com".to_string(),
"123-456-7890".to_string(),
"www.ftc.com".to_string(),
"123 Factory St".to_string(),
BusinessType::new(BusinessType::GLOBAL.to_string())
.unwrap_or_else(|e| {
eprintln!("Warning: {}", e);
BusinessType::new_unchecked(BusinessType::GLOBAL.to_string())
}),
"Technology".to_string(),
"A test company for the factory pattern".to_string(),
CompanyStatus::Active,
);
// Insert the company
db.set(&company).expect("Failed to insert company");
// Retrieve the company
let retrieved: Company = db.get(&company.id.to_string()).expect("Failed to retrieve company");
// Verify the company is correct
assert_eq!(company.name, retrieved.name);
assert_eq!(company.registration_number, retrieved.registration_number);
println!("All zaz DB factory tests passed successfully!");
}
#[test]
fn test_db_builder() {
// Create a temporary directory for testing
let temp_dir = tempdir().expect("Failed to create temp directory");
println!("Created temporary directory at: {:?}", temp_dir.path());
// Create a DB with selectively registered models using the builder pattern
let db = DBBuilder::new(temp_dir.path())
.register_model::<User>()
.register_model::<Company>()
.build()
.expect("Failed to build DB");
// Test with a user model
let user = User::new(
2,
"Builder Test User".to_string(),
"builder@example.com".to_string(),
"password".to_string(),
"Test Company".to_string(),
"User".to_string(),
);
// Insert the user
db.set(&user).expect("Failed to insert user");
// Retrieve the user
let retrieved: User = db.get(&user.id.to_string()).expect("Failed to retrieve user");
// Verify the user is correct
assert_eq!(user.name, retrieved.name);
assert_eq!(user.email, retrieved.email);
// Test that unregistered models cause an error
let product = Product::new(
1,
"Unregistered Product".to_string(),
"A test product".to_string(),
Currency::new(100.0, "USD".to_string()),
ProductType::Product,
"Test".to_string(),
ProductStatus::Available,
10, // max_amount
30, // validity_days
);
// This should fail because Product was not registered
let result = db.set(&product);
assert!(result.is_err(), "Setting unregistered model should fail");
println!("All DB builder tests passed successfully!");
}
#[test]
fn test_dynamic_registration() {
// Create a temporary directory for testing
let temp_dir = tempdir().expect("Failed to create temp directory");
println!("Created temporary directory at: {:?}", temp_dir.path());
// Create an empty DB
let mut db = DB::new(temp_dir.path()).expect("Failed to create empty DB");
// Register User model dynamically
db.register::<User>().expect("Failed to register User");
// Test with a user model
let user = User::new(
3,
"Dynamic Test User".to_string(),
"dynamic@example.com".to_string(),
"password".to_string(),
"Test Company".to_string(),
"User".to_string(),
);
// Insert the user
db.set(&user).expect("Failed to insert user");
// Retrieve the user
let retrieved: User = db.get(&user.id.to_string()).expect("Failed to retrieve user");
// Verify the user is correct
assert_eq!(user.name, retrieved.name);
assert_eq!(user.email, retrieved.email);
// Now dynamically register Company
db.register::<Company>().expect("Failed to register Company");
// Test with a company model
let company = Company::new(
3,
"Dynamic Test Corp".to_string(),
"DTC-123".to_string(),
Utc::now(),
"12-31".to_string(),
"info@dtc.com".to_string(),
"123-456-7890".to_string(),
"www.dtc.com".to_string(),
"123 Dynamic St".to_string(),
BusinessType::new(BusinessType::GLOBAL.to_string())
.unwrap_or_else(|e| {
eprintln!("Warning: {}", e);
BusinessType::new_unchecked(BusinessType::GLOBAL.to_string())
}),
"Technology".to_string(),
"A test company for dynamic registration".to_string(),
CompanyStatus::Active,
);
// Insert the company
db.set(&company).expect("Failed to insert company");
// Retrieve the company
let retrieved: Company = db.get(&company.id.to_string()).expect("Failed to retrieve company");
// Verify the company is correct
assert_eq!(company.name, retrieved.name);
println!("All dynamic registration tests passed successfully!");
}

View File

@ -1,265 +0,0 @@
//! Transaction tests for the zaz database module
use sled;
use bincode;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::Path;
use tempfile::tempdir;
use std::collections::HashMap;
/// Test the transaction-like behavior capabilities
#[test]
fn test_transaction_operations() {
match run_transaction_test() {
Ok(_) => println!("All transaction tests passed successfully!"),
Err(e) => panic!("Error in transaction tests: {}", e),
}
}
fn run_transaction_test() -> Result<(), Box<dyn std::error::Error>> {
// Create a temporary directory for testing
let temp_dir = tempdir()?;
println!("Using temporary directory: {:?}", temp_dir.path());
test_basic_transactions(temp_dir.path())?;
test_rollback_behavior(temp_dir.path())?;
test_concurrent_operations(temp_dir.path())?;
// Clean up
drop(temp_dir);
println!("All transaction tests completed successfully!");
Ok(())
}
/// User model for testing
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct User {
id: u32,
name: String,
email: String,
balance: f64,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
impl User {
fn new(id: u32, name: String, email: String, balance: f64) -> Self {
let now = Utc::now();
Self {
id,
name,
email,
balance,
created_at: now,
updated_at: now,
}
}
}
/// Test basic transaction functionality
fn test_basic_transactions(base_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
// Open the test database
let db = sled::open(base_path.join("basic_tx"))?;
println!("Opened basic transaction test database at: {:?}", base_path.join("basic_tx"));
// Create initial users
let user1 = User::new(
1,
"User One".to_string(),
"one@example.com".to_string(),
100.0,
);
let user2 = User::new(
2,
"User Two".to_string(),
"two@example.com".to_string(),
50.0,
);
// Insert initial users
db.insert(user1.id.to_string().as_bytes(), bincode::serialize(&user1)?)?;
db.insert(user2.id.to_string().as_bytes(), bincode::serialize(&user2)?)?;
db.flush()?;
println!("Inserted initial users");
// Simulate a transaction - transfer 25.0 from user1 to user2
println!("Starting transaction simulation: transfer 25.0 from user1 to user2");
// Create transaction workspace
let mut tx_workspace = HashMap::new();
// Retrieve current state
if let Some(data) = db.get(user1.id.to_string().as_bytes())? {
let user: User = bincode::deserialize(&data)?;
tx_workspace.insert(user1.id.to_string(), user);
} else {
return Err("Failed to find user1".into());
}
if let Some(data) = db.get(user2.id.to_string().as_bytes())? {
let user: User = bincode::deserialize(&data)?;
tx_workspace.insert(user2.id.to_string(), user);
} else {
return Err("Failed to find user2".into());
}
// Modify both users in the transaction
let mut updated_user1 = tx_workspace.get(&user1.id.to_string()).unwrap().clone();
let mut updated_user2 = tx_workspace.get(&user2.id.to_string()).unwrap().clone();
updated_user1.balance -= 25.0;
updated_user2.balance += 25.0;
// Update the workspace
tx_workspace.insert(user1.id.to_string(), updated_user1);
tx_workspace.insert(user2.id.to_string(), updated_user2);
// Commit the transaction
println!("Committing transaction");
for (key, user) in tx_workspace {
let user_bytes = bincode::serialize(&user)?;
db.insert(key.as_bytes(), user_bytes)?;
}
db.flush()?;
// Verify the results
if let Some(data) = db.get(user1.id.to_string().as_bytes())? {
let final_user1: User = bincode::deserialize(&data)?;
assert_eq!(final_user1.balance, 75.0, "User1 balance should be 75.0");
println!("Verified user1 balance is now {}", final_user1.balance);
} else {
return Err("Failed to find user1 after transaction".into());
}
if let Some(data) = db.get(user2.id.to_string().as_bytes())? {
let final_user2: User = bincode::deserialize(&data)?;
assert_eq!(final_user2.balance, 75.0, "User2 balance should be 75.0");
println!("Verified user2 balance is now {}", final_user2.balance);
} else {
return Err("Failed to find user2 after transaction".into());
}
// Clean up
drop(db);
Ok(())
}
/// Test transaction rollback functionality
fn test_rollback_behavior(base_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
// Open the test database
let db = sled::open(base_path.join("rollback_tx"))?;
println!("Opened rollback test database at: {:?}", base_path.join("rollback_tx"));
// Create initial user
let user = User::new(
1,
"Rollback Test".to_string(),
"rollback@example.com".to_string(),
100.0,
);
// Insert initial user
db.insert(user.id.to_string().as_bytes(), bincode::serialize(&user)?)?;
db.flush()?;
println!("Inserted initial user with balance: {}", user.balance);
// Simulate a transaction that shouldn't be committed
println!("Starting transaction that will be rolled back");
// Create transaction workspace (we'd track in memory)
let mut updated_user = user.clone();
updated_user.balance = 0.0; // Drastic change
// Do NOT commit changes to the database (simulating rollback)
println!("Rolling back transaction (by not writing changes)");
// Verify the original data is intact
if let Some(data) = db.get(user.id.to_string().as_bytes())? {
let final_user: User = bincode::deserialize(&data)?;
assert_eq!(final_user.balance, 100.0, "User balance should remain 100.0");
println!("Verified user balance is still {} after rollback", final_user.balance);
} else {
return Err("Failed to find user after rollback".into());
}
// Clean up
drop(db);
Ok(())
}
/// Test multiple operations that might happen concurrently
fn test_concurrent_operations(base_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
// Open the test database
let db = sled::open(base_path.join("concurrent_tx"))?;
println!("Opened concurrent operations test database at: {:?}", base_path.join("concurrent_tx"));
// Create initial user
let user = User::new(
1,
"Concurrent Test".to_string(),
"concurrent@example.com".to_string(),
100.0,
);
// Insert initial user
db.insert(user.id.to_string().as_bytes(), bincode::serialize(&user)?)?;
db.flush()?;
println!("Inserted initial user with balance: {}", user.balance);
// Simulate two concurrent transactions
// Transaction 1: Add 50 to balance
println!("Starting simulated concurrent transaction 1: Add 50 to balance");
// Read current state for TX1
let mut tx1_user = user.clone();
if let Some(data) = db.get(user.id.to_string().as_bytes())? {
tx1_user = bincode::deserialize(&data)?;
}
// Transaction 2: Subtract 30 from balance
println!("Starting simulated concurrent transaction 2: Subtract 30 from balance");
// Read current state for TX2 (same starting point)
let mut tx2_user = user.clone();
if let Some(data) = db.get(user.id.to_string().as_bytes())? {
tx2_user = bincode::deserialize(&data)?;
}
// Modify in TX1
tx1_user.balance += 50.0;
// Modify in TX2
tx2_user.balance -= 30.0;
// Commit TX1 first
println!("Committing TX1");
db.insert(user.id.to_string().as_bytes(), bincode::serialize(&tx1_user)?)?;
db.flush()?;
// Now commit TX2 (would overwrite TX1 in naive implementation)
println!("Committing TX2");
db.insert(user.id.to_string().as_bytes(), bincode::serialize(&tx2_user)?)?;
db.flush()?;
// Verify the final state (last write wins, so should be TX2's value)
if let Some(data) = db.get(user.id.to_string().as_bytes())? {
let final_user: User = bincode::deserialize(&data)?;
assert_eq!(final_user.balance, 70.0, "Final balance should be 70.0 (TX2 overwrote TX1)");
println!("Final user balance is {} after both transactions", final_user.balance);
// In a real implementation with better concurrency control, you'd expect:
// println!("In a proper ACID system, this would have been 120.0 (100.0 - 30.0 + 50.0)");
} else {
return Err("Failed to find user after concurrent transactions".into());
}
// Clean up
drop(db);
Ok(())
}

View File

@ -1,68 +0,0 @@
// Examples for using the Zaz database
use crate::zaz::models::*;
use crate::zaz::factory::create_zaz_db;
use std::path::PathBuf;
use chrono::Utc;
/// Run a simple example of the DB operations
pub fn run_db_examples() -> Result<(), Box<dyn std::error::Error>> {
println!("Running Zaz DB examples...");
// Create a temp DB path
let db_path = PathBuf::from("/tmp/zaz-examples");
std::fs::create_dir_all(&db_path)?;
// Create DB instance
let db = create_zaz_db(&db_path)?;
// Example 1: User operations
println!("\n--- User Examples ---");
let user = User::new(
1,
"John Doe".to_string(),
"john@example.com".to_string(),
"secure123".to_string(),
"Example Corp".to_string(),
"User".to_string(),
);
db.set(&user)?;
println!("Inserted user: {}", user.name);
let retrieved_user = db.get::<User>(&user.id.to_string())?;
println!("Retrieved user: {} ({})", retrieved_user.name, retrieved_user.email);
// Example 2: Company operations
println!("\n--- Company Examples ---");
let company = Company::new(
1,
"Example Corp".to_string(),
"EX123456".to_string(),
Utc::now(),
"12-31".to_string(),
"info@example.com".to_string(),
"123-456-7890".to_string(),
"www.example.com".to_string(),
"123 Example St, Example City".to_string(),
BusinessType::new(BusinessType::GLOBAL.to_string())
.unwrap_or_else(|e| {
eprintln!("Warning: {}", e);
BusinessType::new_unchecked(BusinessType::GLOBAL.to_string())
}),
"Technology".to_string(),
"An example company".to_string(),
CompanyStatus::Active,
);
db.set(&company)?;
println!("Inserted company: {}", company.name);
let companies = db.list::<Company>()?;
println!("Found {} companies", companies.len());
// Clean up
std::fs::remove_dir_all(db_path)?;
Ok(())
}

View File

@ -1,13 +1,7 @@
# Business Models
This directory contains the core business models used throughout the application for representing essential business objects like products, sales, and currency.
## Overview
The business models are implemented as Rust structs and enums with serialization/deserialization support via Serde. These models implement the `SledModel` and `Storable` traits for persistence in the application's database layer.
## Model Relationships
```
┌─────────────┐
│ Customer │