package com.battery.business.service.impl; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import com.ruoyi.common.exception.ServiceException; import com.battery.business.domain.TDetectionCabinet; import com.battery.business.domain.vo.DiscoveredCabinetVo; import com.battery.business.mapper.TDetectionCabinetMapper; import com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore; import com.battery.device.api.discovery.DeviceDiscoveryCandidateStore.CabinetCandidate; import com.battery.device.api.discovery.DeviceDiscoveryCandidateStore.ChannelCandidate; import com.battery.device.api.model.ChannelIdentity; import com.battery.device.api.model.DeviceWorkStatus; /** * 检测柜业务层单元测试,验证字段规范化、状态限制及唯一性校验等关键规则。 */ @ExtendWith(MockitoExtension.class) class TDetectionCabinetServiceImplTest { @Mock private TDetectionCabinetMapper detectionCabinetMapper; private TDetectionCabinetServiceImpl detectionCabinetService; private InMemoryDeviceDiscoveryCandidateStore candidateStore; private TDetectionCabinet detectionCabinet; /** * 初始化一条满足新增校验的检测柜 Mock 数据。 */ @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(" 一号检测柜 "); detectionCabinet.setIpAddress(" 192.168.1.10 "); detectionCabinet.setMacAddress("aa-bb-cc-dd-ee-ff"); detectionCabinet.setManufacturer(" 厂商 "); detectionCabinet.setModel(" 型号A "); detectionCabinet.setCapacity(new BigDecimal("120.50")); detectionCabinet.setChannelCount(8); detectionCabinet.setRemark(" 备注 "); } /** * 验证新增时会规范化基础字段并初始化运行状态。 */ @Test void insertDetectionCabinetShouldPromoteDiscoveredCandidate() { when(detectionCabinetMapper.insertDetectionCabinet(any(TDetectionCabinet.class))).thenAnswer(invocation -> { invocation.getArgument(0, TDetectionCabinet.class).setCabinetId(7L); return 1; }); int rows = detectionCabinetService.insertDetectionCabinet(detectionCabinet); ArgumentCaptor captor = ArgumentCaptor.forClass(TDetectionCabinet.class); verify(detectionCabinetMapper).insertDetectionCabinet(captor.capture()); TDetectionCabinet savedCabinet = captor.getValue(); assertEquals(1, rows); assertEquals(7L, savedCabinet.getCabinetId()); assertEquals("CABINET-001", savedCabinet.getCabinetCode()); assertEquals("一号检测柜", savedCabinet.getCabinetName()); assertEquals("192.168.1.10", savedCabinet.getIpAddress()); assertEquals("AA:BB:CC:DD:EE:FF", savedCabinet.getMacAddress()); assertEquals(new BigDecimal("120.50"), savedCabinet.getCapacity()); assertEquals("OFFLINE", savedCabinet.getOnlineStatus()); assertEquals("0", savedCabinet.getStatus()); assertTrue(savedCabinet.getRelatedTests().isEmpty()); } /** * 验证设备选择查询不再从 MySQL 读取发现候选。 */ @Test void discoveredCabinetSqlShouldNotReadLegacyTestColumns() throws IOException { try (InputStream input = getClass().getResourceAsStream( "/mapper/battery/TDetectionCabinetMapper.xml")) { assertNotNull(input); String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8); assertFalse(mapperXml.contains("selectDiscoveredCabinetList")); assertFalse(mapperXml.contains("registration_status = 'DISCOVERED'")); } } /** * 验证删除正式检测柜只取消人工登记,不删除适配器持续维护的设备事实。 */ @Test void deleteDetectionCabinetSqlShouldReturnOnlineDeviceToDiscoveryPool() throws IOException { try (InputStream input = getClass().getResourceAsStream( "/mapper/battery/TDetectionCabinetMapper.xml")) { assertNotNull(input); String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8); int deleteStart = mapperXml.indexOf("", deleteStart); String deleteSql = mapperXml.substring(deleteStart, deleteEnd); assertTrue(deleteSql.contains("set del_flag = cabinet_id")); assertTrue(deleteSql.contains("registration_status = 'REGISTERED'")); } } /** * 验证检测柜主列表保留操作校验所需派生状态,不读取数据库不存在的列。 */ @Test void detectionCabinetListSqlShouldDeriveOperationStateWithoutCurrentTest() throws IOException { try (InputStream input = getClass().getResourceAsStream( "/mapper/battery/TDetectionCabinetMapper.xml")) { assertNotNull(input); String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8); int selectStart = mapperXml.indexOf("", selectStart); String selectSql = mapperXml.substring(selectStart, selectEnd); assertFalse(selectSql.contains("online_status, test_status")); assertFalse(selectSql.contains("current_test, status")); assertTrue(selectSql.contains("as test_status")); assertFalse(selectSql.contains("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("