feat: 实现数据库迁移、搜索和分页功能
- 添加数据库迁移系统和初始用户表迁移 - 实现搜索功能模块和API - 实现分页功能支持 - 添加相关测试文件 - 更新项目配置和文档
This commit is contained in:
275
tests/database_tests.rs
Normal file
275
tests/database_tests.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
//! SQLite 数据库存储测试
|
||||
|
||||
use rust_user_api::{
|
||||
models::user::User,
|
||||
storage::{database::DatabaseUserStore, UserStore},
|
||||
utils::errors::ApiError,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
use tempfile::tempdir;
|
||||
|
||||
/// 创建临时数据库用于测试
|
||||
async fn create_test_database() -> Result<DatabaseUserStore, ApiError> {
|
||||
// 使用内存数据库避免文件系统问题
|
||||
let database_url = "sqlite::memory:";
|
||||
DatabaseUserStore::from_url(database_url).await
|
||||
}
|
||||
|
||||
/// 创建测试用户
|
||||
fn create_test_user() -> User {
|
||||
User {
|
||||
id: Uuid::new_v4(),
|
||||
username: "testuser".to_string(),
|
||||
email: "test@example.com".to_string(),
|
||||
password_hash: "hashed_password".to_string(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_connection_and_initialization() {
|
||||
let store = create_test_database().await;
|
||||
assert!(store.is_ok(), "Failed to create database store");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_create_user() {
|
||||
let store = create_test_database().await.unwrap();
|
||||
let user = create_test_user();
|
||||
|
||||
let result = store.create_user(user.clone()).await;
|
||||
assert!(result.is_ok(), "Failed to create user: {:?}", result.err());
|
||||
|
||||
let created_user = result.unwrap();
|
||||
assert_eq!(created_user.username, user.username);
|
||||
assert_eq!(created_user.email, user.email);
|
||||
assert_eq!(created_user.id, user.id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_get_user_by_id() {
|
||||
let store = create_test_database().await.unwrap();
|
||||
let user = create_test_user();
|
||||
let user_id = user.id;
|
||||
|
||||
// 先创建用户
|
||||
store.create_user(user.clone()).await.unwrap();
|
||||
|
||||
// 然后获取用户
|
||||
let result = store.get_user(&user_id).await;
|
||||
assert!(result.is_ok(), "Failed to get user: {:?}", result.err());
|
||||
|
||||
let retrieved_user = result.unwrap();
|
||||
assert!(retrieved_user.is_some(), "User not found");
|
||||
|
||||
let retrieved_user = retrieved_user.unwrap();
|
||||
assert_eq!(retrieved_user.id, user_id);
|
||||
assert_eq!(retrieved_user.username, user.username);
|
||||
assert_eq!(retrieved_user.email, user.email);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_get_user_by_username() {
|
||||
let store = create_test_database().await.unwrap();
|
||||
let user = create_test_user();
|
||||
let username = user.username.clone();
|
||||
|
||||
// 先创建用户
|
||||
store.create_user(user.clone()).await.unwrap();
|
||||
|
||||
// 然后按用户名获取用户
|
||||
let result = store.get_user_by_username(&username).await;
|
||||
assert!(result.is_ok(), "Failed to get user by username: {:?}", result.err());
|
||||
|
||||
let retrieved_user = result.unwrap();
|
||||
assert!(retrieved_user.is_some(), "User not found by username");
|
||||
|
||||
let retrieved_user = retrieved_user.unwrap();
|
||||
assert_eq!(retrieved_user.username, username);
|
||||
assert_eq!(retrieved_user.id, user.id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_list_users() {
|
||||
let store = create_test_database().await.unwrap();
|
||||
|
||||
// 创建多个用户
|
||||
let user1 = User {
|
||||
id: Uuid::new_v4(),
|
||||
username: "user1".to_string(),
|
||||
email: "user1@example.com".to_string(),
|
||||
password_hash: "hashed_password1".to_string(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
};
|
||||
|
||||
let user2 = User {
|
||||
id: Uuid::new_v4(),
|
||||
username: "user2".to_string(),
|
||||
email: "user2@example.com".to_string(),
|
||||
password_hash: "hashed_password2".to_string(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
};
|
||||
|
||||
store.create_user(user1.clone()).await.unwrap();
|
||||
store.create_user(user2.clone()).await.unwrap();
|
||||
|
||||
// 获取所有用户
|
||||
let result = store.list_users().await;
|
||||
assert!(result.is_ok(), "Failed to list users: {:?}", result.err());
|
||||
|
||||
let users = result.unwrap();
|
||||
assert_eq!(users.len(), 2, "Expected 2 users, got {}", users.len());
|
||||
|
||||
// 验证用户存在
|
||||
let usernames: Vec<String> = users.iter().map(|u| u.username.clone()).collect();
|
||||
assert!(usernames.contains(&"user1".to_string()));
|
||||
assert!(usernames.contains(&"user2".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_update_user() {
|
||||
let store = create_test_database().await.unwrap();
|
||||
let mut user = create_test_user();
|
||||
let user_id = user.id;
|
||||
|
||||
// 先创建用户
|
||||
store.create_user(user.clone()).await.unwrap();
|
||||
|
||||
// 更新用户信息
|
||||
user.username = "updated_user".to_string();
|
||||
user.email = "updated@example.com".to_string();
|
||||
user.updated_at = Utc::now();
|
||||
|
||||
let result = store.update_user(&user_id, user.clone()).await;
|
||||
assert!(result.is_ok(), "Failed to update user: {:?}", result.err());
|
||||
|
||||
let updated_user = result.unwrap();
|
||||
assert!(updated_user.is_some(), "Updated user not returned");
|
||||
|
||||
let updated_user = updated_user.unwrap();
|
||||
assert_eq!(updated_user.username, "updated_user");
|
||||
assert_eq!(updated_user.email, "updated@example.com");
|
||||
|
||||
// 验证数据库中的用户确实被更新了
|
||||
let retrieved_user = store.get_user(&user_id).await.unwrap().unwrap();
|
||||
assert_eq!(retrieved_user.username, "updated_user");
|
||||
assert_eq!(retrieved_user.email, "updated@example.com");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_delete_user() {
|
||||
let store = create_test_database().await.unwrap();
|
||||
let user = create_test_user();
|
||||
let user_id = user.id;
|
||||
|
||||
// 先创建用户
|
||||
store.create_user(user.clone()).await.unwrap();
|
||||
|
||||
// 验证用户存在
|
||||
let user_exists = store.get_user(&user_id).await.unwrap();
|
||||
assert!(user_exists.is_some(), "User should exist before deletion");
|
||||
|
||||
// 删除用户
|
||||
let result = store.delete_user(&user_id).await;
|
||||
assert!(result.is_ok(), "Failed to delete user: {:?}", result.err());
|
||||
assert!(result.unwrap(), "Delete operation should return true");
|
||||
|
||||
// 验证用户已被删除
|
||||
let user_after_delete = store.get_user(&user_id).await.unwrap();
|
||||
assert!(user_after_delete.is_none(), "User should not exist after deletion");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_duplicate_username_constraint() {
|
||||
let store = create_test_database().await.unwrap();
|
||||
|
||||
let user1 = User {
|
||||
id: Uuid::new_v4(),
|
||||
username: "duplicate_test".to_string(),
|
||||
email: "test1@example.com".to_string(),
|
||||
password_hash: "hashed_password1".to_string(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
};
|
||||
|
||||
let user2 = User {
|
||||
id: Uuid::new_v4(),
|
||||
username: "duplicate_test".to_string(), // 相同用户名
|
||||
email: "test2@example.com".to_string(),
|
||||
password_hash: "hashed_password2".to_string(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
};
|
||||
|
||||
// 创建第一个用户应该成功
|
||||
let result1 = store.create_user(user1).await;
|
||||
assert!(result1.is_ok(), "First user creation should succeed");
|
||||
|
||||
// 创建第二个用户应该失败(用户名重复)
|
||||
let result2 = store.create_user(user2).await;
|
||||
assert!(result2.is_err(), "Second user creation should fail due to duplicate username");
|
||||
|
||||
if let Err(ApiError::Conflict(msg)) = result2 {
|
||||
assert!(msg.contains("用户名已存在"), "Error message should mention duplicate username");
|
||||
} else {
|
||||
panic!("Expected Conflict error for duplicate username, got: {:?}", result2);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_nonexistent_user_operations() {
|
||||
let store = create_test_database().await.unwrap();
|
||||
let nonexistent_id = Uuid::new_v4();
|
||||
|
||||
// 获取不存在的用户
|
||||
let result = store.get_user(&nonexistent_id).await;
|
||||
assert!(result.is_ok(), "Getting nonexistent user should not error");
|
||||
assert!(result.unwrap().is_none(), "Nonexistent user should return None");
|
||||
|
||||
// 按用户名获取不存在的用户
|
||||
let result = store.get_user_by_username("nonexistent_user").await;
|
||||
assert!(result.is_ok(), "Getting nonexistent user by username should not error");
|
||||
assert!(result.unwrap().is_none(), "Nonexistent user should return None");
|
||||
|
||||
// 更新不存在的用户
|
||||
let fake_user = create_test_user();
|
||||
let result = store.update_user(&nonexistent_id, fake_user).await;
|
||||
assert!(result.is_ok(), "Updating nonexistent user should not error");
|
||||
assert!(result.unwrap().is_none(), "Updating nonexistent user should return None");
|
||||
|
||||
// 删除不存在的用户
|
||||
let result = store.delete_user(&nonexistent_id).await;
|
||||
assert!(result.is_ok(), "Deleting nonexistent user should not error");
|
||||
assert!(!result.unwrap(), "Deleting nonexistent user should return false");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_data_persistence() {
|
||||
let temp_dir = tempdir().expect("Failed to create temp directory");
|
||||
let db_path = temp_dir.path().join("persistence_test.db");
|
||||
let database_url = format!("sqlite://{}?mode=rwc", db_path.display());
|
||||
|
||||
let user = create_test_user();
|
||||
let user_id = user.id;
|
||||
|
||||
// 第一次连接:创建用户
|
||||
{
|
||||
let store = DatabaseUserStore::from_url(&database_url).await.unwrap();
|
||||
store.create_user(user.clone()).await.unwrap();
|
||||
}
|
||||
|
||||
// 第二次连接:验证用户仍然存在
|
||||
{
|
||||
let store = DatabaseUserStore::from_url(&database_url).await.unwrap();
|
||||
let retrieved_user = store.get_user(&user_id).await.unwrap();
|
||||
assert!(retrieved_user.is_some(), "User should persist across connections");
|
||||
|
||||
let retrieved_user = retrieved_user.unwrap();
|
||||
assert_eq!(retrieved_user.username, user.username);
|
||||
assert_eq!(retrieved_user.email, user.email);
|
||||
}
|
||||
}
|
88
tests/migration_tests.rs
Normal file
88
tests/migration_tests.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
//! 迁移系统测试
|
||||
|
||||
use rust_user_api::storage::{database::DatabaseUserStore, MigrationManager, UserStore};
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_migration_system_integration() {
|
||||
// 创建临时数据库
|
||||
let temp_dir = tempdir().expect("Failed to create temp directory");
|
||||
let db_path = temp_dir.path().join("migration_test.db");
|
||||
let database_url = format!("sqlite://{}?mode=rwc", db_path.display());
|
||||
|
||||
// 创建数据库存储(这会自动运行迁移)
|
||||
let store = DatabaseUserStore::from_url(&database_url)
|
||||
.await
|
||||
.expect("Failed to create database store with migrations");
|
||||
|
||||
// 验证迁移系统创建了正确的表结构
|
||||
// 通过尝试创建用户来验证表结构正确
|
||||
let user = rust_user_api::models::user::User {
|
||||
id: uuid::Uuid::new_v4(),
|
||||
username: "migration_test_user".to_string(),
|
||||
email: "migration_test@example.com".to_string(),
|
||||
password_hash: "hashed_password".to_string(),
|
||||
created_at: chrono::Utc::now(),
|
||||
updated_at: chrono::Utc::now(),
|
||||
};
|
||||
|
||||
let result = store.create_user(user).await;
|
||||
assert!(result.is_ok(), "Failed to create user with migrated database: {:?}", result.err());
|
||||
|
||||
println!("✅ 迁移系统集成测试通过");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_migration_manager_directly() {
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
// 创建内存数据库
|
||||
let pool = SqlitePool::connect("sqlite::memory:")
|
||||
.await
|
||||
.expect("Failed to create test pool");
|
||||
|
||||
let migration_manager = MigrationManager::new(pool.clone());
|
||||
|
||||
// 运行迁移
|
||||
let result = migration_manager.run_migrations().await;
|
||||
assert!(result.is_ok(), "Failed to run migrations: {:?}", result.err());
|
||||
|
||||
// 检查当前版本
|
||||
let version = migration_manager.get_current_version().await.unwrap();
|
||||
assert!(version.is_some(), "No migration version found");
|
||||
assert_eq!(version.unwrap(), 1, "Expected migration version 1");
|
||||
|
||||
// 检查迁移状态
|
||||
let status = migration_manager.get_migration_status().await.unwrap();
|
||||
assert_eq!(status.len(), 1, "Expected 1 executed migration");
|
||||
assert_eq!(status[0].0, 1, "Expected migration version 1");
|
||||
assert_eq!(status[0].1, "initial_users_table", "Expected migration name");
|
||||
|
||||
println!("✅ 迁移管理器直接测试通过");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_migration_idempotency() {
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
// 创建内存数据库
|
||||
let pool = SqlitePool::connect("sqlite::memory:")
|
||||
.await
|
||||
.expect("Failed to create test pool");
|
||||
|
||||
let migration_manager = MigrationManager::new(pool.clone());
|
||||
|
||||
// 第一次运行迁移
|
||||
let result1 = migration_manager.run_migrations().await;
|
||||
assert!(result1.is_ok(), "First migration run failed: {:?}", result1.err());
|
||||
|
||||
// 第二次运行迁移(应该跳过已执行的迁移)
|
||||
let result2 = migration_manager.run_migrations().await;
|
||||
assert!(result2.is_ok(), "Second migration run failed: {:?}", result2.err());
|
||||
|
||||
// 验证只有一个迁移记录
|
||||
let status = migration_manager.get_migration_status().await.unwrap();
|
||||
assert_eq!(status.len(), 1, "Expected only 1 migration record after multiple runs");
|
||||
|
||||
println!("✅ 迁移幂等性测试通过");
|
||||
}
|
391
tests/pagination_integration_tests.rs
Normal file
391
tests/pagination_integration_tests.rs
Normal file
@@ -0,0 +1,391 @@
|
||||
//! 分页功能的HTTP API集成测试
|
||||
|
||||
use reqwest;
|
||||
use serde_json::{json, Value};
|
||||
use tokio;
|
||||
|
||||
const BASE_URL: &str = "http://127.0.0.1:3000";
|
||||
|
||||
/// 测试辅助函数:创建 HTTP 客户端
|
||||
fn create_client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
}
|
||||
|
||||
/// 测试辅助函数:解析 JSON 响应
|
||||
async fn parse_json_response(response: reqwest::Response) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
let text = response.text().await?;
|
||||
let json: Value = serde_json::from_str(&text)?;
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
/// 测试辅助函数:创建测试用户
|
||||
async fn create_test_user(client: &reqwest::Client, username: &str, email: &str) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
let user_data = json!({
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": "password123"
|
||||
});
|
||||
|
||||
let response = client
|
||||
.post(&format!("{}/api/users", BASE_URL))
|
||||
.json(&user_data)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
parse_json_response(response).await
|
||||
} else {
|
||||
Err(format!("Failed to create user: {}", response.status()).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试辅助函数:创建多个测试用户
|
||||
async fn create_multiple_test_users(client: &reqwest::Client, count: usize) -> Result<Vec<Value>, Box<dyn std::error::Error>> {
|
||||
let mut users = Vec::new();
|
||||
|
||||
for i in 0..count {
|
||||
let username = format!("pagination_test_user_{:02}", i + 1);
|
||||
let email = format!("pagination_test_{}@example.com", i + 1);
|
||||
|
||||
match create_test_user(client, &username, &email).await {
|
||||
Ok(user) => users.push(user),
|
||||
Err(e) => {
|
||||
// 如果用户已存在,跳过
|
||||
if e.to_string().contains("409") {
|
||||
continue;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加小延迟确保创建时间不同
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
}
|
||||
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_users_list_pagination_basic() {
|
||||
let client = create_client();
|
||||
|
||||
// 创建测试用户
|
||||
let _users = create_multiple_test_users(&client, 15).await
|
||||
.expect("Failed to create test users");
|
||||
|
||||
// 测试第一页(默认参数)
|
||||
let response = client
|
||||
.get(&format!("{}/api/users", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get users");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
// 验证分页响应结构
|
||||
assert!(json["data"].is_array(), "Response should have data array");
|
||||
assert!(json["pagination"].is_object(), "Response should have pagination object");
|
||||
|
||||
let pagination = &json["pagination"];
|
||||
assert!(pagination["current_page"].is_number(), "Should have current_page");
|
||||
assert!(pagination["per_page"].is_number(), "Should have per_page");
|
||||
assert!(pagination["total_pages"].is_number(), "Should have total_pages");
|
||||
assert!(pagination["total_items"].is_number(), "Should have total_items");
|
||||
assert!(pagination["has_next"].is_boolean(), "Should have has_next");
|
||||
assert!(pagination["has_prev"].is_boolean(), "Should have has_prev");
|
||||
|
||||
// 验证默认值
|
||||
assert_eq!(pagination["current_page"], 1);
|
||||
assert_eq!(pagination["per_page"], 10);
|
||||
assert!(!pagination["has_prev"].as_bool().unwrap(), "First page should not have previous");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_users_list_pagination_with_params() {
|
||||
let client = create_client();
|
||||
|
||||
// 确保有足够的测试数据
|
||||
let _users = create_multiple_test_users(&client, 12).await
|
||||
.expect("Failed to create test users");
|
||||
|
||||
// 测试第一页,每页5个
|
||||
let response = client
|
||||
.get(&format!("{}/api/users?page=1&limit=5", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get users page 1");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let pagination = &json["pagination"];
|
||||
|
||||
assert_eq!(data.len(), 5, "First page should have 5 users");
|
||||
assert_eq!(pagination["current_page"], 1);
|
||||
assert_eq!(pagination["per_page"], 5);
|
||||
assert!(!pagination["has_prev"].as_bool().unwrap());
|
||||
|
||||
// 测试第二页
|
||||
let response = client
|
||||
.get(&format!("{}/api/users?page=2&limit=5", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get users page 2");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let pagination = &json["pagination"];
|
||||
|
||||
assert_eq!(data.len(), 5, "Second page should have 5 users");
|
||||
assert_eq!(pagination["current_page"], 2);
|
||||
assert_eq!(pagination["per_page"], 5);
|
||||
assert!(pagination["has_prev"].as_bool().unwrap(), "Second page should have previous");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_users_list_pagination_edge_cases() {
|
||||
let client = create_client();
|
||||
|
||||
// 测试页码为0(应该被修正为1)
|
||||
let response = client
|
||||
.get(&format!("{}/api/users?page=0&limit=5", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get users with page=0");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let pagination = &json["pagination"];
|
||||
assert_eq!(pagination["current_page"], 1, "Page 0 should be corrected to 1");
|
||||
|
||||
// 测试超大限制(应该被限制为100)
|
||||
let response = client
|
||||
.get(&format!("{}/api/users?page=1&limit=200", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get users with large limit");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let pagination = &json["pagination"];
|
||||
assert_eq!(pagination["per_page"], 100, "Limit should be capped at 100");
|
||||
|
||||
// 测试限制为0(应该被修正为1)
|
||||
let response = client
|
||||
.get(&format!("{}/api/users?page=1&limit=0", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get users with limit=0");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let pagination = &json["pagination"];
|
||||
assert_eq!(pagination["per_page"], 1, "Limit 0 should be corrected to 1");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_users_list_pagination_beyond_range() {
|
||||
let client = create_client();
|
||||
|
||||
// 确保有一些测试数据
|
||||
let _users = create_multiple_test_users(&client, 5).await
|
||||
.expect("Failed to create test users");
|
||||
|
||||
// 测试超出范围的页码
|
||||
let response = client
|
||||
.get(&format!("{}/api/users?page=100&limit=5", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get users beyond range");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let pagination = &json["pagination"];
|
||||
|
||||
assert_eq!(data.len(), 0, "Beyond range page should return empty array");
|
||||
assert_eq!(pagination["current_page"], 100);
|
||||
assert!(!pagination["has_next"].as_bool().unwrap(), "Beyond range should not have next");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_users_search_pagination() {
|
||||
let client = create_client();
|
||||
|
||||
// 创建一些包含特定关键词的用户
|
||||
let admin_users = vec![
|
||||
("admin_user_1", "admin1@example.com"),
|
||||
("admin_user_2", "admin2@example.com"),
|
||||
("admin_user_3", "admin3@example.com"),
|
||||
("admin_user_4", "admin4@example.com"),
|
||||
("admin_user_5", "admin5@example.com"),
|
||||
];
|
||||
|
||||
for (username, email) in admin_users {
|
||||
let _ = create_test_user(&client, username, email).await;
|
||||
}
|
||||
|
||||
// 创建一些普通用户
|
||||
let _ = create_multiple_test_users(&client, 3).await;
|
||||
|
||||
// 搜索admin用户,第一页
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?q=admin&page=1&limit=3", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search users");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
// 验证搜索响应结构
|
||||
assert!(json["data"].is_array(), "Search response should have data array");
|
||||
assert!(json["pagination"].is_object(), "Search response should have pagination");
|
||||
assert!(json["search_params"].is_object(), "Search response should have search_params");
|
||||
assert!(json["total_filtered"].is_number(), "Search response should have total_filtered");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let pagination = &json["pagination"];
|
||||
|
||||
// 验证搜索结果
|
||||
assert!(data.len() <= 3, "Should return at most 3 results per page");
|
||||
assert_eq!(pagination["current_page"], 1);
|
||||
assert_eq!(pagination["per_page"], 3);
|
||||
|
||||
// 验证搜索结果包含关键词
|
||||
for user in data {
|
||||
let username = user["username"].as_str().unwrap();
|
||||
let email = user["email"].as_str().unwrap();
|
||||
assert!(
|
||||
username.contains("admin") || email.contains("admin"),
|
||||
"Search result should contain 'admin' keyword"
|
||||
);
|
||||
}
|
||||
|
||||
// 测试搜索第二页
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?q=admin&page=2&limit=3", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search users page 2");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let pagination = &json["pagination"];
|
||||
assert_eq!(pagination["current_page"], 2);
|
||||
assert!(pagination["has_prev"].as_bool().unwrap(), "Second page should have previous");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_users_search_with_filters_and_pagination() {
|
||||
let client = create_client();
|
||||
|
||||
// 创建一些测试用户
|
||||
let test_users = vec![
|
||||
("filter_test_1", "filter1@test.com"),
|
||||
("filter_test_2", "filter2@test.com"),
|
||||
("filter_test_3", "filter3@test.com"),
|
||||
("other_user_1", "other1@example.com"),
|
||||
("other_user_2", "other2@example.com"),
|
||||
];
|
||||
|
||||
for (username, email) in test_users {
|
||||
let _ = create_test_user(&client, username, email).await;
|
||||
}
|
||||
|
||||
// 按邮箱域名搜索,带分页
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?email=test.com&page=1&limit=2&sort_by=username&sort_order=asc", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search users with email filter");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let pagination = &json["pagination"];
|
||||
let search_params = &json["search_params"];
|
||||
|
||||
// 验证过滤结果
|
||||
assert!(data.len() <= 2, "Should return at most 2 results per page");
|
||||
assert_eq!(pagination["per_page"], 2);
|
||||
|
||||
// 验证搜索参数被正确返回
|
||||
assert_eq!(search_params["email"], "test.com");
|
||||
assert_eq!(search_params["sort_by"], "username");
|
||||
assert_eq!(search_params["sort_order"], "asc");
|
||||
|
||||
// 验证结果包含正确的邮箱域名
|
||||
for user in data {
|
||||
let email = user["email"].as_str().unwrap();
|
||||
assert!(email.contains("test.com"), "Filtered result should contain test.com domain");
|
||||
}
|
||||
|
||||
// 验证排序(用户名升序)
|
||||
if data.len() > 1 {
|
||||
let first_username = data[0]["username"].as_str().unwrap();
|
||||
let second_username = data[1]["username"].as_str().unwrap();
|
||||
assert!(first_username <= second_username, "Results should be sorted by username ascending");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_pagination_consistency_across_requests() {
|
||||
let client = create_client();
|
||||
|
||||
// 创建固定数量的用户
|
||||
let _users = create_multiple_test_users(&client, 10).await
|
||||
.expect("Failed to create test users");
|
||||
|
||||
// 获取第一页
|
||||
let response1 = client
|
||||
.get(&format!("{}/api/users?page=1&limit=3", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get first page");
|
||||
|
||||
let json1 = parse_json_response(response1).await.expect("Failed to parse JSON");
|
||||
let data1 = json1["data"].as_array().unwrap();
|
||||
let total_items1 = json1["pagination"]["total_items"].as_u64().unwrap();
|
||||
|
||||
// 获取第二页
|
||||
let response2 = client
|
||||
.get(&format!("{}/api/users?page=2&limit=3", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to get second page");
|
||||
|
||||
let json2 = parse_json_response(response2).await.expect("Failed to parse JSON");
|
||||
let data2 = json2["data"].as_array().unwrap();
|
||||
let total_items2 = json2["pagination"]["total_items"].as_u64().unwrap();
|
||||
|
||||
// 验证总数一致性
|
||||
assert_eq!(total_items1, total_items2, "Total items should be consistent across pages");
|
||||
|
||||
// 验证没有重复用户
|
||||
let mut user_ids1: Vec<String> = data1.iter()
|
||||
.map(|u| u["id"].as_str().unwrap().to_string())
|
||||
.collect();
|
||||
let user_ids2: Vec<String> = data2.iter()
|
||||
.map(|u| u["id"].as_str().unwrap().to_string())
|
||||
.collect();
|
||||
|
||||
user_ids1.extend(user_ids2);
|
||||
user_ids1.sort();
|
||||
user_ids1.dedup();
|
||||
|
||||
// 去重后的长度应该等于原始长度(没有重复)
|
||||
let expected_length = data1.len() + data2.len();
|
||||
assert_eq!(user_ids1.len(), expected_length, "No duplicate users should appear across pages");
|
||||
}
|
352
tests/pagination_tests.rs
Normal file
352
tests/pagination_tests.rs
Normal file
@@ -0,0 +1,352 @@
|
||||
//! 分页功能专项测试
|
||||
|
||||
use rust_user_api::{
|
||||
models::{
|
||||
user::User,
|
||||
pagination::{PaginationParams, PaginatedResponse},
|
||||
search::UserSearchParams,
|
||||
},
|
||||
storage::{database::DatabaseUserStore, memory::MemoryUserStore, UserStore},
|
||||
utils::errors::ApiError,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// 创建测试用户
|
||||
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_multiple_users(store: &dyn UserStore, count: usize) -> Vec<User> {
|
||||
let mut users = Vec::new();
|
||||
|
||||
for i in 0..count {
|
||||
let user = create_test_user(
|
||||
&format!("user{:02}", i + 1),
|
||||
&format!("user{}@example.com", i + 1),
|
||||
);
|
||||
|
||||
let created_user = store.create_user(user).await.unwrap();
|
||||
users.push(created_user);
|
||||
|
||||
// 添加小延迟确保创建时间不同
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||
}
|
||||
|
||||
users
|
||||
}
|
||||
|
||||
/// 测试内存存储的分页功能
|
||||
#[tokio::test]
|
||||
async fn test_memory_store_pagination() {
|
||||
let store = MemoryUserStore::new();
|
||||
|
||||
// 创建15个用户
|
||||
let users = create_multiple_users(&store, 15).await;
|
||||
|
||||
// 测试第一页(每页5个)
|
||||
let params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(5),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 15, "总用户数应该是15");
|
||||
assert_eq!(paginated_users.len(), 5, "第一页应该有5个用户");
|
||||
|
||||
// 测试第二页
|
||||
let params = PaginationParams {
|
||||
page: Some(2),
|
||||
limit: Some(5),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 15, "总用户数应该是15");
|
||||
assert_eq!(paginated_users.len(), 5, "第二页应该有5个用户");
|
||||
|
||||
// 测试第三页
|
||||
let params = PaginationParams {
|
||||
page: Some(3),
|
||||
limit: Some(5),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 15, "总用户数应该是15");
|
||||
assert_eq!(paginated_users.len(), 5, "第三页应该有5个用户");
|
||||
|
||||
// 测试第四页(超出范围)
|
||||
let params = PaginationParams {
|
||||
page: Some(4),
|
||||
limit: Some(5),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 15, "总用户数应该是15");
|
||||
assert_eq!(paginated_users.len(), 0, "第四页应该没有用户");
|
||||
}
|
||||
|
||||
/// 测试数据库存储的分页功能
|
||||
#[tokio::test]
|
||||
async fn test_database_store_pagination() {
|
||||
let database_url = "sqlite::memory:";
|
||||
let store = DatabaseUserStore::from_url(database_url).await.unwrap();
|
||||
|
||||
// 创建12个用户
|
||||
let users = create_multiple_users(&store, 12).await;
|
||||
|
||||
// 测试第一页(每页4个)
|
||||
let params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(4),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 12, "总用户数应该是12");
|
||||
assert_eq!(paginated_users.len(), 4, "第一页应该有4个用户");
|
||||
|
||||
// 验证排序(应该按创建时间倒序)
|
||||
for i in 0..paginated_users.len() - 1 {
|
||||
assert!(
|
||||
paginated_users[i].created_at >= paginated_users[i + 1].created_at,
|
||||
"用户应该按创建时间倒序排列"
|
||||
);
|
||||
}
|
||||
|
||||
// 测试最后一页
|
||||
let params = PaginationParams {
|
||||
page: Some(3),
|
||||
limit: Some(4),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 12, "总用户数应该是12");
|
||||
assert_eq!(paginated_users.len(), 4, "第三页应该有4个用户");
|
||||
}
|
||||
|
||||
/// 测试分页参数的默认值和边界情况
|
||||
#[tokio::test]
|
||||
async fn test_pagination_params_edge_cases() {
|
||||
let store = MemoryUserStore::new();
|
||||
|
||||
// 创建8个用户
|
||||
create_multiple_users(&store, 8).await;
|
||||
|
||||
// 测试默认参数
|
||||
let params = PaginationParams {
|
||||
page: None,
|
||||
limit: None,
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 8, "总用户数应该是8");
|
||||
assert_eq!(paginated_users.len(), 8, "默认应该返回所有用户(限制为10)");
|
||||
|
||||
// 测试页码为0(应该被修正为1)
|
||||
let params = PaginationParams {
|
||||
page: Some(0),
|
||||
limit: Some(3),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 8, "总用户数应该是8");
|
||||
assert_eq!(paginated_users.len(), 3, "页码0应该被修正为1,返回3个用户");
|
||||
|
||||
// 测试超大限制(应该被限制为100)
|
||||
let params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(200),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 8, "总用户数应该是8");
|
||||
assert_eq!(paginated_users.len(), 8, "应该返回所有8个用户(限制为100)");
|
||||
|
||||
// 测试限制为0(应该被修正为1)
|
||||
let params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(0),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 8, "总用户数应该是8");
|
||||
assert_eq!(paginated_users.len(), 1, "限制0应该被修正为1");
|
||||
}
|
||||
|
||||
/// 测试空数据库的分页
|
||||
#[tokio::test]
|
||||
async fn test_pagination_empty_database() {
|
||||
let store = MemoryUserStore::new();
|
||||
|
||||
let params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(10),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 0, "空数据库总用户数应该是0");
|
||||
assert_eq!(paginated_users.len(), 0, "空数据库应该返回空列表");
|
||||
}
|
||||
|
||||
/// 测试搜索功能的分页
|
||||
#[tokio::test]
|
||||
async fn test_search_pagination() {
|
||||
let store = MemoryUserStore::new();
|
||||
|
||||
// 创建用户,其中一些包含"admin"
|
||||
let users = vec![
|
||||
create_test_user("admin1", "admin1@example.com"),
|
||||
create_test_user("user1", "user1@example.com"),
|
||||
create_test_user("admin2", "admin2@example.com"),
|
||||
create_test_user("user2", "user2@example.com"),
|
||||
create_test_user("admin3", "admin3@example.com"),
|
||||
create_test_user("user3", "user3@example.com"),
|
||||
];
|
||||
|
||||
for user in users {
|
||||
store.create_user(user).await.unwrap();
|
||||
}
|
||||
|
||||
// 搜索包含"admin"的用户,第一页
|
||||
let search_params = UserSearchParams {
|
||||
q: Some("admin".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(2),
|
||||
};
|
||||
|
||||
let (search_results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 3, "应该找到3个admin用户");
|
||||
assert_eq!(search_results.len(), 2, "第一页应该返回2个用户");
|
||||
|
||||
// 验证搜索结果
|
||||
for user in &search_results {
|
||||
assert!(
|
||||
user.username.contains("admin") || user.email.contains("admin"),
|
||||
"搜索结果应该包含admin关键词"
|
||||
);
|
||||
}
|
||||
|
||||
// 搜索第二页
|
||||
let pagination_params = PaginationParams {
|
||||
page: Some(2),
|
||||
limit: Some(2),
|
||||
};
|
||||
|
||||
let (search_results, total_count) = store.search_users(&search_params, &pagination_params).await.unwrap();
|
||||
|
||||
assert_eq!(total_count, 3, "总数应该仍然是3");
|
||||
assert_eq!(search_results.len(), 1, "第二页应该返回1个用户");
|
||||
}
|
||||
|
||||
/// 测试PaginatedResponse结构
|
||||
#[tokio::test]
|
||||
async fn test_paginated_response_structure() {
|
||||
let store = MemoryUserStore::new();
|
||||
|
||||
// 创建7个用户
|
||||
create_multiple_users(&store, 7).await;
|
||||
|
||||
let params = PaginationParams {
|
||||
page: Some(2),
|
||||
limit: Some(3),
|
||||
};
|
||||
|
||||
let (users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
let response = PaginatedResponse::new(
|
||||
users.into_iter().map(|u| u.username).collect(),
|
||||
¶ms,
|
||||
total_count
|
||||
);
|
||||
|
||||
// 验证分页信息
|
||||
assert_eq!(response.pagination.current_page, 2);
|
||||
assert_eq!(response.pagination.per_page, 3);
|
||||
assert_eq!(response.pagination.total_pages, 3); // 7个用户,每页3个,共3页
|
||||
assert_eq!(response.pagination.total_items, 7);
|
||||
assert!(response.pagination.has_prev, "第二页应该有上一页");
|
||||
assert!(response.pagination.has_next, "第二页应该有下一页");
|
||||
|
||||
// 测试第一页
|
||||
let params = PaginationParams {
|
||||
page: Some(1),
|
||||
limit: Some(3),
|
||||
};
|
||||
|
||||
let (users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
let response = PaginatedResponse::new(
|
||||
users.into_iter().map(|u| u.username).collect(),
|
||||
¶ms,
|
||||
total_count
|
||||
);
|
||||
|
||||
assert!(!response.pagination.has_prev, "第一页不应该有上一页");
|
||||
assert!(response.pagination.has_next, "第一页应该有下一页");
|
||||
|
||||
// 测试最后一页
|
||||
let params = PaginationParams {
|
||||
page: Some(3),
|
||||
limit: Some(3),
|
||||
};
|
||||
|
||||
let (users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
let response = PaginatedResponse::new(
|
||||
users.into_iter().map(|u| u.username).collect(),
|
||||
¶ms,
|
||||
total_count
|
||||
);
|
||||
|
||||
assert!(response.pagination.has_prev, "最后一页应该有上一页");
|
||||
assert!(!response.pagination.has_next, "最后一页不应该有下一页");
|
||||
assert_eq!(response.data.len(), 1, "最后一页应该有1个用户");
|
||||
}
|
||||
|
||||
/// 测试分页功能的性能(大数据量)
|
||||
#[tokio::test]
|
||||
async fn test_pagination_performance() {
|
||||
let store = MemoryUserStore::new();
|
||||
|
||||
// 创建100个用户
|
||||
create_multiple_users(&store, 100).await;
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
// 测试获取中间页面的性能
|
||||
let params = PaginationParams {
|
||||
page: Some(50),
|
||||
limit: Some(2),
|
||||
};
|
||||
|
||||
let (paginated_users, total_count) = store.list_users_paginated(¶ms).await.unwrap();
|
||||
|
||||
let duration = start.elapsed();
|
||||
|
||||
assert_eq!(total_count, 100, "总用户数应该是100");
|
||||
assert_eq!(paginated_users.len(), 2, "应该返回2个用户");
|
||||
|
||||
// 性能检查:应该在合理时间内完成(这里设置为100ms,实际应该更快)
|
||||
assert!(duration.as_millis() < 100, "分页查询应该在100ms内完成,实际用时: {:?}", duration);
|
||||
}
|
332
tests/search_api_tests.rs
Normal file
332
tests/search_api_tests.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
//! 搜索API端点测试
|
||||
|
||||
use reqwest;
|
||||
use serde_json::{json, Value};
|
||||
use tokio;
|
||||
|
||||
const BASE_URL: &str = "http://127.0.0.1:3000";
|
||||
|
||||
/// 测试辅助函数:创建 HTTP 客户端
|
||||
fn create_client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
}
|
||||
|
||||
/// 测试辅助函数:解析 JSON 响应
|
||||
async fn parse_json_response(response: reqwest::Response) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
let text = response.text().await?;
|
||||
let json: Value = serde_json::from_str(&text)?;
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
/// 测试辅助函数:创建测试用户
|
||||
async fn create_test_user(client: &reqwest::Client, username: &str, email: &str) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
let user_data = json!({
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": "password123"
|
||||
});
|
||||
|
||||
let response = client
|
||||
.post(&format!("{}/api/users", BASE_URL))
|
||||
.json(&user_data)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
parse_json_response(response).await
|
||||
} else {
|
||||
Err(format!("Failed to create user: {}", response.status()).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_api_basic() {
|
||||
let client = create_client();
|
||||
|
||||
// 创建一些测试用户
|
||||
let test_users = vec![
|
||||
("search_admin_1", "admin1@company.com"),
|
||||
("search_user_1", "user1@example.com"),
|
||||
("search_admin_2", "admin2@company.com"),
|
||||
("search_manager", "manager@company.com"),
|
||||
];
|
||||
|
||||
for (username, email) in test_users {
|
||||
let _ = create_test_user(&client, username, email).await;
|
||||
}
|
||||
|
||||
// 测试基本搜索功能
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?q=admin", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search users");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
// 验证搜索响应结构
|
||||
assert!(json["data"].is_array(), "Response should have data array");
|
||||
assert!(json["pagination"].is_object(), "Response should have pagination object");
|
||||
assert!(json["search_params"].is_object(), "Response should have search_params object");
|
||||
assert!(json["total_filtered"].is_number(), "Response should have total_filtered");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let search_params = &json["search_params"];
|
||||
|
||||
// 验证搜索参数被正确返回
|
||||
assert_eq!(search_params["q"], "admin");
|
||||
|
||||
// 验证搜索结果
|
||||
for user in data {
|
||||
let username = user["username"].as_str().unwrap();
|
||||
let email = user["email"].as_str().unwrap();
|
||||
assert!(
|
||||
username.contains("admin") || email.contains("admin"),
|
||||
"Search result should contain 'admin' keyword"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_api_with_filters() {
|
||||
let client = create_client();
|
||||
|
||||
// 创建测试用户
|
||||
let test_users = vec![
|
||||
("filter_test_1", "test1@example.com"),
|
||||
("filter_test_2", "test2@company.com"),
|
||||
("other_user", "other@example.com"),
|
||||
];
|
||||
|
||||
for (username, email) in test_users {
|
||||
let _ = create_test_user(&client, username, email).await;
|
||||
}
|
||||
|
||||
// 测试用户名过滤
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?username=filter_test", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search users by username");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let search_params = &json["search_params"];
|
||||
|
||||
// 验证搜索参数
|
||||
assert_eq!(search_params["username"], "filter_test");
|
||||
|
||||
// 验证结果
|
||||
for user in data {
|
||||
let username = user["username"].as_str().unwrap();
|
||||
assert!(username.contains("filter_test"), "Username should contain filter_test");
|
||||
}
|
||||
|
||||
// 测试邮箱过滤
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?email=company.com", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search users by email");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let search_params = &json["search_params"];
|
||||
|
||||
// 验证搜索参数
|
||||
assert_eq!(search_params["email"], "company.com");
|
||||
|
||||
// 验证结果
|
||||
for user in data {
|
||||
let email = user["email"].as_str().unwrap();
|
||||
assert!(email.contains("company.com"), "Email should contain company.com");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_api_with_sorting() {
|
||||
let client = create_client();
|
||||
|
||||
// 创建测试用户
|
||||
let test_users = vec![
|
||||
("sort_zebra", "zebra@test.com"),
|
||||
("sort_alpha", "alpha@test.com"),
|
||||
("sort_beta", "beta@test.com"),
|
||||
];
|
||||
|
||||
for (username, email) in test_users {
|
||||
let _ = create_test_user(&client, username, email).await;
|
||||
}
|
||||
|
||||
// 测试按用户名升序排序
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?username=sort_&sort_by=username&sort_order=asc", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search users with sorting");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let search_params = &json["search_params"];
|
||||
|
||||
// 验证搜索参数
|
||||
assert_eq!(search_params["sort_by"], "username");
|
||||
assert_eq!(search_params["sort_order"], "asc");
|
||||
|
||||
// 验证排序结果
|
||||
if data.len() > 1 {
|
||||
for i in 0..data.len() - 1 {
|
||||
let current_username = data[i]["username"].as_str().unwrap();
|
||||
let next_username = data[i + 1]["username"].as_str().unwrap();
|
||||
assert!(
|
||||
current_username <= next_username,
|
||||
"Results should be sorted by username in ascending order"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_api_with_pagination() {
|
||||
let client = create_client();
|
||||
|
||||
// 创建多个测试用户
|
||||
for i in 1..=5 {
|
||||
let username = format!("page_test_{}", i);
|
||||
let email = format!("page{}@test.com", i);
|
||||
let _ = create_test_user(&client, &username, &email).await;
|
||||
}
|
||||
|
||||
// 测试第一页
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?username=page_test&page=1&limit=2", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search users with pagination");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let pagination = &json["pagination"];
|
||||
|
||||
assert_eq!(data.len(), 2, "First page should have 2 users");
|
||||
assert_eq!(pagination["current_page"], 1);
|
||||
assert_eq!(pagination["per_page"], 2);
|
||||
assert!(!pagination["has_prev"].as_bool().unwrap(), "First page should not have previous");
|
||||
|
||||
// 测试第二页
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?username=page_test&page=2&limit=2", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search users page 2");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let pagination = &json["pagination"];
|
||||
|
||||
assert_eq!(data.len(), 2, "Second page should have 2 users");
|
||||
assert_eq!(pagination["current_page"], 2);
|
||||
assert!(pagination["has_prev"].as_bool().unwrap(), "Second page should have previous");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_api_validation_errors() {
|
||||
let client = create_client();
|
||||
|
||||
// 测试无效的排序字段
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?sort_by=invalid_field", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request with invalid sort field");
|
||||
|
||||
assert_eq!(response.status(), 400);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
assert!(json["error"].as_str().unwrap().contains("无效的排序字段"));
|
||||
|
||||
// 测试无效的排序方向
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?sort_order=invalid_order", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request with invalid sort order");
|
||||
|
||||
assert_eq!(response.status(), 400);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
assert!(json["error"].as_str().unwrap().contains("无效的排序方向"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_api_empty_results() {
|
||||
let client = create_client();
|
||||
|
||||
// 搜索不存在的关键词
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?q=nonexistent_keyword_12345", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to search for nonexistent keyword");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let total_filtered = json["total_filtered"].as_i64().unwrap();
|
||||
|
||||
assert_eq!(data.len(), 0, "Should return empty results");
|
||||
assert_eq!(total_filtered, 0, "Total filtered should be 0");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_api_complex_query() {
|
||||
let client = create_client();
|
||||
|
||||
// 创建测试用户
|
||||
let test_users = vec![
|
||||
("complex_admin", "admin@company.com"),
|
||||
("complex_user", "user@company.com"),
|
||||
("simple_admin", "admin@example.com"),
|
||||
];
|
||||
|
||||
for (username, email) in test_users {
|
||||
let _ = create_test_user(&client, username, email).await;
|
||||
}
|
||||
|
||||
// 复合搜索:用户名包含admin且邮箱包含company
|
||||
let response = client
|
||||
.get(&format!("{}/api/users/search?username=admin&email=company&sort_by=username&sort_order=asc", BASE_URL))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to perform complex search");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
let json = parse_json_response(response).await.expect("Failed to parse JSON");
|
||||
|
||||
let data = json["data"].as_array().unwrap();
|
||||
let search_params = &json["search_params"];
|
||||
|
||||
// 验证搜索参数
|
||||
assert_eq!(search_params["username"], "admin");
|
||||
assert_eq!(search_params["email"], "company");
|
||||
assert_eq!(search_params["sort_by"], "username");
|
||||
assert_eq!(search_params["sort_order"], "asc");
|
||||
|
||||
// 验证结果同时满足两个条件
|
||||
for user in data {
|
||||
let username = user["username"].as_str().unwrap();
|
||||
let email = user["email"].as_str().unwrap();
|
||||
assert!(username.contains("admin"), "Username should contain admin");
|
||||
assert!(email.contains("company"), "Email should contain company");
|
||||
}
|
||||
}
|
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