From fffa9f35c0993f0f32fc408b2c589cf5c7df5381 Mon Sep 17 00:00:00 2001
From: continuist <continuist02@gmail.com>
Date: Mon, 23 Jun 2025 20:24:52 -0400
Subject: [PATCH] Add api crate unit tests

---
 backend/crates/api/Cargo.toml |    6 +-
 backend/crates/api/src/lib.rs | 1313 +++++++++++++++++++++++++++++++++
 2 files changed, 1318 insertions(+), 1 deletion(-)

diff --git a/backend/crates/api/Cargo.toml b/backend/crates/api/Cargo.toml
index 5d37a35..93a78e0 100644
--- a/backend/crates/api/Cargo.toml
+++ b/backend/crates/api/Cargo.toml
@@ -10,10 +10,14 @@ domain = { path = "../domain" }
 application = { path = "../application" }
 axum = { workspace = true }
 tokio = { workspace = true }
-tower = "0.4"
+tower = { version = "0.4", features = ["util"] }
 tower-http = { version = "0.5", features = ["trace", "cors"] }
 tracing = { workspace = true }
 tracing-subscriber = { workspace = true, features = ["env-filter"] }
 serde = { workspace = true }
 serde_json = "1.0"
 uuid = { workspace = true }
+
+[dev-dependencies]
+chrono = { workspace = true }
+hyper = "1.0"
diff --git a/backend/crates/api/src/lib.rs b/backend/crates/api/src/lib.rs
index 2f2185e..e3821e5 100644
--- a/backend/crates/api/src/lib.rs
+++ b/backend/crates/api/src/lib.rs
@@ -1,3 +1,74 @@
+//! # api
+//!
+//! This crate provides a RESTful HTTP API for the Sharenet application using Axum.
+//! It implements CRUD operations for users and products with proper error handling,
+//! CORS support, and comprehensive testing.
+//!
+//! ## Features
+//! - RESTful HTTP API with Axum framework
+//! - CRUD operations for `User` and `Product` entities
+//! - Generic service layer supporting different backends (memory, PostgreSQL)
+//! - CORS support for cross-origin requests
+//! - Request/response logging with tracing
+//! - Comprehensive unit tests with mock services
+//! - Proper HTTP status codes and error handling
+//!
+//! ## API Endpoints
+//!
+//! ### Users
+//! - `POST /users` - Create a new user
+//! - `GET /users/:id` - Get a user by ID
+//! - `GET /users` - List all users
+//! - `PUT /users/:id` - Update a user
+//! - `DELETE /users/:id` - Delete a user
+//!
+//! ### Products
+//! - `POST /products` - Create a new product
+//! - `GET /products/:id` - Get a product by ID
+//! - `GET /products` - List all products
+//! - `PUT /products/:id` - Update a product
+//! - `DELETE /products/:id` - Delete a product
+//!
+//! ## Usage
+//!
+//! ```rust
+//! use api::run;
+//! use std::net::SocketAddr;
+//!
+//! // Example with any service implementing UseCase<User> and UseCase<Product>
+//! #[tokio::main]
+//! async fn main() {
+//!     let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
+//!     
+//!     // Create your user and product services here
+//!     // let user_service = YourUserService::new();
+//!     // let product_service = YourProductService::new();
+//!
+//!     // run(addr, user_service, product_service).await;
+//! }
+//! ```
+//!
+//! ## Testing
+//!
+//! The crate includes comprehensive unit tests using mock services:
+//! - Tests for all CRUD operations
+//! - Error handling scenarios (not found, validation errors)
+//! - Integration tests covering full entity lifecycles
+//! - Uses `tower::ServiceExt::oneshot()` for testing Axum applications
+//!
+//! Run tests with:
+//! ```bash
+//! cargo test
+//! ```
+//!
+//! ## Dependencies
+//! - `axum` - HTTP web framework
+//! - `tower` - Middleware framework with `util` feature for testing
+//! - `tower-http` - HTTP-specific middleware (CORS, tracing)
+//! - `tracing` - Application logging and observability
+//! - `serde` - Serialization/deserialization
+//! - `uuid` - Unique identifier generation
+
 /*
  * This file is part of Sharenet.
  * 
@@ -26,6 +97,11 @@ use tower_http::cors::{CorsLayer, Any};
 use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, filter::EnvFilter};
 use uuid::Uuid;
 
+/// Application state containing user and product services.
+///
+/// This struct holds references to the service implementations that handle
+/// business logic for users and products. It's generic over the service types
+/// to allow different implementations (memory, PostgreSQL, etc.).
 pub struct AppState<U, P> {
     user_service: Arc<U>,
     product_service: Arc<P>,
@@ -44,6 +120,18 @@ where
     }
 }
 
+/// Starts the HTTP server with the provided services.
+///
+/// This function sets up the Axum application with all routes, middleware,
+/// and state management. It configures CORS, request logging, and starts
+/// listening on the specified address.
+///
+/// # Arguments
+/// * `addr` - The socket address to bind the server to
+/// * `user_service` - Service implementation for user operations
+/// * `product_service` - Service implementation for product operations
+///
+/// See the module-level documentation for usage examples.
 pub async fn run<U, P>(addr: SocketAddr, user_service: U, product_service: P)
 where
     U: UseCase<User> + Clone + Send + Sync + 'static,
@@ -87,6 +175,14 @@ where
     axum::serve(listener, app).await.unwrap();
 }
 
+/// Creates a new user.
+///
+/// Accepts a JSON payload with `username` and `email` fields.
+/// Returns the created user with generated ID and timestamps.
+///
+/// # Response
+/// - `201 Created` - User successfully created
+/// - `500 Internal Server Error` - Service error
 async fn create_user<U>(
     State(state): State<AppState<U, impl UseCase<Product>>>,
     Json(data): Json<CreateUser>,
@@ -100,6 +196,11 @@ where
     }
 }
 
+/// Retrieves a user by ID.
+///
+/// # Response
+/// - `200 OK` - User found and returned
+/// - `404 Not Found` - User with specified ID not found
 async fn get_user<U>(
     State(state): State<AppState<U, impl UseCase<Product>>>,
     Path(id): Path<Uuid>,
@@ -113,6 +214,11 @@ where
     }
 }
 
+/// Lists all users.
+///
+/// # Response
+/// - `200 OK` - List of all users
+/// - `500 Internal Server Error` - Service error
 async fn list_users<U>(
     State(state): State<AppState<U, impl UseCase<Product>>>,
 ) -> impl IntoResponse
@@ -125,6 +231,14 @@ where
     }
 }
 
+/// Updates a user by ID.
+///
+/// Accepts a JSON payload with optional `username` and `email` fields.
+/// Only provided fields will be updated.
+///
+/// # Response
+/// - `200 OK` - User successfully updated
+/// - `404 Not Found` - User with specified ID not found
 async fn update_user<U>(
     State(state): State<AppState<U, impl UseCase<Product>>>,
     Path(id): Path<Uuid>,
@@ -139,6 +253,11 @@ where
     }
 }
 
+/// Deletes a user by ID.
+///
+/// # Response
+/// - `204 No Content` - User successfully deleted
+/// - `404 Not Found` - User with specified ID not found
 async fn delete_user<U>(
     State(state): State<AppState<U, impl UseCase<Product>>>,
     Path(id): Path<Uuid>,
@@ -152,6 +271,14 @@ where
     }
 }
 
+/// Creates a new product.
+///
+/// Accepts a JSON payload with `name` and `description` fields.
+/// Returns the created product with generated ID and timestamps.
+///
+/// # Response
+/// - `201 Created` - Product successfully created
+/// - `500 Internal Server Error` - Service error
 async fn create_product<P>(
     State(state): State<AppState<impl UseCase<User>, P>>,
     Json(data): Json<CreateProduct>,
@@ -165,6 +292,11 @@ where
     }
 }
 
+/// Retrieves a product by ID.
+///
+/// # Response
+/// - `200 OK` - Product found and returned
+/// - `404 Not Found` - Product with specified ID not found
 async fn get_product<P>(
     State(state): State<AppState<impl UseCase<User>, P>>,
     Path(id): Path<Uuid>,
@@ -178,6 +310,11 @@ where
     }
 }
 
+/// Lists all products.
+///
+/// # Response
+/// - `200 OK` - List of all products
+/// - `500 Internal Server Error` - Service error
 async fn list_products<P>(
     State(state): State<AppState<impl UseCase<User>, P>>,
 ) -> impl IntoResponse
@@ -190,6 +327,14 @@ where
     }
 }
 
+/// Updates a product by ID.
+///
+/// Accepts a JSON payload with optional `name` and `description` fields.
+/// Only provided fields will be updated.
+///
+/// # Response
+/// - `200 OK` - Product successfully updated
+/// - `404 Not Found` - Product with specified ID not found
 async fn update_product<P>(
     State(state): State<AppState<impl UseCase<User>, P>>,
     Path(id): Path<Uuid>,
@@ -204,6 +349,11 @@ where
     }
 }
 
+/// Deletes a product by ID.
+///
+/// # Response
+/// - `204 No Content` - Product successfully deleted
+/// - `404 Not Found` - Product with specified ID not found
 async fn delete_product<P>(
     State(state): State<AppState<impl UseCase<User>, P>>,
     Path(id): Path<Uuid>,
@@ -216,3 +366,1166 @@ where
         Err(e) => (StatusCode::NOT_FOUND, e.to_string()).into_response(),
     }
 }
+
+#[cfg(test)]
+mod tests {
+    //! # API Tests
+    //!
+    //! This module contains comprehensive unit tests for the API endpoints.
+    //! Tests use mock services to verify HTTP request/response behavior
+    //! without requiring external dependencies.
+    //!
+    //! ## Test Structure
+    //! - `user_endpoints` - Tests for user CRUD operations
+    //! - `product_endpoints` - Tests for product CRUD operations  
+    //! - `integration_tests` - End-to-end lifecycle tests
+    //!
+    //! ## Testing Approach
+    //! - Uses `tower::ServiceExt::oneshot()` for testing Axum applications
+    //! - Mock services implement the `UseCase` trait
+    //! - Tests verify HTTP status codes, response bodies, and error handling
+    //! - Integration tests cover complete entity lifecycles
+
+    use super::*;
+    use axum::{
+        body::Body,
+        http::{Request, StatusCode},
+        response::Response,
+    };
+    use domain::{CreateProduct, CreateUser, UpdateProduct, UpdateUser, User, Product, DomainError};
+    use application::{UseCase, ApplicationError};
+    use std::sync::Arc;
+    use tokio::sync::RwLock;
+    use std::collections::HashMap;
+    use chrono::Utc;
+    use serde_json::json;
+    use tower::ServiceExt;
+
+    /// Mock user service for testing.
+    ///
+    /// Implements the `UseCase<User>` trait using an in-memory HashMap
+    /// for storing test data. Provides thread-safe access via `RwLock`.
+    #[derive(Clone)]
+    struct MockUserService {
+        users: Arc<RwLock<HashMap<Uuid, User>>>,
+    }
+
+    impl MockUserService {
+        /// Creates a new mock user service with empty storage.
+        fn new() -> Self {
+            Self {
+                users: Arc::new(RwLock::new(HashMap::new())),
+            }
+        }
+    }
+
+    impl UseCase<User> for MockUserService {
+        fn create(&self, data: CreateUser) -> impl std::future::Future<Output = Result<User, ApplicationError>> + Send {
+            let users = self.users.clone();
+            async move {
+                let mut guard = users.write().await;
+                let id = Uuid::new_v4();
+                let user = User {
+                    id,
+                    username: data.username,
+                    email: data.email,
+                    created_at: Utc::now(),
+                    updated_at: Utc::now(),
+                };
+                guard.insert(id, user.clone());
+                Ok(user)
+            }
+        }
+
+        fn get(&self, id: Uuid) -> impl std::future::Future<Output = Result<User, ApplicationError>> + Send {
+            let users = self.users.clone();
+            async move {
+                let guard = users.read().await;
+                guard.get(&id)
+                    .cloned()
+                    .ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("User not found: {}", id))))
+            }
+        }
+
+        fn list(&self) -> impl std::future::Future<Output = Result<Vec<User>, ApplicationError>> + Send {
+            let users = self.users.clone();
+            async move {
+                let guard = users.read().await;
+                Ok(guard.values().cloned().collect())
+            }
+        }
+
+        fn update(&self, id: Uuid, data: UpdateUser) -> impl std::future::Future<Output = Result<User, ApplicationError>> + Send {
+            let users = self.users.clone();
+            async move {
+                let mut guard = users.write().await;
+                let user = guard.get_mut(&id)
+                    .ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("User not found: {}", id))))?;
+                
+                if let Some(username) = data.username {
+                    user.username = username;
+                }
+                if let Some(email) = data.email {
+                    user.email = email;
+                }
+                user.updated_at = Utc::now();
+                Ok(user.clone())
+            }
+        }
+
+        fn delete(&self, id: Uuid) -> impl std::future::Future<Output = Result<(), ApplicationError>> + Send {
+            let users = self.users.clone();
+            async move {
+                let mut guard = users.write().await;
+                guard.remove(&id)
+                    .ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("User not found: {}", id))))?;
+                Ok(())
+            }
+        }
+    }
+
+    /// Mock product service for testing.
+    ///
+    /// Implements the `UseCase<Product>` trait using an in-memory HashMap
+    /// for storing test data. Provides thread-safe access via `RwLock`.
+    #[derive(Clone)]
+    struct MockProductService {
+        products: Arc<RwLock<HashMap<Uuid, Product>>>,
+    }
+
+    impl MockProductService {
+        /// Creates a new mock product service with empty storage.
+        fn new() -> Self {
+            Self {
+                products: Arc::new(RwLock::new(HashMap::new())),
+            }
+        }
+    }
+
+    impl UseCase<Product> for MockProductService {
+        fn create(&self, data: CreateProduct) -> impl std::future::Future<Output = Result<Product, ApplicationError>> + Send {
+            let products = self.products.clone();
+            async move {
+                let mut guard = products.write().await;
+                let id = Uuid::new_v4();
+                let product = Product {
+                    id,
+                    name: data.name,
+                    description: data.description,
+                    created_at: Utc::now(),
+                    updated_at: Utc::now(),
+                };
+                guard.insert(id, product.clone());
+                Ok(product)
+            }
+        }
+
+        fn get(&self, id: Uuid) -> impl std::future::Future<Output = Result<Product, ApplicationError>> + Send {
+            let products = self.products.clone();
+            async move {
+                let guard = products.read().await;
+                guard.get(&id)
+                    .cloned()
+                    .ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("Product not found: {}", id))))
+            }
+        }
+
+        fn list(&self) -> impl std::future::Future<Output = Result<Vec<Product>, ApplicationError>> + Send {
+            let products = self.products.clone();
+            async move {
+                let guard = products.read().await;
+                Ok(guard.values().cloned().collect())
+            }
+        }
+
+        fn update(&self, id: Uuid, data: UpdateProduct) -> impl std::future::Future<Output = Result<Product, ApplicationError>> + Send {
+            let products = self.products.clone();
+            async move {
+                let mut guard = products.write().await;
+                let product = guard.get_mut(&id)
+                    .ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("Product not found: {}", id))))?;
+                
+                if let Some(name) = data.name {
+                    product.name = name;
+                }
+                if let Some(description) = data.description {
+                    product.description = description;
+                }
+                product.updated_at = Utc::now();
+                Ok(product.clone())
+            }
+        }
+
+        fn delete(&self, id: Uuid) -> impl std::future::Future<Output = Result<(), ApplicationError>> + Send {
+            let products = self.products.clone();
+            async move {
+                let mut guard = products.write().await;
+                guard.remove(&id)
+                    .ok_or_else(|| ApplicationError::Domain(DomainError::NotFound(format!("Product not found: {}", id))))?;
+                Ok(())
+            }
+        }
+    }
+
+    /// Creates a test Axum application with mock services.
+    ///
+    /// Returns a configured `Router` with all endpoints and mock services
+    /// for testing purposes.
+    fn create_test_app() -> Router {
+        let user_service = MockUserService::new();
+        let product_service = MockProductService::new();
+        
+        let state = AppState {
+            user_service: Arc::new(user_service),
+            product_service: Arc::new(product_service),
+        };
+
+        Router::new()
+            .route("/users", post(create_user::<MockUserService>))
+            .route("/users/:id", get(get_user::<MockUserService>))
+            .route("/users", get(list_users::<MockUserService>))
+            .route("/users/:id", put(update_user::<MockUserService>))
+            .route("/users/:id", delete(delete_user::<MockUserService>))
+            .route("/products", post(create_product::<MockProductService>))
+            .route("/products/:id", get(get_product::<MockProductService>))
+            .route("/products", get(list_products::<MockProductService>))
+            .route("/products/:id", put(update_product::<MockProductService>))
+            .route("/products/:id", delete(delete_product::<MockProductService>))
+            .with_state(state)
+    }
+
+    /// Extracts JSON data from an HTTP response.
+    ///
+    /// Helper function for tests to deserialize response bodies.
+    async fn extract_json<T: serde::de::DeserializeOwned>(response: Response) -> T {
+        let bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap();
+        serde_json::from_slice(&bytes).unwrap()
+    }
+
+    mod user_endpoints {
+        //! # User Endpoint Tests
+        //!
+        //! Tests for all user-related API endpoints including CRUD operations
+        //! and error handling scenarios.
+
+        use super::*;
+
+        /// Tests user creation with valid data.
+        #[tokio::test]
+        async fn test_create_user() {
+            let app = create_test_app();
+            let create_data = json!({
+                "username": "testuser",
+                "email": "test@example.com"
+            });
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/users")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::CREATED);
+            
+            let user: User = extract_json(response).await;
+            assert_eq!(user.username, "testuser");
+            assert_eq!(user.email, "test@example.com");
+            assert!(!user.id.is_nil());
+        }
+
+        #[tokio::test]
+        async fn test_get_user() {
+            let app = create_test_app();
+            
+            // First create a user
+            let create_data = json!({
+                "username": "testuser",
+                "email": "test@example.com"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/users")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            let created_user: User = extract_json(create_response).await;
+
+            // Then get the user
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/users/{}", created_user.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::OK);
+            
+            let user: User = extract_json(response).await;
+            assert_eq!(user.id, created_user.id);
+            assert_eq!(user.username, "testuser");
+            assert_eq!(user.email, "test@example.com");
+        }
+
+        #[tokio::test]
+        async fn test_get_user_not_found() {
+            let app = create_test_app();
+            let non_existent_id = Uuid::new_v4();
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/users/{}", non_existent_id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::NOT_FOUND);
+        }
+
+        #[tokio::test]
+        async fn test_list_users() {
+            let app = create_test_app();
+            
+            // Create two users
+            let user1_data = json!({
+                "username": "user1",
+                "email": "user1@example.com"
+            });
+
+            let user2_data = json!({
+                "username": "user2",
+                "email": "user2@example.com"
+            });
+
+            app.clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/users")
+                        .header("content-type", "application/json")
+                        .body(Body::from(user1_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            app.clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/users")
+                        .header("content-type", "application/json")
+                        .body(Body::from(user2_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            // List users
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri("/users")
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::OK);
+            
+            let users: Vec<User> = extract_json(response).await;
+            assert_eq!(users.len(), 2);
+            assert!(users.iter().any(|u| u.username == "user1"));
+            assert!(users.iter().any(|u| u.username == "user2"));
+        }
+
+        #[tokio::test]
+        async fn test_update_user() {
+            let app = create_test_app();
+            
+            // First create a user
+            let create_data = json!({
+                "username": "olduser",
+                "email": "old@example.com"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/users")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            let created_user: User = extract_json(create_response).await;
+
+            // Update the user
+            let update_data = json!({
+                "username": "newuser",
+                "email": "new@example.com"
+            });
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("PUT")
+                        .uri(format!("/users/{}", created_user.id))
+                        .header("content-type", "application/json")
+                        .body(Body::from(update_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::OK);
+            
+            let user: User = extract_json(response).await;
+            assert_eq!(user.id, created_user.id);
+            assert_eq!(user.username, "newuser");
+            assert_eq!(user.email, "new@example.com");
+        }
+
+        #[tokio::test]
+        async fn test_update_user_partial() {
+            let app = create_test_app();
+            
+            // First create a user
+            let create_data = json!({
+                "username": "testuser",
+                "email": "test@example.com"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/users")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            let created_user: User = extract_json(create_response).await;
+
+            // Update only username
+            let update_data = json!({
+                "username": "newuser"
+            });
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("PUT")
+                        .uri(format!("/users/{}", created_user.id))
+                        .header("content-type", "application/json")
+                        .body(Body::from(update_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::OK);
+            
+            let user: User = extract_json(response).await;
+            assert_eq!(user.id, created_user.id);
+            assert_eq!(user.username, "newuser");
+            assert_eq!(user.email, "test@example.com"); // Should remain unchanged
+        }
+
+        #[tokio::test]
+        async fn test_update_user_not_found() {
+            let app = create_test_app();
+            let non_existent_id = Uuid::new_v4();
+            let update_data = json!({
+                "username": "newuser"
+            });
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("PUT")
+                        .uri(format!("/users/{}", non_existent_id))
+                        .header("content-type", "application/json")
+                        .body(Body::from(update_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::NOT_FOUND);
+        }
+
+        #[tokio::test]
+        async fn test_delete_user() {
+            let app = create_test_app();
+            
+            // First create a user
+            let create_data = json!({
+                "username": "testuser",
+                "email": "test@example.com"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/users")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            let created_user: User = extract_json(create_response).await;
+
+            // Delete the user
+            let response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("DELETE")
+                        .uri(format!("/users/{}", created_user.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::NO_CONTENT);
+
+            // Verify user is deleted
+            let get_response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/users/{}", created_user.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(get_response.status(), StatusCode::NOT_FOUND);
+        }
+
+        #[tokio::test]
+        async fn test_delete_user_not_found() {
+            let app = create_test_app();
+            let non_existent_id = Uuid::new_v4();
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("DELETE")
+                        .uri(format!("/users/{}", non_existent_id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::NOT_FOUND);
+        }
+    }
+
+    mod product_endpoints {
+        //! # Product Endpoint Tests
+        //!
+        //! Tests for all product-related API endpoints including CRUD operations
+        //! and error handling scenarios.
+
+        use super::*;
+
+        /// Tests product creation with valid data.
+        #[tokio::test]
+        async fn test_create_product() {
+            let app = create_test_app();
+            let create_data = json!({
+                "name": "Test Product",
+                "description": "Test Description"
+            });
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/products")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::CREATED);
+            
+            let product: Product = extract_json(response).await;
+            assert_eq!(product.name, "Test Product");
+            assert_eq!(product.description, "Test Description");
+            assert!(!product.id.is_nil());
+        }
+
+        /// Tests retrieving a product by ID.
+        #[tokio::test]
+        async fn test_get_product() {
+            let app = create_test_app();
+            
+            // First create a product
+            let create_data = json!({
+                "name": "Test Product",
+                "description": "Test Description"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/products")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            let created_product: Product = extract_json(create_response).await;
+
+            // Then get the product
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/products/{}", created_product.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::OK);
+            
+            let product: Product = extract_json(response).await;
+            assert_eq!(product.id, created_product.id);
+            assert_eq!(product.name, "Test Product");
+            assert_eq!(product.description, "Test Description");
+        }
+
+        /// Tests retrieving a non-existent product.
+        #[tokio::test]
+        async fn test_get_product_not_found() {
+            let app = create_test_app();
+            let non_existent_id = Uuid::new_v4();
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/products/{}", non_existent_id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::NOT_FOUND);
+        }
+
+        /// Tests listing all products.
+        #[tokio::test]
+        async fn test_list_products() {
+            let app = create_test_app();
+            
+            // Create two products
+            let product1_data = json!({
+                "name": "Product 1",
+                "description": "Description 1"
+            });
+
+            let product2_data = json!({
+                "name": "Product 2",
+                "description": "Description 2"
+            });
+
+            app.clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/products")
+                        .header("content-type", "application/json")
+                        .body(Body::from(product1_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            app.clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/products")
+                        .header("content-type", "application/json")
+                        .body(Body::from(product2_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            // List products
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri("/products")
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::OK);
+            
+            let products: Vec<Product> = extract_json(response).await;
+            assert_eq!(products.len(), 2);
+            assert!(products.iter().any(|p| p.name == "Product 1"));
+            assert!(products.iter().any(|p| p.name == "Product 2"));
+        }
+
+        /// Tests updating a product with all fields.
+        #[tokio::test]
+        async fn test_update_product() {
+            let app = create_test_app();
+            
+            // First create a product
+            let create_data = json!({
+                "name": "Old Product",
+                "description": "Old Description"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/products")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            let created_product: Product = extract_json(create_response).await;
+
+            // Update the product
+            let update_data = json!({
+                "name": "New Product",
+                "description": "New Description"
+            });
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("PUT")
+                        .uri(format!("/products/{}", created_product.id))
+                        .header("content-type", "application/json")
+                        .body(Body::from(update_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::OK);
+            
+            let product: Product = extract_json(response).await;
+            assert_eq!(product.id, created_product.id);
+            assert_eq!(product.name, "New Product");
+            assert_eq!(product.description, "New Description");
+        }
+
+        /// Tests partial product updates.
+        #[tokio::test]
+        async fn test_update_product_partial() {
+            let app = create_test_app();
+            
+            // First create a product
+            let create_data = json!({
+                "name": "Test Product",
+                "description": "Test Description"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/products")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            let created_product: Product = extract_json(create_response).await;
+
+            // Update only name
+            let update_data = json!({
+                "name": "New Product"
+            });
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("PUT")
+                        .uri(format!("/products/{}", created_product.id))
+                        .header("content-type", "application/json")
+                        .body(Body::from(update_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::OK);
+            
+            let product: Product = extract_json(response).await;
+            assert_eq!(product.id, created_product.id);
+            assert_eq!(product.name, "New Product");
+            assert_eq!(product.description, "Test Description"); // Should remain unchanged
+        }
+
+        /// Tests updating a non-existent product.
+        #[tokio::test]
+        async fn test_update_product_not_found() {
+            let app = create_test_app();
+            let non_existent_id = Uuid::new_v4();
+            let update_data = json!({
+                "name": "New Product"
+            });
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("PUT")
+                        .uri(format!("/products/{}", non_existent_id))
+                        .header("content-type", "application/json")
+                        .body(Body::from(update_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::NOT_FOUND);
+        }
+
+        /// Tests product deletion.
+        #[tokio::test]
+        async fn test_delete_product() {
+            let app = create_test_app();
+            
+            // First create a product
+            let create_data = json!({
+                "name": "Test Product",
+                "description": "Test Description"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/products")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            let created_product: Product = extract_json(create_response).await;
+
+            // Delete the product
+            let response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("DELETE")
+                        .uri(format!("/products/{}", created_product.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::NO_CONTENT);
+
+            // Verify product is deleted
+            let get_response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/products/{}", created_product.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(get_response.status(), StatusCode::NOT_FOUND);
+        }
+
+        /// Tests deleting a non-existent product.
+        #[tokio::test]
+        async fn test_delete_product_not_found() {
+            let app = create_test_app();
+            let non_existent_id = Uuid::new_v4();
+
+            let response = app
+                .oneshot(
+                    Request::builder()
+                        .method("DELETE")
+                        .uri(format!("/products/{}", non_existent_id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(response.status(), StatusCode::NOT_FOUND);
+        }
+    }
+
+    mod integration_tests {
+        //! # Integration Tests
+        //!
+        //! End-to-end tests that verify complete entity lifecycles
+        //! including create, read, update, and delete operations.
+
+        use super::*;
+
+        /// Tests complete user lifecycle (create, read, update, delete).
+        #[tokio::test]
+        async fn test_user_lifecycle() {
+            let app = create_test_app();
+            
+            // Create user
+            let create_data = json!({
+                "username": "testuser",
+                "email": "test@example.com"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/users")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(create_response.status(), StatusCode::CREATED);
+            let user: User = extract_json(create_response).await;
+
+            // Get user
+            let get_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/users/{}", user.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(get_response.status(), StatusCode::OK);
+            let retrieved_user: User = extract_json(get_response).await;
+            assert_eq!(retrieved_user.id, user.id);
+
+            // Update user
+            let update_data = json!({
+                "username": "updateduser"
+            });
+
+            let update_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("PUT")
+                        .uri(format!("/users/{}", user.id))
+                        .header("content-type", "application/json")
+                        .body(Body::from(update_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(update_response.status(), StatusCode::OK);
+            let updated_user: User = extract_json(update_response).await;
+            assert_eq!(updated_user.username, "updateduser");
+
+            // Delete user
+            let delete_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("DELETE")
+                        .uri(format!("/users/{}", user.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(delete_response.status(), StatusCode::NO_CONTENT);
+
+            // Verify deletion
+            let verify_response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/users/{}", user.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(verify_response.status(), StatusCode::NOT_FOUND);
+        }
+
+        /// Tests complete product lifecycle (create, read, update, delete).
+        #[tokio::test]
+        async fn test_product_lifecycle() {
+            let app = create_test_app();
+            
+            // Create product
+            let create_data = json!({
+                "name": "Test Product",
+                "description": "Test Description"
+            });
+
+            let create_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("POST")
+                        .uri("/products")
+                        .header("content-type", "application/json")
+                        .body(Body::from(create_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(create_response.status(), StatusCode::CREATED);
+            let product: Product = extract_json(create_response).await;
+
+            // Get product
+            let get_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/products/{}", product.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(get_response.status(), StatusCode::OK);
+            let retrieved_product: Product = extract_json(get_response).await;
+            assert_eq!(retrieved_product.id, product.id);
+
+            // Update product
+            let update_data = json!({
+                "name": "Updated Product"
+            });
+
+            let update_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("PUT")
+                        .uri(format!("/products/{}", product.id))
+                        .header("content-type", "application/json")
+                        .body(Body::from(update_data.to_string()))
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(update_response.status(), StatusCode::OK);
+            let updated_product: Product = extract_json(update_response).await;
+            assert_eq!(updated_product.name, "Updated Product");
+
+            // Delete product
+            let delete_response = app
+                .clone()
+                .oneshot(
+                    Request::builder()
+                        .method("DELETE")
+                        .uri(format!("/products/{}", product.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(delete_response.status(), StatusCode::NO_CONTENT);
+
+            // Verify deletion
+            let verify_response = app
+                .oneshot(
+                    Request::builder()
+                        .method("GET")
+                        .uri(format!("/products/{}", product.id))
+                        .body(Body::empty())
+                        .unwrap(),
+                )
+                .await
+                .unwrap();
+
+            assert_eq!(verify_response.status(), StatusCode::NOT_FOUND);
+        }
+    }
+}