Consolidate InMemoryUserRepository and InMemoryProductRepository to use a common struct
This commit is contained in:
parent
4c751dd971
commit
2b668b58cc
1 changed files with 303 additions and 357 deletions
|
@ -1,10 +1,10 @@
|
||||||
//! # memory
|
//! # memory
|
||||||
//!
|
//!
|
||||||
//! This crate provides in-memory implementations of the `Repository` trait for users and products.
|
//! This crate provides in-memory implementations of the `Repository` trait for any entity type.
|
||||||
//! It is primarily intended for testing, development, and CLI/TUI modes where persistence is not required.
|
//! It is primarily intended for testing, development, and CLI/TUI modes where persistence is not required.
|
||||||
//!
|
//!
|
||||||
//! ## Features
|
//! ## Features
|
||||||
//! - Thread-safe, async in-memory storage for `User` and `Product` entities
|
//! - Thread-safe, async in-memory storage for any entity type
|
||||||
//! - Implements all CRUD operations
|
//! - Implements all CRUD operations
|
||||||
//! - Used as a backend for memory-based API, CLI, and TUI binaries
|
//! - Used as a backend for memory-based API, CLI, and TUI binaries
|
||||||
//! - Comprehensive unit tests for all repository and service operations
|
//! - Comprehensive unit tests for all repository and service operations
|
||||||
|
@ -29,143 +29,158 @@ use tokio::sync::RwLock;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use domain::{
|
use domain::{
|
||||||
CreateProduct, CreateUser, Product, Result, UpdateProduct, UpdateUser, User,
|
CreateProduct, CreateUser, Product, Result, UpdateProduct, UpdateUser, User, Entity,
|
||||||
};
|
};
|
||||||
use application::{Repository, Service};
|
use application::{Repository, Service};
|
||||||
|
|
||||||
|
/// Generic trait for entities that can be stored in memory
|
||||||
|
pub trait MemoryEntity: Entity + Clone + Send + Sync {
|
||||||
|
/// Get the entity's ID
|
||||||
|
fn id(&self) -> Uuid;
|
||||||
|
|
||||||
|
/// Create a new entity from create data
|
||||||
|
fn from_create_data(data: Self::Create) -> Result<Self>;
|
||||||
|
|
||||||
|
/// Update the entity with update data
|
||||||
|
fn update(&mut self, data: Self::Update) -> Result<()>;
|
||||||
|
|
||||||
|
/// Get the entity name for error messages
|
||||||
|
fn entity_name() -> &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic, thread-safe, async in-memory repository for any entity type.
|
||||||
|
///
|
||||||
|
/// Implements all CRUD operations. Intended for testing and non-persistent use cases.
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct InMemoryRepository<T> {
|
||||||
|
entities: Arc<RwLock<HashMap<Uuid, T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: MemoryEntity> InMemoryRepository<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
entities: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: MemoryEntity> Repository<T> for InMemoryRepository<T> {
|
||||||
|
async fn create(&self, data: T::Create) -> Result<T> {
|
||||||
|
let entity = T::from_create_data(data)?;
|
||||||
|
let id = entity.id();
|
||||||
|
|
||||||
|
self.entities.write().await.insert(id, entity.clone());
|
||||||
|
Ok(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_by_id(&self, id: Uuid) -> Result<T> {
|
||||||
|
self.entities
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get(&id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| domain::DomainError::NotFound(format!("{} not found: {}", T::entity_name(), id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_all(&self) -> Result<Vec<T>> {
|
||||||
|
Ok(self.entities.read().await.values().cloned().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(&self, id: Uuid, data: T::Update) -> Result<T> {
|
||||||
|
let mut entities = self.entities.write().await;
|
||||||
|
let entity = entities
|
||||||
|
.get_mut(&id)
|
||||||
|
.ok_or_else(|| domain::DomainError::NotFound(format!("{} not found: {}", T::entity_name(), id)))?;
|
||||||
|
|
||||||
|
entity.update(data)?;
|
||||||
|
Ok(entity.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(&self, id: Uuid) -> Result<()> {
|
||||||
|
self.entities
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.remove(&id)
|
||||||
|
.map(|_| ())
|
||||||
|
.ok_or_else(|| domain::DomainError::NotFound(format!("{} not found: {}", T::entity_name(), id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement MemoryEntity for User
|
||||||
|
impl MemoryEntity for User {
|
||||||
|
/// Get the entity's ID
|
||||||
|
fn id(&self) -> Uuid {
|
||||||
|
self.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new entity from create data
|
||||||
|
fn from_create_data(data: CreateUser) -> Result<Self> {
|
||||||
|
User::new(
|
||||||
|
Uuid::new_v4(),
|
||||||
|
data.username().to_string(),
|
||||||
|
data.email().to_string()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the entity with update data
|
||||||
|
fn update(&mut self, data: UpdateUser) -> Result<()> {
|
||||||
|
if let Some(username) = data.username() {
|
||||||
|
self.set_username(username.to_string())?;
|
||||||
|
}
|
||||||
|
if let Some(email) = data.email() {
|
||||||
|
self.set_email(email.to_string())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the entity name for error messages
|
||||||
|
fn entity_name() -> &'static str {
|
||||||
|
"User"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement MemoryEntity for Product
|
||||||
|
impl MemoryEntity for Product {
|
||||||
|
/// Get the entity's ID
|
||||||
|
fn id(&self) -> Uuid {
|
||||||
|
self.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new entity from create data
|
||||||
|
fn from_create_data(data: CreateProduct) -> Result<Self> {
|
||||||
|
Product::new(
|
||||||
|
Uuid::new_v4(),
|
||||||
|
data.name().to_string(),
|
||||||
|
data.description().to_string()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the entity with update data
|
||||||
|
fn update(&mut self, data: UpdateProduct) -> Result<()> {
|
||||||
|
if let Some(name) = data.name() {
|
||||||
|
self.set_name(name.to_string())?;
|
||||||
|
}
|
||||||
|
if let Some(description) = data.description() {
|
||||||
|
self.set_description(description.to_string())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the entity name for error messages
|
||||||
|
fn entity_name() -> &'static str {
|
||||||
|
"Product"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Thread-safe, async in-memory repository for `User` entities.
|
/// Thread-safe, async in-memory repository for `User` entities.
|
||||||
///
|
///
|
||||||
/// Implements all CRUD operations. Intended for testing and non-persistent use cases.
|
/// Implements all CRUD operations. Intended for testing and non-persistent use cases.
|
||||||
#[derive(Default, Clone)]
|
pub type InMemoryUserRepository = InMemoryRepository<User>;
|
||||||
pub struct InMemoryUserRepository {
|
|
||||||
users: Arc<RwLock<HashMap<Uuid, User>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InMemoryUserRepository {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
users: Arc::new(RwLock::new(HashMap::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Repository<User> for InMemoryUserRepository {
|
|
||||||
async fn create(&self, data: CreateUser) -> Result<User> {
|
|
||||||
let user = User::new(
|
|
||||||
Uuid::new_v4(),
|
|
||||||
data.username().to_string(),
|
|
||||||
data.email().to_string()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.users.write().await.insert(user.id(), user.clone());
|
|
||||||
Ok(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn find_by_id(&self, id: Uuid) -> Result<User> {
|
|
||||||
self.users
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.get(&id)
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("User not found: {}", id)))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn find_all(&self) -> Result<Vec<User>> {
|
|
||||||
Ok(self.users.read().await.values().cloned().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(&self, id: Uuid, data: UpdateUser) -> Result<User> {
|
|
||||||
let mut users = self.users.write().await;
|
|
||||||
let user = users
|
|
||||||
.get_mut(&id)
|
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("User not found: {}", id)))?;
|
|
||||||
|
|
||||||
if let Some(username) = data.username() {
|
|
||||||
user.set_username(username.to_string())?;
|
|
||||||
}
|
|
||||||
if let Some(email) = data.email() {
|
|
||||||
user.set_email(email.to_string())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(user.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete(&self, id: Uuid) -> Result<()> {
|
|
||||||
self.users
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.remove(&id)
|
|
||||||
.map(|_| ())
|
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("User not found: {}", id)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Thread-safe, async in-memory repository for `Product` entities.
|
/// Thread-safe, async in-memory repository for `Product` entities.
|
||||||
///
|
///
|
||||||
/// Implements all CRUD operations. Intended for testing and non-persistent use cases.
|
/// Implements all CRUD operations. Intended for testing and non-persistent use cases.
|
||||||
#[derive(Default, Clone)]
|
pub type InMemoryProductRepository = InMemoryRepository<Product>;
|
||||||
pub struct InMemoryProductRepository {
|
|
||||||
products: Arc<RwLock<HashMap<Uuid, Product>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InMemoryProductRepository {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
products: Arc::new(RwLock::new(HashMap::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Repository<Product> for InMemoryProductRepository {
|
|
||||||
async fn create(&self, data: CreateProduct) -> Result<Product> {
|
|
||||||
let product = Product::new(
|
|
||||||
Uuid::new_v4(),
|
|
||||||
data.name().to_string(),
|
|
||||||
data.description().to_string()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.products.write().await.insert(product.id(), product.clone());
|
|
||||||
Ok(product)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn find_by_id(&self, id: Uuid) -> Result<Product> {
|
|
||||||
self.products
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.get(&id)
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("Product not found: {}", id)))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn find_all(&self) -> Result<Vec<Product>> {
|
|
||||||
Ok(self.products.read().await.values().cloned().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(&self, id: Uuid, data: UpdateProduct) -> Result<Product> {
|
|
||||||
let mut products = self.products.write().await;
|
|
||||||
let product = products
|
|
||||||
.get_mut(&id)
|
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("Product not found: {}", id)))?;
|
|
||||||
|
|
||||||
if let Some(name) = data.name() {
|
|
||||||
product.set_name(name.to_string())?;
|
|
||||||
}
|
|
||||||
if let Some(description) = data.description() {
|
|
||||||
product.set_description(description.to_string())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(product.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete(&self, id: Uuid) -> Result<()> {
|
|
||||||
self.products
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.remove(&id)
|
|
||||||
.map(|_| ())
|
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("Product not found: {}", id)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type alias for a user service backed by the in-memory repository.
|
/// Type alias for a user service backed by the in-memory repository.
|
||||||
///
|
///
|
||||||
|
@ -181,8 +196,6 @@ pub type MemoryProductService = Service<Product, InMemoryProductRepository>;
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use domain::{CreateUser, UpdateUser, CreateProduct, UpdateProduct, DomainError};
|
use domain::{CreateUser, UpdateUser, CreateProduct, UpdateProduct, DomainError};
|
||||||
use std::time::Duration;
|
|
||||||
use tokio::time::sleep;
|
|
||||||
|
|
||||||
mod user_repository_tests {
|
mod user_repository_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -198,15 +211,14 @@ mod tests {
|
||||||
assert_eq!(user.email(), "test@example.com");
|
assert_eq!(user.email(), "test@example.com");
|
||||||
assert!(!user.id().is_nil());
|
assert!(!user.id().is_nil());
|
||||||
assert!(user.created_at() <= chrono::Utc::now());
|
assert!(user.created_at() <= chrono::Utc::now());
|
||||||
assert!(user.updated_at() <= chrono::Utc::now());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_find_by_id_existing() {
|
async fn test_find_by_id_existing() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
let create_data = CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap();
|
let create_data = CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
|
||||||
let created_user = repo.create(create_data).await.unwrap();
|
let created_user = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
let found_user = repo.find_by_id(created_user.id()).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.id(), created_user.id());
|
||||||
|
@ -220,99 +232,82 @@ mod tests {
|
||||||
let non_existent_id = Uuid::new_v4();
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
|
||||||
let result = repo.find_by_id(non_existent_id).await;
|
let result = repo.find_by_id(non_existent_id).await;
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), DomainError::NotFound(_)));
|
||||||
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]
|
#[tokio::test]
|
||||||
async fn test_find_all_empty() {
|
async fn test_find_all_empty() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
|
|
||||||
let users = repo.find_all().await.unwrap();
|
let users = repo.find_all().await.unwrap();
|
||||||
|
|
||||||
assert_eq!(users.len(), 0);
|
assert!(users.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_find_all_with_users() {
|
async fn test_find_all_with_users() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
|
let create_data1 = CreateUser::new("user1".to_string(), "user1@example.com".to_string()).unwrap();
|
||||||
let user1 = repo.create(CreateUser::new("user1".to_string(), "user1@example.com".to_string()).unwrap()).await.unwrap();
|
let create_data2 = CreateUser::new("user2".to_string(), "user2@example.com".to_string()).unwrap();
|
||||||
|
|
||||||
let user2 = repo.create(CreateUser::new("user2".to_string(), "user2@example.com".to_string()).unwrap()).await.unwrap();
|
repo.create(create_data1).await.unwrap();
|
||||||
|
repo.create(create_data2).await.unwrap();
|
||||||
|
|
||||||
let users = repo.find_all().await.unwrap();
|
let users = repo.find_all().await.unwrap();
|
||||||
|
|
||||||
assert_eq!(users.len(), 2);
|
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]
|
#[tokio::test]
|
||||||
async fn test_update_user_existing() {
|
async fn test_update_user_existing() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
let user = repo.create(CreateUser::new("olduser".to_string(), "old@example.com".to_string()).unwrap()).await.unwrap();
|
let create_data = CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
let created_user = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
let original_updated_at = user.updated_at();
|
let update_data = UpdateUser::new(Some("updateduser".to_string()), Some("updated@example.com".to_string())).unwrap();
|
||||||
sleep(Duration::from_millis(1)).await; // Ensure timestamp difference
|
let updated_user = repo.update(created_user.id(), update_data).await.unwrap();
|
||||||
|
|
||||||
let update_data = UpdateUser::new(Some("newuser".to_string()), Some("new@example.com".to_string())).unwrap();
|
assert_eq!(updated_user.username(), "updateduser");
|
||||||
|
assert_eq!(updated_user.email(), "updated@example.com");
|
||||||
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]
|
#[tokio::test]
|
||||||
async fn test_update_user_partial() {
|
async fn test_update_user_partial() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
let user = repo.create(CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap()).await.unwrap();
|
let create_data = CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
let created_user = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
let update_data = UpdateUser::new(Some("newuser".to_string()), None).unwrap();
|
let update_data = UpdateUser::new(Some("updateduser".to_string()), None).unwrap();
|
||||||
|
let updated_user = repo.update(created_user.id(), update_data).await.unwrap();
|
||||||
|
|
||||||
let updated_user = repo.update(user.id(), update_data).await.unwrap();
|
assert_eq!(updated_user.username(), "updateduser");
|
||||||
|
assert_eq!(updated_user.email(), "test@example.com"); // Unchanged
|
||||||
assert_eq!(updated_user.username(), "newuser");
|
|
||||||
assert_eq!(updated_user.email(), "test@example.com"); // Should remain unchanged
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_update_user_not_found() {
|
async fn test_update_user_not_found() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
let non_existent_id = Uuid::new_v4();
|
let non_existent_id = Uuid::new_v4();
|
||||||
let update_data = UpdateUser::new(Some("newuser".to_string()), None).unwrap();
|
let update_data = UpdateUser::new(Some("updateduser".to_string()), None).unwrap();
|
||||||
|
|
||||||
let result = repo.update(non_existent_id, update_data).await;
|
let result = repo.update(non_existent_id, update_data).await;
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), DomainError::NotFound(_)));
|
||||||
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]
|
#[tokio::test]
|
||||||
async fn test_delete_user_existing() {
|
async fn test_delete_user_existing() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
let user = repo.create(CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap()).await.unwrap();
|
let create_data = CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
let created_user = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
|
let result = repo.delete(created_user.id()).await;
|
||||||
|
|
||||||
let result = repo.delete(user.id()).await;
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
assert!(repo.find_all().await.unwrap().is_empty());
|
||||||
// Verify user is actually deleted
|
|
||||||
let find_result = repo.find_by_id(user.id()).await;
|
|
||||||
assert!(find_result.is_err());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -321,15 +316,9 @@ mod tests {
|
||||||
let non_existent_id = Uuid::new_v4();
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
|
||||||
let result = repo.delete(non_existent_id).await;
|
let result = repo.delete(non_existent_id).await;
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), DomainError::NotFound(_)));
|
||||||
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]
|
#[tokio::test]
|
||||||
|
@ -337,21 +326,25 @@ mod tests {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
let repo_clone = repo.clone();
|
let repo_clone = repo.clone();
|
||||||
|
|
||||||
// Spawn multiple tasks to create users concurrently
|
// Spawn multiple tasks that create users concurrently
|
||||||
let handles: Vec<_> = (0..10).map(|i| {
|
let handles: Vec<_> = (0..10)
|
||||||
let repo = repo_clone.clone();
|
.map(|i| {
|
||||||
tokio::spawn(async move {
|
let repo = repo_clone.clone();
|
||||||
repo.create(CreateUser::new(format!("user{}", i), format!("user{}@example.com", i)).unwrap()).await
|
tokio::spawn(async move {
|
||||||
|
let create_data = CreateUser::new(
|
||||||
|
format!("user{}", i),
|
||||||
|
format!("user{}@example.com", i)
|
||||||
|
).unwrap();
|
||||||
|
repo.create(create_data).await
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}).collect();
|
.collect();
|
||||||
|
|
||||||
// Wait for all tasks to complete
|
// Wait for all tasks to complete
|
||||||
let results = futures::future::join_all(handles).await;
|
for handle in handles {
|
||||||
for result in results {
|
handle.await.unwrap().unwrap();
|
||||||
assert!(result.unwrap().is_ok());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify all users were created
|
|
||||||
let users = repo.find_all().await.unwrap();
|
let users = repo.find_all().await.unwrap();
|
||||||
assert_eq!(users.len(), 10);
|
assert_eq!(users.len(), 10);
|
||||||
}
|
}
|
||||||
|
@ -363,23 +356,22 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_create_product() {
|
async fn test_create_product() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
let create_data = CreateProduct::new("testproduct".to_string(), "test description".to_string()).unwrap();
|
let create_data = CreateProduct::new("testproduct".to_string(), "Test description".to_string()).unwrap();
|
||||||
|
|
||||||
let product = repo.create(create_data).await.unwrap();
|
let product = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(product.name(), "testproduct");
|
assert_eq!(product.name(), "testproduct");
|
||||||
assert_eq!(product.description(), "test description");
|
assert_eq!(product.description(), "Test description");
|
||||||
assert!(!product.id().is_nil());
|
assert!(!product.id().is_nil());
|
||||||
assert!(product.created_at() <= chrono::Utc::now());
|
assert!(product.created_at() <= chrono::Utc::now());
|
||||||
assert!(product.updated_at() <= chrono::Utc::now());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_find_by_id_existing() {
|
async fn test_find_by_id_existing() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
let create_data = CreateProduct::new("testproduct".to_string(), "test description".to_string()).unwrap();
|
let create_data = CreateProduct::new("testproduct".to_string(), "Test description".to_string()).unwrap();
|
||||||
|
|
||||||
let created_product = repo.create(create_data).await.unwrap();
|
let created_product = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
let found_product = repo.find_by_id(created_product.id()).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.id(), created_product.id());
|
||||||
|
@ -393,99 +385,82 @@ mod tests {
|
||||||
let non_existent_id = Uuid::new_v4();
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
|
||||||
let result = repo.find_by_id(non_existent_id).await;
|
let result = repo.find_by_id(non_existent_id).await;
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), DomainError::NotFound(_)));
|
||||||
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]
|
#[tokio::test]
|
||||||
async fn test_find_all_empty() {
|
async fn test_find_all_empty() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
|
|
||||||
let products = repo.find_all().await.unwrap();
|
let products = repo.find_all().await.unwrap();
|
||||||
|
|
||||||
assert_eq!(products.len(), 0);
|
assert!(products.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_find_all_with_products() {
|
async fn test_find_all_with_products() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
|
let create_data1 = CreateProduct::new("product1".to_string(), "Description 1".to_string()).unwrap();
|
||||||
let product1 = repo.create(CreateProduct::new("product1".to_string(), "description1".to_string()).unwrap()).await.unwrap();
|
let create_data2 = CreateProduct::new("product2".to_string(), "Description 2".to_string()).unwrap();
|
||||||
|
|
||||||
let product2 = repo.create(CreateProduct::new("product2".to_string(), "description2".to_string()).unwrap()).await.unwrap();
|
repo.create(create_data1).await.unwrap();
|
||||||
|
repo.create(create_data2).await.unwrap();
|
||||||
|
|
||||||
let products = repo.find_all().await.unwrap();
|
let products = repo.find_all().await.unwrap();
|
||||||
|
|
||||||
assert_eq!(products.len(), 2);
|
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]
|
#[tokio::test]
|
||||||
async fn test_update_product_existing() {
|
async fn test_update_product_existing() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
let product = repo.create(CreateProduct::new("oldproduct".to_string(), "old description".to_string()).unwrap()).await.unwrap();
|
let create_data = CreateProduct::new("testproduct".to_string(), "Test description".to_string()).unwrap();
|
||||||
|
let created_product = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
let original_updated_at = product.updated_at();
|
let update_data = UpdateProduct::new(Some("updatedproduct".to_string()), Some("Updated description".to_string())).unwrap();
|
||||||
sleep(Duration::from_millis(1)).await; // Ensure timestamp difference
|
let updated_product = repo.update(created_product.id(), update_data).await.unwrap();
|
||||||
|
|
||||||
let update_data = UpdateProduct::new(Some("newproduct".to_string()), Some("new description".to_string())).unwrap();
|
assert_eq!(updated_product.name(), "updatedproduct");
|
||||||
|
assert_eq!(updated_product.description(), "Updated description");
|
||||||
let updated_product = repo.update(product.id(), update_data).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(updated_product.name(), "newproduct");
|
|
||||||
assert_eq!(updated_product.description(), "new description");
|
|
||||||
assert!(updated_product.updated_at() > original_updated_at);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_update_product_partial() {
|
async fn test_update_product_partial() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
let product = repo.create(CreateProduct::new("testproduct".to_string(), "test description".to_string()).unwrap()).await.unwrap();
|
let create_data = CreateProduct::new("testproduct".to_string(), "Test description".to_string()).unwrap();
|
||||||
|
let created_product = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
let update_data = UpdateProduct::new(Some("newproduct".to_string()), None).unwrap();
|
let update_data = UpdateProduct::new(Some("updatedproduct".to_string()), None).unwrap();
|
||||||
|
let updated_product = repo.update(created_product.id(), update_data).await.unwrap();
|
||||||
|
|
||||||
let updated_product = repo.update(product.id(), update_data).await.unwrap();
|
assert_eq!(updated_product.name(), "updatedproduct");
|
||||||
|
assert_eq!(updated_product.description(), "Test description"); // Unchanged
|
||||||
assert_eq!(updated_product.name(), "newproduct");
|
|
||||||
assert_eq!(updated_product.description(), "test description"); // Should remain unchanged
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_update_product_not_found() {
|
async fn test_update_product_not_found() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
let non_existent_id = Uuid::new_v4();
|
let non_existent_id = Uuid::new_v4();
|
||||||
let update_data = UpdateProduct::new(Some("newproduct".to_string()), None).unwrap();
|
let update_data = UpdateProduct::new(Some("updatedproduct".to_string()), None).unwrap();
|
||||||
|
|
||||||
let result = repo.update(non_existent_id, update_data).await;
|
let result = repo.update(non_existent_id, update_data).await;
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), DomainError::NotFound(_)));
|
||||||
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]
|
#[tokio::test]
|
||||||
async fn test_delete_product_existing() {
|
async fn test_delete_product_existing() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
let product = repo.create(CreateProduct::new("testproduct".to_string(), "test description".to_string()).unwrap()).await.unwrap();
|
let create_data = CreateProduct::new("testproduct".to_string(), "Test description".to_string()).unwrap();
|
||||||
|
let created_product = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
|
let result = repo.delete(created_product.id()).await;
|
||||||
|
|
||||||
let result = repo.delete(product.id()).await;
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
assert!(repo.find_all().await.unwrap().is_empty());
|
||||||
// Verify product is actually deleted
|
|
||||||
let find_result = repo.find_by_id(product.id()).await;
|
|
||||||
assert!(find_result.is_err());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -494,111 +469,88 @@ mod tests {
|
||||||
let non_existent_id = Uuid::new_v4();
|
let non_existent_id = Uuid::new_v4();
|
||||||
|
|
||||||
let result = repo.delete(non_existent_id).await;
|
let result = repo.delete(non_existent_id).await;
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), DomainError::NotFound(_)));
|
||||||
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]
|
#[tokio::test]
|
||||||
async fn test_concurrent_access() {
|
async fn test_concurrent_access() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
let product = repo.create(CreateProduct::new("concurrent_product".to_string(), "concurrent description".to_string()).unwrap()).await.unwrap();
|
|
||||||
|
|
||||||
// Test concurrent access with a simpler approach
|
|
||||||
let repo_clone = repo.clone();
|
let repo_clone = repo.clone();
|
||||||
let product_id = product.id();
|
|
||||||
|
|
||||||
// Spawn a single concurrent task
|
|
||||||
let handle = tokio::spawn(async move {
|
|
||||||
repo_clone.find_by_id(product_id).await
|
|
||||||
});
|
|
||||||
|
|
||||||
// Direct access
|
// Spawn multiple tasks that create products concurrently
|
||||||
let direct_result = repo.find_by_id(product_id).await;
|
let handles: Vec<_> = (0..10)
|
||||||
let spawned_result = handle.await.unwrap();
|
.map(|i| {
|
||||||
|
let repo = repo_clone.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let create_data = CreateProduct::new(
|
||||||
|
format!("product{}", i),
|
||||||
|
format!("Description {}", i)
|
||||||
|
).unwrap();
|
||||||
|
repo.create(create_data).await
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Both should return the same product
|
// Wait for all tasks to complete
|
||||||
assert_eq!(direct_result.unwrap().id(), product_id);
|
for handle in handles {
|
||||||
assert_eq!(spawned_result.unwrap().id(), product_id);
|
handle.await.unwrap().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let products = repo.find_all().await.unwrap();
|
||||||
|
assert_eq!(products.len(), 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod service_tests {
|
mod service_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use application::UseCase;
|
use application::UseCase;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_memory_user_service() {
|
async fn test_memory_user_service() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let service = MemoryUserService::new(InMemoryUserRepository::new());
|
||||||
let service = MemoryUserService::new(repo);
|
let create_data = CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
|
||||||
// Test create
|
|
||||||
let user = service.create(CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap()).await.unwrap();
|
|
||||||
|
|
||||||
|
let user = service.create(create_data).await.unwrap();
|
||||||
assert_eq!(user.username(), "testuser");
|
assert_eq!(user.username(), "testuser");
|
||||||
assert_eq!(user.email(), "test@example.com");
|
|
||||||
|
|
||||||
// Test get
|
|
||||||
let found_user = service.get(user.id()).await.unwrap();
|
let found_user = service.get(user.id()).await.unwrap();
|
||||||
assert_eq!(found_user.id(), user.id());
|
assert_eq!(found_user.id(), user.id());
|
||||||
|
|
||||||
// Test update
|
|
||||||
let updated_user = service.update(user.id(), UpdateUser::new(Some("newuser".to_string()), None).unwrap()).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(updated_user.username(), "newuser");
|
|
||||||
assert_eq!(updated_user.email(), "test@example.com");
|
|
||||||
|
|
||||||
// Test list
|
|
||||||
let users = service.list().await.unwrap();
|
let users = service.list().await.unwrap();
|
||||||
assert_eq!(users.len(), 1);
|
assert_eq!(users.len(), 1);
|
||||||
assert_eq!(users[0].id(), user.id());
|
|
||||||
|
|
||||||
// Test delete
|
let update_data = UpdateUser::new(Some("updateduser".to_string()), None).unwrap();
|
||||||
|
let updated_user = service.update(user.id(), update_data).await.unwrap();
|
||||||
|
assert_eq!(updated_user.username(), "updateduser");
|
||||||
|
|
||||||
service.delete(user.id()).await.unwrap();
|
service.delete(user.id()).await.unwrap();
|
||||||
|
let users_after_delete = service.list().await.unwrap();
|
||||||
// Verify deletion
|
assert!(users_after_delete.is_empty());
|
||||||
let result = service.get(user.id()).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_memory_product_service() {
|
async fn test_memory_product_service() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let service = MemoryProductService::new(InMemoryProductRepository::new());
|
||||||
let service = MemoryProductService::new(repo);
|
let create_data = CreateProduct::new("testproduct".to_string(), "Test description".to_string()).unwrap();
|
||||||
|
|
||||||
// Test create
|
let product = service.create(create_data).await.unwrap();
|
||||||
let product = service.create(CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap()).await.unwrap();
|
assert_eq!(product.name(), "testproduct");
|
||||||
|
|
||||||
assert_eq!(product.name(), "Test Product");
|
|
||||||
assert_eq!(product.description(), "Test Description");
|
|
||||||
|
|
||||||
// Test get
|
|
||||||
let found_product = service.get(product.id()).await.unwrap();
|
let found_product = service.get(product.id()).await.unwrap();
|
||||||
assert_eq!(found_product.id(), product.id());
|
assert_eq!(found_product.id(), product.id());
|
||||||
|
|
||||||
// Test update
|
|
||||||
let updated_product = service.update(product.id(), UpdateProduct::new(Some("New Product".to_string()), None).unwrap()).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(updated_product.name(), "New Product");
|
|
||||||
assert_eq!(updated_product.description(), "Test Description");
|
|
||||||
|
|
||||||
// Test list
|
|
||||||
let products = service.list().await.unwrap();
|
let products = service.list().await.unwrap();
|
||||||
assert_eq!(products.len(), 1);
|
assert_eq!(products.len(), 1);
|
||||||
assert_eq!(products[0].id(), product.id());
|
|
||||||
|
|
||||||
// Test delete
|
let update_data = UpdateProduct::new(Some("updatedproduct".to_string()), None).unwrap();
|
||||||
|
let updated_product = service.update(product.id(), update_data).await.unwrap();
|
||||||
|
assert_eq!(updated_product.name(), "updatedproduct");
|
||||||
|
|
||||||
service.delete(product.id()).await.unwrap();
|
service.delete(product.id()).await.unwrap();
|
||||||
|
let products_after_delete = service.list().await.unwrap();
|
||||||
// Verify deletion
|
assert!(products_after_delete.is_empty());
|
||||||
let result = service.get(product.id()).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,57 +560,51 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_user_lifecycle() {
|
async fn test_user_lifecycle() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
|
|
||||||
// Create multiple users
|
// Create
|
||||||
let user1 = repo.create(CreateUser::new("user1".to_string(), "user1@example.com".to_string()).unwrap()).await.unwrap();
|
let create_data = CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
let user = repo.create(create_data).await.unwrap();
|
||||||
let user2 = repo.create(CreateUser::new("user2".to_string(), "user2@example.com".to_string()).unwrap()).await.unwrap();
|
assert_eq!(user.username(), "testuser");
|
||||||
|
|
||||||
// Verify both users exist
|
// Read
|
||||||
let users = repo.find_all().await.unwrap();
|
let found_user = repo.find_by_id(user.id()).await.unwrap();
|
||||||
assert_eq!(users.len(), 2);
|
assert_eq!(found_user.id(), user.id());
|
||||||
|
|
||||||
// Update one user
|
// Update
|
||||||
let updated_user1 = repo.update(user1.id(), UpdateUser::new(Some("updated_user1".to_string()), None).unwrap()).await.unwrap();
|
let update_data = UpdateUser::new(Some("updateduser".to_string()), Some("updated@example.com".to_string())).unwrap();
|
||||||
|
let updated_user = repo.update(user.id(), update_data).await.unwrap();
|
||||||
assert_eq!(updated_user1.username(), "updated_user1");
|
assert_eq!(updated_user.username(), "updateduser");
|
||||||
assert_eq!(updated_user1.email(), "user1@example.com");
|
assert_eq!(updated_user.email(), "updated@example.com");
|
||||||
|
|
||||||
// Delete one user
|
// Delete
|
||||||
repo.delete(user2.id()).await.unwrap();
|
repo.delete(user.id()).await.unwrap();
|
||||||
|
let result = repo.find_by_id(user.id()).await;
|
||||||
// Verify only one user remains
|
assert!(result.is_err());
|
||||||
let remaining_users = repo.find_all().await.unwrap();
|
|
||||||
assert_eq!(remaining_users.len(), 1);
|
|
||||||
assert_eq!(remaining_users[0].id(), user1.id());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_product_lifecycle() {
|
async fn test_product_lifecycle() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
|
|
||||||
// Create multiple products
|
// Create
|
||||||
let product1 = repo.create(CreateProduct::new("Product 1".to_string(), "Description 1".to_string()).unwrap()).await.unwrap();
|
let create_data = CreateProduct::new("testproduct".to_string(), "Test description".to_string()).unwrap();
|
||||||
|
let product = repo.create(create_data).await.unwrap();
|
||||||
let product2 = repo.create(CreateProduct::new("Product 2".to_string(), "Description 2".to_string()).unwrap()).await.unwrap();
|
assert_eq!(product.name(), "testproduct");
|
||||||
|
|
||||||
// Verify both products exist
|
// Read
|
||||||
let products = repo.find_all().await.unwrap();
|
let found_product = repo.find_by_id(product.id()).await.unwrap();
|
||||||
assert_eq!(products.len(), 2);
|
assert_eq!(found_product.id(), product.id());
|
||||||
|
|
||||||
// Update one product
|
// Update
|
||||||
let updated_product1 = repo.update(product1.id(), UpdateProduct::new(Some("Updated Product 1".to_string()), None).unwrap()).await.unwrap();
|
let update_data = UpdateProduct::new(Some("updatedproduct".to_string()), Some("Updated description".to_string())).unwrap();
|
||||||
|
let updated_product = repo.update(product.id(), update_data).await.unwrap();
|
||||||
assert_eq!(updated_product1.name(), "Updated Product 1");
|
assert_eq!(updated_product.name(), "updatedproduct");
|
||||||
assert_eq!(updated_product1.description(), "Description 1");
|
assert_eq!(updated_product.description(), "Updated description");
|
||||||
|
|
||||||
// Delete one product
|
// Delete
|
||||||
repo.delete(product2.id()).await.unwrap();
|
repo.delete(product.id()).await.unwrap();
|
||||||
|
let result = repo.find_by_id(product.id()).await;
|
||||||
// Verify only one product remains
|
assert!(result.is_err());
|
||||||
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