1125 lines
38 KiB
Rust
1125 lines
38 KiB
Rust
/*
|
|
* 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 anyhow::Result;
|
|
use clap::Parser;
|
|
use domain::{CreateProduct, CreateUser, Product, User, UpdateProduct, UpdateUser};
|
|
use application::UseCase;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Parser)]
|
|
#[command(author, version, about, long_about = None)]
|
|
pub struct Cli {
|
|
#[command(subcommand)]
|
|
command: Option<Commands>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub enum Commands {
|
|
/// User management commands
|
|
User {
|
|
#[command(subcommand)]
|
|
command: UserCommands,
|
|
},
|
|
/// Product management commands
|
|
Product {
|
|
#[command(subcommand)]
|
|
command: ProductCommands,
|
|
},
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub enum UserCommands {
|
|
/// Create a new user
|
|
Create {
|
|
/// Username
|
|
#[arg(short, long)]
|
|
username: String,
|
|
/// Email
|
|
#[arg(short, long)]
|
|
email: String,
|
|
},
|
|
/// List all users
|
|
List,
|
|
/// Get a user by ID
|
|
Get {
|
|
/// User ID
|
|
#[arg(short, long)]
|
|
id: Uuid,
|
|
},
|
|
/// Update a user
|
|
Update {
|
|
/// User ID
|
|
#[arg(short, long)]
|
|
id: Uuid,
|
|
/// New username
|
|
#[arg(short, long)]
|
|
username: Option<String>,
|
|
/// New email
|
|
#[arg(short, long)]
|
|
email: Option<String>,
|
|
},
|
|
/// Delete a user
|
|
Delete {
|
|
/// User ID
|
|
#[arg(short, long)]
|
|
id: Uuid,
|
|
},
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub enum ProductCommands {
|
|
/// Create a new product
|
|
Create {
|
|
/// Product name
|
|
#[arg(short, long)]
|
|
name: String,
|
|
/// Product description
|
|
#[arg(short, long)]
|
|
description: String,
|
|
},
|
|
/// List all products
|
|
List,
|
|
/// Get a product by ID
|
|
Get {
|
|
/// Product ID
|
|
#[arg(short, long)]
|
|
id: Uuid,
|
|
},
|
|
/// Update a product
|
|
Update {
|
|
/// Product ID
|
|
#[arg(short, long)]
|
|
id: Uuid,
|
|
/// New name
|
|
#[arg(short, long)]
|
|
name: Option<String>,
|
|
/// New description
|
|
#[arg(short, long)]
|
|
description: Option<String>,
|
|
},
|
|
/// Delete a product
|
|
Delete {
|
|
/// Product ID
|
|
#[arg(short, long)]
|
|
id: Uuid,
|
|
},
|
|
}
|
|
|
|
impl Cli {
|
|
pub async fn run<U, P>(self, user_service: U, product_service: P) -> Result<()>
|
|
where
|
|
U: UseCase<User>,
|
|
P: UseCase<Product>,
|
|
{
|
|
match self.command {
|
|
Some(Commands::User { command }) => match command {
|
|
UserCommands::Create { username, email } => {
|
|
let user = user_service.create(CreateUser::new(username, email)?).await?;
|
|
println!("Created user: {:?}", user);
|
|
}
|
|
UserCommands::List => {
|
|
let users = user_service.list().await?;
|
|
println!("Users: {:?}", users);
|
|
}
|
|
UserCommands::Get { id } => {
|
|
let user = user_service.get(id).await?;
|
|
println!("User: {:?}", user);
|
|
}
|
|
UserCommands::Update { id, username, email } => {
|
|
let update = UpdateUser::new(username, email)?;
|
|
let user = user_service.update(id, update).await?;
|
|
println!("Updated user: {:?}", user);
|
|
}
|
|
UserCommands::Delete { id } => {
|
|
user_service.delete(id).await?;
|
|
println!("Deleted user {}", id);
|
|
}
|
|
},
|
|
Some(Commands::Product { command }) => match command {
|
|
ProductCommands::Create { name, description } => {
|
|
let product = product_service.create(CreateProduct::new(name, description)?).await?;
|
|
println!("Created product: {:?}", product);
|
|
}
|
|
ProductCommands::List => {
|
|
let products = product_service.list().await?;
|
|
println!("Products: {:?}", products);
|
|
}
|
|
ProductCommands::Get { id } => {
|
|
let product = product_service.get(id).await?;
|
|
println!("Product: {:?}", product);
|
|
}
|
|
ProductCommands::Update { id, name, description } => {
|
|
let update = UpdateProduct::new(name, description)?;
|
|
let product = product_service.update(id, update).await?;
|
|
println!("Updated product: {:?}", product);
|
|
}
|
|
ProductCommands::Delete { id } => {
|
|
product_service.delete(id).await?;
|
|
println!("Deleted product {}", id);
|
|
}
|
|
},
|
|
None => {
|
|
println!("No command provided. Use --help for usage information.");
|
|
}
|
|
}
|
|
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;
|
|
|
|
/// 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::new(id, data.username().to_string(), data.email().to_string())?;
|
|
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.set_username(username.to_string())?;
|
|
}
|
|
if let Some(email) = data.email() {
|
|
user.set_email(email.to_string())?;
|
|
}
|
|
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::new(id, data.name().to_string(), data.description().to_string())?;
|
|
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.set_name(name.to_string())?;
|
|
}
|
|
if let Some(description) = data.description() {
|
|
product.set_description(description.to_string())?;
|
|
}
|
|
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_err()); // Should fail due to empty username validation
|
|
}
|
|
|
|
/// 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_err()); // Should fail due to empty product name validation
|
|
}
|
|
|
|
/// 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());
|
|
}
|
|
}
|
|
}
|