package com.battery.business.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.assertNull; 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.battery.business.domain.TCollector; import com.battery.business.domain.TDeviceAdapter; import com.battery.business.mapper.TCollectorMapper; import com.battery.business.mapper.TDeviceAdapterMapper; /** * 采集器 Service 单元测试。 * *

采集器按 ADR-0064 不建独立设备模型:登记不再依赖发现候选(真实来源不产出采集器候选), * 由操作员手工登记来源身份,在线状态取所链接来源实例的连接结果。 */ @ExtendWith(MockitoExtension.class) class TCollectorServiceImplTest { @Mock private TCollectorMapper collectorMapper; @Mock private TDeviceAdapterMapper adapterMapper; private TCollectorServiceImpl collectorService; private TCollector collector; @BeforeEach void setUp() { collectorService = new TCollectorServiceImpl(collectorMapper, adapterMapper); 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/battery/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 onlineStatusSqlShouldDeriveFromLinkedAdapter() throws IOException { try (InputStream input = getClass().getResourceAsStream( "/mapper/battery/TCollectorMapper.xml")) { assertNotNull(input); String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8); int fragmentStart = mapperXml.indexOf("= 0, "在线状态的派生表达式应当是一个共用片段"); String fragment = mapperXml.substring(fragmentStart, mapperXml.indexOf("", fragmentStart)); assertTrue(fragment.contains("from t_device_adapter adapter_fact"), fragment); // 存储列不再参与:只有派生的那个表达式。 assertFalse(mapperXml.contains("upper(c.online_status)")); // SELECT 与 WHERE 各用一次(WHERE 里不能用别名,所以走同一个片段)。 assertEquals(2, mapperXml.split("", -1).length - 1); } } /** 登记不再要求发现候选:手工填写的来源身份即可落库。 */ @Test void insertCollectorShouldNotRequireADiscoveryCandidate() { collector.setCollectorId(null); when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1); assertEquals(1, collectorService.insertCollector(collector)); verify(collectorMapper).insertCollector(any(TCollector.class)); } @Test void insertCollectorShouldAcceptCollectorWithoutIpAndMac() { collector.setCollectorId(null); collector.setIpAddress(null); collector.setMacAddress(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()); assertNull(captor.getValue().getIpAddress()); assertNull(captor.getValue().getMacAddress()); } @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 insertCollectorShouldRejectMalformedIpAndMacInsteadOfGuessing() { collector.setCollectorId(null); collector.setIpAddress("invalid-host"); collector.setMacAddress("AA:BB:CC:DD:EE:FF"); ServiceException ip = assertThrows(ServiceException.class, () -> collectorService.insertCollector(collector)); assertEquals("IP地址格式不正确", ip.getMessage()); collector.setIpAddress("192.168.1.10"); collector.setMacAddress("invalid-mac"); ServiceException mac = assertThrows(ServiceException.class, () -> collectorService.insertCollector(collector)); assertEquals("MAC地址格式不正确", mac.getMessage()); verify(collectorMapper, never()).insertCollector(any(TCollector.class)); } @Test void insertCollectorShouldRejectUnknownLinkedSource() { collector.setCollectorId(null); collector.setAdapterId(9L); when(adapterMapper.selectDeviceAdapterById(9L)).thenReturn(null); ServiceException exception = assertThrows(ServiceException.class, () -> collectorService.insertCollector(collector)); assertEquals("所链接的数据来源不存在", exception.getMessage()); verify(collectorMapper, never()).insertCollector(any(TCollector.class)); } /** 链接到来源实例后,登记保存该链接:采集器的辅助测量由它提供。 */ @Test void insertCollectorShouldPersistLinkedSource() { collector.setCollectorId(null); collector.setAdapterId(3L); when(adapterMapper.selectDeviceAdapterById(3L)).thenReturn(new TDeviceAdapter()); 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(Long.valueOf(3L), captor.getValue().getAdapterId()); } @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 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); } /** 编辑只提交可改字段:未提交的 IP/MAC 不能被当成"清空"。 */ @Test void updateCollectorShouldKeepUnsubmittedIpAndMac() { 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); TCollector edit = new TCollector(); edit.setCollectorId(1L); edit.setCollectorName("改名后"); assertEquals(1, collectorService.updateCollector(edit)); ArgumentCaptor captor = ArgumentCaptor.forClass(TCollector.class); verify(collectorMapper).updateCollector(captor.capture()); assertEquals("192.168.1.10", captor.getValue().getIpAddress()); assertEquals("AA:BB:CC:DD:EE:FF", captor.getValue().getMacAddress()); assertEquals("改名后", captor.getValue().getCollectorName()); } @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 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; } }