Add CLI integration tests
This commit is contained in:
parent
fbe383b76d
commit
bb1bc4bf9b
3 changed files with 411 additions and 0 deletions
|
@ -21,12 +21,15 @@ serial_test = "2.0"
|
|||
# Local crates
|
||||
api = { path = "../api" }
|
||||
application = { path = "../application" }
|
||||
cli = { path = "../cli" }
|
||||
domain = { path = "../domain" }
|
||||
memory = { path = "../memory" }
|
||||
postgres = { path = "../postgres" }
|
||||
|
||||
# Additional test dependencies
|
||||
hyper = { version = "1.0", features = ["full"] }
|
||||
tower-http = { version = "0.5", features = ["cors", "trace"] }
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "2.0"
|
406
backend/crates/integration-tests/src/cli_tests.rs
Normal file
406
backend/crates/integration-tests/src/cli_tests.rs
Normal file
|
@ -0,0 +1,406 @@
|
|||
/*
|
||||
* 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 cli::{Cli, Commands, UserCommands, ProductCommands};
|
||||
use domain::{User, Product, CreateUser, UpdateUser, CreateProduct, UpdateProduct};
|
||||
use application::Service;
|
||||
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;
|
||||
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))
|
||||
}
|
||||
|
||||
// Test CLI with memory repository
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_cli_with_memory_user_lifecycle() -> Result<()> {
|
||||
let user_repo = InMemoryUserRepository::new();
|
||||
let product_repo = InMemoryProductRepository::new();
|
||||
let user_service = Service::new(user_repo);
|
||||
let product_service = Service::new(product_repo);
|
||||
|
||||
// Test user create
|
||||
let (username, email) = unique_test_data("test_user");
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "create", "--username", &username, "--email", &email
|
||||
]);
|
||||
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test user list
|
||||
let cli = Cli::parse_from(&["cli", "user", "list"]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test user get (we need to get the ID from the list first)
|
||||
let users = user_service.list().await?;
|
||||
assert!(!users.is_empty());
|
||||
let user_id = users[0].id;
|
||||
|
||||
let cli = Cli::parse_from(&["cli", "user", "get", "--id", &user_id.to_string()]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test user update
|
||||
let new_username = format!("{}_updated", username);
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "update", "--id", &user_id.to_string(), "--username", &new_username
|
||||
]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test user delete
|
||||
let cli = Cli::parse_from(&["cli", "user", "delete", "--id", &user_id.to_string()]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_cli_with_memory_product_lifecycle() -> Result<()> {
|
||||
let user_repo = InMemoryUserRepository::new();
|
||||
let product_repo = InMemoryProductRepository::new();
|
||||
let user_service = Service::new(user_repo);
|
||||
let product_service = Service::new(product_repo);
|
||||
|
||||
// Test product create
|
||||
let (name, description) = unique_test_data("test_product");
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "product", "create", "--name", &name, "--description", &description
|
||||
]);
|
||||
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test product list
|
||||
let cli = Cli::parse_from(&["cli", "product", "list"]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test product get (we need to get the ID from the list first)
|
||||
let products = product_service.list().await?;
|
||||
assert!(!products.is_empty());
|
||||
let product_id = products[0].id;
|
||||
|
||||
let cli = Cli::parse_from(&["cli", "product", "get", "--id", &product_id.to_string()]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test product update
|
||||
let new_name = format!("{}_updated", name);
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "product", "update", "--id", &product_id.to_string(), "--name", &new_name
|
||||
]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test product delete
|
||||
let cli = Cli::parse_from(&["cli", "product", "delete", "--id", &product_id.to_string()]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_cli_with_memory_mixed_operations() -> Result<()> {
|
||||
let user_repo = InMemoryUserRepository::new();
|
||||
let product_repo = InMemoryProductRepository::new();
|
||||
let user_service = Service::new(user_repo);
|
||||
let product_service = Service::new(product_repo);
|
||||
|
||||
// Create multiple users and products
|
||||
for i in 1..=3 {
|
||||
let (username, email) = unique_test_data(&format!("user_{}", i));
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "create", "--username", &username, "--email", &email
|
||||
]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
let (name, description) = unique_test_data(&format!("product_{}", i));
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "product", "create", "--name", &name, "--description", &description
|
||||
]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
}
|
||||
|
||||
// List all users and products
|
||||
let cli = Cli::parse_from(&["cli", "user", "list"]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
let cli = Cli::parse_from(&["cli", "product", "list"]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Verify counts
|
||||
let users = user_service.list().await?;
|
||||
let products = product_service.list().await?;
|
||||
assert_eq!(users.len(), 3);
|
||||
assert_eq!(products.len(), 3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test CLI with PostgreSQL repository
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_cli_with_postgres_user_lifecycle() -> Result<()> {
|
||||
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);
|
||||
|
||||
// Test user create
|
||||
let (username, email) = unique_test_data("test_user");
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "create", "--username", &username, "--email", &email
|
||||
]);
|
||||
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test user list
|
||||
let cli = Cli::parse_from(&["cli", "user", "list"]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test user get (we need to get the ID from the list first)
|
||||
let users = user_service.list().await?;
|
||||
assert!(!users.is_empty());
|
||||
let user_id = users[0].id;
|
||||
|
||||
let cli = Cli::parse_from(&["cli", "user", "get", "--id", &user_id.to_string()]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test user update
|
||||
let new_username = format!("{}_updated", username);
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "update", "--id", &user_id.to_string(), "--username", &new_username
|
||||
]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test user delete
|
||||
let cli = Cli::parse_from(&["cli", "user", "delete", "--id", &user_id.to_string()]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_cli_with_postgres_product_lifecycle() -> Result<()> {
|
||||
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);
|
||||
|
||||
// Test product create
|
||||
let (name, description) = unique_test_data("test_product");
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "product", "create", "--name", &name, "--description", &description
|
||||
]);
|
||||
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test product list
|
||||
let cli = Cli::parse_from(&["cli", "product", "list"]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test product get (we need to get the ID from the list first)
|
||||
let products = product_service.list().await?;
|
||||
assert!(!products.is_empty());
|
||||
let product_id = products[0].id;
|
||||
|
||||
let cli = Cli::parse_from(&["cli", "product", "get", "--id", &product_id.to_string()]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test product update
|
||||
let new_name = format!("{}_updated", name);
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "product", "update", "--id", &product_id.to_string(), "--name", &new_name
|
||||
]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Test product delete
|
||||
let cli = Cli::parse_from(&["cli", "product", "delete", "--id", &product_id.to_string()]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_cli_with_postgres_mixed_operations() -> Result<()> {
|
||||
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);
|
||||
|
||||
// Create multiple users and products
|
||||
for i in 1..=3 {
|
||||
let (username, email) = unique_test_data(&format!("user_{}", i));
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "create", "--username", &username, "--email", &email
|
||||
]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
let (name, description) = unique_test_data(&format!("product_{}", i));
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "product", "create", "--name", &name, "--description", &description
|
||||
]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
}
|
||||
|
||||
// List all users and products
|
||||
let cli = Cli::parse_from(&["cli", "user", "list"]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
let cli = Cli::parse_from(&["cli", "product", "list"]);
|
||||
cli.run(user_service.clone(), product_service.clone()).await?;
|
||||
|
||||
// Verify counts
|
||||
let users = user_service.list().await?;
|
||||
let products = product_service.list().await?;
|
||||
assert_eq!(users.len(), 3);
|
||||
assert_eq!(products.len(), 3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test error handling
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_cli_error_handling() -> Result<()> {
|
||||
let user_repo = InMemoryUserRepository::new();
|
||||
let product_repo = InMemoryProductRepository::new();
|
||||
let user_service = Service::new(user_repo);
|
||||
let product_service = Service::new(product_repo);
|
||||
|
||||
// Test getting non-existent user
|
||||
let non_existent_id = Uuid::new_v4();
|
||||
let cli = Cli::parse_from(&["cli", "user", "get", "--id", &non_existent_id.to_string()]);
|
||||
let result = cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test getting non-existent product
|
||||
let cli = Cli::parse_from(&["cli", "product", "get", "--id", &non_existent_id.to_string()]);
|
||||
let result = cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test updating non-existent user
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "update", "--id", &non_existent_id.to_string(), "--username", "new_name"
|
||||
]);
|
||||
let result = cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test deleting non-existent user
|
||||
let cli = Cli::parse_from(&["cli", "user", "delete", "--id", &non_existent_id.to_string()]);
|
||||
let result = cli.run(user_service.clone(), product_service.clone()).await;
|
||||
assert!(result.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test edge cases
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_cli_edge_cases() -> Result<()> {
|
||||
let user_repo = InMemoryUserRepository::new();
|
||||
let product_repo = InMemoryProductRepository::new();
|
||||
let user_service = Service::new(user_repo);
|
||||
let product_service = Service::new(product_repo);
|
||||
|
||||
// Test empty strings (currently allowed by domain layer)
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "create", "--username", "", "--email", ""
|
||||
]);
|
||||
let result = cli.run(user_service.clone(), product_service.clone()).await;
|
||||
// Note: Empty strings are currently allowed by the domain layer
|
||||
// This test documents the current behavior
|
||||
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "product", "create", "--name", "", "--description", ""
|
||||
]);
|
||||
let result = cli.run(user_service.clone(), product_service.clone()).await;
|
||||
// Note: Empty strings are currently allowed by the domain layer
|
||||
// This test documents the current behavior
|
||||
|
||||
// Test very long strings
|
||||
let long_string = "a".repeat(1000);
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "create", "--username", &long_string, "--email", "test@example.com"
|
||||
]);
|
||||
let result = cli.run(user_service.clone(), product_service.clone()).await;
|
||||
// Note: Very long strings are currently allowed by the domain layer
|
||||
// This test documents the current behavior
|
||||
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "product", "create", "--name", &long_string, "--description", "test"
|
||||
]);
|
||||
let result = cli.run(user_service.clone(), product_service.clone()).await;
|
||||
// Note: Very long strings are currently allowed by the domain layer
|
||||
// This test documents the current behavior
|
||||
|
||||
// Test special characters
|
||||
let special_chars = "!@#$%^&*()_+-=[]{}|;':\",./<>?";
|
||||
let cli = Cli::parse_from(&[
|
||||
"cli", "user", "create", "--username", special_chars, "--email", "test@example.com"
|
||||
]);
|
||||
let result = cli.run(user_service.clone(), product_service.clone()).await;
|
||||
// Note: Special characters are currently allowed by the domain layer
|
||||
// This test documents the current behavior
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Test no command provided
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_cli_no_command() -> Result<()> {
|
||||
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 cli = Cli::parse_from(&["cli"]);
|
||||
cli.run(user_service, product_service).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -12,6 +12,8 @@
|
|||
#[cfg(test)]
|
||||
pub mod api_postgres_tests;
|
||||
#[cfg(test)]
|
||||
pub mod cli_tests;
|
||||
#[cfg(test)]
|
||||
pub mod migration_tests;
|
||||
#[cfg(test)]
|
||||
pub mod performance_tests;
|
Loading…
Add table
Reference in a new issue