刘光辉
昨天 bb638871a7fb692d80f1b7a758f991dc0879002c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package jnpf.bizcommon.audit.service.title;
 
import java.util.List;
 
/**
 * 表单勾选({@code __config__.biz_log_enabled})解析的纯 JVM 回归网。
 *
 * <p><b>为什么不复用 {@code AuditFormRegistry.collectFields}</b>:它递归时把子表 children
 * 拍平进同一个 map,天生分不出主表/子表;而它被 {@code resolveFieldMetadata} 与
 * {@code resolveBaselineFields} 两条已验证路径依赖、压着 {@code AuditL0PartialSnapshotProbe}
 * 的 46 条断言。标题是新语义,另起纯函数,风险隔离。
 *
 * <p><b>本组管的错法</b>:顺序被打乱(业务勾 a/c 显示成 c/a)、子表字段混进主表标题、
 * 布局容器(栅格/标签页)里的字段被漏掉。这三种端到端都只表现为"标题看着不太对",
 * 没有报错、没有日志,只能靠断言钉死。
 *
 * <p>runner:{@code tasks/audit-title-spec-checks.sh}
 */
public final class AuditTitleSpecProbe {
 
    private static int pass;
    private static int fail;
 
    public static void main(String[] args) {
        noneEnabled();
        mainFieldsKeepFormOrder();
        layoutContainersAreTraversed();
        subTableIsSeparated();
        subTableFieldsNeverLeakIntoMain();
        malformedInputDegradesToEmpty();
        System.out.println("=== 表单勾选解析断言:" + pass + " 通过 / " + fail + " 失败 ===");
        if (fail > 0) {
            System.exit(1);
        }
    }
 
    /** T1 一个都没勾 → EMPTY。这是当前库里 148 份表单的真实状态,必须是安全默认。 */
    private static void noneEnabled() {
        String json = "{\"fields\":["
                + field("a", "input", false)
                + "," + field("b", "input", false)
                + "]}";
        AuditTitleSpec spec = AuditTitleSpecParser.parse(json);
        check("T1 一个都没勾 → isEmpty", spec.isEmpty(), "勾选为空时不应产出任何标题字段");
    }
 
    /** T2 顺序必须是表单出现顺序,不是字母序——业务勾 a/c 就得是 a/c。 */
    private static void mainFieldsKeepFormOrder() {
        String json = "{\"fields\":["
                + field("zebra", "input", true)
                + "," + field("apple", "input", true)
                + "," + field("mango", "input", false)
                + "]}";
        List<String> main = AuditTitleSpecParser.parse(json).mainFields();
        check("T2 只收勾选字段", main.size() == 2, "实际 " + main);
        check("T2 保持表单顺序(非字母序)",
                "zebra".equals(main.get(0)) && "apple".equals(main.get(1)),
                "实际 " + main);
    }
 
    /** T3 栅格/标签页这类布局容器的 children 是主表字段,必须继续递归。 */
    private static void layoutContainersAreTraversed() {
        String json = "{\"fields\":[{\"__config__\":{\"jnpfKey\":\"row\",\"children\":["
                + field("inside", "input", true)
                + "]}}]}";
        List<String> main = AuditTitleSpecParser.parse(json).mainFields();
        check("T3 布局容器内的勾选字段被收进主表",
                main.size() == 1 && "inside".equals(main.get(0)), "实际 " + main);
    }
 
    /** T4 子表控件独立成组,带上表名。 */
    private static void subTableIsSeparated() {
        String json = "{\"fields\":["
                + field("main1", "input", true)
                + ",{\"__vModel__\":\"tableField6da682\",\"__config__\":{\"jnpfKey\":\"table\","
                + "\"tableName\":\"lims_jianyan_xiang\",\"children\":["
                + field("sub1", "input", true)
                + "," + field("sub2", "input", false)
                + "," + field("sub3", "input", true)
                + "]}}]}";
        AuditTitleSpec spec = AuditTitleSpecParser.parse(json);
        check("T4 主表只有 main1",
                spec.mainFields().size() == 1 && "main1".equals(spec.mainFields().get(0)),
                "实际 " + spec.mainFields());
        check("T4 识别出 1 个子表", spec.subTables().size() == 1,
                "实际 " + spec.subTables().size());
        AuditTitleSpec.SubTableSpec sub = spec.subTables().get(0);
        check("T4 子表 vModel", "tableField6da682".equals(sub.vModel()), "实际 " + sub.vModel());
        check("T4 子表表名", "lims_jianyan_xiang".equals(sub.tableName()), "实际 " + sub.tableName());
        check("T4 子表勾选字段按顺序",
                sub.fields().size() == 2 && "sub1".equals(sub.fields().get(0))
                        && "sub3".equals(sub.fields().get(1)),
                "实际 " + sub.fields());
    }
 
    /** T5 子表字段绝不能出现在主表标题里——否则主表那一行会混进子表的值。 */
    private static void subTableFieldsNeverLeakIntoMain() {
        String json = "{\"fields\":[{\"__vModel__\":\"tf1\",\"__config__\":{\"jnpfKey\":\"table\","
                + "\"tableName\":\"t_sub\",\"children\":[" + field("leaky", "input", true) + "]}}]}";
        AuditTitleSpec spec = AuditTitleSpecParser.parse(json);
        check("T5 子表字段不进主表", spec.mainFields().isEmpty(),
                "主表混入了子表字段:" + spec.mainFields());
    }
 
    /** T6 非法/空输入一律退化成 EMPTY,绝不抛异常——层 0 的 handler 外层虽有兜底,但那会丢整条事件。 */
    private static void malformedInputDegradesToEmpty() {
        check("T6 null 输入", AuditTitleSpecParser.parse(null).isEmpty(), "应为 EMPTY");
        check("T6 空串输入", AuditTitleSpecParser.parse("   ").isEmpty(), "应为 EMPTY");
        check("T6 非法 JSON", AuditTitleSpecParser.parse("{not json").isEmpty(), "应为 EMPTY");
        check("T6 没有 fields 键", AuditTitleSpecParser.parse("{\"x\":1}").isEmpty(), "应为 EMPTY");
    }
 
    private static String field(String vModel, String jnpfKey, boolean enabled) {
        return "{\"__vModel__\":\"" + vModel + "\",\"__config__\":{\"jnpfKey\":\"" + jnpfKey
                + "\",\"biz_log_enabled\":" + enabled + "}}";
    }
 
    private static void check(String name, boolean ok, String detail) {
        if (ok) {
            pass++;
            System.out.println("  ✅ " + name);
        } else {
            fail++;
            System.out.println("  ❌ " + name + " —— " + detail);
        }
    }
}