package cn.iocoder.yudao.module.mes.service.trace.consumer; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionReqVO; import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionRespVO; import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanSummaryRespVO; import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanTrendRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO; import cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer.MesTraceConsumerScanEventMapper; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; import java.time.LocalDateTime; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; /** * {@link MesTraceConsumerScanStatisticsServiceImpl} 的单元测试 * * @author 超级管理员 */ @Import(MesTraceConsumerScanStatisticsServiceImpl.class) public class MesTraceConsumerScanStatisticsServiceImplTest extends BaseDbUnitTest { @Resource private MesTraceConsumerScanStatisticsService statisticsService; @Resource private MesTraceConsumerScanEventMapper eventMapper; private MesTraceConsumerScanEventDO buildEvent(String traceCode, String sourceType, String channel, String province, Long batchId, boolean success, LocalDateTime eventTime) { return MesTraceConsumerScanEventDO.builder() .traceCode(traceCode).normalizedCode(traceCode).sourceType(sourceType) .channel(channel).province(province).batchId(batchId).success(success) .eventTime(eventTime).build(); } @Test public void testGetSummary_统计口径() { LocalDateTime t1 = LocalDateTime.of(2026, 9, 1, 10, 0); LocalDateTime t2 = LocalDateTime.of(2026, 9, 2, 11, 0); eventMapper.insert(buildEvent("B1", "BAG", "微信", "江苏省", 100L, true, t1)); eventMapper.insert(buildEvent("B2", "BAG", "浏览器", "江苏省", 100L, true, t2)); eventMapper.insert(buildEvent("B3", "BATCH", null, null, null, false, t2)); MesTraceConsumerScanSummaryRespVO vo = statisticsService.getSummary( LocalDateTime.of(2026, 9, 1, 0, 0), LocalDateTime.of(2026, 9, 3, 0, 0)); assertNotNull(vo); assertEquals(3L, vo.getTotal()); assertEquals(2L, vo.getSuccess()); assertEquals(1L, vo.getFailed()); assertEquals(1L, vo.getUniqueBatchCount()); // 仅 batchId 非空且去重 } @Test public void testGetTrend_补齐无数据日期() { // 9 月 1 日两条、9 月 3 日一条;9 月 2 日无数据应补 0 eventMapper.insert(buildEvent("B1", "BAG", "微信", null, 100L, true, LocalDateTime.of(2026, 9, 1, 9, 0))); eventMapper.insert(buildEvent("B2", "BATCH", null, null, null, false, LocalDateTime.of(2026, 9, 1, 10, 0))); eventMapper.insert(buildEvent("B3", "BAG", "浏览器", null, 100L, true, LocalDateTime.of(2026, 9, 3, 9, 0))); List list = statisticsService.getTrend( LocalDateTime.of(2026, 9, 1, 0, 0), LocalDateTime.of(2026, 9, 4, 0, 0)); assertNotNull(list); assertEquals(3, list.size()); assertEquals("2026-09-01", list.get(0).getDate()); assertEquals(2L, list.get(0).getTotal()); assertEquals("2026-09-02", list.get(1).getDate()); assertEquals(0L, list.get(1).getTotal()); // 补 0 assertEquals("2026-09-03", list.get(2).getDate()); assertEquals(1L, list.get(2).getTotal()); } @Test public void testGetDistribution_bySourceType() { eventMapper.insert(buildEvent("B1", "BAG", null, null, 100L, true, LocalDateTime.of(2026, 9, 1, 9, 0))); eventMapper.insert(buildEvent("B2", "PALLET", null, null, 100L, true, LocalDateTime.of(2026, 9, 1, 10, 0))); eventMapper.insert(buildEvent("B3", "PALLET", null, null, null, false, LocalDateTime.of(2026, 9, 1, 11, 0))); MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO(); reqVO.setGroupBy("sourceType"); List list = statisticsService.getDistribution(reqVO); assertNotNull(list); // PALLET 2 条 > BAG 1 条(按 count 倒序) assertEquals("PALLET", list.get(0).getName()); assertEquals(2L, list.get(0).getCount()); assertEquals("BAG", list.get(1).getName()); assertEquals(1L, list.get(1).getCount()); } @Test public void testGetDistribution_nullKey映射未知() { // 解析失败的事件 sourceType 为 null,不得因 groupBy 空键抛 NPE,应聚合为"未知" eventMapper.insert(buildEvent("B1", "BAG", null, null, 100L, true, LocalDateTime.of(2026, 9, 1, 9, 0))); eventMapper.insert(buildEvent("B2", null, null, null, null, false, LocalDateTime.of(2026, 9, 1, 10, 0))); eventMapper.insert(buildEvent("B3", null, null, null, null, false, LocalDateTime.of(2026, 9, 1, 11, 0))); MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO(); reqVO.setGroupBy("sourceType"); List list = statisticsService.getDistribution(reqVO); assertNotNull(list); // 按 count 倒序:未知(2) 在 BAG(1) 之前 assertEquals(2, list.size()); assertEquals("未知", list.get(0).getName()); assertEquals(2L, list.get(0).getCount()); assertEquals("BAG", list.get(1).getName()); assertEquals(1L, list.get(1).getCount()); } @Test public void testGetDistribution_success显示名称() { eventMapper.insert(buildEvent("B1", "BAG", null, null, 100L, true, LocalDateTime.of(2026, 9, 1, 9, 0))); eventMapper.insert(buildEvent("B2", "BAG", null, null, null, false, LocalDateTime.of(2026, 9, 1, 10, 0))); MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO(); reqVO.setGroupBy("success"); List list = statisticsService.getDistribution(reqVO); assertNotNull(list); assertEquals(2, list.size()); // 数量相同时按分组名升序:"0"(失败) 在 "1"(成功) 之前 assertEquals("失败", list.get(0).getName()); assertEquals("成功", list.get(1).getName()); } @Test public void testGetDistribution_非法维度拒绝() { MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO(); reqVO.setGroupBy("item_code; DROP TABLE x"); assertThrows(IllegalArgumentException.class, () -> statisticsService.getDistribution(reqVO)); } }