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

@ -2,12 +2,6 @@
This directory contains the core business models used throughout the application for representing essential business objects like products, sales, and currency. 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 │ │ Customer │