186 lines
5.4 KiB
Go
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)
|
|
}
|
|
}
|