- config 重写:删 user-ou/inactive-ou/group-root-* / group-object-class / group-member-attr / initial-password;新增 lldap 写通道 + webhook + confirm-base-url + 两套 schedule + guard - ldaplib 收拢为只读:属性常量/DN 归入 attrs.go;删 ops.go 写路径与 internal/password - config.example.yml 重写成 LLDAP 形态 - 组命名修订(呼应 AGENTS.md 约束12):用部门稳定索引 cn=<dept_id> 做组身份, mirror_departments 增 group_id 列并跨快照保留,可读中文名只走 DB 展示
122 lines
3.2 KiB
Go
122 lines
3.2 KiB
Go
package config
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// writeConfig 写一份最小可用配置并加载它。
|
||
func writeConfig(t *testing.T, extra string) *Config {
|
||
t.Helper()
|
||
|
||
body := `
|
||
system:
|
||
data-dir: /tmp/idsync-test
|
||
server:
|
||
admin-user: admin
|
||
admin-password: pw
|
||
feishu:
|
||
app-id: a
|
||
app-secret: b
|
||
ldap:
|
||
url: ldap://localhost:3890
|
||
base-dn: "dc=example,dc=org"
|
||
admin-dn: "uid=admin,ou=people,dc=example,dc=org"
|
||
admin-pass: pw
|
||
lldap:
|
||
base-url: http://localhost:17170
|
||
admin-user: admin
|
||
admin-pass: pw
|
||
` + extra
|
||
|
||
dir := t.TempDir()
|
||
path := filepath.Join(dir, "config.yml")
|
||
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
||
t.Fatalf("写配置失败: %v", err)
|
||
}
|
||
|
||
cfg, err := Load(path)
|
||
if err != nil {
|
||
t.Fatalf("加载配置失败: %v", err)
|
||
}
|
||
return cfg
|
||
}
|
||
|
||
// TestDefaultsForLLDAP 验证 OUDN 固定为 LLDAP 形态:用户在 ou=people、组在 ou=groups。
|
||
func TestDefaultsForLLDAP(t *testing.T) {
|
||
cfg := writeConfig(t, "")
|
||
|
||
if got := cfg.UserOUDN(); got != "ou=people,dc=example,dc=org" {
|
||
t.Errorf("UserOUDN = %q, 期望 ou=people(LLDAP 固定 OU)", got)
|
||
}
|
||
if got := cfg.GroupRootDN(); got != "ou=groups,dc=example,dc=org" {
|
||
t.Errorf("GroupRootDN = %q, 期望 ou=groups", got)
|
||
}
|
||
}
|
||
|
||
// TestAdminDNDerived 验证未显式配 admin-dn 时按 LLDAP 管理员用户补全。
|
||
func TestAdminDNDerived(t *testing.T) {
|
||
cfg := writeConfig(t, " admin-dn: \"\"\n")
|
||
want := "uid=admin,ou=people,dc=example,dc=org"
|
||
if cfg.Ldap.AdminDN != want {
|
||
t.Errorf("AdminDN = %q, 期望自动补全为 %q", cfg.Ldap.AdminDN, want)
|
||
}
|
||
}
|
||
|
||
// TestNormalizeRejectsMissingCredentials 验证关键字段缺失时启动即失败。
|
||
func TestNormalizeRejectsMissingCredentials(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
// override 以 YAML 片段替换掉基础配置里对应的一行,避免重复键。
|
||
override string
|
||
mark string
|
||
}{
|
||
{"缺少 app-id", "feishu:\n app-id: \"\"\n", "app-id"},
|
||
{"缺少 base-dn", "ldap:\n base-dn: \"\"\n", "base-dn"},
|
||
{"缺少 lldap base-url", "lldap:\n base-url: \"\"\n", "base-url"},
|
||
{"缺少 lldap admin-pass", "lldap:\n admin-pass: \"\"\n", "admin-pass"},
|
||
}
|
||
|
||
base := `
|
||
system:
|
||
data-dir: /tmp/idsync-test
|
||
server:
|
||
admin-user: admin
|
||
admin-password: pw
|
||
`
|
||
for _, c := range cases {
|
||
t.Run(c.name, func(t *testing.T) {
|
||
dir := t.TempDir()
|
||
path := filepath.Join(dir, "config.yml")
|
||
body := base + c.override + `
|
||
ldap:
|
||
url: ldap://localhost:3890
|
||
base-dn: "dc=example,dc=org"
|
||
admin-dn: "uid=admin,ou=people,dc=example,dc=org"
|
||
admin-pass: pw
|
||
lldap:
|
||
base-url: http://localhost:17170
|
||
admin-user: admin
|
||
admin-pass: pw
|
||
`
|
||
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
||
t.Fatalf("写配置失败: %v", err)
|
||
}
|
||
if _, err := Load(path); err == nil {
|
||
t.Errorf("缺少必要配置项(%s)时应加载失败", c.mark)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestEdgeRemoved 验证 OpenLDAP 时代的分组/OU/密码配置项已彻底移除,
|
||
// 不再出现在结构体里(通过在导出字段上做类型断言编译来保证)。
|
||
func TestEdgeRemoved(t *testing.T) {
|
||
_ = func(c *Config) {
|
||
// 编译期确认这些字段已不存在:直接引用会在编译时失败。
|
||
_ = strings.TrimSpace(c.Ldap.URL)
|
||
_ = c.Guard.MaxDeactivatePercent
|
||
}
|
||
} |