Files
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

234 lines
6.8 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Black-box integration tests for IUserRepository (test-plan T2.1 T2.13).
//!
//! Tests exercise the public trait interface against an in-memory SQLite database.
//! Internal details like SQL queries or column names are not referenced.
use std::sync::Arc;
use nomifun_db::{DbError, IUserRepository, SqliteUserRepository, init_database_memory};
async fn repo() -> Arc<dyn IUserRepository> {
let db = init_database_memory().await.unwrap();
Arc::new(SqliteUserRepository::new(db.pool().clone()))
}
// -- T2.1 Create user --
#[tokio::test]
async fn t2_1_create_user_returns_user_with_populated_fields() {
let r = repo().await;
let user = r.create_user("testuser", "$2b$12$fakehash").await.unwrap();
assert!(!user.id.is_empty(), "id should be non-empty");
assert_eq!(user.username, "testuser");
assert_eq!(user.password_hash, "$2b$12$fakehash");
assert!(user.created_at > 0);
assert!(user.updated_at > 0);
}
#[tokio::test]
async fn t2_1_create_user_duplicate_username_returns_conflict() {
let r = repo().await;
r.create_user("dup", "h1").await.unwrap();
let err = r.create_user("dup", "h2").await.unwrap_err();
assert!(matches!(err, DbError::Conflict(_)), "expected Conflict, got: {err:?}");
}
// -- T2.2 Find by username --
#[tokio::test]
async fn t2_2_find_by_username_existing() {
let r = repo().await;
r.create_user("findme", "h").await.unwrap();
let found = r.find_by_username("findme").await.unwrap();
assert!(found.is_some());
assert_eq!(found.unwrap().username, "findme");
}
#[tokio::test]
async fn t2_2_find_by_username_nonexistent_returns_none() {
let r = repo().await;
assert!(r.find_by_username("ghost").await.unwrap().is_none());
}
// -- T2.3 Find by ID --
#[tokio::test]
async fn t2_3_find_by_id_existing() {
let r = repo().await;
let user = r.create_user("byid", "h").await.unwrap();
let found = r.find_by_id(&user.id).await.unwrap();
assert!(found.is_some());
assert_eq!(found.unwrap().id, user.id);
}
#[tokio::test]
async fn t2_3_find_by_id_nonexistent_returns_none() {
let r = repo().await;
assert!(r.find_by_id("no_such_id").await.unwrap().is_none());
}
// -- T2.4 List all users --
#[tokio::test]
async fn t2_4_list_users_returns_all() {
let r = repo().await;
r.create_user("u1", "h").await.unwrap();
r.create_user("u2", "h").await.unwrap();
let users = r.list_users().await.unwrap();
// system_default_user + u1 + u2
assert_eq!(users.len(), 3);
}
// -- T2.5 Count users --
#[tokio::test]
async fn t2_5_count_users() {
let r = repo().await;
r.create_user("counted", "h").await.unwrap();
// system_default_user + counted
assert_eq!(r.count_users().await.unwrap(), 2);
}
// -- T2.6 has_users --
#[tokio::test]
async fn t2_6_has_users_false_with_only_empty_password_system_user() {
let r = repo().await;
assert!(!r.has_users().await.unwrap());
}
#[tokio::test]
async fn t2_6_has_users_true_with_real_user() {
let r = repo().await;
r.create_user("real", "bcrypt_hash").await.unwrap();
assert!(r.has_users().await.unwrap());
}
// -- T2.7 Get system user --
#[tokio::test]
async fn t2_7_get_system_user_returns_default() {
let r = repo().await;
let user = r.get_system_user().await.unwrap();
assert!(user.is_some());
let user = user.unwrap();
assert_eq!(user.id, "system_default_user");
}
// -- T2.8 Get primary WebUI user --
#[tokio::test]
async fn t2_8_primary_webui_user_is_system_user_when_only_system() {
let r = repo().await;
let user = r.get_primary_webui_user().await.unwrap().unwrap();
assert_eq!(user.id, "system_default_user");
}
#[tokio::test]
async fn t2_8_primary_webui_user_prefers_system_over_admin() {
let r = repo().await;
// Can't create another user called "admin" now that seed uses it.
// The priority check still holds: any non-system user must not shadow system.
r.create_user("other", "h").await.unwrap();
let user = r.get_primary_webui_user().await.unwrap().unwrap();
assert_eq!(
user.id, "system_default_user",
"system user should take priority over non-system users"
);
}
// -- T2.9 Set system user credentials --
#[tokio::test]
async fn t2_9_set_system_user_credentials_updates_username_and_hash() {
let r = repo().await;
r.set_system_user_credentials("newadmin", "secure_hash").await.unwrap();
let user = r.get_system_user().await.unwrap().unwrap();
assert_eq!(user.username, "newadmin");
assert_eq!(user.password_hash, "secure_hash");
}
#[tokio::test]
async fn t2_9_set_system_user_credentials_conflict_with_existing_username() {
let r = repo().await;
r.create_user("existing", "h").await.unwrap();
let err = r.set_system_user_credentials("existing", "hash").await.unwrap_err();
assert!(matches!(err, DbError::Conflict(_)), "expected Conflict, got: {err:?}");
}
// -- T2.10 Update password --
#[tokio::test]
async fn t2_10_update_password_changes_hash_and_updated_at() {
let r = repo().await;
let user = r.create_user("pwduser", "old").await.unwrap();
r.update_password(&user.id, "new_hash").await.unwrap();
let updated = r.find_by_id(&user.id).await.unwrap().unwrap();
assert_eq!(updated.password_hash, "new_hash");
assert!(updated.updated_at >= user.updated_at);
}
// -- T2.11 Update username --
#[tokio::test]
async fn t2_11_update_username_succeeds() {
let r = repo().await;
let user = r.create_user("oldname", "h").await.unwrap();
r.update_username(&user.id, "newname").await.unwrap();
let updated = r.find_by_id(&user.id).await.unwrap().unwrap();
assert_eq!(updated.username, "newname");
}
#[tokio::test]
async fn t2_11_update_username_conflict_with_existing() {
let r = repo().await;
r.create_user("taken", "h").await.unwrap();
let other = r.create_user("free", "h").await.unwrap();
let err = r.update_username(&other.id, "taken").await.unwrap_err();
assert!(matches!(err, DbError::Conflict(_)), "expected Conflict, got: {err:?}");
}
// -- T2.12 Update last login --
#[tokio::test]
async fn t2_12_update_last_login_sets_timestamp() {
let r = repo().await;
let user = r.create_user("loginuser", "h").await.unwrap();
assert!(user.last_login.is_none());
r.update_last_login(&user.id).await.unwrap();
let updated = r.find_by_id(&user.id).await.unwrap().unwrap();
assert!(updated.last_login.is_some());
assert!(updated.last_login.unwrap() > 0);
}
// -- T2.13 Update JWT secret --
#[tokio::test]
async fn t2_13_update_jwt_secret_sets_value() {
let r = repo().await;
let user = r.create_user("jwtuser", "h").await.unwrap();
assert!(user.jwt_secret.is_none());
r.update_jwt_secret(&user.id, "my_secret").await.unwrap();
let updated = r.find_by_id(&user.id).await.unwrap().unwrap();
assert_eq!(updated.jwt_secret.as_deref(), Some("my_secret"));
}