liu
8 小时以前 da93fafc417b4f590b6ab639ccbbe67c14c8753c
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package cn.iocoder.yudao.module.mes.framework.config;
 
import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
 
import java.util.List;
 
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
 
/**
 * 字典解析器测试:守住「字典优先、配置文件兜底」的合并规则与「绝不抛异常」的约定。
 */
class MesTraceConfigResolverTest {
 
    private static final String YAML_URL = "http://yaml.example.org/trace.html";
    private static final String DICT_URL = "http://1.95.78.124:9008/prod/trace.html";
 
    private final DictDataApi dictDataApi = mock(DictDataApi.class);
 
    @Test
    void dictValueWinsOverYaml() {
        stubDict(dict("追溯网址", DICT_URL));
 
        MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
 
        assertEquals(DICT_URL, snapshot.traceUrl());
        assertEquals(MesTraceUrlSource.DICT, snapshot.traceUrlSource());
    }
 
    @Test
    void fallsBackToYamlWhenDictTypeMissing() {
        stubDict(); // 字典类型尚未初始化,属于正常的「尚未配置」路径
 
        MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
 
        assertEquals(YAML_URL, snapshot.traceUrl());
        assertEquals(MesTraceUrlSource.YAML, snapshot.traceUrlSource());
    }
 
    @Test
    void disabledDictItemIsTreatedAsAbsent() {
        // 禁用是不删数据即停用某项配置的手段,必须按「未配置」处理
        DictDataRespDTO disabled = dict("追溯网址", DICT_URL);
        disabled.setStatus(CommonStatusEnum.DISABLE.getStatus());
        stubDict(disabled);
 
        MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
 
        assertEquals(YAML_URL, snapshot.traceUrl());
        assertEquals(MesTraceUrlSource.YAML, snapshot.traceUrlSource());
    }
 
    @Test
    void blankDictValueFallsBackToYaml() {
        stubDict(dict("追溯网址", "   "));
 
        MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
 
        assertEquals(YAML_URL, snapshot.traceUrl());
        assertEquals(MesTraceUrlSource.YAML, snapshot.traceUrlSource());
    }
 
    @Test
    void invalidDictValueFallsBackToYamlAndFlagsSource() {
        // 字典里填了内网地址:降级回配置文件而不是抛异常,避免一次手误让全平台导出停摆
        stubDict(dict("追溯网址", "http://192.168.1.1/trace.html"));
 
        MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
 
        assertEquals(YAML_URL, snapshot.traceUrl());
        assertEquals(MesTraceUrlSource.YAML_INVALID_DICT, snapshot.traceUrlSource());
    }
 
    @Test
    void dictReadFailureFallsBackToYamlWithoutThrowing() {
        when(dictDataApi.getDictDataList(MesTraceConfigResolver.DICT_TYPE))
                .thenThrow(new RuntimeException("数据库抖动"));
 
        MesTraceConfigSnapshot snapshot = assertDoesNotThrow(() -> newResolver().resolveSnapshot());
 
        assertEquals(YAML_URL, snapshot.traceUrl());
        assertEquals(MesTraceUrlSource.YAML, snapshot.traceUrlSource());
    }
 
    @Test
    void dictIsReadOncePerCacheWindow() {
        stubDict(dict("追溯网址", DICT_URL));
        MesTraceConfigResolver resolver = newResolver();
 
        resolver.resolveSnapshot();
        resolver.resolveSnapshot();
        resolver.resolveSnapshot();
 
        // 标签导出会成千上万次取快照,绝不能每次都查库
        verify(dictDataApi, times(1)).getDictDataList(MesTraceConfigResolver.DICT_TYPE);
    }
 
    @Test
    void resolvesOtherFieldsIndependentlyFromDict() {
        stubDict(dict("追溯网址", DICT_URL),
                dict("生产经营者名称", "新疆塔里木河种业股份有限公司"),
                dict("进口商名称", "某进口商"),
                dict("是否进口种子", "true"));
 
        MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
 
        assertEquals("新疆塔里木河种业股份有限公司", snapshot.operatorName());
        assertEquals("某进口商", snapshot.importerName());
        assertTrue(snapshot.imported());
    }
 
    @Test
    void fieldsFallBackIndependentlyWhenOnlyUrlIsConfigured() {
        // 只配了追溯网址:其余三项仍应回退配置文件,而不是被清空
        stubDict(dict("追溯网址", DICT_URL));
 
        MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
 
        assertEquals(DICT_URL, snapshot.traceUrl());
        assertEquals("YAML 生产经营者", snapshot.operatorName());
        assertEquals("YAML 进口商", snapshot.importerName());
        assertFalse(snapshot.imported());
    }
 
    @Test
    void unrecognizedImportedValueFallsBackToYaml() {
        stubDict(dict("是否进口种子", "也许吧"));
 
        assertFalse(newResolver().resolveSnapshot().imported());
    }
 
    @Test
    void firstEnabledItemWinsWhenDuplicated() {
        // 同一标签重复配置时以排序靠前者为准(底层查询已按 sort 排序)
        stubDict(dict("追溯网址", DICT_URL), dict("追溯网址", "http://second.example.org/trace.html"));
 
        assertEquals(DICT_URL, newResolver().resolveSnapshot().traceUrl());
    }
 
    private MesTraceConfigResolver newResolver() {
        MesTraceConfigResolver resolver = new MesTraceConfigResolver();
        MesProperties properties = new MesProperties();
        properties.setTraceBaseUrl(YAML_URL);
        properties.setOperatorName("YAML 生产经营者");
        properties.setImporterName("YAML 进口商");
        properties.setImported(false);
        ReflectionTestUtils.setField(resolver, "dictDataApi", dictDataApi);
        ReflectionTestUtils.setField(resolver, "mesProperties", properties);
        return resolver;
    }
 
    private void stubDict(DictDataRespDTO... data) {
        when(dictDataApi.getDictDataList(MesTraceConfigResolver.DICT_TYPE)).thenReturn(List.of(data));
    }
 
    private static DictDataRespDTO dict(String label, String value) {
        DictDataRespDTO dto = new DictDataRespDTO();
        dto.setDictType(MesTraceConfigResolver.DICT_TYPE);
        dto.setLabel(label);
        dto.setValue(value);
        dto.setStatus(CommonStatusEnum.ENABLE.getStatus());
        return dto;
    }
 
}