Files
hotime/example/app/makecode_tree_test.go
T

150 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
// ======== 修复回归:文本字段部分关键词模糊匹配 + 非文本字段等值筛选不受影响 ========
// 背景 bugcode/makecode.go Search):文本类字段(如 name)曾同时生成等值条件
// name=值 与模糊条件 name[~]=值,二者在 where 中以 AND 叠加;前端筛选框传入
// 部分关键词时等值条件必不命中,AND 之后查询恒为空。
// 修复后:文本字段只走模糊匹配;非文本字段(如 state,规则覆盖为 select 类型)维持等值匹配现状。
textId := a.DB().Insert("department", Map{
"name": "综合行政部", "state": 5, "parent_id": nodeX,
"create_time[#]": "NOW()", "modify_time[#]": "NOW()",
})
a.DB().Update("department", Map{"parent_ids": "," + ObjToStr(rootId) + "," + ObjToStr(nodeX) + "," + ObjToStr(textId) + ","}, Map{"id": textId})
a.WithSession(session).
Note("修复前:name 等值(综合行政部≠行政)与模糊[~]同时AND叠加,传部分关键词恒查不到;修复后应命中模糊匹配").
Query(Map{"parent_id": rootId, "showself": "1", "showall": "1", "pageSize": "50", "name": "行政"}).
Verify(func(a *Api) error {
ids := resultIds(a)
if !ids[textId] {
return fmt.Errorf("文本字段部分关键词模糊查询应命中 id=%d, 实际: %v", textId, ids)
}
return nil
}).
Get("文本字段部分关键词命中模糊匹配", 0, rowSample)
a.WithSession(session).
Note("state 规则覆盖为 select 类型(非text),应维持等值匹配现状,只精确命中 state=5 的记录").
Query(Map{"parent_id": rootId, "showself": "1", "showall": "1", "pageSize": "50", "state": "5"}).
Verify(func(a *Api) error {
ids := resultIds(a)
if !ids[textId] {
return fmt.Errorf("非文本字段等值筛选应命中 id=%d, 实际: %v", textId, ids)
}
for id := range ids {
if id != textId {
return fmt.Errorf("非文本字段等值筛选不应包含其它 state 的记录,实际额外命中: id=%d", id)
}
}
return nil
}).
Get("非文本字段等值筛选行为不变", 0, rowSample)
}},
},
}