Files
yunzerwebsiteallinone/go/controllers/backend_organization_test.go
T
2026-08-27 21:25:39 +08:00

186 lines
5.4 KiB
Go

package controllers
import (
"io"
"strings"
"testing"
)
// 这些用例只覆盖组织架构模块中不依赖数据库的纯函数:树组装、层级计算、CSV 解析与编码生成。
func TestBuildOrganizationTree(t *testing.T) {
list := []organizationDTO{
{ID: 1, OrgName: "总公司", ParentID: 0},
{ID: 2, OrgName: "研发部", ParentID: 1},
{ID: 3, OrgName: "前端组", ParentID: 2},
{ID: 4, OrgName: "孤儿部门", ParentID: 999}, // 上级不在列表中,应作为根节点保留
}
tree := buildOrganizationTree(list)
if len(tree) != 2 {
t.Fatalf("期望 2 个根节点,实际 %d", len(tree))
}
root := tree[0]
if root["org_name"] != "总公司" {
t.Fatalf("第一个根节点应为总公司,实际 %v", root["org_name"])
}
children, ok := root["children"].([]map[string]interface{})
if !ok || len(children) != 1 {
t.Fatalf("总公司应有 1 个子节点,实际 %v", root["children"])
}
grandChildren, ok := children[0]["children"].([]map[string]interface{})
if !ok || len(grandChildren) != 1 || grandChildren[0]["org_name"] != "前端组" {
t.Fatalf("研发部下应有前端组,实际 %v", children[0]["children"])
}
if tree[1]["org_name"] != "孤儿部门" {
t.Fatalf("上级缺失的节点应作为根节点保留,实际 %v", tree[1]["org_name"])
}
}
func TestTreeDepth(t *testing.T) {
childrenOf := map[uint64][]uint64{
0: {1},
1: {2, 3},
2: {4},
}
if got := treeDepth(childrenOf, 0, 0); got != 3 {
t.Fatalf("期望深度 3,实际 %d", got)
}
if got := treeDepth(map[uint64][]uint64{}, 0, 0); got != 0 {
t.Fatalf("空树深度应为 0,实际 %d", got)
}
}
func TestSubtreeHeightFrom(t *testing.T) {
childrenOf := map[uint64][]uint64{
1: {2, 3},
2: {4},
}
if got := subtreeHeightFrom(childrenOf, 1, 0); got != 3 {
t.Fatalf("以 1 为根的子树高度应为 3,实际 %d", got)
}
if got := subtreeHeightFrom(childrenOf, 4, 0); got != 1 {
t.Fatalf("叶子节点高度应为 1,实际 %d", got)
}
}
func TestCSVHelpers(t *testing.T) {
record := []string{" COM001 ", "总公司", "", "1"}
if got := csvField(record, 0); got != "COM001" {
t.Fatalf("csvField 应去掉空格,实际 %q", got)
}
if got := csvField(record, 9); got != "" {
t.Fatalf("越界应返回空串,实际 %q", got)
}
if got := csvInt(record, 3, 0); got != 1 {
t.Fatalf("csvInt 应解析出 1,实际 %d", got)
}
if got := csvInt(record, 2, 7); got != 7 {
t.Fatalf("空值应返回默认值 7,实际 %d", got)
}
if got := csvInt(record, 1, 5); got != 5 {
t.Fatalf("非数字应返回默认值 5,实际 %d", got)
}
}
func TestBOMTrimReader(t *testing.T) {
withBOM := append([]byte{0xEF, 0xBB, 0xBF}, []byte("编码,名称\n")...)
got, err := io.ReadAll(newBOMTrimReader(strings.NewReader(string(withBOM))))
if err != nil {
t.Fatalf("读取失败: %v", err)
}
if string(got) != "编码,名称\n" {
t.Fatalf("BOM 未被去掉,实际 %q", string(got))
}
got, err = io.ReadAll(newBOMTrimReader(strings.NewReader("编码,名称\n")))
if err != nil {
t.Fatalf("读取失败: %v", err)
}
if string(got) != "编码,名称\n" {
t.Fatalf("无 BOM 时内容被破坏,实际 %q", string(got))
}
// 内容短于 3 字节时不能丢数据
got, err = io.ReadAll(newBOMTrimReader(strings.NewReader("ab")))
if err != nil {
t.Fatalf("读取失败: %v", err)
}
if string(got) != "ab" {
t.Fatalf("短内容被破坏,实际 %q", string(got))
}
}
func TestGenerateCode(t *testing.T) {
c := &BackendOrganizationController{}
code := c.generateCode("ORG", 8)
if len(code) != 8 || !strings.HasPrefix(code, "ORG") {
t.Fatalf("期望 8 位且以 ORG 开头,实际 %q", code)
}
// 长度限制小于前缀长度时不截断前缀
code = c.generateCode("PREFIX", 3)
if !strings.HasPrefix(code, "PREFIX") {
t.Fatalf("前缀不应被截断,实际 %q", code)
}
// 不限制长度时返回完整前缀 + 时间戳
code = c.generateCode("EMP", 0)
if len(code) != len("EMP")+14 {
t.Fatalf("未限制长度时应为前缀加 14 位时间戳,实际 %q", code)
}
}
func TestUniqueMessageAndClamp(t *testing.T) {
if got := uniqueMessage(true, "编码"); got != "编码可用" {
t.Fatalf("实际 %q", got)
}
if got := uniqueMessage(false, "账号"); got != "账号已存在" {
t.Fatalf("实际 %q", got)
}
if got := clampInt(0, 4, 32); got != 4 {
t.Fatalf("下界钳制失败,实际 %d", got)
}
if got := clampInt(99, 4, 32); got != 32 {
t.Fatalf("上界钳制失败,实际 %d", got)
}
if got := clampInt(10, 4, 32); got != 10 {
t.Fatalf("区间内不应改变,实际 %d", got)
}
}
func TestHashEmployeePassword(t *testing.T) {
if got := hashEmployeePassword(" "); got != "" {
t.Fatalf("空密码应返回空串,实际 %q", got)
}
got := hashEmployeePassword("secret123")
if len(got) != 64 {
t.Fatalf("sha256 hex 应为 64 位,实际 %d", len(got))
}
if got != hashEmployeePassword("secret123") {
t.Fatal("相同输入应得到相同结果")
}
}
func TestOrgNameByIDString(t *testing.T) {
nameByID := map[uint64]string{7: "研发部"}
if got := orgNameByIDString(nameByID, "7"); got != "研发部" {
t.Fatalf("实际 %q", got)
}
if got := orgNameByIDString(nameByID, ""); got != "" {
t.Fatalf("空值应返回空串,实际 %q", got)
}
if got := orgNameByIDString(nameByID, "abc"); got != "" {
t.Fatalf("非数字应返回空串,实际 %q", got)
}
if got := orgNameByIDString(nameByID, "99"); got != "" {
t.Fatalf("未命中应返回空串,实际 %q", got)
}
}