feat: 实现数据库迁移、搜索和分页功能
- 添加数据库迁移系统和初始用户表迁移 - 实现搜索功能模块和API - 实现分页功能支持 - 添加相关测试文件 - 更新项目配置和文档
This commit is contained in:
373
tests/search_tests.rs
Normal file
373
tests/search_tests.rs
Normal file
@@ -0,0 +1,373 @@
|
||||
//! 搜索功能专项测试
|
||||
|
||||
use rust_user_api::{
|
||||
models::{
|
||||
user::User,
|
||||
search::UserSearchParams,
|
||||
pagination::PaginationParams,
|
||||
},
|
||||
storage::{database::DatabaseUserStore, memory::MemoryUserStore, UserStore},
|
||||
utils::errors::ApiError,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
|
||||
/// 创建测试用户
|
||||
fn create_test_user(username: &str, email: &str) -> User {
|
||||
User {
|
||||
id: Uuid::new_v4(),
|
||||
username: username.to_string(),
|
||||
email: email.to_string(),
|
||||
password_hash: "hashed_password".to_string(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建多个测试用户用于搜索
|
||||
async fn create_search_test_users(store: &dyn UserStore) -> Vec<User> {
|
||||
let users = vec![
|
||||
create_test_user("admin_user", "admin@company.com"),
|
||||
create_test_user("john_doe", "john@example.com"),
|
||||
create_test_user("jane_smith", "jane@company.com"),
|
||||
create_test_user("admin_root", "root@admin.com"),
|
||||
create_test_user("test_user", "test@example.com"),
|
||||
create_test_user("manager_bob", "bob@company.com"),
|
||||
];
|
||||
|
||||
let mut created_users = Vec::new();
|
||||
for user in users {
|
||||
let created = store.create_user(user).await.unwrap();
|
||||
created_users.push(created);
|
||||
// 添加小延迟确保创建时间不同
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||
}
|
||||
|
||||
created_users
|
||||
}
|
||||
|
||||
/// 测试内存存储的通用搜索功能
|
||||
#[tokio::test]
|
||||
async fn test_memory_store_general_search() {
|
||||
let store = MemoryUserStore::new();
|
||||
let _users = create_search_test_users(&store).await;
|
||||
|
||||
// 搜索包含"admin"的用户
|
||||
let search_params = UserSearchParams {
|
||||
q: Some("admin".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(10),
|
||||
};
|
||||
|
||||
let (results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 2, "应该找到2个包含admin的用户");
|
||||
assert_eq!(results.len(), 2, "结果应该包含2个用户");
|
||||
|
||||
// 验证搜索结果
|
||||
for user in &results {
|
||||
assert!(
|
||||
user.username.contains("admin") || user.email.contains("admin"),
|
||||
"搜索结果应该包含admin关键词"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试数据库存储的通用搜索功能
|
||||
#[tokio::test]
|
||||
async fn test_database_store_general_search() {
|
||||
let database_url = "sqlite::memory:";
|
||||
let store = DatabaseUserStore::from_url(database_url).await.unwrap();
|
||||
let _users = create_search_test_users(&store).await;
|
||||
|
||||
// 搜索包含"company"的用户
|
||||
let search_params = UserSearchParams {
|
||||
q: Some("company".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(10),
|
||||
};
|
||||
|
||||
let (results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 3, "应该找到3个包含company的用户");
|
||||
assert_eq!(results.len(), 3, "结果应该包含3个用户");
|
||||
|
||||
// 验证搜索结果
|
||||
for user in &results {
|
||||
assert!(
|
||||
user.username.contains("company") || user.email.contains("company"),
|
||||
"搜索结果应该包含company关键词"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试用户名过滤
|
||||
#[tokio::test]
|
||||
async fn test_username_filter() {
|
||||
let store = MemoryUserStore::new();
|
||||
let _users = create_search_test_users(&store).await;
|
||||
|
||||
// 按用户名过滤
|
||||
let search_params = UserSearchParams {
|
||||
username: Some("admin".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(10),
|
||||
};
|
||||
|
||||
let (results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 2, "应该找到2个用户名包含admin的用户");
|
||||
|
||||
for user in &results {
|
||||
assert!(
|
||||
user.username.to_lowercase().contains("admin"),
|
||||
"用户名应该包含admin"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试邮箱过滤
|
||||
#[tokio::test]
|
||||
async fn test_email_filter() {
|
||||
let store = MemoryUserStore::new();
|
||||
let _users = create_search_test_users(&store).await;
|
||||
|
||||
// 按邮箱域名过滤
|
||||
let search_params = UserSearchParams {
|
||||
email: Some("example.com".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(10),
|
||||
};
|
||||
|
||||
let (results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 2, "应该找到2个example.com域名的用户");
|
||||
|
||||
for user in &results {
|
||||
assert!(
|
||||
user.email.contains("example.com"),
|
||||
"邮箱应该包含example.com"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试排序功能
|
||||
#[tokio::test]
|
||||
async fn test_search_sorting() {
|
||||
let store = MemoryUserStore::new();
|
||||
let _users = create_search_test_users(&store).await;
|
||||
|
||||
// 按用户名升序排序
|
||||
let search_params = UserSearchParams {
|
||||
sort_by: Some("username".to_string()),
|
||||
sort_order: Some("asc".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(10),
|
||||
};
|
||||
|
||||
let (results, _) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
// 验证排序
|
||||
for i in 0..results.len() - 1 {
|
||||
assert!(
|
||||
results[i].username <= results[i + 1].username,
|
||||
"结果应该按用户名升序排列"
|
||||
);
|
||||
}
|
||||
|
||||
// 测试降序排序
|
||||
let search_params = UserSearchParams {
|
||||
sort_by: Some("username".to_string()),
|
||||
sort_order: Some("desc".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (results, _) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
// 验证降序排序
|
||||
for i in 0..results.len() - 1 {
|
||||
assert!(
|
||||
results[i].username >= results[i + 1].username,
|
||||
"结果应该按用户名降序排列"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试搜索参数验证
|
||||
#[tokio::test]
|
||||
async fn test_search_params_validation() {
|
||||
let search_params = UserSearchParams {
|
||||
sort_by: Some("invalid_field".to_string()),
|
||||
sort_order: Some("asc".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(!search_params.is_valid_sort_field(), "无效的排序字段应该被拒绝");
|
||||
|
||||
let search_params = UserSearchParams {
|
||||
sort_by: Some("username".to_string()),
|
||||
sort_order: Some("invalid_order".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(!search_params.is_valid_sort_order(), "无效的排序方向应该被拒绝");
|
||||
|
||||
// 测试有效参数
|
||||
let search_params = UserSearchParams {
|
||||
sort_by: Some("username".to_string()),
|
||||
sort_order: Some("asc".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(search_params.is_valid_sort_field(), "有效的排序字段应该被接受");
|
||||
assert!(search_params.is_valid_sort_order(), "有效的排序方向应该被接受");
|
||||
}
|
||||
|
||||
/// 测试搜索分页功能
|
||||
#[tokio::test]
|
||||
async fn test_search_with_pagination() {
|
||||
let store = MemoryUserStore::new();
|
||||
let _users = create_search_test_users(&store).await;
|
||||
|
||||
// 搜索所有用户,第一页
|
||||
let search_params = UserSearchParams::default();
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(3),
|
||||
};
|
||||
|
||||
let (results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 6, "总共应该有6个用户");
|
||||
assert_eq!(results.len(), 3, "第一页应该有3个用户");
|
||||
|
||||
// 第二页
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(2),
|
||||
limit: Some(3),
|
||||
};
|
||||
|
||||
let (results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 6, "总数应该保持一致");
|
||||
assert_eq!(results.len(), 3, "第二页应该有3个用户");
|
||||
|
||||
// 第三页(超出范围)
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(3),
|
||||
limit: Some(3),
|
||||
};
|
||||
|
||||
let (results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 6, "总数应该保持一致");
|
||||
assert_eq!(results.len(), 0, "第三页应该没有用户");
|
||||
}
|
||||
|
||||
/// 测试复合搜索条件
|
||||
#[tokio::test]
|
||||
async fn test_complex_search() {
|
||||
let store = MemoryUserStore::new();
|
||||
let _users = create_search_test_users(&store).await;
|
||||
|
||||
// 搜索用户名包含"admin"且邮箱包含"company"的用户
|
||||
let search_params = UserSearchParams {
|
||||
username: Some("admin".to_string()),
|
||||
email: Some("company".to_string()),
|
||||
sort_by: Some("username".to_string()),
|
||||
sort_order: Some("asc".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(10),
|
||||
};
|
||||
|
||||
let (results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 1, "应该找到1个同时满足条件的用户");
|
||||
assert_eq!(results.len(), 1, "结果应该包含1个用户");
|
||||
|
||||
let user = &results[0];
|
||||
assert!(user.username.contains("admin"), "用户名应该包含admin");
|
||||
assert!(user.email.contains("company"), "邮箱应该包含company");
|
||||
}
|
||||
|
||||
/// 测试空搜索结果
|
||||
#[tokio::test]
|
||||
async fn test_empty_search_results() {
|
||||
let store = MemoryUserStore::new();
|
||||
let _users = create_search_test_users(&store).await;
|
||||
|
||||
// 搜索不存在的关键词
|
||||
let search_params = UserSearchParams {
|
||||
q: Some("nonexistent".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(10),
|
||||
};
|
||||
|
||||
let (results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 0, "应该没有找到任何用户");
|
||||
assert_eq!(results.len(), 0, "结果应该为空");
|
||||
}
|
||||
|
||||
/// 测试搜索参数默认值
|
||||
#[tokio::test]
|
||||
async fn test_search_params_defaults() {
|
||||
let search_params = UserSearchParams::default();
|
||||
|
||||
assert_eq!(search_params.get_sort_by(), "created_at", "默认排序字段应该是created_at");
|
||||
assert_eq!(search_params.get_sort_order(), "desc", "默认排序方向应该是desc");
|
||||
assert!(!search_params.has_filters(), "默认参数不应该有过滤条件");
|
||||
}
|
||||
|
||||
/// 测试搜索参数的has_filters方法
|
||||
#[tokio::test]
|
||||
async fn test_search_params_has_filters() {
|
||||
let search_params = UserSearchParams {
|
||||
q: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
assert!(search_params.has_filters(), "有搜索关键词时应该返回true");
|
||||
|
||||
let search_params = UserSearchParams {
|
||||
username: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
assert!(search_params.has_filters(), "有用户名过滤时应该返回true");
|
||||
|
||||
let search_params = UserSearchParams {
|
||||
email: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
assert!(search_params.has_filters(), "有邮箱过滤时应该返回true");
|
||||
|
||||
let search_params = UserSearchParams::default();
|
||||
assert!(!search_params.has_filters(), "默认参数应该返回false");
|
||||
}
|
Reference in New Issue
Block a user