Add memory crate unit tests
This commit is contained in:
parent
816f2d46b8
commit
35a95fbc3d
2 changed files with 613 additions and 0 deletions
|
@ -11,3 +11,6 @@ application = { path = "../application" }
|
||||||
tokio = { workspace = true, features = ["sync"] }
|
tokio = { workspace = true, features = ["sync"] }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
futures = "0.3"
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
//! # memory
|
||||||
|
//!
|
||||||
|
//! This crate provides in-memory implementations of the `Repository` trait for users and products.
|
||||||
|
//! It is primarily intended for testing, development, and CLI/TUI modes where persistence is not required.
|
||||||
|
//!
|
||||||
|
//! ## Features
|
||||||
|
//! - Thread-safe, async in-memory storage for `User` and `Product` entities
|
||||||
|
//! - Implements all CRUD operations
|
||||||
|
//! - Used as a backend for memory-based API, CLI, and TUI binaries
|
||||||
|
//! - Comprehensive unit tests for all repository and service operations
|
||||||
|
//!
|
||||||
|
//! ## Usage
|
||||||
|
//! Use `InMemoryUserRepository` and `InMemoryProductRepository` directly, or via the `MemoryUserService` and `MemoryProductService` type aliases.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is part of Sharenet.
|
* This file is part of Sharenet.
|
||||||
*
|
*
|
||||||
|
@ -19,6 +33,9 @@ use domain::{
|
||||||
};
|
};
|
||||||
use application::{Repository, Service};
|
use application::{Repository, Service};
|
||||||
|
|
||||||
|
/// Thread-safe, async in-memory repository for `User` entities.
|
||||||
|
///
|
||||||
|
/// Implements all CRUD operations. Intended for testing and non-persistent use cases.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct InMemoryUserRepository {
|
pub struct InMemoryUserRepository {
|
||||||
users: Arc<RwLock<HashMap<Uuid, User>>>,
|
users: Arc<RwLock<HashMap<Uuid, User>>>,
|
||||||
|
@ -86,6 +103,9 @@ impl Repository<User> for InMemoryUserRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Thread-safe, async in-memory repository for `Product` entities.
|
||||||
|
///
|
||||||
|
/// Implements all CRUD operations. Intended for testing and non-persistent use cases.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct InMemoryProductRepository {
|
pub struct InMemoryProductRepository {
|
||||||
products: Arc<RwLock<HashMap<Uuid, Product>>>,
|
products: Arc<RwLock<HashMap<Uuid, Product>>>,
|
||||||
|
@ -153,5 +173,595 @@ impl Repository<Product> for InMemoryProductRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type alias for a user service backed by the in-memory repository.
|
||||||
|
///
|
||||||
|
/// Provides a high-level API for user operations using `InMemoryUserRepository`.
|
||||||
pub type MemoryUserService = Service<User, InMemoryUserRepository>;
|
pub type MemoryUserService = Service<User, InMemoryUserRepository>;
|
||||||
|
|
||||||
|
/// Type alias for a product service backed by the in-memory repository.
|
||||||
|
///
|
||||||
|
/// Provides a high-level API for product operations using `InMemoryProductRepository`.
|
||||||
pub type MemoryProductService = Service<Product, InMemoryProductRepository>;
|
pub type MemoryProductService = Service<Product, InMemoryProductRepository>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use domain::{CreateUser, UpdateUser, CreateProduct, UpdateProduct, DomainError};
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
mod user_repository_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_user() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let create_data = CreateUser {
|
||||||
|
username: "testuser".to_string(),
|
||||||
|
email: "test@example.com".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let user = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(user.username, "testuser");
|
||||||
|
assert_eq!(user.email, "test@example.com");
|
||||||
|
assert!(!user.id.is_nil());
|
||||||
|
assert!(user.created_at <= chrono::Utc::now());
|
||||||
|
assert!(user.updated_at <= chrono::Utc::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_by_id_existing() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let create_data = CreateUser {
|
||||||
|
username: "testuser".to_string(),
|
||||||
|
email: "test@example.com".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let created_user = repo.create(create_data).await.unwrap();
|
||||||
|
let found_user = repo.find_by_id(created_user.id).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(found_user.id, created_user.id);
|
||||||
|
assert_eq!(found_user.username, created_user.username);
|
||||||
|
assert_eq!(found_user.email, created_user.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_by_id_not_found() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
let result = repo.find_by_id(non_existent_id).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
match result.unwrap_err() {
|
||||||
|
DomainError::NotFound(msg) => {
|
||||||
|
assert!(msg.contains("User not found"));
|
||||||
|
assert!(msg.contains(&non_existent_id.to_string()));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected NotFound error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_all_empty() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let users = repo.find_all().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(users.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_all_with_users() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
|
||||||
|
let user1 = repo.create(CreateUser {
|
||||||
|
username: "user1".to_string(),
|
||||||
|
email: "user1@example.com".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let user2 = repo.create(CreateUser {
|
||||||
|
username: "user2".to_string(),
|
||||||
|
email: "user2@example.com".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let users = repo.find_all().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(users.len(), 2);
|
||||||
|
assert!(users.iter().any(|u| u.id == user1.id));
|
||||||
|
assert!(users.iter().any(|u| u.id == user2.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_update_user_existing() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let user = repo.create(CreateUser {
|
||||||
|
username: "olduser".to_string(),
|
||||||
|
email: "old@example.com".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let original_updated_at = user.updated_at;
|
||||||
|
sleep(Duration::from_millis(1)).await; // Ensure timestamp difference
|
||||||
|
|
||||||
|
let update_data = UpdateUser {
|
||||||
|
username: Some("newuser".to_string()),
|
||||||
|
email: Some("new@example.com".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let updated_user = repo.update(user.id, update_data).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated_user.username, "newuser");
|
||||||
|
assert_eq!(updated_user.email, "new@example.com");
|
||||||
|
assert!(updated_user.updated_at > original_updated_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_update_user_partial() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let user = repo.create(CreateUser {
|
||||||
|
username: "testuser".to_string(),
|
||||||
|
email: "test@example.com".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let update_data = UpdateUser {
|
||||||
|
username: Some("newuser".to_string()),
|
||||||
|
email: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let updated_user = repo.update(user.id, update_data).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated_user.username, "newuser");
|
||||||
|
assert_eq!(updated_user.email, "test@example.com"); // Should remain unchanged
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_update_user_not_found() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
let update_data = UpdateUser {
|
||||||
|
username: Some("newuser".to_string()),
|
||||||
|
email: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = repo.update(non_existent_id, update_data).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
match result.unwrap_err() {
|
||||||
|
DomainError::NotFound(msg) => {
|
||||||
|
assert!(msg.contains("User not found"));
|
||||||
|
assert!(msg.contains(&non_existent_id.to_string()));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected NotFound error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_user_existing() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let user = repo.create(CreateUser {
|
||||||
|
username: "testuser".to_string(),
|
||||||
|
email: "test@example.com".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let result = repo.delete(user.id).await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
// Verify user is actually deleted
|
||||||
|
let find_result = repo.find_by_id(user.id).await;
|
||||||
|
assert!(find_result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_user_not_found() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
let result = repo.delete(non_existent_id).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
match result.unwrap_err() {
|
||||||
|
DomainError::NotFound(msg) => {
|
||||||
|
assert!(msg.contains("User not found"));
|
||||||
|
assert!(msg.contains(&non_existent_id.to_string()));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected NotFound error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_concurrent_access() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let repo_clone = repo.clone();
|
||||||
|
|
||||||
|
// Spawn multiple tasks to create users concurrently
|
||||||
|
let handles: Vec<_> = (0..10).map(|i| {
|
||||||
|
let repo = repo_clone.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
repo.create(CreateUser {
|
||||||
|
username: format!("user{}", i),
|
||||||
|
email: format!("user{}@example.com", i),
|
||||||
|
}).await
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
// Wait for all tasks to complete
|
||||||
|
let results = futures::future::join_all(handles).await;
|
||||||
|
for result in results {
|
||||||
|
assert!(result.unwrap().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all users were created
|
||||||
|
let users = repo.find_all().await.unwrap();
|
||||||
|
assert_eq!(users.len(), 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod product_repository_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_product() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let create_data = CreateProduct {
|
||||||
|
name: "Test Product".to_string(),
|
||||||
|
description: "Test Description".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let product = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(product.name, "Test Product");
|
||||||
|
assert_eq!(product.description, "Test Description");
|
||||||
|
assert!(!product.id.is_nil());
|
||||||
|
assert!(product.created_at <= chrono::Utc::now());
|
||||||
|
assert!(product.updated_at <= chrono::Utc::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_by_id_existing() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let create_data = CreateProduct {
|
||||||
|
name: "Test Product".to_string(),
|
||||||
|
description: "Test Description".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let created_product = repo.create(create_data).await.unwrap();
|
||||||
|
let found_product = repo.find_by_id(created_product.id).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(found_product.id, created_product.id);
|
||||||
|
assert_eq!(found_product.name, created_product.name);
|
||||||
|
assert_eq!(found_product.description, created_product.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_by_id_not_found() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
let result = repo.find_by_id(non_existent_id).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
match result.unwrap_err() {
|
||||||
|
DomainError::NotFound(msg) => {
|
||||||
|
assert!(msg.contains("Product not found"));
|
||||||
|
assert!(msg.contains(&non_existent_id.to_string()));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected NotFound error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_all_empty() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let products = repo.find_all().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(products.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_find_all_with_products() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
|
||||||
|
let product1 = repo.create(CreateProduct {
|
||||||
|
name: "Product 1".to_string(),
|
||||||
|
description: "Description 1".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let product2 = repo.create(CreateProduct {
|
||||||
|
name: "Product 2".to_string(),
|
||||||
|
description: "Description 2".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let products = repo.find_all().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(products.len(), 2);
|
||||||
|
assert!(products.iter().any(|p| p.id == product1.id));
|
||||||
|
assert!(products.iter().any(|p| p.id == product2.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_update_product_existing() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let product = repo.create(CreateProduct {
|
||||||
|
name: "Old Product".to_string(),
|
||||||
|
description: "Old Description".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let original_updated_at = product.updated_at;
|
||||||
|
sleep(Duration::from_millis(1)).await; // Ensure timestamp difference
|
||||||
|
|
||||||
|
let update_data = UpdateProduct {
|
||||||
|
name: Some("New Product".to_string()),
|
||||||
|
description: Some("New Description".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let updated_product = repo.update(product.id, update_data).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated_product.name, "New Product");
|
||||||
|
assert_eq!(updated_product.description, "New Description");
|
||||||
|
assert!(updated_product.updated_at > original_updated_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_update_product_partial() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let product = repo.create(CreateProduct {
|
||||||
|
name: "Test Product".to_string(),
|
||||||
|
description: "Test Description".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let update_data = UpdateProduct {
|
||||||
|
name: Some("New Product".to_string()),
|
||||||
|
description: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let updated_product = repo.update(product.id, update_data).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated_product.name, "New Product");
|
||||||
|
assert_eq!(updated_product.description, "Test Description"); // Should remain unchanged
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_update_product_not_found() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
let update_data = UpdateProduct {
|
||||||
|
name: Some("New Product".to_string()),
|
||||||
|
description: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = repo.update(non_existent_id, update_data).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
match result.unwrap_err() {
|
||||||
|
DomainError::NotFound(msg) => {
|
||||||
|
assert!(msg.contains("Product not found"));
|
||||||
|
assert!(msg.contains(&non_existent_id.to_string()));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected NotFound error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_product_existing() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let product = repo.create(CreateProduct {
|
||||||
|
name: "Test Product".to_string(),
|
||||||
|
description: "Test Description".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let result = repo.delete(product.id).await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
// Verify product is actually deleted
|
||||||
|
let find_result = repo.find_by_id(product.id).await;
|
||||||
|
assert!(find_result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_product_not_found() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
let result = repo.delete(non_existent_id).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
match result.unwrap_err() {
|
||||||
|
DomainError::NotFound(msg) => {
|
||||||
|
assert!(msg.contains("Product not found"));
|
||||||
|
assert!(msg.contains(&non_existent_id.to_string()));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected NotFound error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_concurrent_access() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let repo_clone = repo.clone();
|
||||||
|
|
||||||
|
// Spawn multiple tasks to create products concurrently
|
||||||
|
let handles: Vec<_> = (0..10).map(|i| {
|
||||||
|
let repo = repo_clone.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
repo.create(CreateProduct {
|
||||||
|
name: format!("Product {}", i),
|
||||||
|
description: format!("Description {}", i),
|
||||||
|
}).await
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
// Wait for all tasks to complete
|
||||||
|
let results = futures::future::join_all(handles).await;
|
||||||
|
for result in results {
|
||||||
|
assert!(result.unwrap().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all products were created
|
||||||
|
let products = repo.find_all().await.unwrap();
|
||||||
|
assert_eq!(products.len(), 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod service_tests {
|
||||||
|
use super::*;
|
||||||
|
use application::UseCase;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_memory_user_service() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let service = MemoryUserService::new(repo);
|
||||||
|
|
||||||
|
// Test create
|
||||||
|
let user = service.create(CreateUser {
|
||||||
|
username: "testuser".to_string(),
|
||||||
|
email: "test@example.com".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(user.username, "testuser");
|
||||||
|
assert_eq!(user.email, "test@example.com");
|
||||||
|
|
||||||
|
// Test get
|
||||||
|
let found_user = service.get(user.id).await.unwrap();
|
||||||
|
assert_eq!(found_user.id, user.id);
|
||||||
|
|
||||||
|
// Test update
|
||||||
|
let updated_user = service.update(user.id, UpdateUser {
|
||||||
|
username: Some("newuser".to_string()),
|
||||||
|
email: None,
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated_user.username, "newuser");
|
||||||
|
assert_eq!(updated_user.email, "test@example.com");
|
||||||
|
|
||||||
|
// Test list
|
||||||
|
let users = service.list().await.unwrap();
|
||||||
|
assert_eq!(users.len(), 1);
|
||||||
|
assert_eq!(users[0].id, user.id);
|
||||||
|
|
||||||
|
// Test delete
|
||||||
|
service.delete(user.id).await.unwrap();
|
||||||
|
|
||||||
|
// Verify deletion
|
||||||
|
let result = service.get(user.id).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_memory_product_service() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let service = MemoryProductService::new(repo);
|
||||||
|
|
||||||
|
// Test create
|
||||||
|
let product = service.create(CreateProduct {
|
||||||
|
name: "Test Product".to_string(),
|
||||||
|
description: "Test Description".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(product.name, "Test Product");
|
||||||
|
assert_eq!(product.description, "Test Description");
|
||||||
|
|
||||||
|
// Test get
|
||||||
|
let found_product = service.get(product.id).await.unwrap();
|
||||||
|
assert_eq!(found_product.id, product.id);
|
||||||
|
|
||||||
|
// Test update
|
||||||
|
let updated_product = service.update(product.id, UpdateProduct {
|
||||||
|
name: Some("New Product".to_string()),
|
||||||
|
description: None,
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated_product.name, "New Product");
|
||||||
|
assert_eq!(updated_product.description, "Test Description");
|
||||||
|
|
||||||
|
// Test list
|
||||||
|
let products = service.list().await.unwrap();
|
||||||
|
assert_eq!(products.len(), 1);
|
||||||
|
assert_eq!(products[0].id, product.id);
|
||||||
|
|
||||||
|
// Test delete
|
||||||
|
service.delete(product.id).await.unwrap();
|
||||||
|
|
||||||
|
// Verify deletion
|
||||||
|
let result = service.get(product.id).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod integration_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_user_lifecycle() {
|
||||||
|
let repo = InMemoryUserRepository::new();
|
||||||
|
|
||||||
|
// Create multiple users
|
||||||
|
let user1 = repo.create(CreateUser {
|
||||||
|
username: "user1".to_string(),
|
||||||
|
email: "user1@example.com".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let user2 = repo.create(CreateUser {
|
||||||
|
username: "user2".to_string(),
|
||||||
|
email: "user2@example.com".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
// Verify both users exist
|
||||||
|
let users = repo.find_all().await.unwrap();
|
||||||
|
assert_eq!(users.len(), 2);
|
||||||
|
|
||||||
|
// Update one user
|
||||||
|
let updated_user1 = repo.update(user1.id, UpdateUser {
|
||||||
|
username: Some("updated_user1".to_string()),
|
||||||
|
email: None,
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated_user1.username, "updated_user1");
|
||||||
|
assert_eq!(updated_user1.email, "user1@example.com");
|
||||||
|
|
||||||
|
// Delete one user
|
||||||
|
repo.delete(user2.id).await.unwrap();
|
||||||
|
|
||||||
|
// Verify only one user remains
|
||||||
|
let remaining_users = repo.find_all().await.unwrap();
|
||||||
|
assert_eq!(remaining_users.len(), 1);
|
||||||
|
assert_eq!(remaining_users[0].id, user1.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_product_lifecycle() {
|
||||||
|
let repo = InMemoryProductRepository::new();
|
||||||
|
|
||||||
|
// Create multiple products
|
||||||
|
let product1 = repo.create(CreateProduct {
|
||||||
|
name: "Product 1".to_string(),
|
||||||
|
description: "Description 1".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
let product2 = repo.create(CreateProduct {
|
||||||
|
name: "Product 2".to_string(),
|
||||||
|
description: "Description 2".to_string(),
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
// Verify both products exist
|
||||||
|
let products = repo.find_all().await.unwrap();
|
||||||
|
assert_eq!(products.len(), 2);
|
||||||
|
|
||||||
|
// Update one product
|
||||||
|
let updated_product1 = repo.update(product1.id, UpdateProduct {
|
||||||
|
name: Some("Updated Product 1".to_string()),
|
||||||
|
description: None,
|
||||||
|
}).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated_product1.name, "Updated Product 1");
|
||||||
|
assert_eq!(updated_product1.description, "Description 1");
|
||||||
|
|
||||||
|
// Delete one product
|
||||||
|
repo.delete(product2.id).await.unwrap();
|
||||||
|
|
||||||
|
// Verify only one product remains
|
||||||
|
let remaining_products = repo.find_all().await.unwrap();
|
||||||
|
assert_eq!(remaining_products.len(), 1);
|
||||||
|
assert_eq!(remaining_products[0].id, product1.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue