refactor: mapping 落库化(期望状态改从镜像视图构造 + 拼音拍平组名)

- mapping.BuildFromMirror:由镜像 active 用户/部门构造期望 LLDAP 状态(纯函数),
  组名/DN 直接复用快照落库时算好的拍平拼音,用户 DN 固定 ou=people、去掉离职 OU
- Feat: FlatDeptInfo 已在本阶段完成测试覆盖(拍平拼音父子/空拼音回落/重名消歧/DN 稳定)
- 移除已被目标架构取代的旧「嵌套 CN」测试断言
- 旧 Build(OpenLDAP 单阶段)暂留保证过渡期编译,阶段6退役
This commit is contained in:
2026-09-29 23:25:50 +08:00
parent f997e48a75
commit 8fd10f134b
4 changed files with 304 additions and 27 deletions
+98
View File
@@ -0,0 +1,98 @@
package mapping
// BuildFromMirror(镜像 → 期望状态)的单测。
//
// 覆盖:用户 DN 固定 ou=people、组名直接复用镜像拍平 DN、成员关系按用户所属部门
// 归集、分组按层级父先于子排序。
import (
"testing"
"idsync/internal/store"
)
func mirrorUser(uid, email, deptID string) *store.MirrorUser {
return &store.MirrorUser{
UID: uid,
Email: email,
DisplayName: uid,
SN: uid,
GivenName: uid,
DepartmentIDs: []string{deptID},
}
}
func mirrorDept(id, name, pinyin, dn string, level int, parent string) *store.MirrorDepartment {
return &store.MirrorDepartment{
DeptID: id, Name: name, PinyinName: pinyin, DN: dn, Level: level, ParentID: parent,
}
}
// TestBuildFromMirrorUserDN 验证用户 DN 固定挂在 ou=people 下,不再有离职 OU。
func TestBuildFromMirrorUserDN(t *testing.T) {
depts := []*store.MirrorDepartment{
mirrorDept("od-root", "Acme", "Acme", "cn=Acme,ou=groups,dc=example,dc=com", 0, ""),
}
users := []*store.MirrorUser{mirrorUser("alice", "alice@example.com", "od-root")}
st := BuildFromMirror(users, depts, "dc=example,dc=com")
if len(st.Users) != 1 {
t.Fatalf("期望 1 个用户,实际 %d", len(st.Users))
}
u := st.Users[0]
if u.DN != "uid=alice,ou=people,dc=example,dc=com" {
t.Errorf("用户 DN = %q, 期望固定 ou=people", u.DN)
}
if u.Mail != "alice@example.com" {
t.Errorf("mail = %q", u.Mail)
}
}
// TestBuildFromMirrorGroupMembers 验证成员关系按用户所属部门归集到对应的组。
func TestBuildFromMirrorGroupMembers(t *testing.T) {
depts := []*store.MirrorDepartment{
mirrorDept("od-root", "Acme", "Acme", "cn=Acme,ou=groups,dc=example,dc=com", 0, ""),
mirrorDept("od-rd", "研发部", "Acme.yanfabu", "cn=Acme.yanfabu,ou=groups,dc=example,dc=com", 1, "od-root"),
}
users := []*store.MirrorUser{
mirrorUser("alice", "alice@example.com", "od-rd"),
// lisi 属多部门
{UID: "lisi", Email: "lisi@example.com", DisplayName: "lisi", SN: "lisi",
GivenName: "lisi", DepartmentIDs: []string{"od-root", "od-rd"}},
}
st := BuildFromMirror(users, depts, "dc=example,dc=com")
rd := st.GroupByDeptID["od-rd"]
if _, ok := rd.Members["uid=alice,ou=people,dc=example,dc=com"]; !ok {
t.Errorf("alice 未加入 %s", rd.DN)
}
if _, ok := rd.Members["uid=lisi,ou=people,dc=example,dc=com"]; !ok {
t.Errorf("lisi 未加入 %s", rd.DN)
}
root := st.GroupByDeptID["od-root"]
if _, ok := root.Members["uid=alice,ou=people,dc=example,dc=com"]; ok {
t.Errorf("alice 只属研发部,不应出现在根部门")
}
if _, ok := root.Members["uid=lisi,ou=people,dc=example,dc=com"]; !ok {
t.Errorf("lisi 属根部门,应出现")
}
}
// TestBuildFromMirrorGroupOrderIsParentFirst 验证分组输出按层级父先于子排序。
func TestBuildFromMirrorGroupOrderIsParentFirst(t *testing.T) {
depts := []*store.MirrorDepartment{
// 刻意让子部门先给出,验证 BuildFromMirror 内部仍按 Level 排回父先于子。
mirrorDept("od-rd", "研发部", "Acme.yanfabu", "cn=Acme.yanfabu,ou=groups,dc=example,dc=com", 1, "od-root"),
mirrorDept("od-root", "Acme", "Acme", "cn=Acme,ou=groups,dc=example,dc=com", 0, ""),
}
st := BuildFromMirror(nil, depts, "dc=example,dc=com")
seen := map[string]int{}
for i, g := range st.Groups {
seen[g.DeptID] = i
}
if seen["od-root"] > seen["od-rd"] {
t.Errorf("父部门应排在子部门之前:%v", seen)
}
}