package com.ruoyi.system.service.impl; 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; 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.Mock; import org.mockito.junit.jupiter.MockitoExtension; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.system.domain.TCollector; import com.ruoyi.system.mapper.TCollectorMapper; import com.battery.device.discovery.InMemoryDeviceDiscoveryCandidateStore; import com.battery.device.discovery.DeviceDiscoveryCandidateStore.CollectorCandidate; /** * 采集器 Service 单元测试。 */ @ExtendWith(MockitoExtension.class) class TCollectorServiceImplTest { @Mock private TCollectorMapper collectorMapper; private TCollectorServiceImpl collectorService; private InMemoryDeviceDiscoveryCandidateStore candidateStore; private TCollector collector; @BeforeEach void setUp() { candidateStore = new InMemoryDeviceDiscoveryCandidateStore(); collectorService = new TCollectorServiceImpl(collectorMapper, candidateStore); java.time.Instant now = java.time.Instant.now(); candidateStore.replaceCollectors("dev-sim", java.util.List.of(new CollectorCandidate( "dev-sim", "COL-001", "192.168.1.10", "AA:BB:CC:DD:EE:FF", "一号采集器", "开发模拟器", "Virtual Collector", 8, true, now, now.plusSeconds(60)))); collector = new TCollector(); collector.setCollectorId(1L); collector.setCollectorCode("COL-001"); collector.setIpAddress("192.168.1.10"); collector.setMacAddress("AA:BB:CC:DD:EE:FF"); collector.setCollectorName("一号采集器"); collector.setChannelCount(8); collector.setOnlineStatus("0"); collector.setTestStatus("0"); collector.setStatus("0"); } @Test void insertCollectorSqlShouldNotWriteUpdateAuditFields() throws IOException { try (InputStream input = getClass().getResourceAsStream( "/mapper/system/TCollectorMapper.xml")) { assertNotNull(input); String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8); int insertStart = mapperXml.indexOf("", insertStart); String insertSql = mapperXml.substring(insertStart, insertEnd); assertFalse(insertSql.contains("update_by")); assertFalse(insertSql.contains("update_time")); } } @Test void registeredCollectorCodeSqlShouldIgnoreDeletedRows() throws IOException { try (InputStream input = getClass().getResourceAsStream( "/mapper/system/TCollectorMapper.xml")) { assertNotNull(input); String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8); int selectStart = mapperXml.indexOf("", selectStart); String selectSql = mapperXml.substring(selectStart, selectEnd); assertTrue(selectSql.contains("where del_flag = '0'")); } } @Test void insertCollectorShouldNormalizeFieldsAndInitializeStatus() { collector.setCollectorId(null); collector.setCollectorCode(" COL-001 "); collector.setMacAddress("aa-bb-cc-dd-ee-ff"); when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1); int rows = collectorService.insertCollector(collector); ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class); verify(collectorMapper).insertCollector(captor.capture()); TCollector saved = captor.getValue(); assertEquals(1, rows); assertEquals("COL-001", saved.getCollectorCode()); assertEquals("AA:BB:CC:DD:EE:FF", saved.getMacAddress()); assertEquals("0", saved.getOnlineStatus()); assertEquals("0", saved.getTestStatus()); assertEquals("0", saved.getStatus()); assertEquals("0", saved.getDelFlag()); } @Test void insertCollectorShouldRetainRequestedDisabledStatus() { collector.setCollectorId(null); collector.setStatus("1"); when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1); assertEquals(1, collectorService.insertCollector(collector)); ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class); verify(collectorMapper).insertCollector(captor.capture()); assertEquals("1", captor.getValue().getStatus()); } @Test void insertCollectorShouldDefaultToEnabledWhenStatusIsMissing() { collector.setCollectorId(null); collector.setStatus(null); when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1); assertEquals(1, collectorService.insertCollector(collector)); ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class); verify(collectorMapper).insertCollector(captor.capture()); assertEquals("0", captor.getValue().getStatus()); } @Test void insertCollectorShouldValidateAuthoritativeCandidateTopology() { collector.setCollectorId(null); collector.setIpAddress("invalid-host"); collector.setMacAddress("invalid-mac"); collector.setChannelCount(99); when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1); assertEquals(1, collectorService.insertCollector(collector)); ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class); verify(collectorMapper).insertCollector(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()); } @Test void insertCollectorShouldRejectDuplicateCode() { collector.setCollectorId(null); TCollector existing = new TCollector(); existing.setCollectorId(2L); when(collectorMapper.selectCollectorByCode("COL-001")).thenReturn(existing); ServiceException exception = assertThrows(ServiceException.class, () -> collectorService.insertCollector(collector)); assertEquals("设备编码已存在", exception.getMessage()); verify(collectorMapper, never()).insertCollector(any(TCollector.class)); } @Test void updateCollectorShouldAllowCurrentUniqueValues() { when(collectorMapper.selectCollectorById(1L)).thenReturn(collector); when(collectorMapper.selectCollectorByCode("COL-001")).thenReturn(collector); when(collectorMapper.selectCollectorByMacAddress("AA:BB:CC:DD:EE:FF")).thenReturn(collector); when(collectorMapper.updateCollector(any(TCollector.class))).thenReturn(1); assertEquals(1, collectorService.updateCollector(collector)); verify(collectorMapper).updateCollector(collector); } @Test void updateCollectorShouldRejectDisablingTestingCollector() { collector.setStatus("1"); collector.setTestStatus("1"); when(collectorMapper.selectCollectorById(1L)).thenReturn(collector); ServiceException exception = assertThrows(ServiceException.class, () -> collectorService.updateCollector(collector)); assertEquals("采集器正在测试,不能禁用", exception.getMessage()); verify(collectorMapper, never()).updateCollector(any(TCollector.class)); } @Test void updateCollectorStatusShouldRejectDisablingTestingCollector() { collector.setTestStatus("1"); when(collectorMapper.selectCollectorById(1L)).thenReturn(collector); TCollector command = statusCommand("1"); ServiceException exception = assertThrows(ServiceException.class, () -> collectorService.updateCollectorStatus(command)); assertEquals("采集器正在测试,不能禁用", exception.getMessage()); verify(collectorMapper, never()).updateCollectorStatus(any(TCollector.class)); } @Test void updateCollectorStatusShouldAllowEnablingTestingCollector() { collector.setTestStatus("1"); collector.setStatus("1"); when(collectorMapper.selectCollectorById(1L)).thenReturn(collector); when(collectorMapper.updateCollectorStatus(any(TCollector.class))).thenReturn(1); TCollector command = statusCommand("0"); assertEquals(1, collectorService.updateCollectorStatus(command)); verify(collectorMapper).updateCollectorStatus(command); } @Test void updateCollectorStatusShouldBeIdempotent() { when(collectorMapper.selectCollectorById(1L)).thenReturn(collector); TCollector command = statusCommand("0"); assertEquals(1, collectorService.updateCollectorStatus(command)); verify(collectorMapper, never()).updateCollectorStatus(any(TCollector.class)); } @Test void selectRegisteredCollectorCodesShouldReturnUndeletedCodes() { when(collectorMapper.selectRegisteredCollectorCodes()).thenReturn(java.util.List.of("COL-001")); assertEquals(java.util.Set.of("COL-001"), collectorService.selectRegisteredCollectorCodes()); } @Test void deleteCollectorByIdsShouldRejectTestingCollectorBeforeAnyDelete() { TCollector idle = new TCollector(); idle.setCollectorId(1L); idle.setTestStatus("0"); TCollector testing = new TCollector(); testing.setCollectorId(2L); testing.setTestStatus("1"); when(collectorMapper.selectCollectorById(1L)).thenReturn(idle); when(collectorMapper.selectCollectorById(2L)).thenReturn(testing); ServiceException exception = assertThrows(ServiceException.class, () -> collectorService.deleteCollectorByIds(new Long[] { 1L, 2L }, "admin")); assertEquals("采集器正在测试,不能删除", exception.getMessage()); verify(collectorMapper, never()).deleteCollectorById(anyLong(), any(String.class)); } private TCollector statusCommand(String status) { TCollector command = new TCollector(); command.setCollectorId(1L); command.setStatus(status); command.setUpdateBy("admin"); return command; } }