Add tui unit tests and make sure all unit tests pass
This commit is contained in:
parent
246acd7eba
commit
05683d5f32
4 changed files with 396 additions and 83 deletions
|
@ -15,3 +15,4 @@ chrono = { workspace = true }
|
|||
|
||||
[dev-dependencies]
|
||||
futures = "0.3"
|
||||
serial_test = "2"
|
||||
|
|
|
@ -241,6 +241,7 @@ mod tests {
|
|||
use sqlx::postgres::PgPoolOptions;
|
||||
use std::env;
|
||||
use chrono::Utc;
|
||||
use serial_test::serial;
|
||||
|
||||
// Test database setup
|
||||
async fn setup_test_db() -> PgPool {
|
||||
|
@ -267,18 +268,33 @@ mod tests {
|
|||
|
||||
// Clean up test data
|
||||
async fn cleanup_test_data(pool: &PgPool) {
|
||||
sqlx::query("DELETE FROM products").execute(pool).await.ok();
|
||||
sqlx::query("DELETE FROM users").execute(pool).await.ok();
|
||||
let mut tx = pool.begin().await.expect("Failed to begin transaction");
|
||||
|
||||
// Delete in reverse order of foreign key dependencies
|
||||
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");
|
||||
}
|
||||
|
||||
// Generate unique test data to avoid conflicts between concurrent tests
|
||||
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))
|
||||
}
|
||||
|
||||
mod user_repository_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_create_user() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let create_data = CreateUser {
|
||||
username: "testuser".to_string(),
|
||||
email: "test@example.com".to_string(),
|
||||
|
@ -298,10 +314,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_create_user_with_duplicate_username() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let create_data = CreateUser {
|
||||
username: "duplicate_user".to_string(),
|
||||
email: "test1@example.com".to_string(),
|
||||
|
@ -323,10 +343,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_find_user_by_id() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let create_data = CreateUser {
|
||||
username: "finduser".to_string(),
|
||||
email: "find@example.com".to_string(),
|
||||
|
@ -345,10 +369,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_find_user_by_nonexistent_id() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let nonexistent_id = Uuid::new_v4();
|
||||
let result = repo.find_by_id(nonexistent_id).await;
|
||||
|
||||
|
@ -364,36 +392,47 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_find_all_users() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Create multiple users
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
// Create multiple users with unique usernames
|
||||
let (username1, email1) = unique_test_data("user1");
|
||||
let (username2, email2) = unique_test_data("user2");
|
||||
|
||||
let _user1 = repo.create(CreateUser {
|
||||
username: "user1".to_string(),
|
||||
email: "user1@example.com".to_string(),
|
||||
username: username1.clone(),
|
||||
email: email1,
|
||||
}).await.unwrap();
|
||||
|
||||
let _user2 = repo.create(CreateUser {
|
||||
username: "user2".to_string(),
|
||||
email: "user2@example.com".to_string(),
|
||||
username: username2.clone(),
|
||||
email: email2,
|
||||
}).await.unwrap();
|
||||
|
||||
let users = repo.find_all().await.unwrap();
|
||||
assert_eq!(users.len(), 2);
|
||||
|
||||
let usernames: Vec<String> = users.iter().map(|u| u.username.clone()).collect();
|
||||
assert!(usernames.contains(&"user1".to_string()));
|
||||
assert!(usernames.contains(&"user2".to_string()));
|
||||
assert!(usernames.contains(&username1));
|
||||
assert!(usernames.contains(&username2));
|
||||
|
||||
cleanup_test_data(&pool).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_find_all_users_empty() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let users = repo.find_all().await.unwrap();
|
||||
assert_eq!(users.len(), 0);
|
||||
|
||||
|
@ -401,10 +440,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_update_user() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let create_data = CreateUser {
|
||||
username: "updateuser".to_string(),
|
||||
email: "update@example.com".to_string(),
|
||||
|
@ -428,10 +471,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_update_user_email_only() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let create_data = CreateUser {
|
||||
username: "emailuser".to_string(),
|
||||
email: "old@example.com".to_string(),
|
||||
|
@ -452,10 +499,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_update_user_both_fields() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let create_data = CreateUser {
|
||||
username: "bothuser".to_string(),
|
||||
email: "both@example.com".to_string(),
|
||||
|
@ -476,10 +527,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_update_nonexistent_user() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let nonexistent_id = Uuid::new_v4();
|
||||
let update_data = UpdateUser {
|
||||
username: Some("nonexistent".to_string()),
|
||||
|
@ -499,13 +554,18 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_delete_user() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let (username, email) = unique_test_data("delete_user");
|
||||
let create_data = CreateUser {
|
||||
username: "deleteuser".to_string(),
|
||||
email: "delete@example.com".to_string(),
|
||||
username: username.clone(),
|
||||
email: email.clone(),
|
||||
};
|
||||
|
||||
let user = repo.create(create_data).await.unwrap();
|
||||
|
@ -527,10 +587,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_delete_nonexistent_user() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let nonexistent_id = Uuid::new_v4();
|
||||
let result = repo.delete(nonexistent_id).await;
|
||||
|
||||
|
@ -544,18 +608,67 @@ mod tests {
|
|||
|
||||
cleanup_test_data(&pool).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_concurrent_access() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let (username, email) = unique_test_data("concurrent_user");
|
||||
let create_data = CreateUser {
|
||||
username: username.clone(),
|
||||
email: email.clone(),
|
||||
};
|
||||
|
||||
// Create a user
|
||||
let user = repo.create(create_data).await.unwrap();
|
||||
|
||||
// Test concurrent access with a simpler approach
|
||||
let repo_clone = repo.clone();
|
||||
let user_id = user.id;
|
||||
|
||||
// Spawn a single concurrent task
|
||||
let handle = tokio::spawn(async move {
|
||||
repo_clone.find_by_id(user_id).await
|
||||
});
|
||||
|
||||
// Also do a direct access
|
||||
let direct_result = repo.find_by_id(user_id).await;
|
||||
|
||||
// Wait for the spawned task
|
||||
let spawned_result = handle.await.unwrap();
|
||||
|
||||
// Both should succeed
|
||||
assert!(direct_result.is_ok());
|
||||
assert!(spawned_result.is_ok());
|
||||
|
||||
// Both should return the same user
|
||||
assert_eq!(direct_result.unwrap().id, user_id);
|
||||
assert_eq!(spawned_result.unwrap().id, user_id);
|
||||
|
||||
cleanup_test_data(&pool).await;
|
||||
}
|
||||
}
|
||||
|
||||
mod product_repository_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_create_product() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let (name, _) = unique_test_data("test_product");
|
||||
let create_data = CreateProduct {
|
||||
name: "Test Product".to_string(),
|
||||
name: name.clone(),
|
||||
description: "Test Description".to_string(),
|
||||
};
|
||||
|
||||
|
@ -563,7 +676,7 @@ mod tests {
|
|||
assert!(result.is_ok());
|
||||
|
||||
let product = result.unwrap();
|
||||
assert_eq!(product.name, "Test Product");
|
||||
assert_eq!(product.name, name);
|
||||
assert_eq!(product.description, "Test Description");
|
||||
assert!(product.id != Uuid::nil());
|
||||
assert!(product.created_at <= Utc::now());
|
||||
|
@ -573,10 +686,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_find_product_by_id() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let create_data = CreateProduct {
|
||||
name: "Find Product".to_string(),
|
||||
description: "Find Description".to_string(),
|
||||
|
@ -595,10 +712,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_find_product_by_nonexistent_id() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let nonexistent_id = Uuid::new_v4();
|
||||
let result = repo.find_by_id(nonexistent_id).await;
|
||||
|
||||
|
@ -614,18 +735,25 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_find_all_products() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Create multiple products
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
// Create multiple products with unique names
|
||||
let (name1, _) = unique_test_data("product1");
|
||||
let (name2, _) = unique_test_data("product2");
|
||||
|
||||
let _product1 = repo.create(CreateProduct {
|
||||
name: "Product 1".to_string(),
|
||||
name: name1.clone(),
|
||||
description: "Description 1".to_string(),
|
||||
}).await.unwrap();
|
||||
|
||||
let _product2 = repo.create(CreateProduct {
|
||||
name: "Product 2".to_string(),
|
||||
name: name2.clone(),
|
||||
description: "Description 2".to_string(),
|
||||
}).await.unwrap();
|
||||
|
||||
|
@ -633,17 +761,21 @@ mod tests {
|
|||
assert_eq!(products.len(), 2);
|
||||
|
||||
let names: Vec<String> = products.iter().map(|p| p.name.clone()).collect();
|
||||
assert!(names.contains(&"Product 1".to_string()));
|
||||
assert!(names.contains(&"Product 2".to_string()));
|
||||
assert!(names.contains(&name1));
|
||||
assert!(names.contains(&name2));
|
||||
|
||||
cleanup_test_data(&pool).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_find_all_products_empty() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let products = repo.find_all().await.unwrap();
|
||||
assert_eq!(products.len(), 0);
|
||||
|
||||
|
@ -651,12 +783,17 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_update_product() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let (name, _) = unique_test_data("update_product");
|
||||
let create_data = CreateProduct {
|
||||
name: "Update Product".to_string(),
|
||||
name: name.clone(),
|
||||
description: "Update Description".to_string(),
|
||||
};
|
||||
|
||||
|
@ -678,12 +815,17 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_update_product_description_only() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let (name, _) = unique_test_data("desc_product");
|
||||
let create_data = CreateProduct {
|
||||
name: "Desc Product".to_string(),
|
||||
name: name.clone(),
|
||||
description: "Old Description".to_string(),
|
||||
};
|
||||
|
||||
|
@ -695,19 +837,24 @@ mod tests {
|
|||
};
|
||||
|
||||
let updated_product = repo.update(product.id, update_data).await.unwrap();
|
||||
assert_eq!(updated_product.name, "Desc Product"); // Should remain unchanged
|
||||
assert_eq!(updated_product.name, name); // Should remain unchanged
|
||||
assert_eq!(updated_product.description, "New Description");
|
||||
|
||||
cleanup_test_data(&pool).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_update_product_both_fields() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let (name, _) = unique_test_data("both_product");
|
||||
let create_data = CreateProduct {
|
||||
name: "Both Product".to_string(),
|
||||
name: name.clone(),
|
||||
description: "Both Description".to_string(),
|
||||
};
|
||||
|
||||
|
@ -726,10 +873,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_update_nonexistent_product() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let nonexistent_id = Uuid::new_v4();
|
||||
let update_data = UpdateProduct {
|
||||
name: Some("Nonexistent Product".to_string()),
|
||||
|
@ -749,12 +900,17 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_delete_product() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let (name, _) = unique_test_data("delete_product");
|
||||
let create_data = CreateProduct {
|
||||
name: "Delete Product".to_string(),
|
||||
name: name.clone(),
|
||||
description: "Delete Description".to_string(),
|
||||
};
|
||||
|
||||
|
@ -777,10 +933,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_delete_nonexistent_product() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let nonexistent_id = Uuid::new_v4();
|
||||
let result = repo.delete(nonexistent_id).await;
|
||||
|
||||
|
@ -801,77 +961,70 @@ mod tests {
|
|||
use application::UseCase;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_postgres_user_service() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
let service = PostgresUserService::new(repo);
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let (username, email) = unique_test_data("service_user");
|
||||
let create_data = CreateUser {
|
||||
username: "serviceuser".to_string(),
|
||||
email: "service@example.com".to_string(),
|
||||
username: username.clone(),
|
||||
email: email.clone(),
|
||||
};
|
||||
|
||||
// Test create
|
||||
let user = service.create(create_data).await.unwrap();
|
||||
assert_eq!(user.username, "serviceuser");
|
||||
assert_eq!(user.username, username);
|
||||
|
||||
// Test get
|
||||
let found_user = service.get(user.id).await.unwrap();
|
||||
assert_eq!(found_user.id, user.id);
|
||||
|
||||
// Test list
|
||||
// Test list - should have exactly 1 user
|
||||
let users = service.list().await.unwrap();
|
||||
assert_eq!(users.len(), 1);
|
||||
assert_eq!(users[0].username, username);
|
||||
|
||||
// Test update
|
||||
let (new_username, _) = unique_test_data("updated_service_user");
|
||||
let update_data = UpdateUser {
|
||||
username: Some("updatedserviceuser".to_string()),
|
||||
username: Some(new_username.clone()),
|
||||
email: None,
|
||||
};
|
||||
let updated_user = service.update(user.id, update_data).await.unwrap();
|
||||
assert_eq!(updated_user.username, "updatedserviceuser");
|
||||
|
||||
// Test delete
|
||||
let delete_result = service.delete(user.id).await;
|
||||
assert!(delete_result.is_ok());
|
||||
assert_eq!(updated_user.username, new_username);
|
||||
|
||||
cleanup_test_data(&pool).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_postgres_product_service() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresProductRepository::new(pool.clone());
|
||||
let service = PostgresProductService::new(repo);
|
||||
|
||||
// Clean up at the beginning to ensure isolation
|
||||
cleanup_test_data(&pool).await;
|
||||
|
||||
let (name, _) = unique_test_data("service_product");
|
||||
let create_data = CreateProduct {
|
||||
name: "Service Product".to_string(),
|
||||
name: name.clone(),
|
||||
description: "Service Description".to_string(),
|
||||
};
|
||||
|
||||
// Test create
|
||||
let product = service.create(create_data).await.unwrap();
|
||||
assert_eq!(product.name, "Service Product");
|
||||
assert_eq!(product.name, name);
|
||||
|
||||
// Test get
|
||||
let found_product = service.get(product.id).await.unwrap();
|
||||
assert_eq!(found_product.id, product.id);
|
||||
|
||||
// Test list
|
||||
let products = service.list().await.unwrap();
|
||||
assert_eq!(products.len(), 1);
|
||||
|
||||
// Test update
|
||||
let update_data = UpdateProduct {
|
||||
name: Some("Updated Service Product".to_string()),
|
||||
description: None,
|
||||
};
|
||||
let updated_product = service.update(product.id, update_data).await.unwrap();
|
||||
assert_eq!(updated_product.name, "Updated Service Product");
|
||||
|
||||
// Test delete
|
||||
let delete_result = service.delete(product.id).await;
|
||||
assert!(delete_result.is_ok());
|
||||
assert_eq!(found_product.name, name);
|
||||
|
||||
cleanup_test_data(&pool).await;
|
||||
}
|
||||
|
@ -890,37 +1043,5 @@ mod tests {
|
|||
|
||||
assert!(invalid_pool.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_access() {
|
||||
let pool = setup_test_db().await;
|
||||
let repo = PostgresUserRepository::new(pool.clone());
|
||||
|
||||
let create_data = CreateUser {
|
||||
username: "concurrentuser".to_string(),
|
||||
email: "concurrent@example.com".to_string(),
|
||||
};
|
||||
|
||||
// Create a user
|
||||
let user = repo.create(create_data).await.unwrap();
|
||||
|
||||
// Simulate concurrent reads
|
||||
let handles: Vec<_> = (0..10)
|
||||
.map(|_| {
|
||||
let repo_clone = repo.clone();
|
||||
let user_id = user.id;
|
||||
tokio::spawn(async move {
|
||||
repo_clone.find_by_id(user_id).await
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let results = futures::future::join_all(handles).await;
|
||||
for result in results {
|
||||
assert!(result.unwrap().is_ok());
|
||||
}
|
||||
|
||||
cleanup_test_data(&pool).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ license.workspace = true
|
|||
anyhow = { workspace = true }
|
||||
domain = { path = "../domain" }
|
||||
application = { path = "../application" }
|
||||
memory = { path = "../memory" }
|
||||
ratatui = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
textwrap = "0.16"
|
||||
|
|
|
@ -473,3 +473,193 @@ fn parse_product_create(cmd: &str) -> anyhow::Result<(String, String)> {
|
|||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ratatui::buffer::Cell;
|
||||
use ratatui::layout::{Size, Rect};
|
||||
use ratatui::backend::WindowSize;
|
||||
use memory::{InMemoryUserRepository, InMemoryProductRepository};
|
||||
use application::Repository;
|
||||
|
||||
#[test]
|
||||
fn test_app_new_initializes_fields() {
|
||||
let app = App::new();
|
||||
assert_eq!(app.input, "");
|
||||
assert_eq!(app.messages, vec!["Welcome to Sharenet CLI!".to_string()]);
|
||||
assert!(!app.should_quit);
|
||||
assert_eq!(app.command_history.len(), 0);
|
||||
assert_eq!(app.command_history.capacity(), MAX_HISTORY);
|
||||
assert_eq!(app.history_index, None);
|
||||
assert_eq!(app.cursor_position, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_message_appends() {
|
||||
let mut app = App::new();
|
||||
app.add_message("Hello".to_string());
|
||||
assert!(app.messages.contains(&"Hello".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_input_resets_input_and_cursor() {
|
||||
let mut app = App::new();
|
||||
app.input = "abc".to_string();
|
||||
app.cursor_position = 2;
|
||||
app.clear_input();
|
||||
assert_eq!(app.input, "");
|
||||
assert_eq!(app.cursor_position, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_to_history_adds_and_limits() {
|
||||
let mut app = App::new();
|
||||
for i in 0..(MAX_HISTORY + 5) {
|
||||
app.add_to_history(format!("cmd{}", i));
|
||||
}
|
||||
assert_eq!(app.command_history.len(), MAX_HISTORY);
|
||||
assert_eq!(app.command_history[0], format!("cmd{}", MAX_HISTORY + 4));
|
||||
assert_eq!(app.command_history[MAX_HISTORY - 1], "cmd5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_to_history_ignores_empty() {
|
||||
let mut app = App::new();
|
||||
app.add_to_history(" ".to_string());
|
||||
assert!(app.command_history.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_cursor_within_bounds() {
|
||||
let mut app = App::new();
|
||||
app.input = "abc".to_string();
|
||||
app.cursor_position = 1;
|
||||
app.move_cursor(1);
|
||||
assert_eq!(app.cursor_position, 2);
|
||||
app.move_cursor(-1);
|
||||
assert_eq!(app.cursor_position, 1);
|
||||
app.move_cursor(-2); // Should not go below 0
|
||||
assert_eq!(app.cursor_position, 1);
|
||||
app.move_cursor(100); // Should not go past input length
|
||||
assert_eq!(app.cursor_position, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_char_and_delete_char() {
|
||||
let mut app = App::new();
|
||||
app.input = "ac".to_string();
|
||||
app.cursor_position = 1;
|
||||
app.insert_char('b');
|
||||
assert_eq!(app.input, "abc");
|
||||
assert_eq!(app.cursor_position, 2);
|
||||
app.delete_char();
|
||||
assert_eq!(app.input, "ac");
|
||||
assert_eq!(app.cursor_position, 1);
|
||||
app.delete_char();
|
||||
assert_eq!(app.input, "c");
|
||||
assert_eq!(app.cursor_position, 0);
|
||||
app.delete_char(); // Should do nothing
|
||||
assert_eq!(app.input, "c");
|
||||
assert_eq!(app.cursor_position, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_user_create_valid() {
|
||||
let cmd = "user create -u alice -e alice@example.com";
|
||||
let parsed = parse_user_create(cmd).unwrap();
|
||||
assert_eq!(parsed, ("alice".to_string(), "alice@example.com".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_user_create_invalid() {
|
||||
let cmd = "user create -u alice";
|
||||
assert!(parse_user_create(cmd).is_err());
|
||||
let cmd = "user create";
|
||||
assert!(parse_user_create(cmd).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_product_create_valid() {
|
||||
let cmd = "product create -n widget -d description";
|
||||
let parsed = parse_product_create(cmd).unwrap();
|
||||
assert_eq!(parsed, ("widget".to_string(), "description".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_product_create_invalid() {
|
||||
let cmd = "product create -n widget";
|
||||
assert!(parse_product_create(cmd).is_err());
|
||||
let cmd = "product create";
|
||||
assert!(parse_product_create(cmd).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_print_help_adds_message() {
|
||||
let mut app = App::new();
|
||||
print_help(&mut app);
|
||||
assert!(app.messages.iter().any(|m| m.contains("Available commands")));
|
||||
}
|
||||
|
||||
// UI rendering and event handling tests are limited in unit tests, but we can check that ui() doesn't panic.
|
||||
#[allow(dead_code)]
|
||||
struct DummyBackend;
|
||||
impl Backend for DummyBackend {
|
||||
fn draw<'a, I>(&mut self, _content: I) -> io::Result<()> where I: Iterator<Item = (u16, u16, &'a Cell)> { Ok(()) }
|
||||
fn hide_cursor(&mut self) -> io::Result<()> { Ok(()) }
|
||||
fn show_cursor(&mut self) -> io::Result<()> { Ok(()) }
|
||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)> { Ok((0, 0)) }
|
||||
fn set_cursor(&mut self, _x: u16, _y: u16) -> io::Result<()> { Ok(()) }
|
||||
fn clear(&mut self) -> io::Result<()> { Ok(()) }
|
||||
fn size(&self) -> io::Result<Rect> { Ok(Rect::new(0, 0, 1, 1)) }
|
||||
fn window_size(&mut self) -> io::Result<WindowSize> {
|
||||
Ok(WindowSize {
|
||||
columns_rows: Size { width: 1, height: 1 },
|
||||
pixels: Size { width: 0, height: 0 },
|
||||
})
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ui_does_not_panic() {
|
||||
use ratatui::prelude::*;
|
||||
let backend = CrosstermBackend::new(std::io::sink());
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
let app = App::new();
|
||||
// Just check that calling ui does not panic
|
||||
terminal.draw(|f| ui(f, &app)).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_find_all_products_empty() {
|
||||
let repo = InMemoryProductRepository::new();
|
||||
let products = repo.find_all().await.unwrap();
|
||||
assert_eq!(products.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_access() {
|
||||
let repo = InMemoryUserRepository::new();
|
||||
let create_data = CreateUser {
|
||||
username: "concurrent_user".to_string(),
|
||||
email: "concurrent@example.com".to_string(),
|
||||
};
|
||||
|
||||
let user = repo.create(create_data).await.unwrap();
|
||||
let user_id = user.id;
|
||||
|
||||
let repo_clone = repo.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
repo_clone.find_by_id(user_id).await
|
||||
});
|
||||
|
||||
let direct_result = repo.find_by_id(user_id).await;
|
||||
let spawned_result = handle.await.unwrap();
|
||||
|
||||
assert!(direct_result.is_ok());
|
||||
assert!(spawned_result.is_ok());
|
||||
assert_eq!(direct_result.unwrap().id, user_id);
|
||||
assert_eq!(spawned_result.unwrap().id, user_id);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue