- 集成测试重构:syncer e2e/materialize、ldaplib 读路径、lldapgql 全改用本地 LLDAP 容器(LDAP 3890 / GraphQL /api/graphql 17170),删旧 OpenLDAP 断言 - 修复 1:ActiveUsers 不填 DepartmentIDs(存于关系表),BuildFromMirror 拿到空成员; 新增 ActiveUsersWithDepts,物化 plan 用之 - 修复 2:deleted 用户重新可见后不回 active,无法重建;RevertToActive 纳入 deleted, 快照 reconcile 对其置回 active + 作废 token - 关键安全回归单测保留并配变异验证:未确认不删、非受管不删、admin 永不移出、 删除最后阶段、绝不碰密码。全量单测+vet+集成全绿
104 lines
2.7 KiB
Go
104 lines
2.7 KiB
Go
//go:build integration
|
|
|
|
// LLDAP 读路径(LDAP)集成测试:读取对象重,验证 objectClass 命中与双成员属性读取。
|
|
//
|
|
// 默认不参与 `go test ./...`,需显式启用:
|
|
//
|
|
// LLDAP_TEST_URL=ldap://127.0.0.1:3890 go test -tags=integration ./internal/ldaplib/ -v
|
|
package ldaplib_test
|
|
|
|
import (
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"testing"
|
|
|
|
"idsync/internal/config"
|
|
"idsync/internal/ldaplib"
|
|
)
|
|
|
|
func discardLogger() *slog.Logger {
|
|
return slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
}
|
|
|
|
func testConfig() config.Ldap {
|
|
base := os.Getenv("LDAP_TEST_BASE_DN")
|
|
if base == "" {
|
|
base = "dc=example,dc=com"
|
|
}
|
|
url := os.Getenv("LDAP_TEST_URL")
|
|
if url == "" {
|
|
url = "ldap://127.0.0.1:3890"
|
|
}
|
|
return config.Ldap{
|
|
URL: url, BaseDN: base,
|
|
AdminDN: "uid=admin,ou=people," + base, AdminPass: "adminpass",
|
|
TimeoutSec: 10,
|
|
}
|
|
}
|
|
|
|
func dialT(t *testing.T) *ldaplib.Conn {
|
|
t.Helper()
|
|
c, err := ldaplib.NewClient(testConfig(), discardLogger())
|
|
if err != nil {
|
|
t.Fatalf("构造客户端失败: %v", err)
|
|
}
|
|
conn, err := c.Dial()
|
|
if err != nil {
|
|
t.Skipf("LLDAP 不可用,跳过集成测试: %v", err)
|
|
}
|
|
return conn
|
|
}
|
|
|
|
// TestIntegrationReadUsers 验证用户读视图:LLDAP 默认 admin 用户应能被 ListUsers 读到。
|
|
func TestIntegrationReadUsers(t *testing.T) {
|
|
conn := dialT(t)
|
|
defer conn.Close()
|
|
|
|
users, err := conn.ListUsers("ou=people,dc=example,dc=com")
|
|
if err != nil {
|
|
t.Fatalf("ListUsers 失败: %v", err)
|
|
}
|
|
if _, ok := users["admin"]; !ok {
|
|
t.Errorf("LLDAP 默认的 admin 用户应在 ou=people 读到;现有: %v", keysOf(users))
|
|
}
|
|
}
|
|
|
|
// TestIntegrationReadGroups 验证组读视图:能列出 ou=groups 下的 LLDAP 默认组。
|
|
func TestIntegrationReadGroups(t *testing.T) {
|
|
conn := dialT(t)
|
|
defer conn.Close()
|
|
|
|
groups, err := conn.ListGroups("ou=groups,dc=example,dc=com")
|
|
if err != nil {
|
|
t.Fatalf("ListGroups 失败: %v", err)
|
|
}
|
|
if len(groups) == 0 {
|
|
t.Errorf("ou=groups 下应至少有 LLDAP 默认组")
|
|
}
|
|
// 结构上应能解析成员集合(即便为空),确认 member/uniqueMember 双读不崩。
|
|
for dn := range groups {
|
|
_ = groups[dn].Members()
|
|
}
|
|
}
|
|
|
|
// TestIntegrationDNHelpers 验证 DN 构造与过滤器转义(供读侧比对复用)。
|
|
func TestIntegrationDNHelpers(t *testing.T) {
|
|
if got := ldaplib.UserDN("alice", "ou=people,dc=example,dc=com"); got != "uid=alice,ou=people,dc=example,dc=com" {
|
|
t.Errorf("UserDN = %q", got)
|
|
}
|
|
if got := ldaplib.GroupDN("od-1", "ou=groups,dc=example,dc=com"); got != "cn=od-1,ou=groups,dc=example,dc=com" {
|
|
t.Errorf("GroupDN = %q", got)
|
|
}
|
|
if ldaplib.EscapeFilter("a(b)") == "a(b)" {
|
|
t.Errorf("EscapeFilter 未转义")
|
|
}
|
|
}
|
|
|
|
func keysOf[V any](m map[string]V) []string {
|
|
out := make([]string, 0, len(m))
|
|
for k := range m {
|
|
out = append(out, k)
|
|
}
|
|
return out
|
|
} |