test: add 149 unit tests across core, auth, config, message crates

Test coverage increased from ~34 to 183 tests (zero failures):

- erp-core (21): version check, pagination, API response, error mapping
- erp-auth (39): org tree building, DTO validation, error conversion,
  password hashing, user model mapping
- erp-config (57): DTO validation, numbering reset logic, menu tree
  building, error conversion. Fixed BatchSaveMenusReq nested validation
- erp-message (50): DTO validation, template rendering, query defaults,
  error conversion
- erp-workflow (16): unchanged (parser + expression tests)

All tests are pure unit tests requiring no database.
This commit is contained in:
iven
2026-04-15 01:06:34 +08:00
parent 9568dd7875
commit ee65b6e3c9
13 changed files with 1995 additions and 4 deletions

View File

@@ -292,7 +292,7 @@ impl OrgService {
///
/// Root nodes (parent_id = None) form the top level. Each node recursively
/// includes its children grouped by parent_id.
fn build_org_tree(items: &[organization::Model]) -> Vec<OrganizationResp> {
pub(crate) fn build_org_tree(items: &[organization::Model]) -> Vec<OrganizationResp> {
let mut children_map: HashMap<Option<Uuid>, Vec<&organization::Model>> = HashMap::new();
for item in items {
children_map.entry(item.parent_id).or_default().push(item);
@@ -329,3 +329,121 @@ fn build_org_tree(items: &[organization::Model]) -> Vec<OrganizationResp> {
})
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use chrono::Utc;
use uuid::Uuid;
use crate::entity::organization;
use super::*;
fn make_org(
id: Uuid,
tenant_id: Uuid,
name: &str,
parent_id: Option<Uuid>,
level: i32,
version: i32,
) -> organization::Model {
organization::Model {
id,
tenant_id,
name: name.to_string(),
code: None,
parent_id,
path: None,
level,
sort_order: 0,
created_at: Utc::now(),
updated_at: Utc::now(),
created_by: Uuid::now_v7(),
updated_by: Uuid::now_v7(),
deleted_at: None,
version,
}
}
#[test]
fn build_org_tree_empty() {
let tree = build_org_tree(&[]);
assert!(tree.is_empty());
}
#[test]
fn build_org_tree_single_root() {
let tid = Uuid::now_v7();
let root_id = Uuid::now_v7();
let items = vec![make_org(root_id, tid, "总公司", None, 1, 1)];
let tree = build_org_tree(&items);
assert_eq!(tree.len(), 1);
assert_eq!(tree[0].name, "总公司");
assert!(tree[0].children.is_empty());
}
#[test]
fn build_org_tree_multiple_roots() {
let tid = Uuid::now_v7();
let items = vec![
make_org(Uuid::now_v7(), tid, "公司A", None, 1, 1),
make_org(Uuid::now_v7(), tid, "公司B", None, 1, 1),
];
let tree = build_org_tree(&items);
assert_eq!(tree.len(), 2);
}
#[test]
fn build_org_tree_nested_children() {
let tid = Uuid::now_v7();
let root_id = Uuid::now_v7();
let child1_id = Uuid::now_v7();
let child2_id = Uuid::now_v7();
let grandchild_id = Uuid::now_v7();
let items = vec![
make_org(root_id, tid, "总公司", None, 1, 1),
make_org(child1_id, tid, "分公司A", Some(root_id), 2, 1),
make_org(child2_id, tid, "分公司B", Some(root_id), 2, 1),
make_org(grandchild_id, tid, "部门A1", Some(child1_id), 3, 1),
];
let tree = build_org_tree(&items);
assert_eq!(tree.len(), 1); // one root
assert_eq!(tree[0].children.len(), 2); // two children
assert_eq!(tree[0].children[0].children.len(), 1); // one grandchild
assert_eq!(tree[0].children[0].children[0].name, "部门A1");
}
#[test]
fn build_org_tree_deep_nesting() {
let tid = Uuid::now_v7();
let l1 = Uuid::now_v7();
let l2 = Uuid::now_v7();
let l3 = Uuid::now_v7();
let l4 = Uuid::now_v7();
let items = vec![
make_org(l1, tid, "L1", None, 1, 1),
make_org(l2, tid, "L2", Some(l1), 2, 1),
make_org(l3, tid, "L3", Some(l2), 3, 1),
make_org(l4, tid, "L4", Some(l3), 4, 1),
];
let tree = build_org_tree(&items);
assert_eq!(tree.len(), 1);
assert_eq!(tree[0].children[0].children[0].children[0].name, "L4");
}
#[test]
fn build_org_tree_preserves_version() {
let tid = Uuid::now_v7();
let root_id = Uuid::now_v7();
let items = vec![make_org(root_id, tid, "测试", None, 1, 5)];
let tree = build_org_tree(&items);
assert_eq!(tree[0].version, 5);
}
}

View File

@@ -393,7 +393,7 @@ impl UserService {
}
/// Convert a SeaORM user Model and its role DTOs into a UserResp.
fn model_to_resp(m: &user::Model, roles: Vec<RoleResp>) -> UserResp {
pub(crate) fn model_to_resp(m: &user::Model, roles: Vec<RoleResp>) -> UserResp {
UserResp {
id: m.id,
username: m.username.clone(),
@@ -406,3 +406,82 @@ fn model_to_resp(m: &user::Model, roles: Vec<RoleResp>) -> UserResp {
version: m.version,
}
}
#[cfg(test)]
mod tests {
use chrono::Utc;
use uuid::Uuid;
use crate::dto::RoleResp;
use crate::entity::user;
use super::*;
fn make_user_model(
id: Uuid,
tenant_id: Uuid,
username: &str,
status: &str,
version: i32,
) -> user::Model {
user::Model {
id,
tenant_id,
username: username.to_string(),
email: None,
phone: None,
display_name: None,
avatar_url: None,
status: status.to_string(),
last_login_at: None,
created_at: Utc::now(),
updated_at: Utc::now(),
created_by: Uuid::now_v7(),
updated_by: Uuid::now_v7(),
deleted_at: None,
version,
}
}
#[test]
fn model_to_resp_maps_basic_fields() {
let id = Uuid::now_v7();
let tid = Uuid::now_v7();
let m = make_user_model(id, tid, "alice", "active", 1);
let resp = model_to_resp(&m, vec![]);
assert_eq!(resp.id, id);
assert_eq!(resp.username, "alice");
assert_eq!(resp.status, "active");
assert_eq!(resp.version, 1);
assert!(resp.roles.is_empty());
}
#[test]
fn model_to_resp_includes_roles() {
let id = Uuid::now_v7();
let tid = Uuid::now_v7();
let m = make_user_model(id, tid, "bob", "active", 2);
let roles = vec![
RoleResp {
id: Uuid::now_v7(),
name: "管理员".to_string(),
code: "admin".to_string(),
description: None,
is_system: true,
version: 1,
},
RoleResp {
id: Uuid::now_v7(),
name: "用户".to_string(),
code: "user".to_string(),
description: None,
is_system: false,
version: 1,
},
];
let resp = model_to_resp(&m, roles);
assert_eq!(resp.roles.len(), 2);
assert_eq!(resp.roles[0].code, "admin");
assert_eq!(resp.version, 2);
}
}