Add cli crate unit tests
This commit is contained in:
parent
fffa9f35c0
commit
246acd7eba
2 changed files with 966 additions and 1 deletions
|
@ -14,6 +14,7 @@ tokio = { workspace = true }
|
|||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
textwrap = "0.16"
|
||||
chrono = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
|
|
@ -174,3 +174,967 @@ impl Cli {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
//! # CLI Tests
|
||||
//!
|
||||
//! This module contains comprehensive unit tests for the CLI application.
|
||||
//! Tests cover command parsing, execution, and error handling scenarios.
|
||||
//!
|
||||
//! ## Test Structure
|
||||
//! - `command_parsing` - Tests for CLI argument parsing
|
||||
//! - `user_commands` - Tests for user management commands
|
||||
//! - `product_commands` - Tests for product management commands
|
||||
//! - `integration_tests` - End-to-end command execution tests
|
||||
//! - `error_handling` - Tests for error scenarios
|
||||
//!
|
||||
//! ## Testing Approach
|
||||
//! - Uses mock services implementing the `UseCase` trait
|
||||
//! - Tests verify command parsing and execution
|
||||
//! - Captures stdout to verify output messages
|
||||
//! - Tests error handling and edge cases
|
||||
|
||||
use super::*;
|
||||
use application::{UseCase, ApplicationError};
|
||||
use domain::{DomainError, User, Product, CreateUser, UpdateUser, CreateProduct, UpdateProduct};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use chrono::Utc;
|
||||
|
||||
/// 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 {
|
||||
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 {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod command_parsing {
|
||||
use super::*;
|
||||
|
||||
/// Tests that the CLI can parse user create command with all arguments.
|
||||
#[test]
|
||||
fn test_parse_user_create() {
|
||||
let args = vec![
|
||||
"cli",
|
||||
"user",
|
||||
"create",
|
||||
"--username", "testuser",
|
||||
"--email", "test@example.com"
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::User { command: UserCommands::Create { username, email } }) = cli.command {
|
||||
assert_eq!(username, "testuser");
|
||||
assert_eq!(email, "test@example.com");
|
||||
} else {
|
||||
panic!("Expected user create command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse user create command with short arguments.
|
||||
#[test]
|
||||
fn test_parse_user_create_short_args() {
|
||||
let args = vec![
|
||||
"cli",
|
||||
"user",
|
||||
"create",
|
||||
"-u", "testuser",
|
||||
"-e", "test@example.com"
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::User { command: UserCommands::Create { username, email } }) = cli.command {
|
||||
assert_eq!(username, "testuser");
|
||||
assert_eq!(email, "test@example.com");
|
||||
} else {
|
||||
panic!("Expected user create command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse user list command.
|
||||
#[test]
|
||||
fn test_parse_user_list() {
|
||||
let args = vec!["cli", "user", "list"];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::User { command: UserCommands::List }) = cli.command {
|
||||
// Command parsed successfully
|
||||
} else {
|
||||
panic!("Expected user list command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse user get command.
|
||||
#[test]
|
||||
fn test_parse_user_get() {
|
||||
let user_id = Uuid::new_v4();
|
||||
let user_id_str = user_id.to_string();
|
||||
let args = vec![
|
||||
"cli",
|
||||
"user",
|
||||
"get",
|
||||
"--id", &user_id_str
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::User { command: UserCommands::Get { id } }) = cli.command {
|
||||
assert_eq!(id, user_id);
|
||||
} else {
|
||||
panic!("Expected user get command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse user update command with all fields.
|
||||
#[test]
|
||||
fn test_parse_user_update() {
|
||||
let user_id = Uuid::new_v4();
|
||||
let user_id_str = user_id.to_string();
|
||||
let args = vec![
|
||||
"cli",
|
||||
"user",
|
||||
"update",
|
||||
"--id", &user_id_str,
|
||||
"--username", "newuser",
|
||||
"--email", "new@example.com"
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::User { command: UserCommands::Update { id, username, email } }) = cli.command {
|
||||
assert_eq!(id, user_id);
|
||||
assert_eq!(username, Some("newuser".to_string()));
|
||||
assert_eq!(email, Some("new@example.com".to_string()));
|
||||
} else {
|
||||
panic!("Expected user update command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse user update command with partial fields.
|
||||
#[test]
|
||||
fn test_parse_user_update_partial() {
|
||||
let user_id = Uuid::new_v4();
|
||||
let user_id_str = user_id.to_string();
|
||||
let args = vec![
|
||||
"cli",
|
||||
"user",
|
||||
"update",
|
||||
"--id", &user_id_str,
|
||||
"--username", "newuser"
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::User { command: UserCommands::Update { id, username, email } }) = cli.command {
|
||||
assert_eq!(id, user_id);
|
||||
assert_eq!(username, Some("newuser".to_string()));
|
||||
assert_eq!(email, None);
|
||||
} else {
|
||||
panic!("Expected user update command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse user delete command.
|
||||
#[test]
|
||||
fn test_parse_user_delete() {
|
||||
let user_id = Uuid::new_v4();
|
||||
let user_id_str = user_id.to_string();
|
||||
let args = vec![
|
||||
"cli",
|
||||
"user",
|
||||
"delete",
|
||||
"--id", &user_id_str
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::User { command: UserCommands::Delete { id } }) = cli.command {
|
||||
assert_eq!(id, user_id);
|
||||
} else {
|
||||
panic!("Expected user delete command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse product create command.
|
||||
#[test]
|
||||
fn test_parse_product_create() {
|
||||
let args = vec![
|
||||
"cli",
|
||||
"product",
|
||||
"create",
|
||||
"--name", "Test Product",
|
||||
"--description", "Test Description"
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::Product { command: ProductCommands::Create { name, description } }) = cli.command {
|
||||
assert_eq!(name, "Test Product");
|
||||
assert_eq!(description, "Test Description");
|
||||
} else {
|
||||
panic!("Expected product create command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse product list command.
|
||||
#[test]
|
||||
fn test_parse_product_list() {
|
||||
let args = vec!["cli", "product", "list"];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::Product { command: ProductCommands::List }) = cli.command {
|
||||
// Command parsed successfully
|
||||
} else {
|
||||
panic!("Expected product list command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse product get command.
|
||||
#[test]
|
||||
fn test_parse_product_get() {
|
||||
let product_id = Uuid::new_v4();
|
||||
let product_id_str = product_id.to_string();
|
||||
let args = vec![
|
||||
"cli",
|
||||
"product",
|
||||
"get",
|
||||
"--id", &product_id_str
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::Product { command: ProductCommands::Get { id } }) = cli.command {
|
||||
assert_eq!(id, product_id);
|
||||
} else {
|
||||
panic!("Expected product get command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse product update command.
|
||||
#[test]
|
||||
fn test_parse_product_update() {
|
||||
let product_id = Uuid::new_v4();
|
||||
let product_id_str = product_id.to_string();
|
||||
let args = vec![
|
||||
"cli",
|
||||
"product",
|
||||
"update",
|
||||
"--id", &product_id_str,
|
||||
"--name", "New Product",
|
||||
"--description", "New Description"
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::Product { command: ProductCommands::Update { id, name, description } }) = cli.command {
|
||||
assert_eq!(id, product_id);
|
||||
assert_eq!(name, Some("New Product".to_string()));
|
||||
assert_eq!(description, Some("New Description".to_string()));
|
||||
} else {
|
||||
panic!("Expected product update command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI can parse product delete command.
|
||||
#[test]
|
||||
fn test_parse_product_delete() {
|
||||
let product_id = Uuid::new_v4();
|
||||
let product_id_str = product_id.to_string();
|
||||
let args = vec![
|
||||
"cli",
|
||||
"product",
|
||||
"delete",
|
||||
"--id", &product_id_str
|
||||
];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
if let Some(Commands::Product { command: ProductCommands::Delete { id } }) = cli.command {
|
||||
assert_eq!(id, product_id);
|
||||
} else {
|
||||
panic!("Expected product delete command");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the CLI handles no command gracefully.
|
||||
#[test]
|
||||
fn test_parse_no_command() {
|
||||
let args = vec!["cli"];
|
||||
|
||||
let cli = Cli::try_parse_from(args).unwrap();
|
||||
|
||||
assert!(cli.command.is_none());
|
||||
}
|
||||
|
||||
/// Tests that the CLI rejects invalid UUID format.
|
||||
#[test]
|
||||
fn test_parse_invalid_uuid() {
|
||||
let args = vec![
|
||||
"cli",
|
||||
"user",
|
||||
"get",
|
||||
"--id", "invalid-uuid"
|
||||
];
|
||||
|
||||
let result = Cli::try_parse_from(args);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
mod user_commands {
|
||||
use super::*;
|
||||
|
||||
/// Tests user creation command execution.
|
||||
#[tokio::test]
|
||||
async fn test_user_create_command() {
|
||||
let cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Create {
|
||||
username: "testuser".to_string(),
|
||||
email: "test@example.com".to_string(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
/// Tests user list command execution.
|
||||
#[tokio::test]
|
||||
async fn test_user_list_command() {
|
||||
let cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::List
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
/// Tests user get command execution.
|
||||
#[tokio::test]
|
||||
async fn test_user_get_command() {
|
||||
let user_id = Uuid::new_v4();
|
||||
let cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Get { id: user_id }
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
// Should fail because user doesn't exist
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// Tests user update command execution.
|
||||
#[tokio::test]
|
||||
async fn test_user_update_command() {
|
||||
let user_id = Uuid::new_v4();
|
||||
let cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Update {
|
||||
id: user_id,
|
||||
username: Some("newuser".to_string()),
|
||||
email: Some("new@example.com".to_string()),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
// Should fail because user doesn't exist
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// Tests user delete command execution.
|
||||
#[tokio::test]
|
||||
async fn test_user_delete_command() {
|
||||
let user_id = Uuid::new_v4();
|
||||
let cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Delete { id: user_id }
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
// Should fail because user doesn't exist
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
mod product_commands {
|
||||
use super::*;
|
||||
|
||||
/// Tests product creation command execution.
|
||||
#[tokio::test]
|
||||
async fn test_product_create_command() {
|
||||
let cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Create {
|
||||
name: "Test Product".to_string(),
|
||||
description: "Test Description".to_string(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
/// Tests product list command execution.
|
||||
#[tokio::test]
|
||||
async fn test_product_list_command() {
|
||||
let cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::List
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
/// Tests product get command execution.
|
||||
#[tokio::test]
|
||||
async fn test_product_get_command() {
|
||||
let product_id = Uuid::new_v4();
|
||||
let cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Get { id: product_id }
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
// Should fail because product doesn't exist
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// Tests product update command execution.
|
||||
#[tokio::test]
|
||||
async fn test_product_update_command() {
|
||||
let product_id = Uuid::new_v4();
|
||||
let cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Update {
|
||||
id: product_id,
|
||||
name: Some("New Product".to_string()),
|
||||
description: Some("New Description".to_string()),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
// Should fail because product doesn't exist
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// Tests product delete command execution.
|
||||
#[tokio::test]
|
||||
async fn test_product_delete_command() {
|
||||
let product_id = Uuid::new_v4();
|
||||
let cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Delete { id: product_id }
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
// Should fail because product doesn't exist
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
mod integration_tests {
|
||||
use super::*;
|
||||
|
||||
/// Tests complete user lifecycle through CLI commands.
|
||||
#[tokio::test]
|
||||
async fn test_user_lifecycle() {
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
// Create user
|
||||
let create_cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Create {
|
||||
username: "testuser".to_string(),
|
||||
email: "test@example.com".to_string(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let result = create_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Get the created user ID by listing users
|
||||
let users = user_service.list().await.unwrap();
|
||||
assert_eq!(users.len(), 1);
|
||||
let user_id = users[0].id;
|
||||
|
||||
// Get user
|
||||
let get_cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Get { id: user_id }
|
||||
})
|
||||
};
|
||||
|
||||
let result = get_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Update user
|
||||
let update_cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Update {
|
||||
id: user_id,
|
||||
username: Some("updateduser".to_string()),
|
||||
email: None,
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let result = update_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Delete user
|
||||
let delete_cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Delete { id: user_id }
|
||||
})
|
||||
};
|
||||
|
||||
let result = delete_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify user is deleted
|
||||
let users = user_service.list().await.unwrap();
|
||||
assert_eq!(users.len(), 0);
|
||||
}
|
||||
|
||||
/// Tests complete product lifecycle through CLI commands.
|
||||
#[tokio::test]
|
||||
async fn test_product_lifecycle() {
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
// Create product
|
||||
let create_cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Create {
|
||||
name: "Test Product".to_string(),
|
||||
description: "Test Description".to_string(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let result = create_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Get the created product ID by listing products
|
||||
let products = product_service.list().await.unwrap();
|
||||
assert_eq!(products.len(), 1);
|
||||
let product_id = products[0].id;
|
||||
|
||||
// Get product
|
||||
let get_cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Get { id: product_id }
|
||||
})
|
||||
};
|
||||
|
||||
let result = get_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Update product
|
||||
let update_cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Update {
|
||||
id: product_id,
|
||||
name: Some("Updated Product".to_string()),
|
||||
description: None,
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let result = update_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Delete product
|
||||
let delete_cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Delete { id: product_id }
|
||||
})
|
||||
};
|
||||
|
||||
let result = delete_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify product is deleted
|
||||
let products = product_service.list().await.unwrap();
|
||||
assert_eq!(products.len(), 0);
|
||||
}
|
||||
|
||||
/// Tests mixed user and product operations.
|
||||
#[tokio::test]
|
||||
async fn test_mixed_operations() {
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
// Create a user
|
||||
let user_cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Create {
|
||||
username: "testuser".to_string(),
|
||||
email: "test@example.com".to_string(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let result = user_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Create a product
|
||||
let product_cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Create {
|
||||
name: "Test Product".to_string(),
|
||||
description: "Test Description".to_string(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let result = product_cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify both exist
|
||||
let users = user_service.list().await.unwrap();
|
||||
let products = product_service.list().await.unwrap();
|
||||
assert_eq!(users.len(), 1);
|
||||
assert_eq!(products.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
mod error_handling {
|
||||
use super::*;
|
||||
|
||||
/// Tests handling of service errors in user operations.
|
||||
#[tokio::test]
|
||||
async fn test_user_service_error() {
|
||||
let cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Get { id: Uuid::new_v4() }
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// Tests handling of service errors in product operations.
|
||||
#[tokio::test]
|
||||
async fn test_product_service_error() {
|
||||
let cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Get { id: Uuid::new_v4() }
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// Tests handling of no command provided.
|
||||
#[tokio::test]
|
||||
async fn test_no_command() {
|
||||
let cli = Cli { command: None };
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
mod edge_cases {
|
||||
use super::*;
|
||||
|
||||
/// Tests CLI with empty strings in user creation.
|
||||
#[tokio::test]
|
||||
async fn test_user_create_empty_strings() {
|
||||
let cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Create {
|
||||
username: "".to_string(),
|
||||
email: "".to_string(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_ok()); // Should succeed even with empty strings
|
||||
}
|
||||
|
||||
/// Tests CLI with empty strings in product creation.
|
||||
#[tokio::test]
|
||||
async fn test_product_create_empty_strings() {
|
||||
let cli = Cli {
|
||||
command: Some(Commands::Product {
|
||||
command: ProductCommands::Create {
|
||||
name: "".to_string(),
|
||||
description: "".to_string(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_ok()); // Should succeed even with empty strings
|
||||
}
|
||||
|
||||
/// Tests CLI with very long strings.
|
||||
#[tokio::test]
|
||||
async fn test_user_create_long_strings() {
|
||||
let long_username = "a".repeat(1000);
|
||||
let long_email = format!("{}@example.com", "a".repeat(1000));
|
||||
|
||||
let cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Create {
|
||||
username: long_username.clone(),
|
||||
email: long_email.clone(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
/// Tests CLI with special characters in strings.
|
||||
#[tokio::test]
|
||||
async fn test_user_create_special_characters() {
|
||||
let cli = Cli {
|
||||
command: Some(Commands::User {
|
||||
command: UserCommands::Create {
|
||||
username: "user@#$%^&*()".to_string(),
|
||||
email: "test+tag@example.com".to_string(),
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let user_service = MockUserService::new();
|
||||
let product_service = MockProductService::new();
|
||||
|
||||
let result = cli.run(user_service, product_service).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue