Files
cpdaily_xiaozhushou/backed/internal/handler/leave_xsgz_fill_test.go
T
becomer 6bc7ab359c xsgz regions 字段不再被文本值污染,且必填时能正确拦下
前端 Ie() 对 regions 组件的处理(studentForm-Dx5Fpjfs.js):

  s = (e[fieldId+"_regionsDetail"] ?? e[fieldId+"_detail"])
        ?.replace(/,/g,",")?.trim() || ""
  o.value = 选中值 ? (s ? `${选中值},${s}` : 选中值) : (s || n)

即 value 是「选中值,详情」的拼接串,详情还会把半角逗号换成全角。

本平台不提供地区选择,没有 _regionsDetail/_detail 可拼接,
此前的实现会把「请假原因」当作地区值写进 value,
学校端会收到无法解析的非法地区串。

改动:
- normalizeXsgzFields 对 regions 清空 value(置 "",由 xsgzFlatten 跳过,
  不会提交该键),避免残留脏值;
- fillXsgzFieldValues 跳过 regions(新增 xsgzNonTextFillable 统一判定
  上传类与地区类),不再把文本写进地区字段。

顺带修掉一个逻辑矛盾:checkXsgzUnfilledRequired 原先对「非文本可填」类型
一律 continue,导致**必填的 regions 字段永远不会被拦下**——
请求会带着空地区发出去,然后被学校端含糊拒绝。
现在只有上传类跳过(它由 uploadFiles 承载,无 value 属正常),
regions 必填无值时正常纳入预检,明确提示用户缺哪个字段。

测试:regions 不被填入请假原因、残留 value 被清空、
必填 regions 触发预检拦下(该用例正是暴露上述矛盾的地方)。
2026-10-17 18:24:19 +08:00

224 lines
8.0 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 handler
import "testing"
// fillXsgzFieldValues:优先按标签匹配,未命中时用必填文本字段兜底。
func TestFillXsgzFieldValues(t *testing.T) {
req := &ExecuteLeaveApplyRequest{LeaveReason: "感冒发烧需休息"}
t.Run("按标签命中原因字段", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "请假原因", "fieldType": "TEXTAREA", "required": true},
{"fieldId": "f2", "fieldName": "紧急联系人", "fieldType": "TEXT", "required": true},
}
fillXsgzFieldValues(fields, req)
if fields[0]["value"] != req.LeaveReason {
t.Fatalf("原因字段未被填充: %v", fields[0])
}
if _, ok := fields[1]["value"]; ok {
t.Fatalf("非原因字段不应被填充: %v", fields[1])
}
})
t.Run("学校自定义字段名时用必填文本兜底", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "申请理由说明", "fieldType": "TEXTAREA", "required": true},
{"fieldId": "f2", "fieldName": "备注", "fieldType": "TEXT", "required": false},
}
fillXsgzFieldValues(fields, req)
// "说明" 命中第一轮
if fields[0]["value"] != req.LeaveReason {
t.Fatalf("含'说明'的字段应被填充: %v", fields[0])
}
})
t.Run("无关键词时必填文本字段兜底", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "补充信息", "fieldType": "TEXTAREA", "required": true},
{"fieldId": "f2", "fieldName": "选择项", "fieldType": "RADIO", "required": true},
}
fillXsgzFieldValues(fields, req)
if fields[0]["value"] != req.LeaveReason {
t.Fatalf("必填文本字段应兜底填充: %v", fields[0])
}
if _, ok := fields[1]["value"]; ok {
t.Fatalf("选择类字段不应猜测填充: %v", fields[1])
}
})
t.Run("空 fields 与空原因不 panic", func(t *testing.T) {
fillXsgzFieldValues(nil, req)
fillXsgzFieldValues([]map[string]interface{}{}, req)
fillXsgzFieldValues([]map[string]interface{}{{"fieldId": "f1"}}, &ExecuteLeaveApplyRequest{})
})
}
// checkXsgzUnfilledRequired:找出必填但无值的字段,用于提交前预检。
func TestCheckXsgzUnfilledRequired(t *testing.T) {
t.Run("必填有值时不报告", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "请假原因", "required": true, "value": "感冒"},
}
if got := checkXsgzUnfilledRequired(fields); len(got) != 0 {
t.Fatalf("不应报告缺失,实际: %v", got)
}
})
t.Run("必填无值时报告字段名", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "是否离校", "required": true},
{"fieldId": "f2", "fieldName": "备注", "required": false},
}
got := checkXsgzUnfilledRequired(fields)
if len(got) != 1 || got[0] != "是否离校" {
t.Fatalf("应只报告「是否离校」,实际: %v", got)
}
})
t.Run("value 为空串也算缺失", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "请假事由", "required": true, "value": ""},
}
if got := checkXsgzUnfilledRequired(fields); len(got) != 1 {
t.Fatalf("空串应算缺失,实际: %v", got)
}
})
t.Run("无 fieldName 时回退 fieldId", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "field_abc", "required": true},
}
got := checkXsgzUnfilledRequired(fields)
if len(got) != 1 || got[0] != "field_abc" {
t.Fatalf("应回退到 fieldId,实际: %v", got)
}
})
t.Run("非必填缺失不报告", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "备注", "required": false},
}
if got := checkXsgzUnfilledRequired(fields); len(got) != 0 {
t.Fatalf("非必填不应报告,实际: %v", got)
}
})
}
// normalizeXsgzFields:按前端 Ie() 语义清洗,避免提交学校端读不到的键。
func TestNormalizeXsgzFields(t *testing.T) {
t.Run("上传类字段去掉 value", func(t *testing.T) {
f := map[string]interface{}{
"fieldId": "f1", "componentType": "imageUpload", "value": "脏数据",
}
normalizeXsgzFields([]map[string]interface{}{f})
if _, ok := f["value"]; ok {
t.Fatal("上传类字段不应保留 value(前端走 uploadFiles)")
}
})
t.Run("三种上传类型都处理", func(t *testing.T) {
for _, ct := range []string{"imageUpload", "fileUpload", "smoothSignature"} {
f := map[string]interface{}{"fieldId": "x", "componentType": ct, "value": "v"}
normalizeXsgzFields([]map[string]interface{}{f})
if _, ok := f["value"]; ok {
t.Fatalf("%s 应去掉 value", ct)
}
}
})
t.Run("多选数组 join 成逗号串", func(t *testing.T) {
f := map[string]interface{}{
"fieldId": "f2", "componentType": "checkbox",
"value": []interface{}{"a", "b", "c"},
}
normalizeXsgzFields([]map[string]interface{}{f})
if got := f["value"]; got != "a,b,c" {
t.Fatalf("多选应 join 为 a,b,c,实际 %v", got)
}
})
t.Run("数组中的 nil 与空串被跳过", func(t *testing.T) {
f := map[string]interface{}{
"fieldId": "f3", "componentType": "checkbox",
"value": []interface{}{"a", nil, "", "b"},
}
normalizeXsgzFields([]map[string]interface{}{f})
if got := f["value"]; got != "a,b" {
t.Fatalf("应跳过 nil 与空串,实际 %v", got)
}
})
t.Run("普通字符串字段不受影响", func(t *testing.T) {
f := map[string]interface{}{
"fieldId": "f4", "componentType": "TEXT", "value": "感冒",
}
normalizeXsgzFields([]map[string]interface{}{f})
if got := f["value"]; got != "感冒" {
t.Fatalf("普通字段不应被改动,实际 %v", got)
}
})
t.Run("空输入不 panic", func(t *testing.T) {
normalizeXsgzFields(nil)
normalizeXsgzFields([]map[string]interface{}{})
})
}
// fillXsgzFieldValues 不应把请假原因写进上传类字段。
func TestFillSkipsUploadFields(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "componentType": "imageUpload", "fieldName": "请假原因附件", "required": true},
}
req := &ExecuteLeaveApplyRequest{LeaveReason: "感冒"}
fillXsgzFieldValues(fields, req)
if _, ok := fields[0]["value"]; ok {
t.Fatalf("上传类字段不应被填充 value,实际 %v", fields[0]["value"])
}
}
// 上传类必填字段不应触发「必填未填」拦截。
func TestCheckUnfilledSkipsUpload(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "componentType": "imageUpload", "fieldName": "附件", "required": true},
{"fieldId": "f2", "componentType": "TEXT", "fieldName": "请假原因", "required": true, "value": "感冒"},
}
if missing := checkXsgzUnfilledRequired(fields); len(missing) != 0 {
t.Fatalf("上传类不应算缺失,实际: %v", missing)
}
}
// regions 地区字段:前端 value 是「选中值,详情」拼接串,本平台无此数据,
// 不能把请假原因写进去(会产生非法地区串)。
func TestRegionsFieldNotFilledWithReason(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "r1", "componentType": "regions", "fieldName": "请假原因", "required": false},
}
req := &ExecuteLeaveApplyRequest{LeaveReason: "感冒"}
fillXsgzFieldValues(fields, req)
if v, ok := fields[0]["value"]; ok && v != "" {
t.Fatalf("regions 字段不应被填入请假原因,实际 %v", v)
}
}
// regions 字段残留的非法 value 应被清空。
func TestNormalizeRegionsClearsValue(t *testing.T) {
f := map[string]interface{}{
"fieldId": "r1", "componentType": "regions", "value": "山东省,泰安市,泰山区",
}
normalizeXsgzFields([]map[string]interface{}{f})
if got := f["value"]; got != "" {
t.Fatalf("regions 的 value 应被清空,实际 %v", got)
}
}
// regions 必填但没有可用数据时,应被预检拦下(而不是发出去被学校端拒绝)。
func TestRegionsRequiredTriggersPrecheck(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "r1", "componentType": "regions", "fieldName": "籍贯", "required": true},
}
missing := checkXsgzUnfilledRequired(fields)
if len(missing) != 1 || missing[0] != "籍贯" {
t.Fatalf("必填 regions 无值应被预检拦下,实际: %v", missing)
}
}