/* * 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 anyhow::Result; use application::Service; use domain::{CreateProduct, CreateUser, Product, User, UpdateProduct, UpdateUser}; use memory::{InMemoryUserRepository, InMemoryProductRepository}; use postgres::{PostgresUserRepository, PostgresProductRepository}; use sqlx::PgPool; use sqlx::postgres::PgPoolOptions; use std::env; use std::sync::Arc; use std::collections::HashMap; use tokio::sync::RwLock; use tui::App; use uuid::Uuid; use serial_test::serial; use application::UseCase; // Helper functions for test setup async fn setup_test_db() -> PgPool { 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("DELETE FROM products").execute(&mut *tx).await.expect("Failed to delete products"); sqlx::query("DELETE FROM users").execute(&mut *tx).await.expect("Failed to delete users"); tx.commit().await.expect("Failed to commit cleanup transaction"); } fn unique_test_data(prefix: &str) -> (String, String) { let id = Uuid::new_v4().to_string()[..8].to_string(); (format!("{}_{}", prefix, id), format!("{}_test@example.com", prefix)) } // Mock services for testing TUI without real repositories #[derive(Clone)] struct MockUserService { users: Arc>>, } impl MockUserService { fn new() -> Self { Self { users: Arc::new(RwLock::new(HashMap::new())), } } } impl UseCase for MockUserService { fn create(&self, data: CreateUser) -> impl std::future::Future> + Send { let users = self.users.clone(); async move { let mut guard = users.write().await; let id = Uuid::new_v4(); let user = User::new(id, data.username().to_string(), data.email().to_string()) .map_err(|e| application::ApplicationError::Domain(e))?; guard.insert(id, user.clone()); Ok(user) } } fn get(&self, id: Uuid) -> impl std::future::Future> + Send { let users = self.users.clone(); async move { let guard = users.read().await; guard.get(&id) .cloned() .ok_or_else(|| application::ApplicationError::Domain(domain::DomainError::NotFound(format!("User not found: {}", id)))) } } fn list(&self) -> impl std::future::Future, application::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> + Send { let users = self.users.clone(); async move { let mut guard = users.write().await; let user = guard.get_mut(&id) .ok_or_else(|| application::ApplicationError::Domain(domain::DomainError::NotFound(format!("User not found: {}", id))))?; if let Some(username) = data.username() { user.set_username(username.to_string()) .map_err(|e| application::ApplicationError::Domain(e))?; } if let Some(email) = data.email() { user.set_email(email.to_string()) .map_err(|e| application::ApplicationError::Domain(e))?; } Ok(user.clone()) } } fn delete(&self, id: Uuid) -> impl std::future::Future> + Send { let users = self.users.clone(); async move { let mut guard = users.write().await; guard.remove(&id) .ok_or_else(|| application::ApplicationError::Domain(domain::DomainError::NotFound(format!("User not found: {}", id))))?; Ok(()) } } } #[derive(Clone)] struct MockProductService { products: Arc>>, } impl MockProductService { fn new() -> Self { Self { products: Arc::new(RwLock::new(HashMap::new())), } } } impl UseCase for MockProductService { fn create(&self, data: CreateProduct) -> impl std::future::Future> + Send { let products = self.products.clone(); async move { let mut guard = products.write().await; let id = Uuid::new_v4(); let product = Product::new(id, data.name().to_string(), data.description().to_string()) .map_err(|e| application::ApplicationError::Domain(e))?; guard.insert(id, product.clone()); Ok(product) } } fn get(&self, id: Uuid) -> impl std::future::Future> + Send { let products = self.products.clone(); async move { let guard = products.read().await; guard.get(&id) .cloned() .ok_or_else(|| application::ApplicationError::Domain(domain::DomainError::NotFound(format!("Product not found: {}", id)))) } } fn list(&self) -> impl std::future::Future, application::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> + Send { let products = self.products.clone(); async move { let mut guard = products.write().await; let product = guard.get_mut(&id) .ok_or_else(|| application::ApplicationError::Domain(domain::DomainError::NotFound(format!("Product not found: {}", id))))?; if let Some(name) = data.name() { product.set_name(name.to_string()) .map_err(|e| application::ApplicationError::Domain(e))?; } if let Some(description) = data.description() { product.set_description(description.to_string()) .map_err(|e| application::ApplicationError::Domain(e))?; } Ok(product.clone()) } } fn delete(&self, id: Uuid) -> impl std::future::Future> + Send { let products = self.products.clone(); async move { let mut guard = products.write().await; guard.remove(&id) .ok_or_else(|| application::ApplicationError::Domain(domain::DomainError::NotFound(format!("Product not found: {}", id))))?; Ok(()) } } } // Helper function to simulate TUI command execution async fn execute_tui_command( app: &mut App, command: &str, user_service: &U, product_service: &P, ) where U: UseCase, P: UseCase, { // Simulate the command processing logic from the TUI app.add_message(format!("> {}", command)); match command.trim() { "exit" => { app.add_message("Goodbye!".to_string()); // Note: In the real TUI, this would set should_quit to true // For testing purposes, we just add the message } "help" => { print_help(app); } cmd if cmd.starts_with("user create") => { match parse_user_create(cmd) { Ok((username, email)) => { match CreateUser::new(username, email) { Ok(create_user) => { match user_service.create(create_user).await { Ok(user) => app.add_message(format!("Created user: {:?}", user)), Err(e) => app.add_message(format!("Error: {}", e)), } } Err(e) => app.add_message(format!("Error: {}", e)), } } Err(e) => app.add_message(format!("Error: {}", e)), } } "user list" => { match user_service.list().await { Ok(users) => app.add_message(format!("Users: {:?}", users)), Err(e) => app.add_message(format!("Error: {}", e)), } } cmd if cmd.starts_with("product create") => { match parse_product_create(cmd) { Ok((name, description)) => { match CreateProduct::new(name, description) { Ok(create_product) => { match product_service.create(create_product).await { Ok(product) => app.add_message(format!("Created product: {:?}", product)), Err(e) => app.add_message(format!("Error: {}", e)), } } Err(e) => app.add_message(format!("Error: {}", e)), } } Err(e) => app.add_message(format!("Error: {}", e)), } } "product list" => { match product_service.list().await { Ok(products) => app.add_message(format!("Products: {:?}", products)), Err(e) => app.add_message(format!("Error: {}", e)), } } "" => {} _ => { app.add_message("Unknown command. Type 'help' for available commands.".to_string()); } } } fn print_help(app: &mut App) { app.add_message("\nAvailable commands:".to_string()); app.add_message(" user create -u -e ".to_string()); app.add_message(" Example: user create -u \"john doe\" -e \"john@example.com\"".to_string()); app.add_message(" user list".to_string()); app.add_message(" product create -n -d ".to_string()); app.add_message(" Example: product create -n \"My Product\" -d \"A great product description\"".to_string()); app.add_message(" product list".to_string()); app.add_message("\nTips:".to_string()); app.add_message(" - Use quotes for values with spaces".to_string()); app.add_message(" - Use Up/Down arrows to navigate command history".to_string()); app.add_message(" - Press Esc to exit".to_string()); app.add_message(" - Type 'help' to show this message".to_string()); } fn parse_user_create(cmd: &str) -> anyhow::Result<(String, String)> { let parts: Vec<&str> = cmd.split_whitespace().collect(); if parts.len() < 6 { return Err(anyhow::anyhow!( "Invalid command format. Use: user create -u -e \nExample: user create -u \"john doe\" -e \"john@example.com\"" )); } let mut username = None; let mut email = None; let mut current_arg = None; let mut current_value = Vec::new(); // Skip "user create" command let mut i = 2; while i < parts.len() { match parts[i] { "-u" => { if let Some(arg_type) = current_arg { match arg_type { "username" => username = Some(current_value.join(" ")), "email" => email = Some(current_value.join(" ")), _ => {} } } current_arg = Some("username"); current_value.clear(); i += 1; } "-e" => { if let Some(arg_type) = current_arg { match arg_type { "username" => username = Some(current_value.join(" ")), "email" => email = Some(current_value.join(" ")), _ => {} } } current_arg = Some("email"); current_value.clear(); i += 1; } _ => { if current_arg.is_some() { current_value.push(parts[i].trim_matches('"')); } i += 1; } } } // Handle the last argument if let Some(arg_type) = current_arg { match arg_type { "username" => username = Some(current_value.join(" ")), "email" => email = Some(current_value.join(" ")), _ => {} } } match (username, email) { (Some(u), Some(e)) if !u.is_empty() && !e.is_empty() => Ok((u, e)), _ => Err(anyhow::anyhow!( "Invalid command format. Use: user create -u -e \nExample: user create -u \"john doe\" -e \"john@example.com\"" )), } } fn parse_product_create(cmd: &str) -> anyhow::Result<(String, String)> { let parts: Vec<&str> = cmd.split_whitespace().collect(); if parts.len() < 6 { return Err(anyhow::anyhow!( "Invalid command format. Use: product create -n -d \nExample: product create -n \"My Product\" -d \"A great product description\"" )); } let mut name = None; let mut description = None; let mut current_arg = None; let mut current_value = Vec::new(); // Skip "product create" command let mut i = 2; while i < parts.len() { match parts[i] { "-n" => { if let Some(arg_type) = current_arg { match arg_type { "name" => name = Some(current_value.join(" ")), "description" => description = Some(current_value.join(" ")), _ => {} } } current_arg = Some("name"); current_value.clear(); i += 1; } "-d" => { if let Some(arg_type) = current_arg { match arg_type { "name" => name = Some(current_value.join(" ")), "description" => description = Some(current_value.join(" ")), _ => {} } } current_arg = Some("description"); current_value.clear(); i += 1; } _ => { if current_arg.is_some() { current_value.push(parts[i].trim_matches('"')); } i += 1; } } } // Handle the last argument if let Some(arg_type) = current_arg { match arg_type { "name" => name = Some(current_value.join(" ")), "description" => description = Some(current_value.join(" ")), _ => {} } } match (name, description) { (Some(n), Some(d)) if !n.is_empty() && !d.is_empty() => Ok((n, d)), _ => Err(anyhow::anyhow!( "Invalid command format. Use: product create -n -d \nExample: product create -n \"My Product\" -d \"A great product description\"" )), } } #[cfg(test)] mod tests { use super::*; mod app_initialization { use super::*; #[test] fn test_app_initialization() { let app = App::new(); assert_eq!(app.input(), ""); assert_eq!(app.messages(), &vec!["Welcome to Sharenet CLI!".to_string()]); assert!(!app.should_quit()); } #[test] fn test_app_message_handling() { let mut app = App::new(); app.add_message("Test message".to_string()); assert!(app.messages().contains(&"Test message".to_string())); } #[test] fn test_app_input_handling() { let mut app = App::new(); app.insert_char('a'); app.insert_char('b'); app.insert_char('c'); assert_eq!(app.input(), "abc"); } #[test] fn test_app_cursor_movement() { let mut app = App::new(); // Add some input first app.insert_char('a'); app.insert_char('b'); app.insert_char('c'); // Test cursor movement app.move_cursor(-1); app.move_cursor(-1); app.move_cursor(-1); // We can't directly test cursor position, but we can test that it doesn't panic } #[test] fn test_app_history() { let mut app = App::new(); app.add_to_history("command1".to_string()); app.add_to_history("command2".to_string()); app.add_to_history("command3".to_string()); // We can't directly test history navigation without private field access // but we can test that adding to history doesn't panic } } mod command_parsing { use super::*; #[test] fn test_parse_user_create_valid() { let cmd = "user create -u alice -e alice@example.com"; let result = parse_user_create(cmd); assert!(result.is_ok()); let (username, email) = result.unwrap(); assert_eq!(username, "alice"); assert_eq!(email, "alice@example.com"); } #[test] fn test_parse_user_create_with_spaces() { let cmd = "user create -u \"john doe\" -e \"john@example.com\""; let result = parse_user_create(cmd); assert!(result.is_ok()); let (username, email) = result.unwrap(); assert_eq!(username, "john doe"); assert_eq!(email, "john@example.com"); } #[test] fn test_parse_user_create_invalid_format() { let cmd = "user create -u alice"; let result = parse_user_create(cmd); assert!(result.is_err()); } #[test] fn test_parse_user_create_empty_values() { let cmd = "user create -u \"\" -e \"\""; let result = parse_user_create(cmd); assert!(result.is_err()); } #[test] fn test_parse_product_create_valid() { let cmd = "product create -n widget -d description"; let result = parse_product_create(cmd); assert!(result.is_ok()); let (name, description) = result.unwrap(); assert_eq!(name, "widget"); assert_eq!(description, "description"); } #[test] fn test_parse_product_create_with_spaces() { let cmd = "product create -n \"My Product\" -d \"A great product description\""; let result = parse_product_create(cmd); assert!(result.is_ok()); let (name, description) = result.unwrap(); assert_eq!(name, "My Product"); assert_eq!(description, "A great product description"); } #[test] fn test_parse_product_create_invalid_format() { let cmd = "product create -n widget"; let result = parse_product_create(cmd); assert!(result.is_err()); } #[test] fn test_parse_product_create_empty_values() { let cmd = "product create -n \"\" -d \"\""; let result = parse_product_create(cmd); assert!(result.is_err()); } } mod tui_integration_with_mock_services { use super::*; #[tokio::test] async fn test_tui_user_create_command() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); let (username, email) = unique_test_data("tui_user"); let command = format!("user create -u {} -e {}", username, email); execute_tui_command(&mut app, &command, &user_service, &product_service).await; // Check that the command was displayed assert!(app.messages().iter().any(|msg| msg.contains(&format!("> {}", command)))); // Check that a user was created successfully let users = user_service.list().await.unwrap(); assert_eq!(users.len(), 1); assert_eq!(users[0].username(), username); assert_eq!(users[0].email(), email); // Check that success message was displayed assert!(app.messages().iter().any(|msg| msg.contains("Created user:"))); } #[tokio::test] async fn test_tui_user_list_command() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); // Create a user first let create_user = CreateUser::new("testuser".to_string(), "test@example.com".to_string()).unwrap(); user_service.create(create_user).await.unwrap(); execute_tui_command(&mut app, "user list", &user_service, &product_service).await; // Check that the command was displayed assert!(app.messages().iter().any(|msg| msg.contains("> user list"))); // Check that users were listed assert!(app.messages().iter().any(|msg| msg.contains("Users:"))); assert!(app.messages().iter().any(|msg| msg.contains("testuser"))); } #[tokio::test] async fn test_tui_product_create_command() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); let (name, _) = unique_test_data("tui_product"); let description = "Test product description"; let command = format!("product create -n {} -d {}", name, description); execute_tui_command(&mut app, &command, &user_service, &product_service).await; // Check that the command was displayed assert!(app.messages().iter().any(|msg| msg.contains(&format!("> {}", command)))); // Check that a product was created successfully let products = product_service.list().await.unwrap(); assert_eq!(products.len(), 1); assert_eq!(products[0].name(), name); assert_eq!(products[0].description(), description); // Check that success message was displayed assert!(app.messages().iter().any(|msg| msg.contains("Created product:"))); } #[tokio::test] async fn test_tui_product_list_command() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); // Create a product first let create_product = CreateProduct::new("Test Product".to_string(), "Test Description".to_string()).unwrap(); product_service.create(create_product).await.unwrap(); execute_tui_command(&mut app, "product list", &user_service, &product_service).await; // Check that the command was displayed assert!(app.messages().iter().any(|msg| msg.contains("> product list"))); // Check that products were listed assert!(app.messages().iter().any(|msg| msg.contains("Products:"))); assert!(app.messages().iter().any(|msg| msg.contains("Test Product"))); } #[tokio::test] async fn test_tui_help_command() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); execute_tui_command(&mut app, "help", &user_service, &product_service).await; // Check that help was displayed assert!(app.messages().iter().any(|msg| msg.contains("Available commands:"))); assert!(app.messages().iter().any(|msg| msg.contains("user create"))); assert!(app.messages().iter().any(|msg| msg.contains("product create"))); } #[tokio::test] async fn test_tui_exit_command() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); execute_tui_command(&mut app, "exit", &user_service, &product_service).await; // Check that exit message was displayed assert!(app.messages().iter().any(|msg| msg.contains("Goodbye!"))); // Note: We can't test should_quit() in integration tests since it's private } #[tokio::test] async fn test_tui_unknown_command() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); execute_tui_command(&mut app, "unknown command", &user_service, &product_service).await; // Check that error message was displayed assert!(app.messages().iter().any(|msg| msg.contains("Unknown command"))); } #[tokio::test] async fn test_tui_invalid_user_create() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); execute_tui_command(&mut app, "user create -u", &user_service, &product_service).await; // Check that error message was displayed assert!(app.messages().iter().any(|msg| msg.contains("Error:"))); } #[tokio::test] async fn test_tui_invalid_product_create() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); execute_tui_command(&mut app, "product create -n", &user_service, &product_service).await; // Check that error message was displayed assert!(app.messages().iter().any(|msg| msg.contains("Error:"))); } } mod tui_integration_with_memory_services { use super::*; #[tokio::test] #[serial] async fn test_tui_with_memory_user_lifecycle() { let user_repo = InMemoryUserRepository::new(); let product_repo = InMemoryProductRepository::new(); let user_service = Service::new(user_repo); let product_service = Service::new(product_repo); let mut app = App::new(); // Create user via TUI command let (username, email) = unique_test_data("memory_user"); let command = format!("user create -u {} -e {}", username, email); execute_tui_command(&mut app, &command, &user_service, &product_service).await; // List users via TUI command execute_tui_command(&mut app, "user list", &user_service, &product_service).await; // Verify user was created let users = user_service.list().await.unwrap(); assert_eq!(users.len(), 1); assert_eq!(users[0].username(), username); assert_eq!(users[0].email(), email); } #[tokio::test] #[serial] async fn test_tui_with_memory_product_lifecycle() { let user_repo = InMemoryUserRepository::new(); let product_repo = InMemoryProductRepository::new(); let user_service = Service::new(user_repo); let product_service = Service::new(product_repo); let mut app = App::new(); // Create product via TUI command let (name, _) = unique_test_data("memory_product"); let description = "Test product description"; let command = format!("product create -n {} -d {}", name, description); execute_tui_command(&mut app, &command, &user_service, &product_service).await; // List products via TUI command execute_tui_command(&mut app, "product list", &user_service, &product_service).await; // Verify product was created let products = product_service.list().await.unwrap(); assert_eq!(products.len(), 1); assert_eq!(products[0].name(), name); assert_eq!(products[0].description(), description); } #[tokio::test] #[serial] async fn test_tui_with_memory_mixed_operations() { let user_repo = InMemoryUserRepository::new(); let product_repo = InMemoryProductRepository::new(); let user_service = Service::new(user_repo); let product_service = Service::new(product_repo); let mut app = App::new(); // Create multiple users and products via TUI commands for i in 1..=3 { let (username, email) = unique_test_data(&format!("user_{}", i)); let command = format!("user create -u {} -e {}", username, email); execute_tui_command(&mut app, &command, &user_service, &product_service).await; let (name, _) = unique_test_data(&format!("product_{}", i)); let command = format!("product create -n {} -d \"Description {}\"", name, i); execute_tui_command(&mut app, &command, &user_service, &product_service).await; } // List all users and products execute_tui_command(&mut app, "user list", &user_service, &product_service).await; execute_tui_command(&mut app, "product list", &user_service, &product_service).await; // Verify counts let users = user_service.list().await.unwrap(); let products = product_service.list().await.unwrap(); assert_eq!(users.len(), 3); assert_eq!(products.len(), 3); } } mod tui_integration_with_postgres_services { use super::*; #[tokio::test] #[serial] async fn test_tui_with_postgres_user_lifecycle() { let pool = setup_test_db().await; let user_repo = PostgresUserRepository::new(pool.clone()); let product_repo = PostgresProductRepository::new(pool.clone()); let user_service = Service::new(user_repo); let product_service = Service::new(product_repo); let mut app = App::new(); // Create user via TUI command let (username, email) = unique_test_data("postgres_user"); let command = format!("user create -u {} -e {}", username, email); execute_tui_command(&mut app, &command, &user_service, &product_service).await; // List users via TUI command execute_tui_command(&mut app, "user list", &user_service, &product_service).await; // Verify user was created let users = user_service.list().await.unwrap(); assert_eq!(users.len(), 1); assert_eq!(users[0].username(), username); assert_eq!(users[0].email(), email); } #[tokio::test] #[serial] async fn test_tui_with_postgres_product_lifecycle() { let pool = setup_test_db().await; let user_repo = PostgresUserRepository::new(pool.clone()); let product_repo = PostgresProductRepository::new(pool.clone()); let user_service = Service::new(user_repo); let product_service = Service::new(product_repo); let mut app = App::new(); // Create product via TUI command let (name, _) = unique_test_data("postgres_product"); let description = "Test product description"; let command = format!("product create -n {} -d {}", name, description); execute_tui_command(&mut app, &command, &user_service, &product_service).await; // List products via TUI command execute_tui_command(&mut app, "product list", &user_service, &product_service).await; // Verify product was created let products = product_service.list().await.unwrap(); assert_eq!(products.len(), 1); assert_eq!(products[0].name(), name); assert_eq!(products[0].description(), description); } #[tokio::test] #[serial] async fn test_tui_with_postgres_error_handling() { let pool = setup_test_db().await; let user_repo = PostgresUserRepository::new(pool.clone()); let product_repo = PostgresProductRepository::new(pool.clone()); let user_service = Service::new(user_repo); let product_service = Service::new(product_repo); let mut app = App::new(); // Test invalid user creation execute_tui_command(&mut app, "user create -u \"\" -e \"\"", &user_service, &product_service).await; // Test invalid product creation execute_tui_command(&mut app, "product create -n \"\" -d \"\"", &user_service, &product_service).await; // Verify no data was created let users = user_service.list().await.unwrap(); let products = product_service.list().await.unwrap(); assert_eq!(users.len(), 0); assert_eq!(products.len(), 0); } } mod tui_error_handling { use super::*; #[tokio::test] async fn test_tui_command_parsing_errors() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); // Test various invalid command formats let invalid_commands = vec![ "user create", "user create -u", "user create -u test", "user create -e test@example.com", "product create", "product create -n", "product create -n test", "product create -d test", ]; for command in invalid_commands { execute_tui_command(&mut app, command, &user_service, &product_service).await; assert!(app.messages().iter().any(|msg| msg.contains("Error:"))); } } #[tokio::test] async fn test_tui_empty_commands() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); // Test empty command execute_tui_command(&mut app, "", &user_service, &product_service).await; // Should not add any error messages for empty commands let error_messages: Vec<&String> = app.messages().iter().filter(|msg| msg.contains("Error:")).collect(); assert_eq!(error_messages.len(), 0); } #[tokio::test] async fn test_tui_whitespace_commands() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); // Test whitespace-only command execute_tui_command(&mut app, " ", &user_service, &product_service).await; // Should not add any error messages for whitespace-only commands let error_messages: Vec<&String> = app.messages().iter().filter(|msg| msg.contains("Error:")).collect(); assert_eq!(error_messages.len(), 0); } } mod tui_command_history { use super::*; #[tokio::test] async fn test_tui_command_history_functionality() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); // Execute several commands let commands = vec![ "help", "user list", "product list", "user create -u test -e test@example.com", ]; for command in commands { execute_tui_command(&mut app, command, &user_service, &product_service).await; } // We can't directly test command history without private field access // but we can test that commands are processed without errors assert!(app.messages().len() > 4); // Should have at least the command messages } #[tokio::test] async fn test_tui_empty_commands_not_added_to_history() { let mut app = App::new(); let user_service = MockUserService::new(); let product_service = MockProductService::new(); // Execute empty and whitespace commands execute_tui_command(&mut app, "", &user_service, &product_service).await; execute_tui_command(&mut app, " ", &user_service, &product_service).await; execute_tui_command(&mut app, "help", &user_service, &product_service).await; // We can't directly test command history without private field access // but we can test that commands are processed without errors assert!(app.messages().len() > 1); // Should have at least the help message } } }