Add cross-interface consistency tests

This commit is contained in:
continuist 2025-06-25 22:43:29 -04:00
parent 223d560ca0
commit 4c751dd971
3 changed files with 617 additions and 4 deletions

View file

@ -0,0 +1,604 @@
/*
* 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 <continuist02@gmail.com>
*/
use application::{Service, UseCase};
use domain::{CreateProduct, CreateUser, Product, UpdateProduct, UpdateUser, User};
use memory::{InMemoryUserRepository, InMemoryProductRepository};
use postgres::{PostgresUserRepository, PostgresProductRepository};
use sqlx::PgPool;
use sqlx::postgres::PgPoolOptions;
use std::env;
use uuid::Uuid;
use serial_test::serial;
// Helper functions for test setup
async fn setup_test_db() -> PgPool {
// Explicitly set DATABASE_URL to use sharenet_test database
std::env::set_var("DATABASE_URL", "postgres://postgres:password@localhost:5432/sharenet_test");
let database_url = env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://postgres:password@localhost:5432/sharenet_test".to_string());
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await
.expect("Failed to connect to test database");
sqlx::migrate!("../../migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
cleanup_test_data(&pool).await;
pool
}
async fn cleanup_test_data(pool: &PgPool) {
let mut tx = pool.begin().await.expect("Failed to begin transaction");
sqlx::query("TRUNCATE TABLE products, users RESTART IDENTITY CASCADE").execute(&mut *tx).await.expect("Failed to truncate tables");
tx.commit().await.expect("Failed to commit cleanup transaction");
// Assert tables are empty
let users_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users").fetch_one(pool).await.expect("Failed to count users");
let products_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM products").fetch_one(pool).await.expect("Failed to count products");
assert_eq!(users_count.0, 0, "Users table not empty after cleanup");
assert_eq!(products_count.0, 0, "Products table not empty after cleanup");
}
fn unique_test_data(prefix: &str) -> (String, String) {
let id = Uuid::new_v4().to_string();
(format!("{}_{}", prefix, id), format!("{}_{}@example.com", prefix, id))
}
// 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(users1: &[User], users2: &[User]) {
let data1: Vec<UserData> = users1.iter().map(UserData::from).collect();
let data2: Vec<UserData> = users2.iter().map(UserData::from).collect();
assert_eq!(data1.len(), data2.len(), "User count mismatch");
// Sort by username for consistent comparison
let mut sorted1 = data1.clone();
let mut sorted2 = data2.clone();
sorted1.sort_by(|a, b| a.username.cmp(&b.username));
sorted2.sort_by(|a, b| a.username.cmp(&b.username));
assert_eq!(sorted1, sorted2, "User data mismatch");
}
// Helper function to compare product lists (ignoring IDs and timestamps)
fn compare_product_lists(products1: &[Product], products2: &[Product]) {
let data1: Vec<ProductData> = products1.iter().map(ProductData::from).collect();
let data2: Vec<ProductData> = products2.iter().map(ProductData::from).collect();
assert_eq!(data1.len(), data2.len(), "Product count mismatch");
// Sort by name for consistent comparison
let mut sorted1 = data1.clone();
let mut sorted2 = data2.clone();
sorted1.sort_by(|a, b| a.name.cmp(&b.name));
sorted2.sort_by(|a, b| a.name.cmp(&b.name));
assert_eq!(sorted1, sorted2, "Product data mismatch");
}
// Helper function to create services for different interfaces
fn create_memory_services() -> (Service<User, InMemoryUserRepository>, Service<Product, InMemoryProductRepository>) {
let user_repo = InMemoryUserRepository::new();
let product_repo = InMemoryProductRepository::new();
let user_service = Service::new(user_repo);
let product_service = Service::new(product_repo);
(user_service, product_service)
}
fn create_postgres_services(pool: PgPool) -> (Service<User, PostgresUserRepository>, Service<Product, PostgresProductRepository>) {
let user_repo = PostgresUserRepository::new(pool.clone());
let product_repo = PostgresProductRepository::new(pool);
let user_service = Service::new(user_repo);
let product_service = Service::new(product_repo);
(user_service, product_service)
}
#[tokio::test]
#[serial]
async fn test_api_cli_consistency_with_memory() {
// Create services for both interfaces using memory backend
let (api_user_service, api_product_service) = create_memory_services();
let (cli_user_service, cli_product_service) = create_memory_services();
// Test data
let (username1, email1) = unique_test_data("api_cli_user1");
let (username2, email2) = unique_test_data("api_cli_user2");
let (name1, _) = unique_test_data("api_cli_product1");
let (name2, _) = unique_test_data("api_cli_product2");
// Test 1: Create users via API interface and capture state
let create_user1 = CreateUser::new(username1.clone(), email1.clone()).unwrap();
let create_user2 = CreateUser::new(username2.clone(), email2.clone()).unwrap();
let api_user1 = api_user_service.create(create_user1.clone()).await.unwrap();
let _api_user2 = api_user_service.create(create_user2.clone()).await.unwrap();
let api_users_after_create = api_user_service.list().await.unwrap();
assert_eq!(api_users_after_create.len(), 2);
// Test 2: Create same users via CLI interface and compare
let cli_user1 = cli_user_service.create(create_user1).await.unwrap();
let _cli_user2 = cli_user_service.create(create_user2).await.unwrap();
let cli_users_after_create = cli_user_service.list().await.unwrap();
assert_eq!(cli_users_after_create.len(), 2);
// Compare user data (ignoring IDs and timestamps)
compare_user_lists(&api_users_after_create, &cli_users_after_create);
// Test 3: Create products via API interface and capture state
let create_product1 = CreateProduct::new(name1.clone(), "Description 1".to_string()).unwrap();
let create_product2 = CreateProduct::new(name2.clone(), "Description 2".to_string()).unwrap();
let api_product1 = api_product_service.create(create_product1.clone()).await.unwrap();
let _api_product2 = api_product_service.create(create_product2.clone()).await.unwrap();
let api_products_after_create = api_product_service.list().await.unwrap();
assert_eq!(api_products_after_create.len(), 2);
// Test 4: Create same products via CLI interface and compare
let cli_product1 = cli_product_service.create(create_product1).await.unwrap();
let _cli_product2 = cli_product_service.create(create_product2).await.unwrap();
let cli_products_after_create = cli_product_service.list().await.unwrap();
assert_eq!(cli_products_after_create.len(), 2);
// Compare product data (ignoring IDs and timestamps)
compare_product_lists(&api_products_after_create, &cli_products_after_create);
// Test 5: Test that both interfaces can access the same data
let api_retrieved_user = api_user_service.get(api_user1.id()).await.unwrap();
let cli_retrieved_user = cli_user_service.get(cli_user1.id()).await.unwrap();
assert_eq!(api_retrieved_user.username(), cli_retrieved_user.username());
assert_eq!(api_retrieved_user.email(), cli_retrieved_user.email());
let api_retrieved_product = api_product_service.get(api_product1.id()).await.unwrap();
let cli_retrieved_product = cli_product_service.get(cli_product1.id()).await.unwrap();
assert_eq!(api_retrieved_product.name(), cli_retrieved_product.name());
assert_eq!(api_retrieved_product.description(), cli_retrieved_product.description());
// Test 6: Test update consistency - update same user via both interfaces
let update_user_data = UpdateUser::new(Some("updated_username".to_string()), Some("updated@example.com".to_string())).unwrap();
let api_updated_user = api_user_service.update(api_user1.id(), update_user_data.clone()).await.unwrap();
let cli_updated_user = cli_user_service.update(cli_user1.id(), update_user_data).await.unwrap();
assert_eq!(api_updated_user.username(), cli_updated_user.username());
assert_eq!(api_updated_user.email(), cli_updated_user.email());
// Test 7: Test delete consistency
api_user_service.delete(api_user1.id()).await.unwrap();
cli_user_service.delete(cli_user1.id()).await.unwrap();
let api_users_after_delete = api_user_service.list().await.unwrap();
let cli_users_after_delete = cli_user_service.list().await.unwrap();
assert_eq!(api_users_after_delete.len(), 1);
assert_eq!(cli_users_after_delete.len(), 1);
compare_user_lists(&api_users_after_delete, &cli_users_after_delete);
}
#[tokio::test]
#[serial]
async fn test_api_tui_consistency_with_memory() {
// Create services for both interfaces using memory backend
let (api_user_service, api_product_service) = create_memory_services();
let (tui_user_service, tui_product_service) = create_memory_services();
// Test data
let (username1, email1) = unique_test_data("api_tui_user1");
let (username2, email2) = unique_test_data("api_tui_user2");
let (name1, _) = unique_test_data("api_tui_product1");
let (name2, _) = unique_test_data("api_tui_product2");
// Test 1: Create users via API interface and capture state
let create_user1 = CreateUser::new(username1.clone(), email1.clone()).unwrap();
let create_user2 = CreateUser::new(username2.clone(), email2.clone()).unwrap();
let api_user1 = api_user_service.create(create_user1.clone()).await.unwrap();
let _api_user2 = api_user_service.create(create_user2.clone()).await.unwrap();
let api_users_after_create = api_user_service.list().await.unwrap();
assert_eq!(api_users_after_create.len(), 2);
// Test 2: Create same users via TUI interface and compare
let tui_user1 = tui_user_service.create(create_user1).await.unwrap();
let _tui_user2 = tui_user_service.create(create_user2).await.unwrap();
let tui_users_after_create = tui_user_service.list().await.unwrap();
assert_eq!(tui_users_after_create.len(), 2);
// Compare user data (ignoring IDs and timestamps)
compare_user_lists(&api_users_after_create, &tui_users_after_create);
// Test 3: Create products via API interface and capture state
let create_product1 = CreateProduct::new(name1.clone(), "Description 1".to_string()).unwrap();
let create_product2 = CreateProduct::new(name2.clone(), "Description 2".to_string()).unwrap();
let api_product1 = api_product_service.create(create_product1.clone()).await.unwrap();
let _api_product2 = api_product_service.create(create_product2.clone()).await.unwrap();
let api_products_after_create = api_product_service.list().await.unwrap();
assert_eq!(api_products_after_create.len(), 2);
// Test 4: Create same products via TUI interface and compare
let tui_product1 = tui_product_service.create(create_product1).await.unwrap();
let _tui_product2 = tui_product_service.create(create_product2).await.unwrap();
let tui_products_after_create = tui_product_service.list().await.unwrap();
assert_eq!(tui_products_after_create.len(), 2);
// Compare product data (ignoring IDs and timestamps)
compare_product_lists(&api_products_after_create, &tui_products_after_create);
// Test 5: Test that both interfaces can access the same data
let api_retrieved_user = api_user_service.get(api_user1.id()).await.unwrap();
let tui_retrieved_user = tui_user_service.get(tui_user1.id()).await.unwrap();
assert_eq!(api_retrieved_user.username(), tui_retrieved_user.username());
assert_eq!(api_retrieved_user.email(), tui_retrieved_user.email());
let api_retrieved_product = api_product_service.get(api_product1.id()).await.unwrap();
let tui_retrieved_product = tui_product_service.get(tui_product1.id()).await.unwrap();
assert_eq!(api_retrieved_product.name(), tui_retrieved_product.name());
assert_eq!(api_retrieved_product.description(), tui_retrieved_product.description());
// Test 6: Test update consistency - update same user via both interfaces
let update_user_data = UpdateUser::new(Some("updated_username".to_string()), Some("updated@example.com".to_string())).unwrap();
let api_updated_user = api_user_service.update(api_user1.id(), update_user_data.clone()).await.unwrap();
let tui_updated_user = tui_user_service.update(tui_user1.id(), update_user_data).await.unwrap();
assert_eq!(api_updated_user.username(), tui_updated_user.username());
assert_eq!(api_updated_user.email(), tui_updated_user.email());
// Test 7: Test delete consistency
api_user_service.delete(api_user1.id()).await.unwrap();
tui_user_service.delete(tui_user1.id()).await.unwrap();
let api_users_after_delete = api_user_service.list().await.unwrap();
let tui_users_after_delete = tui_user_service.list().await.unwrap();
assert_eq!(api_users_after_delete.len(), 1);
assert_eq!(tui_users_after_delete.len(), 1);
compare_user_lists(&api_users_after_delete, &tui_users_after_delete);
}
#[tokio::test]
#[serial]
async fn test_cli_tui_consistency_with_memory() {
// Create services for both interfaces using memory backend
let (cli_user_service, cli_product_service) = create_memory_services();
let (tui_user_service, tui_product_service) = create_memory_services();
// Test data
let (username1, email1) = unique_test_data("cli_tui_user1");
let (username2, email2) = unique_test_data("cli_tui_user2");
let (name1, _) = unique_test_data("cli_tui_product1");
let (name2, _) = unique_test_data("cli_tui_product2");
// Test 1: Create users via CLI interface and capture state
let create_user1 = CreateUser::new(username1.clone(), email1.clone()).unwrap();
let create_user2 = CreateUser::new(username2.clone(), email2.clone()).unwrap();
let cli_user1 = cli_user_service.create(create_user1.clone()).await.unwrap();
let _cli_user2 = cli_user_service.create(create_user2.clone()).await.unwrap();
let cli_users_after_create = cli_user_service.list().await.unwrap();
assert_eq!(cli_users_after_create.len(), 2);
// Test 2: Create same users via TUI interface and compare
let tui_user1 = tui_user_service.create(create_user1).await.unwrap();
let _tui_user2 = tui_user_service.create(create_user2).await.unwrap();
let tui_users_after_create = tui_user_service.list().await.unwrap();
assert_eq!(tui_users_after_create.len(), 2);
// Compare user data (ignoring IDs and timestamps)
compare_user_lists(&cli_users_after_create, &tui_users_after_create);
// Test 3: Create products via CLI interface and capture state
let create_product1 = CreateProduct::new(name1.clone(), "Description 1".to_string()).unwrap();
let create_product2 = CreateProduct::new(name2.clone(), "Description 2".to_string()).unwrap();
let cli_product1 = cli_product_service.create(create_product1.clone()).await.unwrap();
let _cli_product2 = cli_product_service.create(create_product2.clone()).await.unwrap();
let cli_products_after_create = cli_product_service.list().await.unwrap();
assert_eq!(cli_products_after_create.len(), 2);
// Test 4: Create same products via TUI interface and compare
let tui_product1 = tui_product_service.create(create_product1).await.unwrap();
let _tui_product2 = tui_product_service.create(create_product2).await.unwrap();
let tui_products_after_create = tui_product_service.list().await.unwrap();
assert_eq!(tui_products_after_create.len(), 2);
// Compare product data (ignoring IDs and timestamps)
compare_product_lists(&cli_products_after_create, &tui_products_after_create);
// Test 5: Test that both interfaces can access the same data
let cli_retrieved_user = cli_user_service.get(cli_user1.id()).await.unwrap();
let tui_retrieved_user = tui_user_service.get(tui_user1.id()).await.unwrap();
assert_eq!(cli_retrieved_user.username(), tui_retrieved_user.username());
assert_eq!(cli_retrieved_user.email(), tui_retrieved_user.email());
let cli_retrieved_product = cli_product_service.get(cli_product1.id()).await.unwrap();
let tui_retrieved_product = tui_product_service.get(tui_product1.id()).await.unwrap();
assert_eq!(cli_retrieved_product.name(), tui_retrieved_product.name());
assert_eq!(cli_retrieved_product.description(), tui_retrieved_product.description());
// Test 6: Test update consistency - update same user via both interfaces
let update_user_data = UpdateUser::new(Some("updated_username".to_string()), Some("updated@example.com".to_string())).unwrap();
let cli_updated_user = cli_user_service.update(cli_user1.id(), update_user_data.clone()).await.unwrap();
let tui_updated_user = tui_user_service.update(tui_user1.id(), update_user_data).await.unwrap();
assert_eq!(cli_updated_user.username(), tui_updated_user.username());
assert_eq!(cli_updated_user.email(), tui_updated_user.email());
// Test 7: Test delete consistency
cli_user_service.delete(cli_user1.id()).await.unwrap();
tui_user_service.delete(tui_user1.id()).await.unwrap();
let cli_users_after_delete = cli_user_service.list().await.unwrap();
let tui_users_after_delete = tui_user_service.list().await.unwrap();
assert_eq!(cli_users_after_delete.len(), 1);
assert_eq!(tui_users_after_delete.len(), 1);
compare_user_lists(&cli_users_after_delete, &tui_users_after_delete);
}
#[tokio::test]
#[serial]
async fn test_api_cli_consistency_with_postgres() {
let pool = setup_test_db().await;
cleanup_test_data(&pool).await;
// Double-check that the database is empty
let users_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users").fetch_one(&pool).await.expect("Failed to count users");
let products_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM products").fetch_one(&pool).await.expect("Failed to count products");
println!("Database state before test: {} users, {} products", users_count.0, products_count.0);
assert_eq!(users_count.0, 0, "Users table not empty before test");
assert_eq!(products_count.0, 0, "Products table not empty before test");
// Test data
let (username1, email1) = unique_test_data("api_cli_user1");
let (username2, email2) = unique_test_data("api_cli_user2");
let (name1, _) = unique_test_data("api_cli_product1");
let (name2, _) = unique_test_data("api_cli_product2");
println!("Generated usernames: {}, {}", username1, username2);
// Test 1: Create users via API interface and capture state
let (api_user_service, _api_product_service) = create_postgres_services(pool.clone());
let create_user1 = CreateUser::new(username1.clone(), email1.clone()).unwrap();
let create_user2 = CreateUser::new(username2.clone(), email2.clone()).unwrap();
println!("Creating API user 1: {}", username1);
let _api_user1 = api_user_service.create(create_user1.clone()).await.unwrap();
println!("Creating API user 2: {}", username2);
let _api_user2 = api_user_service.create(create_user2.clone()).await.unwrap();
let api_users_after_create = api_user_service.list().await.unwrap();
assert_eq!(api_users_after_create.len(), 2);
// Test 2: Create same users via CLI interface and compare
cleanup_test_data(&pool).await;
let (cli_user_service, _cli_product_service) = create_postgres_services(pool.clone());
println!("Creating CLI user 1: {}", username1);
let _cli_user1 = cli_user_service.create(create_user1).await.unwrap();
println!("Creating CLI user 2: {}", username2);
let _cli_user2 = cli_user_service.create(create_user2).await.unwrap();
let cli_users_after_create = cli_user_service.list().await.unwrap();
assert_eq!(cli_users_after_create.len(), 2);
// Compare user data (ignoring IDs and timestamps)
compare_user_lists(&api_users_after_create, &cli_users_after_create);
// Test 3: Create products via API interface and capture state
cleanup_test_data(&pool).await;
let (_api_user_service, api_product_service) = create_postgres_services(pool.clone());
let create_product1 = CreateProduct::new(name1.clone(), "Description 1".to_string()).unwrap();
let create_product2 = CreateProduct::new(name2.clone(), "Description 2".to_string()).unwrap();
let _api_product1 = api_product_service.create(create_product1.clone()).await.unwrap();
let _api_product2 = api_product_service.create(create_product2.clone()).await.unwrap();
let api_products_after_create = api_product_service.list().await.unwrap();
assert_eq!(api_products_after_create.len(), 2);
// Test 4: Create same products via CLI interface and compare
cleanup_test_data(&pool).await;
let (_cli_user_service, cli_product_service) = create_postgres_services(pool.clone());
let _cli_product1 = cli_product_service.create(create_product1).await.unwrap();
let _cli_product2 = cli_product_service.create(create_product2).await.unwrap();
let cli_products_after_create = cli_product_service.list().await.unwrap();
assert_eq!(cli_products_after_create.len(), 2);
// Compare product data (ignoring IDs and timestamps)
compare_product_lists(&api_products_after_create, &cli_products_after_create);
// Test 5: Test that both interfaces can access the same data (using shared database)
cleanup_test_data(&pool).await;
let (api_user_service, _api_product_service) = create_postgres_services(pool.clone());
let (cli_user_service, _cli_product_service) = create_postgres_services(pool);
// Create a user via API
let create_user = CreateUser::new(username1.clone(), email1.clone()).unwrap();
let api_user = api_user_service.create(create_user).await.unwrap();
// Both services should be able to access the same user
let api_retrieved_user = api_user_service.get(api_user.id()).await.unwrap();
let cli_retrieved_user = cli_user_service.get(api_user.id()).await.unwrap();
assert_eq!(api_retrieved_user.username(), cli_retrieved_user.username());
assert_eq!(api_retrieved_user.email(), cli_retrieved_user.email());
// Test 6: Test update consistency - update same user via both interfaces
let update_user_data = UpdateUser::new(Some("updated_username".to_string()), Some("updated@example.com".to_string())).unwrap();
let api_updated_user = api_user_service.update(api_user.id(), update_user_data.clone()).await.unwrap();
let cli_updated_user = cli_user_service.update(api_user.id(), update_user_data).await.unwrap();
assert_eq!(api_updated_user.username(), cli_updated_user.username());
assert_eq!(api_updated_user.email(), cli_updated_user.email());
// Test 7: Test delete consistency
api_user_service.delete(api_user.id()).await.unwrap();
// Both services should see the user as deleted
let api_users_after_delete = api_user_service.list().await.unwrap();
let cli_users_after_delete = cli_user_service.list().await.unwrap();
assert_eq!(api_users_after_delete.len(), 0);
assert_eq!(cli_users_after_delete.len(), 0);
}
#[tokio::test]
#[serial]
async fn test_error_handling_consistency_across_interfaces() {
let pool = setup_test_db().await;
// Create services for all interfaces using PostgreSQL backend
let (api_user_service, api_product_service) = create_postgres_services(pool.clone());
let (cli_user_service, cli_product_service) = create_postgres_services(pool.clone());
let (tui_user_service, tui_product_service) = create_postgres_services(pool);
// Test 1: Get non-existent user via all interfaces
let non_existent_id = Uuid::new_v4();
let api_user_result = api_user_service.get(non_existent_id).await;
let cli_user_result = cli_user_service.get(non_existent_id).await;
let tui_user_result = tui_user_service.get(non_existent_id).await;
assert!(api_user_result.is_err());
assert!(cli_user_result.is_err());
assert!(tui_user_result.is_err());
// All should return the same error type
assert!(matches!(api_user_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
assert!(matches!(cli_user_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
assert!(matches!(tui_user_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
// Test 2: Get non-existent product via all interfaces
let api_product_result = api_product_service.get(non_existent_id).await;
let cli_product_result = cli_product_service.get(non_existent_id).await;
let tui_product_result = tui_product_service.get(non_existent_id).await;
assert!(api_product_result.is_err());
assert!(cli_product_result.is_err());
assert!(tui_product_result.is_err());
assert!(matches!(api_product_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
assert!(matches!(cli_product_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
assert!(matches!(tui_product_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
// Test 3: Update non-existent user via all interfaces
let update_user_data = UpdateUser::new(Some("new_username".to_string()), None).unwrap();
let api_update_result = api_user_service.update(non_existent_id, update_user_data.clone()).await;
let cli_update_result = cli_user_service.update(non_existent_id, update_user_data.clone()).await;
let tui_update_result = tui_user_service.update(non_existent_id, update_user_data).await;
assert!(api_update_result.is_err());
assert!(cli_update_result.is_err());
assert!(tui_update_result.is_err());
assert!(matches!(api_update_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
assert!(matches!(cli_update_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
assert!(matches!(tui_update_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
// Test 4: Delete non-existent user via all interfaces
let api_delete_result = api_user_service.delete(non_existent_id).await;
let cli_delete_result = cli_user_service.delete(non_existent_id).await;
let tui_delete_result = tui_user_service.delete(non_existent_id).await;
assert!(api_delete_result.is_err());
assert!(cli_delete_result.is_err());
assert!(tui_delete_result.is_err());
assert!(matches!(api_delete_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
assert!(matches!(cli_delete_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
assert!(matches!(tui_delete_result.unwrap_err(), application::ApplicationError::Domain(domain::DomainError::NotFound(_))));
}
#[tokio::test]
#[serial]
async fn test_validation_consistency_across_interfaces() {
// Test that all interfaces validate input consistently
// This test doesn't need actual services since validation happens at the domain level
// 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(_)));
}

View file

@ -21,6 +21,9 @@ use serial_test::serial;
// Helper functions for test setup // Helper functions for test setup
async fn setup_test_db() -> PgPool { async fn setup_test_db() -> PgPool {
// Explicitly set DATABASE_URL to use sharenet_test database
std::env::set_var("DATABASE_URL", "postgres://postgres:password@localhost:5432/sharenet_test");
let database_url = env::var("DATABASE_URL") let database_url = env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://postgres:password@localhost:5432/sharenet_test".to_string()); .unwrap_or_else(|_| "postgres://postgres:password@localhost:5432/sharenet_test".to_string());
let pool = PgPoolOptions::new() let pool = PgPoolOptions::new()
@ -38,14 +41,18 @@ async fn setup_test_db() -> PgPool {
async fn cleanup_test_data(pool: &PgPool) { async fn cleanup_test_data(pool: &PgPool) {
let mut tx = pool.begin().await.expect("Failed to begin transaction"); let mut tx = pool.begin().await.expect("Failed to begin transaction");
sqlx::query("DELETE FROM products").execute(&mut *tx).await.expect("Failed to delete products"); sqlx::query("TRUNCATE TABLE products, users RESTART IDENTITY CASCADE").execute(&mut *tx).await.expect("Failed to truncate tables");
sqlx::query("DELETE FROM users").execute(&mut *tx).await.expect("Failed to delete users");
tx.commit().await.expect("Failed to commit cleanup transaction"); tx.commit().await.expect("Failed to commit cleanup transaction");
// Assert tables are empty
let users_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users").fetch_one(pool).await.expect("Failed to count users");
let products_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM products").fetch_one(pool).await.expect("Failed to count products");
assert_eq!(users_count.0, 0, "Users table not empty after cleanup");
assert_eq!(products_count.0, 0, "Products table not empty after cleanup");
} }
fn unique_test_data(prefix: &str) -> (String, String) { fn unique_test_data(prefix: &str) -> (String, String) {
let id = Uuid::new_v4().to_string()[..8].to_string(); let id = Uuid::new_v4().to_string();
(format!("{}_{}", prefix, id), format!("{}_test@example.com", prefix)) (format!("{}_{}", prefix, id), format!("{}_{}@example.com", prefix, id))
} }
// Test data structures for comparison // Test data structures for comparison

View file

@ -16,6 +16,8 @@ pub mod cli_tests;
#[cfg(test)] #[cfg(test)]
pub mod cross_repository_consistency_tests; pub mod cross_repository_consistency_tests;
#[cfg(test)] #[cfg(test)]
pub mod cross_interface_consistency_tests;
#[cfg(test)]
pub mod migration_tests; pub mod migration_tests;
#[cfg(test)] #[cfg(test)]
pub mod performance_tests; pub mod performance_tests;