fix(makecode): 树查询 showself 仅在 showall 时生效

避免 parent_id 懒加载把节点自身混入子级导致无限嵌套,并补充 department 回归用例与文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-22 08:58:46 +08:00
parent 412f08a048
commit b254606003
5 changed files with 151 additions and 2 deletions
+109
View File
@@ -0,0 +1,109 @@
package app
// 通用 CRUD 树表查询(MakeCode.Search 的 parent_id / showself / showall 语义)行为测试。
// 回归背景:parent_id=X 且 showself=1 时旧逻辑拼出 OR(parent_id=X, id=X)
// 把节点 X 自己当作 X 的子级返回,前端树无限嵌套(无限套娃)。
// 依赖 department 树表(表名避开 admin.json 遗留 flow 对 org 的 admin_id 注入;
// 见 setup_mysql.go;表需在 Init 前已存在于测试库)。
import (
"fmt"
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
// resultIds 提取 result.data 的 id 集合
func resultIds(a *Api) map[int64]bool {
ids := map[int64]bool{}
data := a.Resp().GetBody().GetMap("result").GetSlice("data")
for k := range data {
ids[data.GetMap(k).GetCeilInt64("id")] = true
}
return ids
}
var AdminDepartmentTest = ProjTest{
"department": CtrTest{
"search": {Desc: "树表通用查询:parent_id 子级懒加载不得返回自身", Func: func(a *Api) {
// ======== 错误用例 ========
a.Query(Map{"parent_id": "1", "showself": "1"}).
Get("未登录访问", 2, "你还没有登录")
// ======== 准备树数据:root → nodeX → child ========
adminId := a.DB().Insert("admin", Map{
"name": "树测管理员", "phone": "139" + ObjToStr(RandX(10000000, 99999999)),
"state": 1, "password": Md5("tree123"), "role_id": 1,
"create_time[#]": "NOW()", "modify_time[#]": "NOW()",
})
session := Map{"admin_id": adminId}
rootId := a.DB().Insert("department", Map{
"name": "树测根部门", "state": 0,
"create_time[#]": "NOW()", "modify_time[#]": "NOW()",
})
a.DB().Update("department", Map{"parent_ids": "," + ObjToStr(rootId) + ","}, Map{"id": rootId})
nodeX := a.DB().Insert("department", Map{
"name": "树测节点X", "parent_id": rootId, "state": 0,
"create_time[#]": "NOW()", "modify_time[#]": "NOW()",
})
a.DB().Update("department", Map{"parent_ids": "," + ObjToStr(rootId) + "," + ObjToStr(nodeX) + ","}, Map{"id": nodeX})
childId := a.DB().Insert("department", Map{
"name": "树测子节点", "parent_id": nodeX, "state": 0,
"create_time[#]": "NOW()", "modify_time[#]": "NOW()",
})
a.DB().Update("department", Map{"parent_ids": "," + ObjToStr(rootId) + "," + ObjToStr(nodeX) + "," + ObjToStr(childId) + ","}, Map{"id": childId})
rowSample := Map{"count": int64(1), "data": Slice{Map{"id": int64(1), "name": "sample"}}}
// ======== 核心回归:子级懒加载带 showself 不得包含节点自身 ========
a.WithSession(session).
Note("旧缺陷:OR(parent_id=X, id=X) 把 X 自己当作 X 的子级返回,树无限嵌套").
Query(Map{"parent_id": nodeX, "showself": "1", "pageSize": "50"}).
Verify(func(a *Api) error {
ids := resultIds(a)
if ids[nodeX] {
return fmt.Errorf("子级查询结果不应包含节点自身 id=%d", nodeX)
}
if !ids[childId] {
return fmt.Errorf("子级查询结果应包含直接子级 id=%d", childId)
}
return nil
}).
Get("子级懒加载showself不返回自身", 0, rowSample)
// ======== 不传 showself:同样只返回直接子级 ========
a.WithSession(session).
Query(Map{"parent_id": nodeX, "pageSize": "50"}).
Verify(func(a *Api) error {
ids := resultIds(a)
if ids[nodeX] || !ids[childId] {
return fmt.Errorf("普通子级查询应只含直接子级,实际: %v", ids)
}
return nil
}).
Get("普通子级查询只返回子级", 0, rowSample)
// ======== showall+showself(管理端列表按树筛选场景):含自身与全部子孙 ========
a.WithSession(session).
Note("Table.vue 树筛选场景:showall=1 展示选中节点及全部子孙,showself=1 保留自身").
Query(Map{"parent_id": nodeX, "showself": "1", "showall": "1", "pageSize": "50"}).
Verify(func(a *Api) error {
ids := resultIds(a)
if !ids[nodeX] {
return fmt.Errorf("showall+showself 应包含节点自身 id=%d", nodeX)
}
if !ids[childId] {
return fmt.Errorf("showall+showself 应包含子孙 id=%d", childId)
}
if ids[rootId] {
return fmt.Errorf("showall+showself 不应包含父级 id=%d", rootId)
}
return nil
}).
Get("showall加showself含自身与子孙", 0, rowSample)
}},
},
}