152878cc7c
自审本轮改动时发现的不一致:销假路径先按标签用地址兜底填充,
再调用 normalizeXsgzFields,而后者对 regions 是「清空 value」。
两者叠加导致:
- 若字段名含"原因/说明"且是 regions 类型,地址先被写入、又被清空,
等于白填,且语义上互相矛盾;
- 上传类字段同样可能先被写入地址,再被 delete(value)。
请假路径的 fillXsgzFieldValues 已跳过这两类,销假这段循环没跟上。
改动:销假填充循环加 xsgzNonTextFillable 判断,与请假路径保持一致。
测试:一个同时含 regions / imageUpload / TEXT 的销假表单,
断言文本字段得到地址、地区字段为空、上传字段无 value。
261 lines
9.3 KiB
Go
261 lines
9.3 KiB
Go
package handler
|
||
|
||
import (
|
||
"strings"
|
||
"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)
|
||
}
|
||
}
|
||
|
||
// 销假用地址兜底填充时,也不能污染上传类/地区类字段。
|
||
// 该用例防止「填充」与「normalize 清空」互相打架时留下脏值。
|
||
func TestCancelFillSkipsNonTextFields(t *testing.T) {
|
||
fields := []map[string]interface{}{
|
||
{"fieldId": "r1", "componentType": "regions", "fieldName": "销假原因"},
|
||
{"fieldId": "u1", "componentType": "imageUpload", "fieldName": "销假说明附件"},
|
||
{"fieldId": "t1", "componentType": "TEXT", "fieldName": "销假说明"},
|
||
}
|
||
addr := "山东省泰安市"
|
||
for _, f := range fields {
|
||
if xsgzNonTextFillable(mapStr(f, "componentType")) {
|
||
continue
|
||
}
|
||
label := strings.ToLower(mapStr(f, "fieldZh") + mapStr(f, "fieldName") + mapStr(f, "label"))
|
||
if strings.Contains(label, "说明") || strings.Contains(label, "原因") {
|
||
f["value"] = addr
|
||
}
|
||
}
|
||
normalizeXsgzFields(fields)
|
||
|
||
// 文本字段应保留地址
|
||
if fields[2]["value"] != addr {
|
||
t.Fatalf("文本字段应填入地址,实际 %v", fields[2]["value"])
|
||
}
|
||
// 地区字段应被清空(不是地址)
|
||
if got := fields[0]["value"]; got != "" {
|
||
t.Fatalf("地区字段应为空,实际 %v", got)
|
||
}
|
||
// 上传字段不应有 value
|
||
if _, ok := fields[1]["value"]; ok {
|
||
t.Fatalf("上传字段不应有 value,实际 %v", fields[1]["value"])
|
||
}
|
||
}
|