Change user and product entities to use getters and setters instead of pub fields
This commit is contained in:
parent
abbaafe2df
commit
8ef852beac
9 changed files with 928 additions and 559 deletions
|
@ -397,7 +397,6 @@ mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use chrono::Utc;
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
|
|
||||||
|
@ -425,13 +424,7 @@ mod tests {
|
||||||
async move {
|
async move {
|
||||||
let mut guard = users.write().await;
|
let mut guard = users.write().await;
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
let user = User {
|
let user = User::new(id, data.username().to_string(), data.email().to_string())?;
|
||||||
id,
|
|
||||||
username: data.username,
|
|
||||||
email: data.email,
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
guard.insert(id, user.clone());
|
guard.insert(id, user.clone());
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
@ -462,13 +455,12 @@ mod tests {
|
||||||
let user = guard.get_mut(&id)
|
let user = guard.get_mut(&id)
|
||||||
.ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("User not found: {}", id))))?;
|
.ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("User not found: {}", id))))?;
|
||||||
|
|
||||||
if let Some(username) = data.username {
|
if let Some(username) = data.username() {
|
||||||
user.username = username;
|
user.set_username(username.to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(email) = data.email {
|
if let Some(email) = data.email() {
|
||||||
user.email = email;
|
user.set_email(email.to_string())?;
|
||||||
}
|
}
|
||||||
user.updated_at = Utc::now();
|
|
||||||
Ok(user.clone())
|
Ok(user.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -508,13 +500,7 @@ mod tests {
|
||||||
async move {
|
async move {
|
||||||
let mut guard = products.write().await;
|
let mut guard = products.write().await;
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
let product = Product {
|
let product = Product::new(id, data.name().to_string(), data.description().to_string())?;
|
||||||
id,
|
|
||||||
name: data.name,
|
|
||||||
description: data.description,
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
guard.insert(id, product.clone());
|
guard.insert(id, product.clone());
|
||||||
Ok(product)
|
Ok(product)
|
||||||
}
|
}
|
||||||
|
@ -545,13 +531,12 @@ mod tests {
|
||||||
let product = guard.get_mut(&id)
|
let product = guard.get_mut(&id)
|
||||||
.ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("Product not found: {}", id))))?;
|
.ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("Product not found: {}", id))))?;
|
||||||
|
|
||||||
if let Some(name) = data.name {
|
if let Some(name) = data.name() {
|
||||||
product.name = name;
|
product.set_name(name.to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(description) = data.description {
|
if let Some(description) = data.description() {
|
||||||
product.description = description;
|
product.set_description(description.to_string())?;
|
||||||
}
|
}
|
||||||
product.updated_at = Utc::now();
|
|
||||||
Ok(product.clone())
|
Ok(product.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -634,9 +619,9 @@ mod tests {
|
||||||
assert_eq!(response.status(), StatusCode::CREATED);
|
assert_eq!(response.status(), StatusCode::CREATED);
|
||||||
|
|
||||||
let user: User = extract_json(response).await;
|
let user: User = extract_json(response).await;
|
||||||
assert_eq!(user.username, "testuser");
|
assert_eq!(user.username(), "testuser");
|
||||||
assert_eq!(user.email, "test@example.com");
|
assert_eq!(user.email(), "test@example.com");
|
||||||
assert!(!user.id.is_nil());
|
assert!(!user.id().is_nil());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -669,7 +654,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/users/{}", created_user.id))
|
.uri(format!("/users/{}", created_user.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -679,9 +664,9 @@ mod tests {
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let user: User = extract_json(response).await;
|
let user: User = extract_json(response).await;
|
||||||
assert_eq!(user.id, created_user.id);
|
assert_eq!(user.id(), created_user.id());
|
||||||
assert_eq!(user.username, "testuser");
|
assert_eq!(user.username(), "testuser");
|
||||||
assert_eq!(user.email, "test@example.com");
|
assert_eq!(user.email(), "test@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -758,8 +743,8 @@ mod tests {
|
||||||
|
|
||||||
let users: Vec<User> = extract_json(response).await;
|
let users: Vec<User> = extract_json(response).await;
|
||||||
assert_eq!(users.len(), 2);
|
assert_eq!(users.len(), 2);
|
||||||
assert!(users.iter().any(|u| u.username == "user1"));
|
assert!(users.iter().any(|u| u.username() == "user1"));
|
||||||
assert!(users.iter().any(|u| u.username == "user2"));
|
assert!(users.iter().any(|u| u.username() == "user2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -797,7 +782,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("PUT")
|
.method("PUT")
|
||||||
.uri(format!("/users/{}", created_user.id))
|
.uri(format!("/users/{}", created_user.id()))
|
||||||
.header("content-type", "application/json")
|
.header("content-type", "application/json")
|
||||||
.body(Body::from(update_data.to_string()))
|
.body(Body::from(update_data.to_string()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -808,9 +793,9 @@ mod tests {
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let user: User = extract_json(response).await;
|
let user: User = extract_json(response).await;
|
||||||
assert_eq!(user.id, created_user.id);
|
assert_eq!(user.id(), created_user.id());
|
||||||
assert_eq!(user.username, "newuser");
|
assert_eq!(user.username(), "newuser");
|
||||||
assert_eq!(user.email, "new@example.com");
|
assert_eq!(user.email(), "new@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -847,7 +832,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("PUT")
|
.method("PUT")
|
||||||
.uri(format!("/users/{}", created_user.id))
|
.uri(format!("/users/{}", created_user.id()))
|
||||||
.header("content-type", "application/json")
|
.header("content-type", "application/json")
|
||||||
.body(Body::from(update_data.to_string()))
|
.body(Body::from(update_data.to_string()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -858,9 +843,9 @@ mod tests {
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let user: User = extract_json(response).await;
|
let user: User = extract_json(response).await;
|
||||||
assert_eq!(user.id, created_user.id);
|
assert_eq!(user.id(), created_user.id());
|
||||||
assert_eq!(user.username, "newuser");
|
assert_eq!(user.username(), "newuser");
|
||||||
assert_eq!(user.email, "test@example.com"); // Should remain unchanged
|
assert_eq!(user.email(), "test@example.com"); // Should remain unchanged
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -917,7 +902,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.uri(format!("/users/{}", created_user.id))
|
.uri(format!("/users/{}", created_user.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -931,7 +916,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/users/{}", created_user.id))
|
.uri(format!("/users/{}", created_user.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -993,9 +978,9 @@ mod tests {
|
||||||
assert_eq!(response.status(), StatusCode::CREATED);
|
assert_eq!(response.status(), StatusCode::CREATED);
|
||||||
|
|
||||||
let product: Product = extract_json(response).await;
|
let product: Product = extract_json(response).await;
|
||||||
assert_eq!(product.name, "Test Product");
|
assert_eq!(product.name(), "Test Product");
|
||||||
assert_eq!(product.description, "Test Description");
|
assert_eq!(product.description(), "Test Description");
|
||||||
assert!(!product.id.is_nil());
|
assert!(!product.id().is_nil());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests retrieving a product by ID.
|
/// Tests retrieving a product by ID.
|
||||||
|
@ -1029,7 +1014,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/products/{}", created_product.id))
|
.uri(format!("/products/{}", created_product.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -1039,9 +1024,9 @@ mod tests {
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let product: Product = extract_json(response).await;
|
let product: Product = extract_json(response).await;
|
||||||
assert_eq!(product.id, created_product.id);
|
assert_eq!(product.id(), created_product.id());
|
||||||
assert_eq!(product.name, "Test Product");
|
assert_eq!(product.name(), "Test Product");
|
||||||
assert_eq!(product.description, "Test Description");
|
assert_eq!(product.description(), "Test Description");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests retrieving a non-existent product.
|
/// Tests retrieving a non-existent product.
|
||||||
|
@ -1120,8 +1105,8 @@ mod tests {
|
||||||
|
|
||||||
let products: Vec<Product> = extract_json(response).await;
|
let products: Vec<Product> = extract_json(response).await;
|
||||||
assert_eq!(products.len(), 2);
|
assert_eq!(products.len(), 2);
|
||||||
assert!(products.iter().any(|p| p.name == "Product 1"));
|
assert!(products.iter().any(|p| p.name() == "Product 1"));
|
||||||
assert!(products.iter().any(|p| p.name == "Product 2"));
|
assert!(products.iter().any(|p| p.name() == "Product 2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests updating a product with all fields.
|
/// Tests updating a product with all fields.
|
||||||
|
@ -1160,7 +1145,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("PUT")
|
.method("PUT")
|
||||||
.uri(format!("/products/{}", created_product.id))
|
.uri(format!("/products/{}", created_product.id()))
|
||||||
.header("content-type", "application/json")
|
.header("content-type", "application/json")
|
||||||
.body(Body::from(update_data.to_string()))
|
.body(Body::from(update_data.to_string()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -1171,9 +1156,9 @@ mod tests {
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let product: Product = extract_json(response).await;
|
let product: Product = extract_json(response).await;
|
||||||
assert_eq!(product.id, created_product.id);
|
assert_eq!(product.id(), created_product.id());
|
||||||
assert_eq!(product.name, "New Product");
|
assert_eq!(product.name(), "New Product");
|
||||||
assert_eq!(product.description, "New Description");
|
assert_eq!(product.description(), "New Description");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests partial product updates.
|
/// Tests partial product updates.
|
||||||
|
@ -1211,7 +1196,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("PUT")
|
.method("PUT")
|
||||||
.uri(format!("/products/{}", created_product.id))
|
.uri(format!("/products/{}", created_product.id()))
|
||||||
.header("content-type", "application/json")
|
.header("content-type", "application/json")
|
||||||
.body(Body::from(update_data.to_string()))
|
.body(Body::from(update_data.to_string()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -1222,9 +1207,9 @@ mod tests {
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let product: Product = extract_json(response).await;
|
let product: Product = extract_json(response).await;
|
||||||
assert_eq!(product.id, created_product.id);
|
assert_eq!(product.id(), created_product.id());
|
||||||
assert_eq!(product.name, "New Product");
|
assert_eq!(product.name(), "New Product");
|
||||||
assert_eq!(product.description, "Test Description"); // Should remain unchanged
|
assert_eq!(product.description(), "Test Description"); // Should remain unchanged
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests updating a non-existent product.
|
/// Tests updating a non-existent product.
|
||||||
|
@ -1283,7 +1268,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.uri(format!("/products/{}", created_product.id))
|
.uri(format!("/products/{}", created_product.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -1297,7 +1282,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/products/{}", created_product.id))
|
.uri(format!("/products/{}", created_product.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -1369,7 +1354,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/users/{}", user.id))
|
.uri(format!("/users/{}", user.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -1378,7 +1363,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(get_response.status(), StatusCode::OK);
|
assert_eq!(get_response.status(), StatusCode::OK);
|
||||||
let retrieved_user: User = extract_json(get_response).await;
|
let retrieved_user: User = extract_json(get_response).await;
|
||||||
assert_eq!(retrieved_user.id, user.id);
|
assert_eq!(retrieved_user.id(), user.id());
|
||||||
|
|
||||||
// Update user
|
// Update user
|
||||||
let update_data = json!({
|
let update_data = json!({
|
||||||
|
@ -1390,7 +1375,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("PUT")
|
.method("PUT")
|
||||||
.uri(format!("/users/{}", user.id))
|
.uri(format!("/users/{}", user.id()))
|
||||||
.header("content-type", "application/json")
|
.header("content-type", "application/json")
|
||||||
.body(Body::from(update_data.to_string()))
|
.body(Body::from(update_data.to_string()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -1400,7 +1385,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(update_response.status(), StatusCode::OK);
|
assert_eq!(update_response.status(), StatusCode::OK);
|
||||||
let updated_user: User = extract_json(update_response).await;
|
let updated_user: User = extract_json(update_response).await;
|
||||||
assert_eq!(updated_user.username, "updateduser");
|
assert_eq!(updated_user.username(), "updateduser");
|
||||||
|
|
||||||
// Delete user
|
// Delete user
|
||||||
let delete_response = app
|
let delete_response = app
|
||||||
|
@ -1408,7 +1393,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.uri(format!("/users/{}", user.id))
|
.uri(format!("/users/{}", user.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -1422,7 +1407,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/users/{}", user.id))
|
.uri(format!("/users/{}", user.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -1465,7 +1450,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/products/{}", product.id))
|
.uri(format!("/products/{}", product.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -1474,7 +1459,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(get_response.status(), StatusCode::OK);
|
assert_eq!(get_response.status(), StatusCode::OK);
|
||||||
let retrieved_product: Product = extract_json(get_response).await;
|
let retrieved_product: Product = extract_json(get_response).await;
|
||||||
assert_eq!(retrieved_product.id, product.id);
|
assert_eq!(retrieved_product.id(), product.id());
|
||||||
|
|
||||||
// Update product
|
// Update product
|
||||||
let update_data = json!({
|
let update_data = json!({
|
||||||
|
@ -1486,7 +1471,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("PUT")
|
.method("PUT")
|
||||||
.uri(format!("/products/{}", product.id))
|
.uri(format!("/products/{}", product.id()))
|
||||||
.header("content-type", "application/json")
|
.header("content-type", "application/json")
|
||||||
.body(Body::from(update_data.to_string()))
|
.body(Body::from(update_data.to_string()))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -1496,7 +1481,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(update_response.status(), StatusCode::OK);
|
assert_eq!(update_response.status(), StatusCode::OK);
|
||||||
let updated_product: Product = extract_json(update_response).await;
|
let updated_product: Product = extract_json(update_response).await;
|
||||||
assert_eq!(updated_product.name, "Updated Product");
|
assert_eq!(updated_product.name(), "Updated Product");
|
||||||
|
|
||||||
// Delete product
|
// Delete product
|
||||||
let delete_response = app
|
let delete_response = app
|
||||||
|
@ -1504,7 +1489,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.uri(format!("/products/{}", product.id))
|
.uri(format!("/products/{}", product.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -1518,7 +1503,7 @@ mod tests {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/products/{}", product.id))
|
.uri(format!("/products/{}", product.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -95,7 +95,6 @@ mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use chrono::Utc;
|
|
||||||
|
|
||||||
// Mock repository for testing
|
// Mock repository for testing
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -116,13 +115,7 @@ mod tests {
|
||||||
async move {
|
async move {
|
||||||
let mut guard = self.data.write().await;
|
let mut guard = self.data.write().await;
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
let user = User {
|
let user = User::new(id, data.username().to_string(), data.email().to_string())?;
|
||||||
id,
|
|
||||||
username: data.username,
|
|
||||||
email: data.email,
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
guard.insert(id, user.clone());
|
guard.insert(id, user.clone());
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
@ -150,13 +143,12 @@ mod tests {
|
||||||
let user = guard.get_mut(&id)
|
let user = guard.get_mut(&id)
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id)))?;
|
.ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id)))?;
|
||||||
|
|
||||||
if let Some(username) = data.username {
|
if let Some(username) = data.username() {
|
||||||
user.username = username;
|
user.set_username(username.to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(email) = data.email {
|
if let Some(email) = data.email() {
|
||||||
user.email = email;
|
user.set_email(email.to_string())?;
|
||||||
}
|
}
|
||||||
user.updated_at = Utc::now();
|
|
||||||
Ok(user.clone())
|
Ok(user.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,13 +168,7 @@ mod tests {
|
||||||
async move {
|
async move {
|
||||||
let mut guard = self.data.write().await;
|
let mut guard = self.data.write().await;
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
let product = Product {
|
let product = Product::new(id, data.name().to_string(), data.description().to_string())?;
|
||||||
id,
|
|
||||||
name: data.name,
|
|
||||||
description: data.description,
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
guard.insert(id, product.clone());
|
guard.insert(id, product.clone());
|
||||||
Ok(product)
|
Ok(product)
|
||||||
}
|
}
|
||||||
|
@ -210,13 +196,12 @@ mod tests {
|
||||||
let product = guard.get_mut(&id)
|
let product = guard.get_mut(&id)
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id)))?;
|
.ok_or_else(|| domain::DomainError::NotFound(format!("Entity not found: {}", id)))?;
|
||||||
|
|
||||||
if let Some(name) = data.name {
|
if let Some(name) = data.name() {
|
||||||
product.name = name;
|
product.set_name(name.to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(description) = data.description {
|
if let Some(description) = data.description() {
|
||||||
product.description = description;
|
product.set_description(description.to_string())?;
|
||||||
}
|
}
|
||||||
product.updated_at = Utc::now();
|
|
||||||
Ok(product.clone())
|
Ok(product.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,8 +227,8 @@ mod tests {
|
||||||
let create_user = CreateUser::new("test_user".to_string(), "test@example.com".to_string()).unwrap();
|
let create_user = CreateUser::new("test_user".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
|
||||||
let user = service.create(create_user).await.unwrap();
|
let user = service.create(create_user).await.unwrap();
|
||||||
assert_eq!(user.username, "test_user");
|
assert_eq!(user.username(), "test_user");
|
||||||
assert_eq!(user.email, "test@example.com");
|
assert_eq!(user.email(), "test@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -254,8 +239,8 @@ mod tests {
|
||||||
let create_user = CreateUser::new("test_user".to_string(), "test@example.com".to_string()).unwrap();
|
let create_user = CreateUser::new("test_user".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
|
||||||
let created = service.create(create_user).await.unwrap();
|
let created = service.create(create_user).await.unwrap();
|
||||||
let found = service.get(created.id).await.unwrap();
|
let found = service.get(created.id()).await.unwrap();
|
||||||
assert_eq!(found.id, created.id);
|
assert_eq!(found.id(), created.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -283,9 +268,9 @@ mod tests {
|
||||||
let created = service.create(create_user).await.unwrap();
|
let created = service.create(create_user).await.unwrap();
|
||||||
let update = UpdateUser::new(Some("updated_user".to_string()), None).unwrap();
|
let update = UpdateUser::new(Some("updated_user".to_string()), None).unwrap();
|
||||||
|
|
||||||
let updated = service.update(created.id, update).await.unwrap();
|
let updated = service.update(created.id(), update).await.unwrap();
|
||||||
assert_eq!(updated.username, "updated_user");
|
assert_eq!(updated.username(), "updated_user");
|
||||||
assert_eq!(updated.email, "test@example.com");
|
assert_eq!(updated.email(), "test@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -296,8 +281,8 @@ mod tests {
|
||||||
let create_user = CreateUser::new("test_user".to_string(), "test@example.com".to_string()).unwrap();
|
let create_user = CreateUser::new("test_user".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
|
||||||
let created = service.create(create_user).await.unwrap();
|
let created = service.create(create_user).await.unwrap();
|
||||||
service.delete(created.id).await.unwrap();
|
service.delete(created.id()).await.unwrap();
|
||||||
assert!(service.get(created.id).await.is_err());
|
assert!(service.get(created.id()).await.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -308,8 +293,8 @@ mod tests {
|
||||||
let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
||||||
|
|
||||||
let product = service.create(create_product).await.unwrap();
|
let product = service.create(create_product).await.unwrap();
|
||||||
assert_eq!(product.name, "Test Product");
|
assert_eq!(product.name(), "Test Product");
|
||||||
assert_eq!(product.description, "Test Description");
|
assert_eq!(product.description(), "Test Description");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -320,8 +305,8 @@ mod tests {
|
||||||
let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
||||||
|
|
||||||
let created = service.create(create_product).await.unwrap();
|
let created = service.create(create_product).await.unwrap();
|
||||||
let found = service.get(created.id).await.unwrap();
|
let found = service.get(created.id()).await.unwrap();
|
||||||
assert_eq!(found.id, created.id);
|
assert_eq!(found.id(), created.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -349,9 +334,9 @@ mod tests {
|
||||||
let created = service.create(create_product).await.unwrap();
|
let created = service.create(create_product).await.unwrap();
|
||||||
let update = UpdateProduct::new(Some("Updated Product".to_string()), None).unwrap();
|
let update = UpdateProduct::new(Some("Updated Product".to_string()), None).unwrap();
|
||||||
|
|
||||||
let updated = service.update(created.id, update).await.unwrap();
|
let updated = service.update(created.id(), update).await.unwrap();
|
||||||
assert_eq!(updated.name, "Updated Product");
|
assert_eq!(updated.name(), "Updated Product");
|
||||||
assert_eq!(updated.description, "Test Description");
|
assert_eq!(updated.description(), "Test Description");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -362,8 +347,8 @@ mod tests {
|
||||||
let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
||||||
|
|
||||||
let created = service.create(create_product).await.unwrap();
|
let created = service.create(create_product).await.unwrap();
|
||||||
service.delete(created.id).await.unwrap();
|
service.delete(created.id()).await.unwrap();
|
||||||
assert!(service.get(created.id).await.is_err());
|
assert!(service.get(created.id()).await.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -201,7 +201,6 @@ mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use chrono::Utc;
|
|
||||||
|
|
||||||
/// Mock user service for testing.
|
/// Mock user service for testing.
|
||||||
///
|
///
|
||||||
|
@ -226,13 +225,7 @@ mod tests {
|
||||||
async move {
|
async move {
|
||||||
let mut guard = users.write().await;
|
let mut guard = users.write().await;
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
let user = User {
|
let user = User::new(id, data.username().to_string(), data.email().to_string())?;
|
||||||
id,
|
|
||||||
username: data.username,
|
|
||||||
email: data.email,
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
guard.insert(id, user.clone());
|
guard.insert(id, user.clone());
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
@ -263,13 +256,12 @@ mod tests {
|
||||||
let user = guard.get_mut(&id)
|
let user = guard.get_mut(&id)
|
||||||
.ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("User not found: {}", id))))?;
|
.ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("User not found: {}", id))))?;
|
||||||
|
|
||||||
if let Some(username) = data.username {
|
if let Some(username) = data.username() {
|
||||||
user.username = username;
|
user.set_username(username.to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(email) = data.email {
|
if let Some(email) = data.email() {
|
||||||
user.email = email;
|
user.set_email(email.to_string())?;
|
||||||
}
|
}
|
||||||
user.updated_at = Utc::now();
|
|
||||||
Ok(user.clone())
|
Ok(user.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -308,13 +300,7 @@ mod tests {
|
||||||
async move {
|
async move {
|
||||||
let mut guard = products.write().await;
|
let mut guard = products.write().await;
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
let product = Product {
|
let product = Product::new(id, data.name().to_string(), data.description().to_string())?;
|
||||||
id,
|
|
||||||
name: data.name,
|
|
||||||
description: data.description,
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
guard.insert(id, product.clone());
|
guard.insert(id, product.clone());
|
||||||
Ok(product)
|
Ok(product)
|
||||||
}
|
}
|
||||||
|
@ -345,13 +331,12 @@ mod tests {
|
||||||
let product = guard.get_mut(&id)
|
let product = guard.get_mut(&id)
|
||||||
.ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("Product not found: {}", id))))?;
|
.ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("Product not found: {}", id))))?;
|
||||||
|
|
||||||
if let Some(name) = data.name {
|
if let Some(name) = data.name() {
|
||||||
product.name = name;
|
product.set_name(name.to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(description) = data.description {
|
if let Some(description) = data.description() {
|
||||||
product.description = description;
|
product.set_description(description.to_string())?;
|
||||||
}
|
}
|
||||||
product.updated_at = Utc::now();
|
|
||||||
Ok(product.clone())
|
Ok(product.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -863,7 +848,7 @@ mod tests {
|
||||||
// Get the created user ID by listing users
|
// Get the created user ID by listing users
|
||||||
let users = user_service.list().await.unwrap();
|
let users = user_service.list().await.unwrap();
|
||||||
assert_eq!(users.len(), 1);
|
assert_eq!(users.len(), 1);
|
||||||
let user_id = users[0].id;
|
let user_id = users[0].id();
|
||||||
|
|
||||||
// Get user
|
// Get user
|
||||||
let get_cli = Cli {
|
let get_cli = Cli {
|
||||||
|
@ -926,7 +911,7 @@ mod tests {
|
||||||
// Get the created product ID by listing products
|
// Get the created product ID by listing products
|
||||||
let products = product_service.list().await.unwrap();
|
let products = product_service.list().await.unwrap();
|
||||||
assert_eq!(products.len(), 1);
|
assert_eq!(products.len(), 1);
|
||||||
let product_id = products[0].id;
|
let product_id = products[0].id();
|
||||||
|
|
||||||
// Get product
|
// Get product
|
||||||
let get_cli = Cli {
|
let get_cli = Cli {
|
||||||
|
|
|
@ -37,11 +37,74 @@ pub trait Entity: Send + Sync {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: Uuid,
|
id: Uuid,
|
||||||
pub username: String,
|
username: String,
|
||||||
pub email: String,
|
email: String,
|
||||||
pub created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
updated_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
// Constructor with validation
|
||||||
|
pub fn new(id: Uuid, username: String, email: String) -> Result<Self> {
|
||||||
|
if username.trim().is_empty() {
|
||||||
|
return Err(DomainError::InvalidInput("Username cannot be empty".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Utc::now();
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor for database reconstruction (bypasses validation)
|
||||||
|
pub fn from_db(id: Uuid, username: String, email: String, created_at: DateTime<Utc>, updated_at: DateTime<Utc>) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
pub fn id(&self) -> Uuid { self.id }
|
||||||
|
pub fn username(&self) -> &str { &self.username }
|
||||||
|
pub fn email(&self) -> &str { &self.email }
|
||||||
|
pub fn created_at(&self) -> DateTime<Utc> { self.created_at }
|
||||||
|
pub fn updated_at(&self) -> DateTime<Utc> { self.updated_at }
|
||||||
|
|
||||||
|
// Setters with validation
|
||||||
|
pub fn set_username(&mut self, username: String) -> Result<()> {
|
||||||
|
if username.trim().is_empty() {
|
||||||
|
return Err(DomainError::InvalidInput("Username cannot be empty".to_string()));
|
||||||
|
}
|
||||||
|
self.username = username;
|
||||||
|
self.updated_at = Utc::now();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_email(&mut self, email: String) -> Result<()> {
|
||||||
|
self.email = email;
|
||||||
|
self.updated_at = Utc::now();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to update from UpdateUser
|
||||||
|
pub fn update(&mut self, update: UpdateUser) -> Result<()> {
|
||||||
|
if let Some(username) = update.username() {
|
||||||
|
self.set_username(username.to_string())?;
|
||||||
|
}
|
||||||
|
if let Some(email) = update.email() {
|
||||||
|
self.set_email(email.to_string())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for User {
|
impl Entity for User {
|
||||||
|
@ -51,11 +114,74 @@ impl Entity for User {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Product {
|
pub struct Product {
|
||||||
pub id: Uuid,
|
id: Uuid,
|
||||||
pub name: String,
|
name: String,
|
||||||
pub description: String,
|
description: String,
|
||||||
pub created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
updated_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Product {
|
||||||
|
// Constructor with validation
|
||||||
|
pub fn new(id: Uuid, name: String, description: String) -> Result<Self> {
|
||||||
|
if name.trim().is_empty() {
|
||||||
|
return Err(DomainError::InvalidInput("Product name cannot be empty".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Utc::now();
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor for database reconstruction (bypasses validation)
|
||||||
|
pub fn from_db(id: Uuid, name: String, description: String, created_at: DateTime<Utc>, updated_at: DateTime<Utc>) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
pub fn id(&self) -> Uuid { self.id }
|
||||||
|
pub fn name(&self) -> &str { &self.name }
|
||||||
|
pub fn description(&self) -> &str { &self.description }
|
||||||
|
pub fn created_at(&self) -> DateTime<Utc> { self.created_at }
|
||||||
|
pub fn updated_at(&self) -> DateTime<Utc> { self.updated_at }
|
||||||
|
|
||||||
|
// Setters with validation
|
||||||
|
pub fn set_name(&mut self, name: String) -> Result<()> {
|
||||||
|
if name.trim().is_empty() {
|
||||||
|
return Err(DomainError::InvalidInput("Product name cannot be empty".to_string()));
|
||||||
|
}
|
||||||
|
self.name = name;
|
||||||
|
self.updated_at = Utc::now();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_description(&mut self, description: String) -> Result<()> {
|
||||||
|
self.description = description;
|
||||||
|
self.updated_at = Utc::now();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to update from UpdateProduct
|
||||||
|
pub fn update(&mut self, update: UpdateProduct) -> Result<()> {
|
||||||
|
if let Some(name) = update.name() {
|
||||||
|
self.set_name(name.to_string())?;
|
||||||
|
}
|
||||||
|
if let Some(description) = update.description() {
|
||||||
|
self.set_description(description.to_string())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Product {
|
impl Entity for Product {
|
||||||
|
@ -65,8 +191,8 @@ impl Entity for Product {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct CreateUser {
|
pub struct CreateUser {
|
||||||
pub username: String,
|
username: String,
|
||||||
pub email: String,
|
email: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateUser {
|
impl CreateUser {
|
||||||
|
@ -76,12 +202,20 @@ impl CreateUser {
|
||||||
}
|
}
|
||||||
Ok(Self { username, email })
|
Ok(Self { username, email })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
pub fn username(&self) -> &str { &self.username }
|
||||||
|
pub fn email(&self) -> &str { &self.email }
|
||||||
|
|
||||||
|
// Consuming getters for when you need to take ownership
|
||||||
|
pub fn into_username(self) -> String { self.username }
|
||||||
|
pub fn into_email(self) -> String { self.email }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct UpdateUser {
|
pub struct UpdateUser {
|
||||||
pub username: Option<String>,
|
username: Option<String>,
|
||||||
pub email: Option<String>,
|
email: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateUser {
|
impl UpdateUser {
|
||||||
|
@ -93,12 +227,43 @@ impl UpdateUser {
|
||||||
}
|
}
|
||||||
Ok(Self { username, email })
|
Ok(Self { username, email })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
pub fn username(&self) -> Option<&str> { self.username.as_deref() }
|
||||||
|
pub fn email(&self) -> Option<&str> { self.email.as_deref() }
|
||||||
|
|
||||||
|
// Setters with validation
|
||||||
|
pub fn set_username(&mut self, username: Option<String>) -> Result<()> {
|
||||||
|
if let Some(ref username) = username {
|
||||||
|
if username.trim().is_empty() {
|
||||||
|
return Err(DomainError::InvalidInput("Username cannot be empty".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.username = username;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_email(&mut self, email: Option<String>) -> Result<()> {
|
||||||
|
self.email = email;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder pattern methods
|
||||||
|
pub fn with_username(mut self, username: Option<String>) -> Result<Self> {
|
||||||
|
self.set_username(username)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_email(mut self, email: Option<String>) -> Result<Self> {
|
||||||
|
self.set_email(email)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct CreateProduct {
|
pub struct CreateProduct {
|
||||||
pub name: String,
|
name: String,
|
||||||
pub description: String,
|
description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateProduct {
|
impl CreateProduct {
|
||||||
|
@ -108,12 +273,20 @@ impl CreateProduct {
|
||||||
}
|
}
|
||||||
Ok(Self { name, description })
|
Ok(Self { name, description })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
pub fn name(&self) -> &str { &self.name }
|
||||||
|
pub fn description(&self) -> &str { &self.description }
|
||||||
|
|
||||||
|
// Consuming getters for when you need to take ownership
|
||||||
|
pub fn into_name(self) -> String { self.name }
|
||||||
|
pub fn into_description(self) -> String { self.description }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct UpdateProduct {
|
pub struct UpdateProduct {
|
||||||
pub name: Option<String>,
|
name: Option<String>,
|
||||||
pub description: Option<String>,
|
description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateProduct {
|
impl UpdateProduct {
|
||||||
|
@ -125,6 +298,37 @@ impl UpdateProduct {
|
||||||
}
|
}
|
||||||
Ok(Self { name, description })
|
Ok(Self { name, description })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
pub fn name(&self) -> Option<&str> { self.name.as_deref() }
|
||||||
|
pub fn description(&self) -> Option<&str> { self.description.as_deref() }
|
||||||
|
|
||||||
|
// Setters with validation
|
||||||
|
pub fn set_name(&mut self, name: Option<String>) -> Result<()> {
|
||||||
|
if let Some(ref name) = name {
|
||||||
|
if name.trim().is_empty() {
|
||||||
|
return Err(DomainError::InvalidInput("Product name cannot be empty".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.name = name;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_description(&mut self, description: Option<String>) -> Result<()> {
|
||||||
|
self.description = description;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder pattern methods
|
||||||
|
pub fn with_name(mut self, name: Option<String>) -> Result<Self> {
|
||||||
|
self.set_name(name)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_description(mut self, description: Option<String>) -> Result<Self> {
|
||||||
|
self.set_description(description)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Repository<T, Create, Update>: Send + Sync
|
pub trait Repository<T, Create, Update>: Send + Sync
|
||||||
|
@ -150,24 +354,97 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_user_entity_impl() {
|
fn test_user_entity_impl() {
|
||||||
let user = User {
|
let user = User::new(
|
||||||
id: Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
username: "test_user".to_string(),
|
"test_user".to_string(),
|
||||||
email: "test@example.com".to_string(),
|
"test@example.com".to_string()
|
||||||
created_at: Utc::now(),
|
).unwrap();
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(user.username, "test_user");
|
assert_eq!(user.username(), "test_user");
|
||||||
assert_eq!(user.email, "test@example.com");
|
assert_eq!(user.email(), "test@example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_user_from_db() {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
let created_at = Utc::now();
|
||||||
|
let updated_at = Utc::now();
|
||||||
|
|
||||||
|
let user = User::from_db(
|
||||||
|
id,
|
||||||
|
"test_user".to_string(),
|
||||||
|
"test@example.com".to_string(),
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(user.id(), id);
|
||||||
|
assert_eq!(user.username(), "test_user");
|
||||||
|
assert_eq!(user.email(), "test@example.com");
|
||||||
|
assert_eq!(user.created_at(), created_at);
|
||||||
|
assert_eq!(user.updated_at(), updated_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_user_setters() {
|
||||||
|
let mut user = User::new(
|
||||||
|
Uuid::new_v4(),
|
||||||
|
"test_user".to_string(),
|
||||||
|
"test@example.com".to_string()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// Test valid updates
|
||||||
|
user.set_username("new_username".to_string()).unwrap();
|
||||||
|
user.set_email("new@example.com".to_string()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(user.username(), "new_username");
|
||||||
|
assert_eq!(user.email(), "new@example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_user_setter_validation() {
|
||||||
|
let mut user = User::new(
|
||||||
|
Uuid::new_v4(),
|
||||||
|
"test_user".to_string(),
|
||||||
|
"test@example.com".to_string()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// Test invalid username
|
||||||
|
let result = user.set_username("".to_string());
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), DomainError::InvalidInput(_)));
|
||||||
|
|
||||||
|
// Test empty email (should be allowed)
|
||||||
|
let result = user.set_email("".to_string());
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(user.email(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_user_update_method() {
|
||||||
|
let mut user = User::new(
|
||||||
|
Uuid::new_v4(),
|
||||||
|
"test_user".to_string(),
|
||||||
|
"test@example.com".to_string()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let update = UpdateUser::new(
|
||||||
|
Some("new_username".to_string()),
|
||||||
|
Some("new@example.com".to_string())
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
user.update(update).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(user.username(), "new_username");
|
||||||
|
assert_eq!(user.email(), "new@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_user_validation() {
|
fn test_create_user_validation() {
|
||||||
let create_user = CreateUser::new("test_user".to_string(), "test@example.com".to_string()).unwrap();
|
let create_user = CreateUser::new("test_user".to_string(), "test@example.com".to_string()).unwrap();
|
||||||
|
|
||||||
assert_eq!(create_user.username, "test_user");
|
assert_eq!(create_user.username(), "test_user");
|
||||||
assert_eq!(create_user.email, "test@example.com");
|
assert_eq!(create_user.email(), "test@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -186,17 +463,26 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_user_empty_email() {
|
fn test_create_user_empty_email() {
|
||||||
let create_user = CreateUser::new("test_user".to_string(), "".to_string()).unwrap();
|
let result = CreateUser::new("test_user".to_string(), "".to_string());
|
||||||
assert_eq!(create_user.username, "test_user");
|
assert!(result.is_ok());
|
||||||
assert_eq!(create_user.email, "");
|
let create_user = result.unwrap();
|
||||||
|
assert_eq!(create_user.email(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_user_whitespace_email() {
|
||||||
|
let result = CreateUser::new("test_user".to_string(), " ".to_string());
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let create_user = result.unwrap();
|
||||||
|
assert_eq!(create_user.email(), " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_user_partial() {
|
fn test_update_user_partial() {
|
||||||
let update_user = UpdateUser::new(Some("new_username".to_string()), None).unwrap();
|
let update_user = UpdateUser::new(Some("new_username".to_string()), None).unwrap();
|
||||||
|
|
||||||
assert_eq!(update_user.username, Some("new_username".to_string()));
|
assert_eq!(update_user.username(), Some("new_username"));
|
||||||
assert_eq!(update_user.email, None);
|
assert_eq!(update_user.email(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -215,9 +501,43 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_user_empty_email() {
|
fn test_update_user_empty_email() {
|
||||||
let update_user = UpdateUser::new(None, Some("".to_string())).unwrap();
|
let result = UpdateUser::new(None, Some("".to_string()));
|
||||||
assert_eq!(update_user.username, None);
|
assert!(result.is_ok());
|
||||||
assert_eq!(update_user.email, Some("".to_string()));
|
let update_user = result.unwrap();
|
||||||
|
assert_eq!(update_user.email(), Some(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_user_whitespace_email() {
|
||||||
|
let result = UpdateUser::new(None, Some(" ".to_string()));
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let update_user = result.unwrap();
|
||||||
|
assert_eq!(update_user.email(), Some(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_user_setters() {
|
||||||
|
let mut update_user = UpdateUser::new(None, None).unwrap();
|
||||||
|
|
||||||
|
// Test valid updates
|
||||||
|
update_user.set_username(Some("new_username".to_string())).unwrap();
|
||||||
|
update_user.set_email(Some("new@example.com".to_string())).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(update_user.username(), Some("new_username"));
|
||||||
|
assert_eq!(update_user.email(), Some("new@example.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_user_builder_pattern() {
|
||||||
|
let update_user = UpdateUser::new(None, None)
|
||||||
|
.unwrap()
|
||||||
|
.with_username(Some("new_username".to_string()))
|
||||||
|
.unwrap()
|
||||||
|
.with_email(Some("new@example.com".to_string()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(update_user.username(), Some("new_username"));
|
||||||
|
assert_eq!(update_user.email(), Some("new@example.com"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,23 +546,91 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_product_entity_impl() {
|
fn test_product_entity_impl() {
|
||||||
let product = Product {
|
let product = Product::new(
|
||||||
id: Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
name: "Test Product".to_string(),
|
"Test Product".to_string(),
|
||||||
description: "Test Description".to_string(),
|
"Test Description".to_string()
|
||||||
created_at: Utc::now(),
|
).unwrap();
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(product.name, "Test Product");
|
assert_eq!(product.name(), "Test Product");
|
||||||
assert_eq!(product.description, "Test Description");
|
assert_eq!(product.description(), "Test Description");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_product_from_db() {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
let created_at = Utc::now();
|
||||||
|
let updated_at = Utc::now();
|
||||||
|
|
||||||
|
let product = Product::from_db(
|
||||||
|
id,
|
||||||
|
"Test Product".to_string(),
|
||||||
|
"Test Description".to_string(),
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(product.id(), id);
|
||||||
|
assert_eq!(product.name(), "Test Product");
|
||||||
|
assert_eq!(product.description(), "Test Description");
|
||||||
|
assert_eq!(product.created_at(), created_at);
|
||||||
|
assert_eq!(product.updated_at(), updated_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_product_setters() {
|
||||||
|
let mut product = Product::new(
|
||||||
|
Uuid::new_v4(),
|
||||||
|
"Test Product".to_string(),
|
||||||
|
"Test Description".to_string()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// Test valid updates
|
||||||
|
product.set_name("New Product Name".to_string()).unwrap();
|
||||||
|
product.set_description("New Description".to_string()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(product.name(), "New Product Name");
|
||||||
|
assert_eq!(product.description(), "New Description");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_product_setter_validation() {
|
||||||
|
let mut product = Product::new(
|
||||||
|
Uuid::new_v4(),
|
||||||
|
"Test Product".to_string(),
|
||||||
|
"Test Description".to_string()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// Test invalid name
|
||||||
|
let result = product.set_name("".to_string());
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), DomainError::InvalidInput(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_product_update_method() {
|
||||||
|
let mut product = Product::new(
|
||||||
|
Uuid::new_v4(),
|
||||||
|
"Test Product".to_string(),
|
||||||
|
"Test Description".to_string()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let update = UpdateProduct::new(
|
||||||
|
Some("New Product Name".to_string()),
|
||||||
|
Some("New Description".to_string())
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
product.update(update).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(product.name(), "New Product Name");
|
||||||
|
assert_eq!(product.description(), "New Description");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_product_validation() {
|
fn test_create_product_validation() {
|
||||||
let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
||||||
assert_eq!(create_product.name, "Test Product");
|
assert_eq!(create_product.name(), "Test Product");
|
||||||
assert_eq!(create_product.description, "Test Description");
|
assert_eq!(create_product.description(), "Test Description");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -262,15 +650,15 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_product_empty_description() {
|
fn test_create_product_empty_description() {
|
||||||
let create_product = CreateProduct::new("Test Product".to_string(), "".to_string()).unwrap();
|
let create_product = CreateProduct::new("Test Product".to_string(), "".to_string()).unwrap();
|
||||||
assert_eq!(create_product.name, "Test Product");
|
assert_eq!(create_product.name(), "Test Product");
|
||||||
assert_eq!(create_product.description, "");
|
assert_eq!(create_product.description(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_product_partial() {
|
fn test_update_product_partial() {
|
||||||
let update_product = UpdateProduct::new(Some("New Product Name".to_string()), None).unwrap();
|
let update_product = UpdateProduct::new(Some("New Product Name".to_string()), None).unwrap();
|
||||||
assert_eq!(update_product.name, Some("New Product Name".to_string()));
|
assert_eq!(update_product.name(), Some("New Product Name"));
|
||||||
assert_eq!(update_product.description, None);
|
assert_eq!(update_product.description(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -290,8 +678,33 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_product_empty_description() {
|
fn test_update_product_empty_description() {
|
||||||
let update_product = UpdateProduct::new(None, Some("".to_string())).unwrap();
|
let update_product = UpdateProduct::new(None, Some("".to_string())).unwrap();
|
||||||
assert_eq!(update_product.name, None);
|
assert_eq!(update_product.name(), None);
|
||||||
assert_eq!(update_product.description, Some("".to_string()));
|
assert_eq!(update_product.description(), Some(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_product_setters() {
|
||||||
|
let mut update_product = UpdateProduct::new(None, None).unwrap();
|
||||||
|
|
||||||
|
// Test valid updates
|
||||||
|
update_product.set_name(Some("New Product Name".to_string())).unwrap();
|
||||||
|
update_product.set_description(Some("New Description".to_string())).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(update_product.name(), Some("New Product Name"));
|
||||||
|
assert_eq!(update_product.description(), Some("New Description"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_product_builder_pattern() {
|
||||||
|
let update_product = UpdateProduct::new(None, None)
|
||||||
|
.unwrap()
|
||||||
|
.with_name(Some("New Product Name".to_string()))
|
||||||
|
.unwrap()
|
||||||
|
.with_description(Some("New Description".to_string()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(update_product.name(), Some("New Product Name"));
|
||||||
|
assert_eq!(update_product.description(), Some("New Description"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,61 +751,53 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_repository_create() {
|
async fn test_repository_create() {
|
||||||
let repo = MockRepository::<User>::new();
|
let repo = MockRepository::<Product>::new();
|
||||||
let create_user = CreateUser::new("test_user".to_string(), "test@example.com".to_string()).unwrap();
|
let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
||||||
|
|
||||||
let user = User {
|
let product = Product::new(
|
||||||
id: Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
username: create_user.username.clone(),
|
create_product.name().to_string(),
|
||||||
email: create_user.email.clone(),
|
create_product.description().to_string()
|
||||||
created_at: Utc::now(),
|
).unwrap();
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
repo.data.write().await.insert(user.id, user.clone());
|
repo.data.write().await.insert(product.id(), product.clone());
|
||||||
let guard = repo.data.read().await;
|
let guard = repo.data.read().await;
|
||||||
let stored = guard.get(&user.id).unwrap();
|
let stored = guard.get(&product.id()).unwrap();
|
||||||
assert_eq!(stored.username, create_user.username);
|
assert_eq!(stored.name(), "Test Product");
|
||||||
assert_eq!(stored.email, create_user.email);
|
assert_eq!(stored.description(), "Test Description");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_repository_find_by_id() {
|
async fn test_repository_find_by_id() {
|
||||||
let repo = MockRepository::<User>::new();
|
let repo = MockRepository::<Product>::new();
|
||||||
let user = User {
|
let product = Product::new(
|
||||||
id: Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
username: "test_user".to_string(),
|
"Test Product".to_string(),
|
||||||
email: "test@example.com".to_string(),
|
"Test Description".to_string()
|
||||||
created_at: Utc::now(),
|
).unwrap();
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
repo.data.write().await.insert(user.id, user.clone());
|
repo.data.write().await.insert(product.id(), product.clone());
|
||||||
let guard = repo.data.read().await;
|
let guard = repo.data.read().await;
|
||||||
let found = guard.get(&user.id).unwrap();
|
let found = guard.get(&product.id()).unwrap();
|
||||||
assert_eq!(found.id, user.id);
|
assert_eq!(found.id(), product.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_repository_find_all() {
|
async fn test_repository_find_all() {
|
||||||
let repo = MockRepository::<User>::new();
|
let repo = MockRepository::<Product>::new();
|
||||||
let user1 = User {
|
let product1 = Product::new(
|
||||||
id: Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
username: "user1".to_string(),
|
"Product 1".to_string(),
|
||||||
email: "user1@example.com".to_string(),
|
"Description 1".to_string()
|
||||||
created_at: Utc::now(),
|
).unwrap();
|
||||||
updated_at: Utc::now(),
|
let product2 = Product::new(
|
||||||
};
|
Uuid::new_v4(),
|
||||||
let user2 = User {
|
"Product 2".to_string(),
|
||||||
id: Uuid::new_v4(),
|
"Description 2".to_string()
|
||||||
username: "user2".to_string(),
|
).unwrap();
|
||||||
email: "user2@example.com".to_string(),
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
repo.data.write().await.insert(user1.id, user1.clone());
|
repo.data.write().await.insert(product1.id(), product1.clone());
|
||||||
repo.data.write().await.insert(user2.id, user2.clone());
|
repo.data.write().await.insert(product2.id(), product2.clone());
|
||||||
|
|
||||||
let all = repo.data.read().await;
|
let all = repo.data.read().await;
|
||||||
assert_eq!(all.len(), 2);
|
assert_eq!(all.len(), 2);
|
||||||
|
@ -400,40 +805,36 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_repository_update() {
|
async fn test_repository_update() {
|
||||||
let repo = MockRepository::<User>::new();
|
let repo = MockRepository::<Product>::new();
|
||||||
let user = User {
|
let product = Product::new(
|
||||||
id: Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
username: "test_user".to_string(),
|
"Test Product".to_string(),
|
||||||
email: "test@example.com".to_string(),
|
"Test Description".to_string()
|
||||||
created_at: Utc::now(),
|
).unwrap();
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
repo.data.write().await.insert(user.id, user.clone());
|
repo.data.write().await.insert(product.id(), product.clone());
|
||||||
let mut guard = repo.data.write().await;
|
let mut guard = repo.data.write().await;
|
||||||
let stored = guard.get_mut(&user.id).unwrap();
|
let stored = guard.get_mut(&product.id()).unwrap();
|
||||||
stored.username = "updated_user".to_string();
|
stored.set_name("Updated Product".to_string()).unwrap();
|
||||||
|
|
||||||
drop(guard);
|
drop(guard);
|
||||||
let read_guard = repo.data.read().await;
|
let read_guard = repo.data.read().await;
|
||||||
let updated = read_guard.get(&user.id).unwrap();
|
let updated = read_guard.get(&product.id()).unwrap();
|
||||||
assert_eq!(updated.username, "updated_user");
|
assert_eq!(updated.name(), "Updated Product");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_repository_delete() {
|
async fn test_repository_delete() {
|
||||||
let repo = MockRepository::<User>::new();
|
let repo = MockRepository::<Product>::new();
|
||||||
let user = User {
|
let product = Product::new(
|
||||||
id: Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
username: "test_user".to_string(),
|
"Test Product".to_string(),
|
||||||
email: "test@example.com".to_string(),
|
"Test Description".to_string()
|
||||||
created_at: Utc::now(),
|
).unwrap();
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
repo.data.write().await.insert(user.id, user.clone());
|
repo.data.write().await.insert(product.id(), product.clone());
|
||||||
repo.data.write().await.remove(&user.id);
|
repo.data.write().await.remove(&product.id());
|
||||||
assert!(repo.data.read().await.get(&user.id).is_none());
|
assert!(repo.data.read().await.get(&product.id()).is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,8 +243,8 @@ async fn test_api_with_postgres_user_lifecycle() {
|
||||||
assert_eq!(create_response.status(), StatusCode::CREATED);
|
assert_eq!(create_response.status(), StatusCode::CREATED);
|
||||||
|
|
||||||
let user: User = extract_json(create_response).await;
|
let user: User = extract_json(create_response).await;
|
||||||
assert_eq!(user.username, username);
|
assert_eq!(user.username(), username);
|
||||||
assert_eq!(user.email, email);
|
assert_eq!(user.email(), email);
|
||||||
|
|
||||||
// Test user retrieval via API
|
// Test user retrieval via API
|
||||||
let get_response = app
|
let get_response = app
|
||||||
|
@ -252,7 +252,7 @@ async fn test_api_with_postgres_user_lifecycle() {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/users/{}", user.id))
|
.uri(format!("/users/{}", user.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -262,8 +262,8 @@ async fn test_api_with_postgres_user_lifecycle() {
|
||||||
assert_eq!(get_response.status(), StatusCode::OK);
|
assert_eq!(get_response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let retrieved_user: User = extract_json(get_response).await;
|
let retrieved_user: User = extract_json(get_response).await;
|
||||||
assert_eq!(retrieved_user.id, user.id);
|
assert_eq!(retrieved_user.id(), user.id());
|
||||||
assert_eq!(retrieved_user.username, username);
|
assert_eq!(retrieved_user.username(), username);
|
||||||
|
|
||||||
// Test user update via API
|
// Test user update via API
|
||||||
let new_username = format!("{}_updated", username);
|
let new_username = format!("{}_updated", username);
|
||||||
|
@ -272,7 +272,7 @@ async fn test_api_with_postgres_user_lifecycle() {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("PUT")
|
.method("PUT")
|
||||||
.uri(format!("/users/{}", user.id))
|
.uri(format!("/users/{}", user.id()))
|
||||||
.header("content-type", "application/json")
|
.header("content-type", "application/json")
|
||||||
.body(Body::from(
|
.body(Body::from(
|
||||||
json!({
|
json!({
|
||||||
|
@ -289,7 +289,7 @@ async fn test_api_with_postgres_user_lifecycle() {
|
||||||
assert_eq!(update_response.status(), StatusCode::OK);
|
assert_eq!(update_response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let updated_user: User = extract_json(update_response).await;
|
let updated_user: User = extract_json(update_response).await;
|
||||||
assert_eq!(updated_user.username, new_username);
|
assert_eq!(updated_user.username(), new_username);
|
||||||
|
|
||||||
// Test user deletion via API
|
// Test user deletion via API
|
||||||
let delete_response = app
|
let delete_response = app
|
||||||
|
@ -297,7 +297,7 @@ async fn test_api_with_postgres_user_lifecycle() {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.uri(format!("/users/{}", user.id))
|
.uri(format!("/users/{}", user.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -312,7 +312,7 @@ async fn test_api_with_postgres_user_lifecycle() {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/users/{}", user.id))
|
.uri(format!("/users/{}", user.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -352,8 +352,8 @@ async fn test_api_with_postgres_product_lifecycle() {
|
||||||
assert_eq!(create_response.status(), StatusCode::CREATED);
|
assert_eq!(create_response.status(), StatusCode::CREATED);
|
||||||
|
|
||||||
let product: Product = extract_json(create_response).await;
|
let product: Product = extract_json(create_response).await;
|
||||||
assert_eq!(product.name, name);
|
assert_eq!(product.name(), name);
|
||||||
assert_eq!(product.description, description);
|
assert_eq!(product.description(), description);
|
||||||
|
|
||||||
// Test product retrieval via API
|
// Test product retrieval via API
|
||||||
let get_response = app
|
let get_response = app
|
||||||
|
@ -361,7 +361,7 @@ async fn test_api_with_postgres_product_lifecycle() {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/products/{}", product.id))
|
.uri(format!("/products/{}", product.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -371,8 +371,8 @@ async fn test_api_with_postgres_product_lifecycle() {
|
||||||
assert_eq!(get_response.status(), StatusCode::OK);
|
assert_eq!(get_response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let retrieved_product: Product = extract_json(get_response).await;
|
let retrieved_product: Product = extract_json(get_response).await;
|
||||||
assert_eq!(retrieved_product.id, product.id);
|
assert_eq!(retrieved_product.id(), product.id());
|
||||||
assert_eq!(retrieved_product.name, name);
|
assert_eq!(retrieved_product.name(), name);
|
||||||
|
|
||||||
// Test product update via API
|
// Test product update via API
|
||||||
let new_name = format!("{}_updated", name);
|
let new_name = format!("{}_updated", name);
|
||||||
|
@ -381,7 +381,7 @@ async fn test_api_with_postgres_product_lifecycle() {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("PUT")
|
.method("PUT")
|
||||||
.uri(format!("/products/{}", product.id))
|
.uri(format!("/products/{}", product.id()))
|
||||||
.header("content-type", "application/json")
|
.header("content-type", "application/json")
|
||||||
.body(Body::from(
|
.body(Body::from(
|
||||||
json!({
|
json!({
|
||||||
|
@ -398,7 +398,7 @@ async fn test_api_with_postgres_product_lifecycle() {
|
||||||
assert_eq!(update_response.status(), StatusCode::OK);
|
assert_eq!(update_response.status(), StatusCode::OK);
|
||||||
|
|
||||||
let updated_product: Product = extract_json(update_response).await;
|
let updated_product: Product = extract_json(update_response).await;
|
||||||
assert_eq!(updated_product.name, new_name);
|
assert_eq!(updated_product.name(), new_name);
|
||||||
|
|
||||||
// Test product deletion via API
|
// Test product deletion via API
|
||||||
let delete_response = app
|
let delete_response = app
|
||||||
|
@ -406,7 +406,7 @@ async fn test_api_with_postgres_product_lifecycle() {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.uri(format!("/products/{}", product.id))
|
.uri(format!("/products/{}", product.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -421,7 +421,7 @@ async fn test_api_with_postgres_product_lifecycle() {
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri(format!("/products/{}", product.id))
|
.uri(format!("/products/{}", product.id()))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -499,7 +499,7 @@ async fn test_api_with_postgres_list_operations() {
|
||||||
let users: Vec<User> = extract_json(list_response).await;
|
let users: Vec<User> = extract_json(list_response).await;
|
||||||
assert_eq!(users.len(), 2);
|
assert_eq!(users.len(), 2);
|
||||||
|
|
||||||
let usernames: Vec<String> = users.iter().map(|u| u.username.clone()).collect();
|
let usernames: Vec<String> = users.iter().map(|u| u.username().to_string()).collect();
|
||||||
assert!(usernames.contains(&username1));
|
assert!(usernames.contains(&username1));
|
||||||
assert!(usernames.contains(&username2));
|
assert!(usernames.contains(&username2));
|
||||||
|
|
||||||
|
@ -566,7 +566,7 @@ async fn test_api_with_postgres_list_operations() {
|
||||||
let products: Vec<Product> = extract_json(list_response).await;
|
let products: Vec<Product> = extract_json(list_response).await;
|
||||||
assert_eq!(products.len(), 2);
|
assert_eq!(products.len(), 2);
|
||||||
|
|
||||||
let names: Vec<String> = products.iter().map(|p| p.name.clone()).collect();
|
let names: Vec<String> = products.iter().map(|p| p.name().to_string()).collect();
|
||||||
assert!(names.contains(&name1));
|
assert!(names.contains(&name1));
|
||||||
assert!(names.contains(&name2));
|
assert!(names.contains(&name2));
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,20 +75,20 @@ async fn test_cli_with_memory_user_lifecycle() -> Result<()> {
|
||||||
// Test user get (we need to get the ID from the list first)
|
// Test user get (we need to get the ID from the list first)
|
||||||
let users = user_service.list().await?;
|
let users = user_service.list().await?;
|
||||||
assert!(!users.is_empty());
|
assert!(!users.is_empty());
|
||||||
let user_id = users[0].id;
|
let user_id = users[0].id().to_string();
|
||||||
|
|
||||||
let cli = Cli::parse_from(&["cli", "user", "get", "--id", &user_id.to_string()]);
|
let cli = Cli::parse_from(&["cli", "user", "get", "--id", &user_id]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
// Test user update
|
// Test user update
|
||||||
let new_username = format!("{}_updated", username);
|
let new_username = format!("{}_updated", username);
|
||||||
let cli = Cli::parse_from(&[
|
let cli = Cli::parse_from(&[
|
||||||
"cli", "user", "update", "--id", &user_id.to_string(), "--username", &new_username
|
"cli", "user", "update", "--id", &user_id, "--username", &new_username
|
||||||
]);
|
]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
// Test user delete
|
// Test user delete
|
||||||
let cli = Cli::parse_from(&["cli", "user", "delete", "--id", &user_id.to_string()]);
|
let cli = Cli::parse_from(&["cli", "user", "delete", "--id", &user_id]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -117,20 +117,20 @@ async fn test_cli_with_memory_product_lifecycle() -> Result<()> {
|
||||||
// Test product get (we need to get the ID from the list first)
|
// Test product get (we need to get the ID from the list first)
|
||||||
let products = product_service.list().await?;
|
let products = product_service.list().await?;
|
||||||
assert!(!products.is_empty());
|
assert!(!products.is_empty());
|
||||||
let product_id = products[0].id;
|
let product_id = products[0].id().to_string();
|
||||||
|
|
||||||
let cli = Cli::parse_from(&["cli", "product", "get", "--id", &product_id.to_string()]);
|
let cli = Cli::parse_from(&["cli", "product", "get", "--id", &product_id]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
// Test product update
|
// Test product update
|
||||||
let new_name = format!("{}_updated", name);
|
let new_name = format!("{}_updated", name);
|
||||||
let cli = Cli::parse_from(&[
|
let cli = Cli::parse_from(&[
|
||||||
"cli", "product", "update", "--id", &product_id.to_string(), "--name", &new_name
|
"cli", "product", "update", "--id", &product_id, "--name", &new_name
|
||||||
]);
|
]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
// Test product delete
|
// Test product delete
|
||||||
let cli = Cli::parse_from(&["cli", "product", "delete", "--id", &product_id.to_string()]);
|
let cli = Cli::parse_from(&["cli", "product", "delete", "--id", &product_id]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -200,20 +200,20 @@ async fn test_cli_with_postgres_user_lifecycle() -> Result<()> {
|
||||||
// Test user get (we need to get the ID from the list first)
|
// Test user get (we need to get the ID from the list first)
|
||||||
let users = user_service.list().await?;
|
let users = user_service.list().await?;
|
||||||
assert!(!users.is_empty());
|
assert!(!users.is_empty());
|
||||||
let user_id = users[0].id;
|
let user_id = users[0].id().to_string();
|
||||||
|
|
||||||
let cli = Cli::parse_from(&["cli", "user", "get", "--id", &user_id.to_string()]);
|
let cli = Cli::parse_from(&["cli", "user", "get", "--id", &user_id]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
// Test user update
|
// Test user update
|
||||||
let new_username = format!("{}_updated", username);
|
let new_username = format!("{}_updated", username);
|
||||||
let cli = Cli::parse_from(&[
|
let cli = Cli::parse_from(&[
|
||||||
"cli", "user", "update", "--id", &user_id.to_string(), "--username", &new_username
|
"cli", "user", "update", "--id", &user_id, "--username", &new_username
|
||||||
]);
|
]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
// Test user delete
|
// Test user delete
|
||||||
let cli = Cli::parse_from(&["cli", "user", "delete", "--id", &user_id.to_string()]);
|
let cli = Cli::parse_from(&["cli", "user", "delete", "--id", &user_id]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -243,20 +243,20 @@ async fn test_cli_with_postgres_product_lifecycle() -> Result<()> {
|
||||||
// Test product get (we need to get the ID from the list first)
|
// Test product get (we need to get the ID from the list first)
|
||||||
let products = product_service.list().await?;
|
let products = product_service.list().await?;
|
||||||
assert!(!products.is_empty());
|
assert!(!products.is_empty());
|
||||||
let product_id = products[0].id;
|
let product_id = products[0].id().to_string();
|
||||||
|
|
||||||
let cli = Cli::parse_from(&["cli", "product", "get", "--id", &product_id.to_string()]);
|
let cli = Cli::parse_from(&["cli", "product", "get", "--id", &product_id]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
// Test product update
|
// Test product update
|
||||||
let new_name = format!("{}_updated", name);
|
let new_name = format!("{}_updated", name);
|
||||||
let cli = Cli::parse_from(&[
|
let cli = Cli::parse_from(&[
|
||||||
"cli", "product", "update", "--id", &product_id.to_string(), "--name", &new_name
|
"cli", "product", "update", "--id", &product_id, "--name", &new_name
|
||||||
]);
|
]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
// Test product delete
|
// Test product delete
|
||||||
let cli = Cli::parse_from(&["cli", "product", "delete", "--id", &product_id.to_string()]);
|
let cli = Cli::parse_from(&["cli", "product", "delete", "--id", &product_id]);
|
||||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -51,15 +51,13 @@ impl InMemoryUserRepository {
|
||||||
|
|
||||||
impl Repository<User> for InMemoryUserRepository {
|
impl Repository<User> for InMemoryUserRepository {
|
||||||
async fn create(&self, data: CreateUser) -> Result<User> {
|
async fn create(&self, data: CreateUser) -> Result<User> {
|
||||||
let user = User {
|
let user = User::new(
|
||||||
id: Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
username: data.username,
|
data.username().to_string(),
|
||||||
email: data.email,
|
data.email().to_string()
|
||||||
created_at: chrono::Utc::now(),
|
)?;
|
||||||
updated_at: chrono::Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.users.write().await.insert(user.id, user.clone());
|
self.users.write().await.insert(user.id(), user.clone());
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,13 +80,12 @@ impl Repository<User> for InMemoryUserRepository {
|
||||||
.get_mut(&id)
|
.get_mut(&id)
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("User not found: {}", id)))?;
|
.ok_or_else(|| domain::DomainError::NotFound(format!("User not found: {}", id)))?;
|
||||||
|
|
||||||
if let Some(username) = data.username {
|
if let Some(username) = data.username() {
|
||||||
user.username = username;
|
user.set_username(username.to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(email) = data.email {
|
if let Some(email) = data.email() {
|
||||||
user.email = email;
|
user.set_email(email.to_string())?;
|
||||||
}
|
}
|
||||||
user.updated_at = chrono::Utc::now();
|
|
||||||
|
|
||||||
Ok(user.clone())
|
Ok(user.clone())
|
||||||
}
|
}
|
||||||
|
@ -121,15 +118,13 @@ impl InMemoryProductRepository {
|
||||||
|
|
||||||
impl Repository<Product> for InMemoryProductRepository {
|
impl Repository<Product> for InMemoryProductRepository {
|
||||||
async fn create(&self, data: CreateProduct) -> Result<Product> {
|
async fn create(&self, data: CreateProduct) -> Result<Product> {
|
||||||
let product = Product {
|
let product = Product::new(
|
||||||
id: Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
name: data.name,
|
data.name().to_string(),
|
||||||
description: data.description,
|
data.description().to_string()
|
||||||
created_at: chrono::Utc::now(),
|
)?;
|
||||||
updated_at: chrono::Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.products.write().await.insert(product.id, product.clone());
|
self.products.write().await.insert(product.id(), product.clone());
|
||||||
Ok(product)
|
Ok(product)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,13 +147,12 @@ impl Repository<Product> for InMemoryProductRepository {
|
||||||
.get_mut(&id)
|
.get_mut(&id)
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("Product not found: {}", id)))?;
|
.ok_or_else(|| domain::DomainError::NotFound(format!("Product not found: {}", id)))?;
|
||||||
|
|
||||||
if let Some(name) = data.name {
|
if let Some(name) = data.name() {
|
||||||
product.name = name;
|
product.set_name(name.to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(description) = data.description {
|
if let Some(description) = data.description() {
|
||||||
product.description = description;
|
product.set_description(description.to_string())?;
|
||||||
}
|
}
|
||||||
product.updated_at = chrono::Utc::now();
|
|
||||||
|
|
||||||
Ok(product.clone())
|
Ok(product.clone())
|
||||||
}
|
}
|
||||||
|
@ -200,11 +194,11 @@ mod tests {
|
||||||
|
|
||||||
let user = repo.create(create_data).await.unwrap();
|
let user = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(user.username, "testuser");
|
assert_eq!(user.username(), "testuser");
|
||||||
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());
|
assert!(user.updated_at() <= chrono::Utc::now());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -213,11 +207,11 @@ mod tests {
|
||||||
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());
|
||||||
assert_eq!(found_user.username, created_user.username);
|
assert_eq!(found_user.username(), created_user.username());
|
||||||
assert_eq!(found_user.email, created_user.email);
|
assert_eq!(found_user.email(), created_user.email());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -256,8 +250,8 @@ mod tests {
|
||||||
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() == user1.id()));
|
||||||
assert!(users.iter().any(|u| u.id == user2.id));
|
assert!(users.iter().any(|u| u.id() == user2.id()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -265,16 +259,16 @@ mod tests {
|
||||||
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 user = repo.create(CreateUser::new("olduser".to_string(), "old@example.com".to_string()).unwrap()).await.unwrap();
|
||||||
|
|
||||||
let original_updated_at = user.updated_at;
|
let original_updated_at = user.updated_at();
|
||||||
sleep(Duration::from_millis(1)).await; // Ensure timestamp difference
|
sleep(Duration::from_millis(1)).await; // Ensure timestamp difference
|
||||||
|
|
||||||
let update_data = UpdateUser::new(Some("newuser".to_string()), Some("new@example.com".to_string())).unwrap();
|
let update_data = UpdateUser::new(Some("newuser".to_string()), Some("new@example.com".to_string())).unwrap();
|
||||||
|
|
||||||
let updated_user = repo.update(user.id, update_data).await.unwrap();
|
let updated_user = repo.update(user.id(), update_data).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated_user.username, "newuser");
|
assert_eq!(updated_user.username(), "newuser");
|
||||||
assert_eq!(updated_user.email, "new@example.com");
|
assert_eq!(updated_user.email(), "new@example.com");
|
||||||
assert!(updated_user.updated_at > original_updated_at);
|
assert!(updated_user.updated_at() > original_updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -284,10 +278,10 @@ mod tests {
|
||||||
|
|
||||||
let update_data = UpdateUser::new(Some("newuser".to_string()), None).unwrap();
|
let update_data = UpdateUser::new(Some("newuser".to_string()), None).unwrap();
|
||||||
|
|
||||||
let updated_user = repo.update(user.id, update_data).await.unwrap();
|
let updated_user = repo.update(user.id(), update_data).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated_user.username, "newuser");
|
assert_eq!(updated_user.username(), "newuser");
|
||||||
assert_eq!(updated_user.email, "test@example.com"); // Should remain unchanged
|
assert_eq!(updated_user.email(), "test@example.com"); // Should remain unchanged
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -313,11 +307,11 @@ mod tests {
|
||||||
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 user = repo.create(CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap()).await.unwrap();
|
||||||
|
|
||||||
let result = repo.delete(user.id).await;
|
let result = repo.delete(user.id()).await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
// Verify user is actually deleted
|
// Verify user is actually deleted
|
||||||
let find_result = repo.find_by_id(user.id).await;
|
let find_result = repo.find_by_id(user.id()).await;
|
||||||
assert!(find_result.is_err());
|
assert!(find_result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,28 +363,28 @@ 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("Test Product".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, "Test Product");
|
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());
|
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("Test Product".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());
|
||||||
assert_eq!(found_product.name, created_product.name);
|
assert_eq!(found_product.name(), created_product.name());
|
||||||
assert_eq!(found_product.description, created_product.description);
|
assert_eq!(found_product.description(), created_product.description());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -422,52 +416,52 @@ mod tests {
|
||||||
async fn test_find_all_with_products() {
|
async fn test_find_all_with_products() {
|
||||||
let repo = InMemoryProductRepository::new();
|
let repo = InMemoryProductRepository::new();
|
||||||
|
|
||||||
let product1 = repo.create(CreateProduct::new("Product 1".to_string(), "Description 1".to_string()).unwrap()).await.unwrap();
|
let product1 = repo.create(CreateProduct::new("product1".to_string(), "description1".to_string()).unwrap()).await.unwrap();
|
||||||
|
|
||||||
let product2 = repo.create(CreateProduct::new("Product 2".to_string(), "Description 2".to_string()).unwrap()).await.unwrap();
|
let product2 = repo.create(CreateProduct::new("product2".to_string(), "description2".to_string()).unwrap()).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() == product1.id()));
|
||||||
assert!(products.iter().any(|p| p.id == product2.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("Old Product".to_string(), "Old Description".to_string()).unwrap()).await.unwrap();
|
let product = repo.create(CreateProduct::new("oldproduct".to_string(), "old description".to_string()).unwrap()).await.unwrap();
|
||||||
|
|
||||||
let original_updated_at = product.updated_at;
|
let original_updated_at = product.updated_at();
|
||||||
sleep(Duration::from_millis(1)).await; // Ensure timestamp difference
|
sleep(Duration::from_millis(1)).await; // Ensure timestamp difference
|
||||||
|
|
||||||
let update_data = UpdateProduct::new(Some("New Product".to_string()), Some("New Description".to_string())).unwrap();
|
let update_data = UpdateProduct::new(Some("newproduct".to_string()), Some("new description".to_string())).unwrap();
|
||||||
|
|
||||||
let updated_product = repo.update(product.id, update_data).await.unwrap();
|
let updated_product = repo.update(product.id(), update_data).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated_product.name, "New Product");
|
assert_eq!(updated_product.name(), "newproduct");
|
||||||
assert_eq!(updated_product.description, "New Description");
|
assert_eq!(updated_product.description(), "new description");
|
||||||
assert!(updated_product.updated_at > original_updated_at);
|
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("Test Product".to_string(), "Test Description".to_string()).unwrap()).await.unwrap();
|
let product = repo.create(CreateProduct::new("testproduct".to_string(), "test description".to_string()).unwrap()).await.unwrap();
|
||||||
|
|
||||||
let update_data = UpdateProduct::new(Some("New Product".to_string()), None).unwrap();
|
let update_data = UpdateProduct::new(Some("newproduct".to_string()), None).unwrap();
|
||||||
|
|
||||||
let updated_product = repo.update(product.id, update_data).await.unwrap();
|
let updated_product = repo.update(product.id(), update_data).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated_product.name, "New Product");
|
assert_eq!(updated_product.name(), "newproduct");
|
||||||
assert_eq!(updated_product.description, "Test Description"); // Should remain unchanged
|
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("New Product".to_string()), None).unwrap();
|
let update_data = UpdateProduct::new(Some("newproduct".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());
|
||||||
|
@ -484,13 +478,13 @@ mod tests {
|
||||||
#[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("Test Product".to_string(), "Test Description".to_string()).unwrap()).await.unwrap();
|
let product = repo.create(CreateProduct::new("testproduct".to_string(), "test description".to_string()).unwrap()).await.unwrap();
|
||||||
|
|
||||||
let result = repo.delete(product.id).await;
|
let result = repo.delete(product.id()).await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
// Verify product is actually deleted
|
// Verify product is actually deleted
|
||||||
let find_result = repo.find_by_id(product.id).await;
|
let find_result = repo.find_by_id(product.id()).await;
|
||||||
assert!(find_result.is_err());
|
assert!(find_result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,25 +508,24 @@ mod tests {
|
||||||
#[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
|
||||||
|
});
|
||||||
|
|
||||||
// Spawn multiple tasks to create products concurrently
|
// Direct access
|
||||||
let handles: Vec<_> = (0..10).map(|i| {
|
let direct_result = repo.find_by_id(product_id).await;
|
||||||
let repo = repo_clone.clone();
|
let spawned_result = handle.await.unwrap();
|
||||||
tokio::spawn(async move {
|
|
||||||
repo.create(CreateProduct::new(format!("Product {}", i), format!("Description {}", i)).unwrap()).await
|
|
||||||
})
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
// Wait for all tasks to complete
|
// Both should return the same product
|
||||||
let results = futures::future::join_all(handles).await;
|
assert_eq!(direct_result.unwrap().id(), product_id);
|
||||||
for result in results {
|
assert_eq!(spawned_result.unwrap().id(), product_id);
|
||||||
assert!(result.unwrap().is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify all products were created
|
|
||||||
let products = repo.find_all().await.unwrap();
|
|
||||||
assert_eq!(products.len(), 10);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,29 +541,29 @@ mod tests {
|
||||||
// Test create
|
// Test create
|
||||||
let user = service.create(CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap()).await.unwrap();
|
let user = service.create(CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap()).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(user.username, "testuser");
|
assert_eq!(user.username(), "testuser");
|
||||||
assert_eq!(user.email, "test@example.com");
|
assert_eq!(user.email(), "test@example.com");
|
||||||
|
|
||||||
// Test get
|
// 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
|
// Test update
|
||||||
let updated_user = service.update(user.id, UpdateUser::new(Some("newuser".to_string()), None).unwrap()).await.unwrap();
|
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.username(), "newuser");
|
||||||
assert_eq!(updated_user.email, "test@example.com");
|
assert_eq!(updated_user.email(), "test@example.com");
|
||||||
|
|
||||||
// Test list
|
// 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);
|
assert_eq!(users[0].id(), user.id());
|
||||||
|
|
||||||
// Test delete
|
// Test delete
|
||||||
service.delete(user.id).await.unwrap();
|
service.delete(user.id()).await.unwrap();
|
||||||
|
|
||||||
// Verify deletion
|
// Verify deletion
|
||||||
let result = service.get(user.id).await;
|
let result = service.get(user.id()).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,29 +575,29 @@ mod tests {
|
||||||
// Test create
|
// Test create
|
||||||
let product = service.create(CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap()).await.unwrap();
|
let product = service.create(CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap()).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(product.name, "Test Product");
|
assert_eq!(product.name(), "Test Product");
|
||||||
assert_eq!(product.description, "Test Description");
|
assert_eq!(product.description(), "Test Description");
|
||||||
|
|
||||||
// Test get
|
// 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
|
// Test update
|
||||||
let updated_product = service.update(product.id, UpdateProduct::new(Some("New Product".to_string()), None).unwrap()).await.unwrap();
|
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.name(), "New Product");
|
||||||
assert_eq!(updated_product.description, "Test Description");
|
assert_eq!(updated_product.description(), "Test Description");
|
||||||
|
|
||||||
// Test list
|
// 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);
|
assert_eq!(products[0].id(), product.id());
|
||||||
|
|
||||||
// Test delete
|
// Test delete
|
||||||
service.delete(product.id).await.unwrap();
|
service.delete(product.id()).await.unwrap();
|
||||||
|
|
||||||
// Verify deletion
|
// Verify deletion
|
||||||
let result = service.get(product.id).await;
|
let result = service.get(product.id()).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -626,18 +619,18 @@ mod tests {
|
||||||
assert_eq!(users.len(), 2);
|
assert_eq!(users.len(), 2);
|
||||||
|
|
||||||
// Update one user
|
// Update one user
|
||||||
let updated_user1 = repo.update(user1.id, UpdateUser::new(Some("updated_user1".to_string()), None).unwrap()).await.unwrap();
|
let updated_user1 = repo.update(user1.id(), UpdateUser::new(Some("updated_user1".to_string()), None).unwrap()).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated_user1.username, "updated_user1");
|
assert_eq!(updated_user1.username(), "updated_user1");
|
||||||
assert_eq!(updated_user1.email, "user1@example.com");
|
assert_eq!(updated_user1.email(), "user1@example.com");
|
||||||
|
|
||||||
// Delete one user
|
// Delete one user
|
||||||
repo.delete(user2.id).await.unwrap();
|
repo.delete(user2.id()).await.unwrap();
|
||||||
|
|
||||||
// Verify only one user remains
|
// Verify only one user remains
|
||||||
let remaining_users = repo.find_all().await.unwrap();
|
let remaining_users = repo.find_all().await.unwrap();
|
||||||
assert_eq!(remaining_users.len(), 1);
|
assert_eq!(remaining_users.len(), 1);
|
||||||
assert_eq!(remaining_users[0].id, user1.id);
|
assert_eq!(remaining_users[0].id(), user1.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -654,18 +647,18 @@ mod tests {
|
||||||
assert_eq!(products.len(), 2);
|
assert_eq!(products.len(), 2);
|
||||||
|
|
||||||
// Update one product
|
// Update one product
|
||||||
let updated_product1 = repo.update(product1.id, UpdateProduct::new(Some("Updated Product 1".to_string()), None).unwrap()).await.unwrap();
|
let updated_product1 = repo.update(product1.id(), UpdateProduct::new(Some("Updated Product 1".to_string()), None).unwrap()).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated_product1.name, "Updated Product 1");
|
assert_eq!(updated_product1.name(), "Updated Product 1");
|
||||||
assert_eq!(updated_product1.description, "Description 1");
|
assert_eq!(updated_product1.description(), "Description 1");
|
||||||
|
|
||||||
// Delete one product
|
// Delete one product
|
||||||
repo.delete(product2.id).await.unwrap();
|
repo.delete(product2.id()).await.unwrap();
|
||||||
|
|
||||||
// Verify only one product remains
|
// Verify only one product remains
|
||||||
let remaining_products = repo.find_all().await.unwrap();
|
let remaining_products = repo.find_all().await.unwrap();
|
||||||
assert_eq!(remaining_products.len(), 1);
|
assert_eq!(remaining_products.len(), 1);
|
||||||
assert_eq!(remaining_products[0].id, product1.id);
|
assert_eq!(remaining_products[0].id(), product1.id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,26 +30,30 @@ impl PostgresUserRepository {
|
||||||
|
|
||||||
impl Repository<User> for PostgresUserRepository {
|
impl Repository<User> for PostgresUserRepository {
|
||||||
async fn create(&self, data: CreateUser) -> Result<User> {
|
async fn create(&self, data: CreateUser) -> Result<User> {
|
||||||
let user = sqlx::query_as!(
|
let rec = sqlx::query!(
|
||||||
User,
|
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO users (username, email)
|
INSERT INTO users (username, email)
|
||||||
VALUES ($1, $2)
|
VALUES ($1, $2)
|
||||||
RETURNING id, username, email, created_at, updated_at
|
RETURNING id, username, email, created_at, updated_at
|
||||||
"#,
|
"#,
|
||||||
data.username,
|
data.username(),
|
||||||
data.email
|
data.email()
|
||||||
)
|
)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| domain::DomainError::Internal(e.to_string()))?;
|
.map_err(|e| domain::DomainError::Internal(e.to_string()))?;
|
||||||
|
|
||||||
Ok(user)
|
Ok(User::from_db(
|
||||||
|
rec.id,
|
||||||
|
rec.username,
|
||||||
|
rec.email,
|
||||||
|
rec.created_at,
|
||||||
|
rec.updated_at,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_by_id(&self, id: Uuid) -> Result<User> {
|
async fn find_by_id(&self, id: Uuid) -> Result<User> {
|
||||||
let user = sqlx::query_as!(
|
let rec = sqlx::query!(
|
||||||
User,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT id, username, email, created_at, updated_at
|
SELECT id, username, email, created_at, updated_at
|
||||||
FROM users
|
FROM users
|
||||||
|
@ -62,12 +66,17 @@ impl Repository<User> for PostgresUserRepository {
|
||||||
.map_err(|e| domain::DomainError::Internal(e.to_string()))?
|
.map_err(|e| domain::DomainError::Internal(e.to_string()))?
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("User not found: {}", id)))?;
|
.ok_or_else(|| domain::DomainError::NotFound(format!("User not found: {}", id)))?;
|
||||||
|
|
||||||
Ok(user)
|
Ok(User::from_db(
|
||||||
|
rec.id,
|
||||||
|
rec.username,
|
||||||
|
rec.email,
|
||||||
|
rec.created_at,
|
||||||
|
rec.updated_at,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_all(&self) -> Result<Vec<User>> {
|
async fn find_all(&self) -> Result<Vec<User>> {
|
||||||
let users = sqlx::query_as!(
|
let recs = sqlx::query!(
|
||||||
User,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT id, username, email, created_at, updated_at
|
SELECT id, username, email, created_at, updated_at
|
||||||
FROM users
|
FROM users
|
||||||
|
@ -77,12 +86,14 @@ impl Repository<User> for PostgresUserRepository {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| domain::DomainError::Internal(e.to_string()))?;
|
.map_err(|e| domain::DomainError::Internal(e.to_string()))?;
|
||||||
|
|
||||||
Ok(users)
|
Ok(recs
|
||||||
|
.into_iter()
|
||||||
|
.map(|rec| User::from_db(rec.id, rec.username, rec.email, rec.created_at, rec.updated_at))
|
||||||
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(&self, id: Uuid, data: UpdateUser) -> Result<User> {
|
async fn update(&self, id: Uuid, data: UpdateUser) -> Result<User> {
|
||||||
let user = sqlx::query_as!(
|
let rec = sqlx::query!(
|
||||||
User,
|
|
||||||
r#"
|
r#"
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET
|
SET
|
||||||
|
@ -92,8 +103,8 @@ impl Repository<User> for PostgresUserRepository {
|
||||||
WHERE id = $3
|
WHERE id = $3
|
||||||
RETURNING id, username, email, created_at, updated_at
|
RETURNING id, username, email, created_at, updated_at
|
||||||
"#,
|
"#,
|
||||||
data.username,
|
data.username(),
|
||||||
data.email,
|
data.email(),
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.fetch_optional(&self.pool)
|
.fetch_optional(&self.pool)
|
||||||
|
@ -101,7 +112,13 @@ impl Repository<User> for PostgresUserRepository {
|
||||||
.map_err(|e| domain::DomainError::Internal(e.to_string()))?
|
.map_err(|e| domain::DomainError::Internal(e.to_string()))?
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("User not found: {}", id)))?;
|
.ok_or_else(|| domain::DomainError::NotFound(format!("User not found: {}", id)))?;
|
||||||
|
|
||||||
Ok(user)
|
Ok(User::from_db(
|
||||||
|
rec.id,
|
||||||
|
rec.username,
|
||||||
|
rec.email,
|
||||||
|
rec.created_at,
|
||||||
|
rec.updated_at,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete(&self, id: Uuid) -> Result<()> {
|
async fn delete(&self, id: Uuid) -> Result<()> {
|
||||||
|
@ -137,26 +154,30 @@ impl PostgresProductRepository {
|
||||||
|
|
||||||
impl Repository<Product> for PostgresProductRepository {
|
impl Repository<Product> for PostgresProductRepository {
|
||||||
async fn create(&self, data: CreateProduct) -> Result<Product> {
|
async fn create(&self, data: CreateProduct) -> Result<Product> {
|
||||||
let product = sqlx::query_as!(
|
let rec = sqlx::query!(
|
||||||
Product,
|
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO products (name, description)
|
INSERT INTO products (name, description)
|
||||||
VALUES ($1, $2)
|
VALUES ($1, $2)
|
||||||
RETURNING id, name, description, created_at, updated_at
|
RETURNING id, name, description, created_at, updated_at
|
||||||
"#,
|
"#,
|
||||||
data.name,
|
data.name(),
|
||||||
data.description
|
data.description()
|
||||||
)
|
)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| domain::DomainError::Internal(e.to_string()))?;
|
.map_err(|e| domain::DomainError::Internal(e.to_string()))?;
|
||||||
|
|
||||||
Ok(product)
|
Ok(Product::from_db(
|
||||||
|
rec.id,
|
||||||
|
rec.name,
|
||||||
|
rec.description,
|
||||||
|
rec.created_at,
|
||||||
|
rec.updated_at,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_by_id(&self, id: Uuid) -> Result<Product> {
|
async fn find_by_id(&self, id: Uuid) -> Result<Product> {
|
||||||
let product = sqlx::query_as!(
|
let rec = sqlx::query!(
|
||||||
Product,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT id, name, description, created_at, updated_at
|
SELECT id, name, description, created_at, updated_at
|
||||||
FROM products
|
FROM products
|
||||||
|
@ -169,12 +190,17 @@ impl Repository<Product> for PostgresProductRepository {
|
||||||
.map_err(|e| domain::DomainError::Internal(e.to_string()))?
|
.map_err(|e| domain::DomainError::Internal(e.to_string()))?
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("Product not found: {}", id)))?;
|
.ok_or_else(|| domain::DomainError::NotFound(format!("Product not found: {}", id)))?;
|
||||||
|
|
||||||
Ok(product)
|
Ok(Product::from_db(
|
||||||
|
rec.id,
|
||||||
|
rec.name,
|
||||||
|
rec.description,
|
||||||
|
rec.created_at,
|
||||||
|
rec.updated_at,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_all(&self) -> Result<Vec<Product>> {
|
async fn find_all(&self) -> Result<Vec<Product>> {
|
||||||
let products = sqlx::query_as!(
|
let recs = sqlx::query!(
|
||||||
Product,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT id, name, description, created_at, updated_at
|
SELECT id, name, description, created_at, updated_at
|
||||||
FROM products
|
FROM products
|
||||||
|
@ -184,12 +210,14 @@ impl Repository<Product> for PostgresProductRepository {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| domain::DomainError::Internal(e.to_string()))?;
|
.map_err(|e| domain::DomainError::Internal(e.to_string()))?;
|
||||||
|
|
||||||
Ok(products)
|
Ok(recs
|
||||||
|
.into_iter()
|
||||||
|
.map(|rec| Product::from_db(rec.id, rec.name, rec.description, rec.created_at, rec.updated_at))
|
||||||
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(&self, id: Uuid, data: UpdateProduct) -> Result<Product> {
|
async fn update(&self, id: Uuid, data: UpdateProduct) -> Result<Product> {
|
||||||
let product = sqlx::query_as!(
|
let rec = sqlx::query!(
|
||||||
Product,
|
|
||||||
r#"
|
r#"
|
||||||
UPDATE products
|
UPDATE products
|
||||||
SET
|
SET
|
||||||
|
@ -199,8 +227,8 @@ impl Repository<Product> for PostgresProductRepository {
|
||||||
WHERE id = $3
|
WHERE id = $3
|
||||||
RETURNING id, name, description, created_at, updated_at
|
RETURNING id, name, description, created_at, updated_at
|
||||||
"#,
|
"#,
|
||||||
data.name,
|
data.name(),
|
||||||
data.description,
|
data.description(),
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.fetch_optional(&self.pool)
|
.fetch_optional(&self.pool)
|
||||||
|
@ -208,7 +236,13 @@ impl Repository<Product> for PostgresProductRepository {
|
||||||
.map_err(|e| domain::DomainError::Internal(e.to_string()))?
|
.map_err(|e| domain::DomainError::Internal(e.to_string()))?
|
||||||
.ok_or_else(|| domain::DomainError::NotFound(format!("Product not found: {}", id)))?;
|
.ok_or_else(|| domain::DomainError::NotFound(format!("Product not found: {}", id)))?;
|
||||||
|
|
||||||
Ok(product)
|
Ok(Product::from_db(
|
||||||
|
rec.id,
|
||||||
|
rec.name,
|
||||||
|
rec.description,
|
||||||
|
rec.created_at,
|
||||||
|
rec.updated_at,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete(&self, id: Uuid) -> Result<()> {
|
async fn delete(&self, id: Uuid) -> Result<()> {
|
||||||
|
@ -301,11 +335,11 @@ mod tests {
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let user = result.unwrap();
|
let user = result.unwrap();
|
||||||
assert_eq!(user.username, "testuser");
|
assert_eq!(user.username(), "testuser");
|
||||||
assert_eq!(user.email, "test@example.com");
|
assert_eq!(user.email(), "test@example.com");
|
||||||
assert!(user.id != Uuid::nil());
|
assert!(user.id() != Uuid::nil());
|
||||||
assert!(user.created_at <= Utc::now());
|
assert!(user.created_at() <= Utc::now());
|
||||||
assert!(user.updated_at <= Utc::now());
|
assert!(user.updated_at() <= Utc::now());
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -345,13 +379,13 @@ mod tests {
|
||||||
let create_data = CreateUser::new("finduser".to_string(), "find@example.com".to_string()).unwrap();
|
let create_data = CreateUser::new("finduser".to_string(), "find@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;
|
let found_user = repo.find_by_id(created_user.id()).await;
|
||||||
|
|
||||||
assert!(found_user.is_ok());
|
assert!(found_user.is_ok());
|
||||||
let user = found_user.unwrap();
|
let user = found_user.unwrap();
|
||||||
assert_eq!(user.id, created_user.id);
|
assert_eq!(user.id(), created_user.id());
|
||||||
assert_eq!(user.username, "finduser");
|
assert_eq!(user.username(), "finduser");
|
||||||
assert_eq!(user.email, "find@example.com");
|
assert_eq!(user.email(), "find@example.com");
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -399,7 +433,7 @@ mod tests {
|
||||||
let users = repo.find_all().await.unwrap();
|
let users = repo.find_all().await.unwrap();
|
||||||
assert_eq!(users.len(), 2);
|
assert_eq!(users.len(), 2);
|
||||||
|
|
||||||
let usernames: Vec<String> = users.iter().map(|u| u.username.clone()).collect();
|
let usernames: Vec<String> = users.iter().map(|u| u.username().to_string()).collect();
|
||||||
assert!(usernames.contains(&username1));
|
assert!(usernames.contains(&username1));
|
||||||
assert!(usernames.contains(&username2));
|
assert!(usernames.contains(&username2));
|
||||||
|
|
||||||
|
@ -433,15 +467,15 @@ mod tests {
|
||||||
let create_data = CreateUser::new("updateuser".to_string(), "update@example.com".to_string()).unwrap();
|
let create_data = CreateUser::new("updateuser".to_string(), "update@example.com".to_string()).unwrap();
|
||||||
|
|
||||||
let user = repo.create(create_data).await.unwrap();
|
let user = repo.create(create_data).await.unwrap();
|
||||||
let original_updated_at = user.updated_at;
|
let original_updated_at = user.updated_at();
|
||||||
|
|
||||||
// Update username only
|
// Update username only
|
||||||
let update_data = UpdateUser::new(Some("updateduser".to_string()), None).unwrap();
|
let update_data = UpdateUser::new(Some("updateduser".to_string()), None).unwrap();
|
||||||
|
|
||||||
let updated_user = repo.update(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.username(), "updateduser");
|
||||||
assert_eq!(updated_user.email, "update@example.com"); // Should remain unchanged
|
assert_eq!(updated_user.email(), "update@example.com"); // Should remain unchanged
|
||||||
assert!(updated_user.updated_at > original_updated_at);
|
assert!(updated_user.updated_at() > original_updated_at);
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -461,9 +495,9 @@ mod tests {
|
||||||
|
|
||||||
let update_data = UpdateUser::new(None, Some("new@example.com".to_string())).unwrap();
|
let update_data = UpdateUser::new(None, Some("new@example.com".to_string())).unwrap();
|
||||||
|
|
||||||
let updated_user = repo.update(user.id, update_data).await.unwrap();
|
let updated_user = repo.update(user.id(), update_data).await.unwrap();
|
||||||
assert_eq!(updated_user.username, "emailuser"); // Should remain unchanged
|
assert_eq!(updated_user.username(), "emailuser"); // Should remain unchanged
|
||||||
assert_eq!(updated_user.email, "new@example.com");
|
assert_eq!(updated_user.email(), "new@example.com");
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -483,9 +517,9 @@ mod tests {
|
||||||
|
|
||||||
let update_data = UpdateUser::new(Some("newbothuser".to_string()), Some("newboth@example.com".to_string())).unwrap();
|
let update_data = UpdateUser::new(Some("newbothuser".to_string()), Some("newboth@example.com".to_string())).unwrap();
|
||||||
|
|
||||||
let updated_user = repo.update(user.id, update_data).await.unwrap();
|
let updated_user = repo.update(user.id(), update_data).await.unwrap();
|
||||||
assert_eq!(updated_user.username, "newbothuser");
|
assert_eq!(updated_user.username(), "newbothuser");
|
||||||
assert_eq!(updated_user.email, "newboth@example.com");
|
assert_eq!(updated_user.email(), "newboth@example.com");
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -527,7 +561,7 @@ mod tests {
|
||||||
let create_data = CreateUser::new(username.clone(), email.clone()).unwrap();
|
let create_data = CreateUser::new(username.clone(), email.clone()).unwrap();
|
||||||
|
|
||||||
let user = repo.create(create_data).await.unwrap();
|
let user = repo.create(create_data).await.unwrap();
|
||||||
let user_id = user.id;
|
let user_id = user.id();
|
||||||
|
|
||||||
// Verify user exists
|
// Verify user exists
|
||||||
let found_user = repo.find_by_id(user_id).await;
|
let found_user = repo.find_by_id(user_id).await;
|
||||||
|
@ -584,7 +618,7 @@ mod tests {
|
||||||
|
|
||||||
// Test concurrent access with a simpler approach
|
// Test concurrent access with a simpler approach
|
||||||
let repo_clone = repo.clone();
|
let repo_clone = repo.clone();
|
||||||
let user_id = user.id;
|
let user_id = user.id();
|
||||||
|
|
||||||
// Spawn a single concurrent task
|
// Spawn a single concurrent task
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
|
@ -602,8 +636,8 @@ mod tests {
|
||||||
assert!(spawned_result.is_ok());
|
assert!(spawned_result.is_ok());
|
||||||
|
|
||||||
// Both should return the same user
|
// Both should return the same user
|
||||||
assert_eq!(direct_result.unwrap().id, user_id);
|
assert_eq!(direct_result.unwrap().id(), user_id);
|
||||||
assert_eq!(spawned_result.unwrap().id, user_id);
|
assert_eq!(spawned_result.unwrap().id(), user_id);
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -621,17 +655,17 @@ mod tests {
|
||||||
// Clean up at the beginning to ensure isolation
|
// Clean up at the beginning to ensure isolation
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
|
|
||||||
let create_data = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap();
|
let create_data = CreateProduct::new("testproduct".to_string(), "test description".to_string()).unwrap();
|
||||||
|
|
||||||
let result = repo.create(create_data).await;
|
let result = repo.create(create_data).await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let product = result.unwrap();
|
let product = result.unwrap();
|
||||||
assert_eq!(product.name, "Test Product");
|
assert_eq!(product.name(), "testproduct");
|
||||||
assert_eq!(product.description, "Test Description");
|
assert_eq!(product.description(), "test description");
|
||||||
assert!(product.id != Uuid::nil());
|
assert!(product.id() != Uuid::nil());
|
||||||
assert!(product.created_at <= Utc::now());
|
assert!(product.created_at() <= Utc::now());
|
||||||
assert!(product.updated_at <= Utc::now());
|
assert!(product.updated_at() <= Utc::now());
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -645,16 +679,16 @@ mod tests {
|
||||||
// Clean up at the beginning to ensure isolation
|
// Clean up at the beginning to ensure isolation
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
|
|
||||||
let create_data = CreateProduct::new("findproduct".to_string(), "finddesc".to_string()).unwrap();
|
let create_data = CreateProduct::new("findproduct".to_string(), "find 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;
|
let found_product = repo.find_by_id(created_product.id()).await;
|
||||||
|
|
||||||
assert!(found_product.is_ok());
|
assert!(found_product.is_ok());
|
||||||
let product = found_product.unwrap();
|
let product = found_product.unwrap();
|
||||||
assert_eq!(product.id, created_product.id);
|
assert_eq!(product.id(), created_product.id());
|
||||||
assert_eq!(product.name, "findproduct");
|
assert_eq!(product.name(), "findproduct");
|
||||||
assert_eq!(product.description, "finddesc");
|
assert_eq!(product.description(), "find description");
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -692,16 +726,17 @@ mod tests {
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
|
|
||||||
// Create multiple products with unique names
|
// Create multiple products with unique names
|
||||||
let (name1, desc1) = unique_test_data("product1");
|
let (name1, description1) = unique_test_data("product1");
|
||||||
let (name2, desc2) = unique_test_data("product2");
|
let (name2, description2) = unique_test_data("product2");
|
||||||
|
|
||||||
let _product1 = repo.create(CreateProduct::new(name1.clone(), desc1).unwrap()).await.unwrap();
|
let _product1 = repo.create(CreateProduct::new(name1.clone(), description1).unwrap()).await.unwrap();
|
||||||
let _product2 = repo.create(CreateProduct::new(name2.clone(), desc2).unwrap()).await.unwrap();
|
|
||||||
|
let _product2 = repo.create(CreateProduct::new(name2.clone(), description2).unwrap()).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);
|
||||||
|
|
||||||
let names: Vec<String> = products.iter().map(|p| p.name.clone()).collect();
|
let names: Vec<String> = products.iter().map(|p| p.name().to_string()).collect();
|
||||||
assert!(names.contains(&name1));
|
assert!(names.contains(&name1));
|
||||||
assert!(names.contains(&name2));
|
assert!(names.contains(&name2));
|
||||||
|
|
||||||
|
@ -732,18 +767,18 @@ mod tests {
|
||||||
// Clean up at the beginning to ensure isolation
|
// Clean up at the beginning to ensure isolation
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
|
|
||||||
let create_data = CreateProduct::new("updateproduct".to_string(), "updatedesc".to_string()).unwrap();
|
let create_data = CreateProduct::new("updateproduct".to_string(), "update description".to_string()).unwrap();
|
||||||
|
|
||||||
let product = repo.create(create_data).await.unwrap();
|
let product = repo.create(create_data).await.unwrap();
|
||||||
let original_updated_at = product.updated_at;
|
let original_updated_at = product.updated_at();
|
||||||
|
|
||||||
// Update name only
|
// Update name only
|
||||||
let update_data = UpdateProduct::new(Some("updatedproduct".to_string()), None).unwrap();
|
let update_data = UpdateProduct::new(Some("updatedproduct".to_string()), None).unwrap();
|
||||||
|
|
||||||
let updated_product = repo.update(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.name(), "updatedproduct");
|
||||||
assert_eq!(updated_product.description, "updatedesc"); // Should remain unchanged
|
assert_eq!(updated_product.description(), "update description"); // Should remain unchanged
|
||||||
assert!(updated_product.updated_at > original_updated_at);
|
assert!(updated_product.updated_at() > original_updated_at);
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -757,22 +792,15 @@ mod tests {
|
||||||
// Clean up at the beginning to ensure isolation
|
// Clean up at the beginning to ensure isolation
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
|
|
||||||
let (name, _) = unique_test_data("desc_product");
|
let create_data = CreateProduct::new("descriptionproduct".to_string(), "old description".to_string()).unwrap();
|
||||||
let create_data = CreateProduct {
|
|
||||||
name: name.clone(),
|
|
||||||
description: "Old Description".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let product = repo.create(create_data).await.unwrap();
|
let product = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
let update_data = UpdateProduct {
|
let update_data = UpdateProduct::new(None, Some("new description".to_string())).unwrap();
|
||||||
name: None,
|
|
||||||
description: Some("New Description".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let updated_product = repo.update(product.id, update_data).await.unwrap();
|
let updated_product = repo.update(product.id(), update_data).await.unwrap();
|
||||||
assert_eq!(updated_product.name, name); // Should remain unchanged
|
assert_eq!(updated_product.name(), "descriptionproduct"); // Should remain unchanged
|
||||||
assert_eq!(updated_product.description, "New Description");
|
assert_eq!(updated_product.description(), "new description");
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -786,22 +814,15 @@ mod tests {
|
||||||
// Clean up at the beginning to ensure isolation
|
// Clean up at the beginning to ensure isolation
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
|
|
||||||
let (name, _) = unique_test_data("both_product");
|
let create_data = CreateProduct::new("bothproduct".to_string(), "both description".to_string()).unwrap();
|
||||||
let create_data = CreateProduct {
|
|
||||||
name: name.clone(),
|
|
||||||
description: "Both Description".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let product = repo.create(create_data).await.unwrap();
|
let product = repo.create(create_data).await.unwrap();
|
||||||
|
|
||||||
let update_data = UpdateProduct {
|
let update_data = UpdateProduct::new(Some("newbothproduct".to_string()), Some("new both description".to_string())).unwrap();
|
||||||
name: Some("New Both Product".to_string()),
|
|
||||||
description: Some("New Both Description".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let updated_product = repo.update(product.id, update_data).await.unwrap();
|
let updated_product = repo.update(product.id(), update_data).await.unwrap();
|
||||||
assert_eq!(updated_product.name, "New Both Product");
|
assert_eq!(updated_product.name(), "newbothproduct");
|
||||||
assert_eq!(updated_product.description, "New Both Description");
|
assert_eq!(updated_product.description(), "new both description");
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -816,10 +837,7 @@ mod tests {
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
|
|
||||||
let nonexistent_id = Uuid::new_v4();
|
let nonexistent_id = Uuid::new_v4();
|
||||||
let update_data = UpdateProduct {
|
let update_data = UpdateProduct::new(Some("nonexistent".to_string()), None).unwrap();
|
||||||
name: Some("Nonexistent Product".to_string()),
|
|
||||||
description: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = repo.update(nonexistent_id, update_data).await;
|
let result = repo.update(nonexistent_id, update_data).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
@ -842,14 +860,11 @@ mod tests {
|
||||||
// Clean up at the beginning to ensure isolation
|
// Clean up at the beginning to ensure isolation
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
|
|
||||||
let (name, _) = unique_test_data("delete_product");
|
let (name, description) = unique_test_data("delete_product");
|
||||||
let create_data = CreateProduct {
|
let create_data = CreateProduct::new(name.clone(), description.clone()).unwrap();
|
||||||
name: name.clone(),
|
|
||||||
description: "Delete Description".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let product = repo.create(create_data).await.unwrap();
|
let product = repo.create(create_data).await.unwrap();
|
||||||
let product_id = product.id;
|
let product_id = product.id();
|
||||||
|
|
||||||
// Verify product exists
|
// Verify product exists
|
||||||
let found_product = repo.find_by_id(product_id).await;
|
let found_product = repo.find_by_id(product_id).await;
|
||||||
|
@ -909,22 +924,22 @@ mod tests {
|
||||||
|
|
||||||
// Test create
|
// Test create
|
||||||
let user = service.create(create_data).await.unwrap();
|
let user = service.create(create_data).await.unwrap();
|
||||||
assert_eq!(user.username, username);
|
assert_eq!(user.username(), username);
|
||||||
|
|
||||||
// Test get
|
// 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 list - should have exactly 1 user
|
// Test list - should have exactly 1 user
|
||||||
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].username, username);
|
assert_eq!(users[0].username(), username);
|
||||||
|
|
||||||
// Test update
|
// Test update
|
||||||
let (new_username, _) = unique_test_data("updated_service_user");
|
let (new_username, _) = unique_test_data("updated_service_user");
|
||||||
let update_data = UpdateUser::new(Some(new_username.clone()), None).unwrap();
|
let update_data = UpdateUser::new(Some(new_username.clone()), None).unwrap();
|
||||||
let updated_user = service.update(user.id, update_data).await.unwrap();
|
let updated_user = service.update(user.id(), update_data).await.unwrap();
|
||||||
assert_eq!(updated_user.username, new_username);
|
assert_eq!(updated_user.username(), new_username);
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
@ -939,20 +954,28 @@ mod tests {
|
||||||
// Clean up at the beginning to ensure isolation
|
// Clean up at the beginning to ensure isolation
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
|
|
||||||
let (name, _) = unique_test_data("service_product");
|
let (name, description) = unique_test_data("service_product");
|
||||||
let create_data = CreateProduct {
|
let create_data = CreateProduct::new(name.clone(), description.clone()).unwrap();
|
||||||
name: name.clone(),
|
|
||||||
description: "Service Description".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Test create
|
// Test create
|
||||||
let product = service.create(create_data).await.unwrap();
|
let product = service.create(create_data).await.unwrap();
|
||||||
assert_eq!(product.name, name);
|
assert_eq!(product.name(), name);
|
||||||
|
|
||||||
// Test get
|
// 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());
|
||||||
assert_eq!(found_product.name, name);
|
assert_eq!(found_product.name(), name);
|
||||||
|
|
||||||
|
// Test list - should have exactly 1 product
|
||||||
|
let products = service.list().await.unwrap();
|
||||||
|
assert_eq!(products.len(), 1);
|
||||||
|
assert_eq!(products[0].name(), name);
|
||||||
|
|
||||||
|
// Test update
|
||||||
|
let (new_name, _) = unique_test_data("updated_service_product");
|
||||||
|
let update_data = UpdateProduct::new(Some(new_name.clone()), None).unwrap();
|
||||||
|
let updated_product = service.update(product.id(), update_data).await.unwrap();
|
||||||
|
assert_eq!(updated_product.name(), new_name);
|
||||||
|
|
||||||
cleanup_test_data(&pool).await;
|
cleanup_test_data(&pool).await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -661,13 +661,10 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_concurrent_access() {
|
async fn test_concurrent_access() {
|
||||||
let repo = InMemoryUserRepository::new();
|
let repo = InMemoryUserRepository::new();
|
||||||
let create_data = CreateUser {
|
let create_data = CreateUser::new("concurrent_user".to_string(), "concurrent@example.com".to_string()).unwrap();
|
||||||
username: "concurrent_user".to_string(),
|
|
||||||
email: "concurrent@example.com".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let user = repo.create(create_data).await.unwrap();
|
let user = repo.create(create_data).await.unwrap();
|
||||||
let user_id = user.id;
|
let user_id = user.id();
|
||||||
|
|
||||||
let repo_clone = repo.clone();
|
let repo_clone = repo.clone();
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
|
@ -679,7 +676,7 @@ mod tests {
|
||||||
|
|
||||||
assert!(direct_result.is_ok());
|
assert!(direct_result.is_ok());
|
||||||
assert!(spawned_result.is_ok());
|
assert!(spawned_result.is_ok());
|
||||||
assert_eq!(direct_result.unwrap().id, user_id);
|
assert_eq!(direct_result.unwrap().id(), user_id);
|
||||||
assert_eq!(spawned_result.unwrap().id, user_id);
|
assert_eq!(spawned_result.unwrap().id(), user_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue