| | |
| | | import com.ruoyi.system.domain.TDetectionCabinet; |
| | | import com.ruoyi.system.domain.vo.DiscoveredCabinetVo; |
| | | import com.ruoyi.system.mapper.TDetectionCabinetMapper; |
| | | import com.ruoyi.device.discovery.InMemoryDeviceDiscoveryCandidateStore; |
| | | import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore.CabinetCandidate; |
| | | import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore.ChannelCandidate; |
| | | import com.ruoyi.device.model.ChannelIdentity; |
| | | import com.ruoyi.device.model.DeviceWorkStatus; |
| | | |
| | | /** |
| | | * 检测柜业务层单元测试,验证字段规范化、状态限制及唯一性校验等关键规则。 |
| | |
| | | @Mock |
| | | private TDetectionCabinetMapper detectionCabinetMapper; |
| | | |
| | | @InjectMocks |
| | | private TDetectionCabinetServiceImpl detectionCabinetService; |
| | | private InMemoryDeviceDiscoveryCandidateStore candidateStore; |
| | | |
| | | private TDetectionCabinet detectionCabinet; |
| | | |
| | |
| | | @BeforeEach |
| | | void setUp() |
| | | { |
| | | candidateStore = new InMemoryDeviceDiscoveryCandidateStore(); |
| | | detectionCabinetService = new TDetectionCabinetServiceImpl(detectionCabinetMapper, candidateStore); |
| | | candidateStore.recordCabinets(List.of(candidate("CABINET-001", true, DeviceWorkStatus.WORKING))); |
| | | detectionCabinet = new TDetectionCabinet(); |
| | | detectionCabinet.setCabinetCode(" CABINET-001 "); |
| | | detectionCabinet.setCabinetName(" 一号检测柜 "); |
| | |
| | | @Test |
| | | void insertDetectionCabinetShouldPromoteDiscoveredCandidate() |
| | | { |
| | | TDetectionCabinet candidate = new TDetectionCabinet(); |
| | | candidate.setCabinetId(7L); |
| | | candidate.setCabinetCode("CABINET-001"); |
| | | candidate.setMacAddress("AA:BB:CC:DD:EE:FF"); |
| | | candidate.setRegistrationStatus("DISCOVERED"); |
| | | when(detectionCabinetMapper.selectByCabinetCode("CABINET-001")).thenReturn(candidate); |
| | | when(detectionCabinetMapper.selectByMacAddress("AA:BB:CC:DD:EE:FF")).thenReturn(candidate); |
| | | when(detectionCabinetMapper.registerDiscoveredCabinet(any(TDetectionCabinet.class))).thenReturn(1); |
| | | when(detectionCabinetMapper.insertDetectionCabinet(any(TDetectionCabinet.class))).thenAnswer(invocation -> { |
| | | invocation.getArgument(0, TDetectionCabinet.class).setCabinetId(7L); |
| | | return 1; |
| | | }); |
| | | |
| | | int rows = detectionCabinetService.insertDetectionCabinet(detectionCabinet); |
| | | |
| | | ArgumentCaptor<TDetectionCabinet> captor = ArgumentCaptor.forClass(TDetectionCabinet.class); |
| | | verify(detectionCabinetMapper).registerDiscoveredCabinet(captor.capture()); |
| | | verify(detectionCabinetMapper).insertDetectionCabinet(captor.capture()); |
| | | TDetectionCabinet savedCabinet = captor.getValue(); |
| | | assertEquals(1, rows); |
| | | assertEquals(7L, savedCabinet.getCabinetId()); |
| | |
| | | assertEquals("192.168.1.10", savedCabinet.getIpAddress()); |
| | | assertEquals("AA:BB:CC:DD:EE:FF", savedCabinet.getMacAddress()); |
| | | assertEquals(new BigDecimal("120.50"), savedCabinet.getCapacity()); |
| | | assertNull(savedCabinet.getOnlineStatus()); |
| | | assertEquals("OFFLINE", savedCabinet.getOnlineStatus()); |
| | | assertEquals("0", savedCabinet.getStatus()); |
| | | assertNull(savedCabinet.getCurrentTest()); |
| | | } |
| | | |
| | | /** |
| | | * 验证设备选择查询不读取旧检测柜测试字段,并改由持久化通道状态聚合。 |
| | | * 验证设备选择查询不再从 MySQL 读取发现候选。 |
| | | */ |
| | | @Test |
| | | void discoveredCabinetSqlShouldNotReadLegacyTestColumns() throws IOException |
| | |
| | | { |
| | | assertNotNull(input); |
| | | String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8); |
| | | int selectStart = mapperXml.indexOf("<select id=\"selectDiscoveredCabinetList\""); |
| | | int selectEnd = mapperXml.indexOf("</select>", selectStart); |
| | | String selectSql = mapperXml.substring(selectStart, selectEnd); |
| | | String activeChannelSql = sqlFragment(mapperXml, "activeCabinetChannelExists"); |
| | | assertFalse(mapperXml.contains("selectDiscoveredCabinetList")); |
| | | assertFalse(mapperXml.contains("registration_status = 'DISCOVERED'")); |
| | | } |
| | | } |
| | | |
| | | assertFalse(selectSql.contains("cabinet.test_status")); |
| | | assertFalse(selectSql.contains("cabinet.current_test")); |
| | | assertTrue(selectSql.contains("activeCabinetChannelExists")); |
| | | assertTrue(activeChannelSql.contains("t_detection_channel")); |
| | | assertTrue(activeChannelSql.contains("work_status")); |
| | | assertTrue(selectSql.contains("registration_status = 'DISCOVERED'")); |
| | | /** |
| | | * 验证删除正式检测柜只取消人工登记,不删除适配器持续维护的设备事实。 |
| | | */ |
| | | @Test |
| | | void deleteDetectionCabinetSqlShouldReturnOnlineDeviceToDiscoveryPool() throws IOException |
| | | { |
| | | try (InputStream input = getClass().getResourceAsStream( |
| | | "/mapper/system/TDetectionCabinetMapper.xml")) |
| | | { |
| | | assertNotNull(input); |
| | | String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8); |
| | | int deleteStart = mapperXml.indexOf("<update id=\"deleteDetectionCabinetById\""); |
| | | int deleteEnd = mapperXml.indexOf("</update>", deleteStart); |
| | | String deleteSql = mapperXml.substring(deleteStart, deleteEnd); |
| | | |
| | | assertTrue(deleteSql.contains("set del_flag = cabinet_id")); |
| | | assertTrue(deleteSql.contains("registration_status = 'REGISTERED'")); |
| | | } |
| | | } |
| | | |
| | |
| | | int selectStart = mapperXml.indexOf("<sql id=\"selectDetectionCabinetVo\""); |
| | | int selectEnd = mapperXml.indexOf("</sql>", selectStart); |
| | | String selectSql = mapperXml.substring(selectStart, selectEnd); |
| | | String activeChannelSql = sqlFragment(mapperXml, "activeCabinetChannelExists"); |
| | | |
| | | assertFalse(selectSql.contains("online_status, test_status")); |
| | | assertFalse(selectSql.contains("current_test, status")); |
| | | assertTrue(selectSql.contains("activeCabinetChannelExists")); |
| | | assertTrue(activeChannelSql.contains("t_detection_channel")); |
| | | assertTrue(activeChannelSql.contains("work_status")); |
| | | assertTrue(selectSql.contains("as test_status")); |
| | | assertTrue(selectSql.contains("as current_test")); |
| | | String activeTestSql = sqlFragment(mapperXml, "activeCabinetTestExists"); |
| | | assertTrue(activeTestSql.contains("t_test_channel_binding")); |
| | | assertTrue(activeTestSql.contains("t_detection_channel")); |
| | | assertTrue(activeTestSql.contains("test_fact.test_status")); |
| | | assertFalse(activeTestSql.contains("t_test_battery")); |
| | | assertFalse(activeTestSql.contains("battery_fact.cabinet_id")); |
| | | int listStart = mapperXml.indexOf("<select id=\"selectDetectionCabinetList\""); |
| | | int listEnd = mapperXml.indexOf("</select>", listStart); |
| | | assertTrue(mapperXml.substring(listStart, listEnd).contains("registration_status = 'REGISTERED'")); |
| | | int registerStart = mapperXml.indexOf("<update id=\"registerDiscoveredCabinet\""); |
| | | int registerEnd = mapperXml.indexOf("</update>", registerStart); |
| | | int registerStart = mapperXml.indexOf("<insert id=\"insertDetectionCabinet\""); |
| | | int registerEnd = mapperXml.indexOf("</insert>", registerStart); |
| | | String registerSql = mapperXml.substring(registerStart, registerEnd); |
| | | assertTrue(registerSql.contains("registration_status = 'REGISTERED'")); |
| | | assertTrue(registerSql.contains("create_by = #{createBy}")); |
| | | assertTrue(registerSql.contains("'REGISTERED'")); |
| | | assertTrue(registerSql.contains("#{createBy}")); |
| | | } |
| | | } |
| | | |
| | |
| | | return mapperXml.substring(start, end); |
| | | } |
| | | |
| | | private CabinetCandidate candidate(String deviceCode, boolean online, DeviceWorkStatus workStatus) |
| | | { |
| | | java.time.Instant observedAt = java.time.Instant.now(); |
| | | List<ChannelCandidate> channels = java.util.stream.IntStream.rangeClosed(1, 8) |
| | | .mapToObj(channelId -> new ChannelCandidate( |
| | | new ChannelIdentity("dev-sim", 22, 1, 1, channelId), online, workStatus, observedAt)) |
| | | .toList(); |
| | | return new CabinetCandidate("dev-sim", null, deviceCode, 22, 1, "192.168.1.10", |
| | | "AA:BB:CC:DD:EE:FF", "一号模拟柜", "开发模拟器", "BTS Virtual Cabinet", |
| | | channels, observedAt, observedAt.plusSeconds(60)); |
| | | } |
| | | |
| | | /** |
| | | * 验证设备选择列表复用已持久化检测柜,并保持原前端字段和状态文本。 |
| | | * 验证设备选择列表读取 Redis 候选,并保持原前端字段和状态文本。 |
| | | */ |
| | | @Test |
| | | void selectDiscoveredCabinetListShouldMapPersistedCabinetFacts() |
| | | { |
| | | TDetectionCabinet persisted = new TDetectionCabinet(); |
| | | persisted.setCabinetCode("CABINET-001"); |
| | | persisted.setIpAddress("192.168.1.10"); |
| | | persisted.setMacAddress("AA:BB:CC:DD:EE:FF"); |
| | | persisted.setCabinetName("一号模拟柜"); |
| | | persisted.setManufacturer("开发模拟器"); |
| | | persisted.setModel("BTS Virtual Cabinet"); |
| | | persisted.setChannelCount(6); |
| | | persisted.setOnlineStatus("ONLINE"); |
| | | persisted.setObservedWorkStatus("ACTIVE"); |
| | | when(detectionCabinetMapper.selectDiscoveredCabinetList()) |
| | | .thenReturn(List.of(persisted)); |
| | | when(detectionCabinetMapper.selectRegisteredCabinetCodes()).thenReturn(List.of()); |
| | | |
| | | List<DiscoveredCabinetVo> devices = detectionCabinetService.selectDiscoveredCabinetList(); |
| | | |
| | |
| | | assertEquals("一号模拟柜", devices.get(0).getName()); |
| | | assertEquals("开发模拟器", devices.get(0).getManufacturer()); |
| | | assertEquals("BTS Virtual Cabinet", devices.get(0).getModel()); |
| | | assertEquals(6, devices.get(0).getChannelCount()); |
| | | assertEquals(8, devices.get(0).getChannelCount()); |
| | | } |
| | | |
| | | /** |
| | |
| | | () -> detectionCabinetService.insertDetectionCabinet(detectionCabinet)); |
| | | |
| | | assertEquals("设备编码已存在", exception.getMessage()); |
| | | verify(detectionCabinetMapper, never()).registerDiscoveredCabinet(any(TDetectionCabinet.class)); |
| | | verify(detectionCabinetMapper, never()).insertDetectionCabinet(any(TDetectionCabinet.class)); |
| | | } |
| | | |
| | | @Test |
| | | void insertDetectionCabinetShouldRequireDiscoveredCandidate() |
| | | { |
| | | when(detectionCabinetMapper.selectByCabinetCode("CABINET-001")).thenReturn(null); |
| | | detectionCabinetService = new TDetectionCabinetServiceImpl(detectionCabinetMapper, |
| | | new InMemoryDeviceDiscoveryCandidateStore()); |
| | | |
| | | ServiceException exception = assertThrows(ServiceException.class, |
| | | () -> detectionCabinetService.insertDetectionCabinet(detectionCabinet)); |
| | | |
| | | assertEquals("请先选择发现候选设备", exception.getMessage()); |
| | | verify(detectionCabinetMapper, never()).registerDiscoveredCabinet(any()); |
| | | assertEquals("请先选择在线的发现候选设备", exception.getMessage()); |
| | | verify(detectionCabinetMapper, never()).insertDetectionCabinet(any()); |
| | | } |
| | | |
| | | /** |
| | | * 验证非法 IP 地址会阻止新增。 |
| | | */ |
| | | @Test |
| | | void insertDetectionCabinetShouldRejectInvalidIpAddress() |
| | | void insertDetectionCabinetShouldValidateAuthoritativeCandidateTopology() |
| | | { |
| | | detectionCabinet.setIpAddress("invalid-ip"); |
| | | detectionCabinet.setMacAddress("invalid-mac"); |
| | | detectionCabinet.setChannelCount(99); |
| | | when(detectionCabinetMapper.insertDetectionCabinet(any(TDetectionCabinet.class))).thenAnswer(invocation -> { |
| | | invocation.getArgument(0, TDetectionCabinet.class).setCabinetId(7L); |
| | | return 1; |
| | | }); |
| | | |
| | | ServiceException exception = assertThrows(ServiceException.class, |
| | | () -> detectionCabinetService.insertDetectionCabinet(detectionCabinet)); |
| | | assertEquals(1, detectionCabinetService.insertDetectionCabinet(detectionCabinet)); |
| | | |
| | | assertEquals("IP地址格式不正确", exception.getMessage()); |
| | | verify(detectionCabinetMapper, never()).registerDiscoveredCabinet(any(TDetectionCabinet.class)); |
| | | ArgumentCaptor<TDetectionCabinet> captor = ArgumentCaptor.forClass(TDetectionCabinet.class); |
| | | verify(detectionCabinetMapper).insertDetectionCabinet(captor.capture()); |
| | | assertEquals("192.168.1.10", captor.getValue().getIpAddress()); |
| | | assertEquals("AA:BB:CC:DD:EE:FF", captor.getValue().getMacAddress()); |
| | | assertEquals(8, captor.getValue().getChannelCount()); |
| | | } |
| | | |
| | | /** |