/* * This file is part of Sharenet. * * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. * * You may obtain a copy of the license at: * https://creativecommons.org/licenses/by-nc-sa/4.0/ * * Copyright (c) 2024 Continuist */ use application::{Service, UseCase}; use domain::{CreateProduct, CreateUser, Product, UpdateProduct, UpdateUser, User}; use memory::{InMemoryUserRepository, InMemoryProductRepository}; use postgres::{PostgresUserRepository, PostgresProductRepository}; use uuid::Uuid; use serial_test::serial; // Import the centralized test setup use crate::test_setup::{setup_test_db, unique_test_data}; // Helper functions are now imported from test_setup module above // Test data structures for comparison #[derive(Debug, PartialEq, Clone)] struct UserData { username: String, email: String, } impl From<&User> for UserData { fn from(user: &User) -> Self { Self { username: user.username().to_string(), email: user.email().to_string(), } } } #[derive(Debug, PartialEq, Clone)] struct ProductData { name: String, description: String, } impl From<&Product> for ProductData { fn from(product: &Product) -> Self { Self { name: product.name().to_string(), description: product.description().to_string(), } } } // Helper function to compare user lists (ignoring IDs and timestamps) fn compare_user_lists(memory_users: &[User], postgres_users: &[User]) { let memory_data: Vec = memory_users.iter().map(UserData::from).collect(); let postgres_data: Vec = postgres_users.iter().map(UserData::from).collect(); assert_eq!(memory_data.len(), postgres_data.len(), "User count mismatch"); // Sort by username for consistent comparison let mut memory_sorted = memory_data.clone(); let mut postgres_sorted = postgres_data.clone(); memory_sorted.sort_by(|a, b| a.username.cmp(&b.username)); postgres_sorted.sort_by(|a, b| a.username.cmp(&b.username)); assert_eq!(memory_sorted, postgres_sorted, "User data mismatch"); } // Helper function to compare product lists (ignoring IDs and timestamps) fn compare_product_lists(memory_products: &[Product], postgres_products: &[Product]) { let memory_data: Vec = memory_products.iter().map(ProductData::from).collect(); let postgres_data: Vec = postgres_products.iter().map(ProductData::from).collect(); assert_eq!(memory_data.len(), postgres_data.len(), "Product count mismatch"); // Sort by name for consistent comparison let mut memory_sorted = memory_data.clone(); let mut postgres_sorted = postgres_data.clone(); memory_sorted.sort_by(|a, b| a.name.cmp(&b.name)); postgres_sorted.sort_by(|a, b| a.name.cmp(&b.name)); assert_eq!(memory_sorted, postgres_sorted, "Product data mismatch"); } #[tokio::test] #[serial] async fn test_user_crud_consistency() { let pool = setup_test_db().await; // Create services let memory_user_repo = InMemoryUserRepository::new(); let postgres_user_repo = PostgresUserRepository::new(pool.clone()); let memory_user_service = Service::new(memory_user_repo); let postgres_user_service = Service::new(postgres_user_repo); // Test data let (username1, email1) = unique_test_data("consistency_user1"); let (username2, email2) = unique_test_data("consistency_user2"); // Test 1: Create users in both repositories let create_user1 = CreateUser::new(username1.clone(), email1.clone()).unwrap(); let create_user2 = CreateUser::new(username2.clone(), email2.clone()).unwrap(); let memory_user1 = memory_user_service.create(create_user1.clone()).await.unwrap(); let memory_user2 = memory_user_service.create(create_user2.clone()).await.unwrap(); let postgres_user1 = postgres_user_service.create(create_user1).await.unwrap(); let postgres_user2 = postgres_user_service.create(create_user2).await.unwrap(); // Verify created users have same data (ignoring IDs and timestamps) assert_eq!(memory_user1.username(), postgres_user1.username()); assert_eq!(memory_user1.email(), postgres_user1.email()); assert_eq!(memory_user2.username(), postgres_user2.username()); assert_eq!(memory_user2.email(), postgres_user2.email()); // Test 2: List users and compare let memory_users = memory_user_service.list().await.unwrap(); let postgres_users = postgres_user_service.list().await.unwrap(); compare_user_lists(&memory_users, &postgres_users); // Test 3: Get individual users let memory_retrieved1 = memory_user_service.get(memory_user1.id()).await.unwrap(); let postgres_retrieved1 = postgres_user_service.get(postgres_user1.id()).await.unwrap(); assert_eq!(memory_retrieved1.username(), postgres_retrieved1.username()); assert_eq!(memory_retrieved1.email(), postgres_retrieved1.email()); // Test 4: Update users let update_data = UpdateUser::new(Some("updated_username".to_string()), Some("updated@example.com".to_string())).unwrap(); let memory_updated = memory_user_service.update(memory_user1.id(), update_data.clone()).await.unwrap(); let postgres_updated = postgres_user_service.update(postgres_user1.id(), update_data).await.unwrap(); assert_eq!(memory_updated.username(), postgres_updated.username()); assert_eq!(memory_updated.email(), postgres_updated.email()); // Test 5: Delete users memory_user_service.delete(memory_user1.id()).await.unwrap(); postgres_user_service.delete(postgres_user1.id()).await.unwrap(); // Verify deletion let memory_users_after_delete = memory_user_service.list().await.unwrap(); let postgres_users_after_delete = postgres_user_service.list().await.unwrap(); assert_eq!(memory_users_after_delete.len(), 1); assert_eq!(postgres_users_after_delete.len(), 1); compare_user_lists(&memory_users_after_delete, &postgres_users_after_delete); } #[tokio::test] #[serial] async fn test_product_crud_consistency() { let pool = setup_test_db().await; // Create services let memory_product_repo = InMemoryProductRepository::new(); let postgres_product_repo = PostgresProductRepository::new(pool.clone()); let memory_product_service = Service::new(memory_product_repo); let postgres_product_service = Service::new(postgres_product_repo); // Test data let (name1, _) = unique_test_data("consistency_product1"); let (name2, _) = unique_test_data("consistency_product2"); let description1 = "Test product description 1"; let description2 = "Test product description 2"; // Test 1: Create products in both repositories let create_product1 = CreateProduct::new(name1.clone(), description1.to_string()).unwrap(); let create_product2 = CreateProduct::new(name2.clone(), description2.to_string()).unwrap(); let memory_product1 = memory_product_service.create(create_product1.clone()).await.unwrap(); let memory_product2 = memory_product_service.create(create_product2.clone()).await.unwrap(); let postgres_product1 = postgres_product_service.create(create_product1).await.unwrap(); let postgres_product2 = postgres_product_service.create(create_product2).await.unwrap(); // Verify created products have same data (ignoring IDs and timestamps) assert_eq!(memory_product1.name(), postgres_product1.name()); assert_eq!(memory_product1.description(), postgres_product1.description()); assert_eq!(memory_product2.name(), postgres_product2.name()); assert_eq!(memory_product2.description(), postgres_product2.description()); // Test 2: List products and compare let memory_products = memory_product_service.list().await.unwrap(); let postgres_products = postgres_product_service.list().await.unwrap(); compare_product_lists(&memory_products, &postgres_products); // Test 3: Get individual products let memory_retrieved1 = memory_product_service.get(memory_product1.id()).await.unwrap(); let postgres_retrieved1 = postgres_product_service.get(postgres_product1.id()).await.unwrap(); assert_eq!(memory_retrieved1.name(), postgres_retrieved1.name()); assert_eq!(memory_retrieved1.description(), postgres_retrieved1.description()); // Test 4: Update products let update_data = UpdateProduct::new(Some("updated_product_name".to_string()), Some("updated description".to_string())).unwrap(); let memory_updated = memory_product_service.update(memory_product1.id(), update_data.clone()).await.unwrap(); let postgres_updated = postgres_product_service.update(postgres_product1.id(), update_data).await.unwrap(); assert_eq!(memory_updated.name(), postgres_updated.name()); assert_eq!(memory_updated.description(), postgres_updated.description()); // Test 5: Delete products memory_product_service.delete(memory_product1.id()).await.unwrap(); postgres_product_service.delete(postgres_product1.id()).await.unwrap(); // Verify deletion let memory_products_after_delete = memory_product_service.list().await.unwrap(); let postgres_products_after_delete = postgres_product_service.list().await.unwrap(); assert_eq!(memory_products_after_delete.len(), 1); assert_eq!(postgres_products_after_delete.len(), 1); compare_product_lists(&memory_products_after_delete, &postgres_products_after_delete); } #[tokio::test] #[serial] async fn test_error_handling_consistency() { let pool = setup_test_db().await; // Create services let memory_user_repo = InMemoryUserRepository::new(); let postgres_user_repo = PostgresUserRepository::new(pool.clone()); let memory_user_service = Service::new(memory_user_repo); let postgres_user_service = Service::new(postgres_user_repo); let memory_product_repo = InMemoryProductRepository::new(); let postgres_product_repo = PostgresProductRepository::new(pool.clone()); let memory_product_service = Service::new(memory_product_repo); let postgres_product_service = Service::new(postgres_product_repo); // Test 1: Get non-existent user let non_existent_id = Uuid::new_v4(); let memory_user_result = memory_user_service.get(non_existent_id).await; let postgres_user_result = postgres_user_service.get(non_existent_id).await; assert!(memory_user_result.is_err()); assert!(postgres_user_result.is_err()); // Both should return NotFound error assert!(matches!(memory_user_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_)))); assert!(matches!(postgres_user_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_)))); // Test 2: Get non-existent product let memory_product_result = memory_product_service.get(non_existent_id).await; let postgres_product_result = postgres_product_service.get(non_existent_id).await; assert!(memory_product_result.is_err()); assert!(postgres_product_result.is_err()); assert!(matches!(memory_product_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_)))); assert!(matches!(postgres_product_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_)))); // Test 3: Update non-existent user let update_data = UpdateUser::new(Some("new_username".to_string()), None).unwrap(); let memory_update_result = memory_user_service.update(non_existent_id, update_data.clone()).await; let postgres_update_result = postgres_user_service.update(non_existent_id, update_data).await; assert!(memory_update_result.is_err()); assert!(postgres_update_result.is_err()); assert!(matches!(memory_update_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_)))); assert!(matches!(postgres_update_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_)))); // Test 4: Delete non-existent user let memory_delete_result = memory_user_service.delete(non_existent_id).await; let postgres_delete_result = postgres_user_service.delete(non_existent_id).await; assert!(memory_delete_result.is_err()); assert!(postgres_delete_result.is_err()); assert!(matches!(memory_delete_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_)))); assert!(matches!(postgres_delete_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_)))); } #[tokio::test] #[serial] async fn test_validation_consistency() { let pool = setup_test_db().await; // Create services let memory_user_repo = InMemoryUserRepository::new(); let postgres_user_repo = PostgresUserRepository::new(pool.clone()); let _memory_user_service = Service::new(memory_user_repo); let _postgres_user_service = Service::new(postgres_user_repo); let memory_product_repo = InMemoryProductRepository::new(); let postgres_product_repo = PostgresProductRepository::new(pool.clone()); let _memory_product_service = Service::new(memory_product_repo); let _postgres_product_service = Service::new(postgres_product_repo); // Test 1: Create user with empty username let create_user_empty_username = CreateUser::new("".to_string(), "test@example.com".to_string()); assert!(create_user_empty_username.is_err()); assert!(matches!(create_user_empty_username.unwrap_err(), domain::DomainError::InvalidInput(_))); // Test 2: Create product with empty name let create_product_empty_name = CreateProduct::new("".to_string(), "description".to_string()); assert!(create_product_empty_name.is_err()); assert!(matches!(create_product_empty_name.unwrap_err(), domain::DomainError::InvalidInput(_))); // Test 3: Update user with empty username let update_user_empty_username = UpdateUser::new(Some("".to_string()), None); assert!(update_user_empty_username.is_err()); assert!(matches!(update_user_empty_username.unwrap_err(), domain::DomainError::InvalidInput(_))); // Test 4: Update product with empty name let update_product_empty_name = UpdateProduct::new(Some("".to_string()), None); assert!(update_product_empty_name.is_err()); assert!(matches!(update_product_empty_name.unwrap_err(), domain::DomainError::InvalidInput(_))); } #[tokio::test] #[serial] async fn test_concurrent_operations_consistency() { let pool = setup_test_db().await; // Create services let memory_user_repo = InMemoryUserRepository::new(); let postgres_user_repo = PostgresUserRepository::new(pool.clone()); let memory_user_service = Service::new(memory_user_repo); let postgres_user_service = Service::new(postgres_user_repo); let memory_product_repo = InMemoryProductRepository::new(); let postgres_product_repo = PostgresProductRepository::new(pool.clone()); let memory_product_service = Service::new(memory_product_repo); let postgres_product_service = Service::new(postgres_product_repo); // Test concurrent creation of multiple users and products let mut memory_user_handles = Vec::new(); let mut postgres_user_handles = Vec::new(); let mut memory_product_handles = Vec::new(); let mut postgres_product_handles = Vec::new(); // Spawn concurrent operations for i in 0..5 { let (username, email) = unique_test_data(&format!("concurrent_user_{}", i)); let create_user = CreateUser::new(username, email).unwrap(); let memory_service = memory_user_service.clone(); let postgres_service = postgres_user_service.clone(); let create_user_for_memory = create_user.clone(); let create_user_for_postgres = create_user; memory_user_handles.push(tokio::spawn(async move { memory_service.create(create_user_for_memory).await })); postgres_user_handles.push(tokio::spawn(async move { postgres_service.create(create_user_for_postgres).await })); let (name, _) = unique_test_data(&format!("concurrent_product_{}", i)); let create_product = CreateProduct::new(name, format!("Description {}", i)).unwrap(); let memory_service = memory_product_service.clone(); let postgres_service = postgres_product_service.clone(); let create_product_for_memory = create_product.clone(); let create_product_for_postgres = create_product; memory_product_handles.push(tokio::spawn(async move { memory_service.create(create_product_for_memory).await })); postgres_product_handles.push(tokio::spawn(async move { postgres_service.create(create_product_for_postgres).await })); } // Wait for all operations to complete for handle in memory_user_handles { handle.await.unwrap().unwrap(); } for handle in postgres_user_handles { handle.await.unwrap().unwrap(); } for handle in memory_product_handles { handle.await.unwrap().unwrap(); } for handle in postgres_product_handles { handle.await.unwrap().unwrap(); } // Verify both repositories have the same data let memory_users = memory_user_service.list().await.unwrap(); let postgres_users = postgres_user_service.list().await.unwrap(); let memory_products = memory_product_service.list().await.unwrap(); let postgres_products = postgres_product_service.list().await.unwrap(); assert_eq!(memory_users.len(), 5); assert_eq!(postgres_users.len(), 5); assert_eq!(memory_products.len(), 5); assert_eq!(postgres_products.len(), 5); compare_user_lists(&memory_users, &postgres_users); compare_product_lists(&memory_products, &postgres_products); } #[tokio::test] #[serial] async fn test_data_persistence_consistency() { let pool = setup_test_db().await; // Create services let memory_user_repo = InMemoryUserRepository::new(); let postgres_user_repo = PostgresUserRepository::new(pool.clone()); let memory_user_service = Service::new(memory_user_repo); let postgres_user_service = Service::new(postgres_user_repo); let memory_product_repo = InMemoryProductRepository::new(); let postgres_product_repo = PostgresProductRepository::new(pool.clone()); let memory_product_service = Service::new(memory_product_repo); let postgres_product_service = Service::new(postgres_product_repo); // Create test data let (username, email) = unique_test_data("persistence_user"); let (name, _) = unique_test_data("persistence_product"); let create_user = CreateUser::new(username.clone(), email.clone()).unwrap(); let create_product = CreateProduct::new(name.clone(), "Test description".to_string()).unwrap(); let memory_user = memory_user_service.create(create_user.clone()).await.unwrap(); let postgres_user = postgres_user_service.create(create_user).await.unwrap(); let memory_product = memory_product_service.create(create_product.clone()).await.unwrap(); let postgres_product = postgres_product_service.create(create_product).await.unwrap(); // Verify data was created assert_eq!(memory_user.username(), username); assert_eq!(postgres_user.username(), username); assert_eq!(memory_product.name(), name); assert_eq!(postgres_product.name(), name); // Test that data persists across service instances (for PostgreSQL) // Memory repository data is lost when service is dropped, but PostgreSQL persists let new_postgres_user_repo = PostgresUserRepository::new(pool.clone()); let new_postgres_product_repo = PostgresProductRepository::new(pool.clone()); let new_postgres_user_service = Service::new(new_postgres_user_repo); let new_postgres_product_service = Service::new(new_postgres_product_repo); let retrieved_user = new_postgres_user_service.get(postgres_user.id()).await.unwrap(); let retrieved_product = new_postgres_product_service.get(postgres_product.id()).await.unwrap(); assert_eq!(retrieved_user.username(), username); assert_eq!(retrieved_user.email(), email); assert_eq!(retrieved_product.name(), name); assert_eq!(retrieved_product.description(), "Test description"); // Memory repository should still have the data in the same service instance let memory_retrieved_user = memory_user_service.get(memory_user.id()).await.unwrap(); let memory_retrieved_product = memory_product_service.get(memory_product.id()).await.unwrap(); assert_eq!(memory_retrieved_user.username(), username); assert_eq!(memory_retrieved_user.email(), email); assert_eq!(memory_retrieved_product.name(), name); assert_eq!(memory_retrieved_product.description(), "Test description"); }