package cn.iocoder.yudao.module.mes.service.safety; import cn.iocoder.yudao.module.hrm.api.employee.HrmEmployeeApi; import cn.iocoder.yudao.module.hrm.api.employee.dto.HrmEmployeeSimpleDTO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkshopDO; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** * {@link SafetyRefResolver} 空集合场景回归测试 * *

背景:{@code Map.of()} 是空且不可变的 Map,它的 {@code get(null)} 会抛 NPE, * 而 {@code Map.of().get(非null)} 不会。安全生产模块里「组织 / 证件 / 隐患」等 * 记录在未关联员工(leaderId、employeeId 为 null)时,就会走到 {@code map.get(null)}, * 从而在列表页直接 500。

* *

这里锁死约定:空集合必须返回可接受 null key 的空 Map。

*/ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class SafetyRefResolverEmptyCollectionTest { @InjectMocks private SafetyRefResolver resolver; @Mock private HrmEmployeeApi hrmEmployeeApi; @Mock private cn.iocoder.yudao.module.mes.dal.mysql.md.workstation.MesMdWorkshopMapper workshopMapper; /** 空集合:必须能 get(null) 而不抛 NPE(这就是线上 NPE 的根因) */ @Test void employeeMap_emptyCollection_getNullShouldNotThrow() { Map map = resolver.employeeMap(List.of()); assertTrue(map.isEmpty()); // Map.of() 在这里会抛 NullPointerException assertNull(map.get(null), "空集合返回的 Map 必须允许用 null 作为 key 查询"); } /** 集合里全是 null:过滤后为空,同样必须能 get(null) */ @Test void employeeMap_allNullElements_getNullShouldNotThrow() { Map map = resolver.employeeMap(java.util.Arrays.asList((Long) null, null)); assertTrue(map.isEmpty()); assertNull(map.get(null)); } /** null 集合本身 */ @Test void employeeMap_nullCollection_getNullShouldNotThrow() { Map map = resolver.employeeMap(null); assertTrue(map.isEmpty()); assertNull(map.get(null)); } @Test void workshopMap_emptyCollection_getNullShouldNotThrow() { Map map = resolver.workshopMap(List.of()); assertTrue(map.isEmpty()); assertNull(map.get(null), "空集合返回的 Map 必须允许用 null 作为 key 查询"); } @Test void workshopMap_allNullElements_getNullShouldNotThrow() { Map map = resolver.workshopMap(java.util.Arrays.asList((Long) null)); assertTrue(map.isEmpty()); assertNull(map.get(null)); } @Test void workshopMap_nullCollection_getNullShouldNotThrow() { Map map = resolver.workshopMap(null); assertTrue(map.isEmpty()); assertNull(map.get(null)); } /** 返回的 Map 应该是可变的(避免后续 put 抛 UnsupportedOperationException) */ @Test void employeeMap_emptyResultShouldBeMutable() { Map map = resolver.employeeMap(List.of()); map.put(1L, new HrmEmployeeSimpleDTO()); assertEquals(1, map.size()); } @Test void workshopMap_emptyResultShouldBeMutable() { Map map = resolver.workshopMap(List.of()); map.put(1L, new MesMdWorkshopDO()); assertEquals(1, map.size()); } }