| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- package com.etotem.cfc.service;
- import com.etotem.cfc.entity.IndicatorDefinition;
- import com.etotem.cfc.entity.IndicatorValue;
- import com.etotem.cfc.mapper.IndicatorDefinitionMapper;
- import com.etotem.cfc.mapper.IndicatorValueMapper;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.springframework.test.util.ReflectionTestUtils;
- import java.math.BigDecimal;
- import java.sql.Date;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- import java.util.Map;
- import static org.junit.jupiter.api.Assertions.*;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.Mockito.*;
- class IndicatorTrendServiceTest {
- private IndicatorTrendService service;
- private IndicatorValueMapper valueMapper;
- private IndicatorDefinitionMapper definitionMapper;
- private IndicatorDefinitionMapper changeDefinitionMapper;
- private IndicatorTimelineService timelineService;
- @BeforeEach
- void setUp() {
- service = new IndicatorTrendService();
- valueMapper = mock(IndicatorValueMapper.class);
- ReflectionTestUtils.setField(service, "valueMapper", valueMapper);
- definitionMapper = mock(IndicatorDefinitionMapper.class);
- ReflectionTestUtils.setField(service, "definitionMapper", definitionMapper);
- // 真实 IndicatorChangeService(Q17 后改走 changeService.loadActiveDefinitions),
- // 注入 mock mapper 保证单测不碰库
- IndicatorChangeService changeService = new IndicatorChangeService();
- changeDefinitionMapper = mock(IndicatorDefinitionMapper.class);
- ReflectionTestUtils.setField(changeService, "definitionMapper", changeDefinitionMapper);
- ReflectionTestUtils.setField(changeService, "valueMapper", valueMapper);
- IndicatorDefinition def = new IndicatorDefinition();
- def.setId(1L);
- def.setName("双歧杆菌属");
- def.setUnit("%");
- def.setCategory("肠道菌群");
- def.setGoodDirection("higher_is_better");
- def.setValueType("numeric");
- when(changeDefinitionMapper.selectList(any())).thenReturn(new ArrayList<>(Arrays.asList(def)));
- ReflectionTestUtils.setField(service, "changeService", changeService);
- timelineService = mock(IndicatorTimelineService.class);
- ReflectionTestUtils.setField(service, "timelineService", timelineService);
- }
- private IndicatorValue value(Long defId, String date, String val) {
- IndicatorValue v = new IndicatorValue();
- v.setDefinitionId(defId);
- v.setReportDate(Date.valueOf(date));
- v.setValue(val);
- v.setNumericValue(new BigDecimal(val));
- v.setUnit("%");
- v.setReportId(100L);
- return v;
- }
- /** report_date 为 null 的观测(用于验收标准 #4:null 日期不得被当成最新值) */
- private IndicatorValue valueWithNullDate(Long defId, String val) {
- IndicatorValue v = new IndicatorValue();
- v.setDefinitionId(defId);
- v.setReportDate(null);
- v.setValue(val);
- v.setNumericValue(new BigDecimal(val));
- v.setUnit("%");
- v.setReportId(101L);
- return v;
- }
- private IndicatorDefinition def(Long id, String name, String goodDirection, String unit) {
- IndicatorDefinition d = new IndicatorDefinition();
- d.setId(id);
- d.setName(name);
- d.setUnit(unit);
- d.setCategory("肠道菌群");
- d.setGoodDirection(goodDirection);
- d.setValueType("numeric");
- d.setStatus(1);
- return d;
- }
- @Test
- void trendDelegatesToTimelineService() {
- Map<String, Object> expected = new java.util.LinkedHashMap<>();
- expected.put("items", new ArrayList<>());
- when(timelineService.timeline(any(), any(), any(), any(), any(), any(), any()))
- .thenReturn(expected);
- Map<String, Object> data = service.trend(3L, Arrays.asList(1L), 2L, null, null, null, "raw", "all");
- assertSame(expected, data);
- verify(timelineService).timeline(eq(3L), eq(Arrays.asList(1L)), eq(null), eq(null), eq(null), eq("raw"), eq("all"));
- }
- @Test
- void changeRankingAdjacentComputesDeltaAndTrendLabel() {
- // 双歧杆菌属 higher_is_better:升幅 → trendLabel=改善
- List<IndicatorValue> rows = new ArrayList<>();
- rows.add(value(1L, "2026-08-15", "10"));
- rows.add(value(1L, "2026-10-01", "12.8"));
- when(valueMapper.selectList(any())).thenReturn(rows);
- java.util.Map<String, Object> data = service.changeRanking(3L, null, null, "adjacent", "magnitude", 50, 2L,
- null, null, null, null, null);
- List<?> items = (List<?>) data.get("items");
- assertEquals(1, items.size());
- Map<?, ?> item = (Map<?, ?>) items.get(0);
- assertEquals("up", item.get("movement"));
- assertEquals("改善", item.get("trendLabel"));
- assertEquals("improved", item.get("trendCode"));
- Map<?, ?> overall = (Map<?, ?>) data.get("overall");
- assertEquals(1, ((Number) overall.get("improvedCount")).intValue());
- }
- @Test
- void quickLookupReturnsItemsAndCategories() {
- List<IndicatorValue> rows = new ArrayList<>();
- rows.add(value(1L, "2026-10-01", "12.8"));
- when(valueMapper.selectList(any())).thenReturn(rows);
- Map<String, Object> data = service.quickLookup(3L, null, 1, 30, 2L, false, 8);
- assertTrue(data.containsKey("items"));
- assertTrue(data.containsKey("categories"));
- List<?> items = (List<?>) data.get("items");
- assertEquals(1, items.size());
- Map<?, ?> item = (Map<?, ?>) items.get(0);
- // reportDate=null 的观测被排在最后(修 bug)
- assertEquals("2026-10-01", item.get("latestReportDate"));
- assertNotNull(item.get("change"));
- }
- /** 验收标准 #4:quick-lookup 中 report_date=null 的观测不再被当成最新值 */
- @Test
- void quickLookup_nullReportDateNotTreatedAsLatest() {
- List<IndicatorValue> rows = new ArrayList<>();
- rows.add(valueWithNullDate(1L, "999")); // null 日期 → 不参与最新值竞争
- rows.add(value(1L, "2026-10-01", "12.8")); // 有日期 → 最新
- // 第一次 selectList 拉全部观测;resolvePrevious 的第二次 selectList 返回空(→ first_record)
- when(valueMapper.selectList(any())).thenReturn(rows, new ArrayList<>());
- Map<String, Object> data = service.quickLookup(3L, null, 1, 30, 2L, false, 8);
- Map<?, ?> item = (Map<?, ?>) ((List<?>) data.get("items")).get(0);
- assertEquals("2026-10-01", item.get("latestReportDate"), "null 日期观测不得排到最新");
- assertEquals("12.8", item.get("value"), "最新值必须取有日期的观测");
- }
- /** 验收标准 #8:quickLookup 对 status=0 的 definition 一律剔除,不降级为「指标」+id 假名称 */
- @Test
- void quickLookup_excludesStatus0Definitions() {
- IndicatorDefinition active = def(1L, "双歧杆菌属", "higher_is_better", "%");
- IndicatorDefinition dead = def(2L, "废弃指标", "higher_is_better", "U");
- dead.setStatus(0);
- // loadActiveDefinitions 仅返回 status=1 的定义(模拟 DB 过滤)
- when(changeDefinitionMapper.selectList(any())).thenReturn(new ArrayList<>(Arrays.asList(active)));
- List<IndicatorValue> rows = new ArrayList<>();
- rows.add(value(1L, "2026-10-01", "12.8"));
- rows.add(value(2L, "2026-10-01", "5.5")); // status=0 → 必须剔除
- when(valueMapper.selectList(any())).thenReturn(rows, new ArrayList<>());
- Map<String, Object> data = service.quickLookup(3L, null, 1, 30, 2L, false, 8);
- List<?> items = (List<?>) data.get("items");
- assertEquals(1, items.size(), "status=0 指标不得出现在结果中");
- Map<?, ?> item = (Map<?, ?>) items.get(0);
- assertEquals("双歧杆菌属", item.get("name"));
- assertFalse(String.valueOf(item.get("name")).startsWith("指标"), "不得生成「指标」+id 的假名称");
- }
- /** 验收标准 #9:magnitude 排序中 deltaPct=null 的项(基数为 0)恒定排最后 */
- @Test
- void changeRanking_magnitudeDeltaPctNullAlwaysLast() {
- // 三个定义:正常幅度 10%、轻微 1%、基数为 0(deltaPct=null)
- IndicatorDefinition d1 = def(1L, "指标A", "higher_is_better", "%");
- IndicatorDefinition d2 = def(2L, "指标B", "higher_is_better", "%");
- IndicatorDefinition d3 = def(3L, "指标C", "higher_is_better", "%");
- d3.setRefRange("0.00-1.00");
- when(changeDefinitionMapper.selectList(any()))
- .thenReturn(new ArrayList<>(Arrays.asList(d1, d2, d3)));
- List<IndicatorValue> rows = new ArrayList<>();
- rows.add(value(1L, "2026-08-01", "100"));
- rows.add(value(1L, "2026-10-01", "110")); // deltaPct = 10%
- rows.add(value(2L, "2026-08-01", "100"));
- rows.add(value(2L, "2026-10-01", "101")); // deltaPct = 1%
- rows.add(value(3L, "2026-08-01", "0"));
- rows.add(value(3L, "2026-10-01", "5")); // 基数为 0 → deltaPct=null
- when(valueMapper.selectList(any())).thenReturn(rows);
- Map<String, Object> data = service.changeRanking(3L, null, null, "adjacent", "magnitude", 50, 2L,
- null, null, null, null, null);
- List<Map<?, ?>> items = (List<Map<?, ?>>) data.get("items");
- assertEquals(3, items.size());
- assertEquals("指标A", items.get(0).get("name"), "|10%| 最大,应排第一");
- assertEquals("指标B", items.get(1).get("name"), "|1%| 次之");
- assertEquals("指标C", items.get(2).get("name"), "deltaPct=null 恒定排最后");
- assertNull(items.get(2).get("deltaPct"), "基数为 0 的百分比必须为 null");
- }
- }
|