yeg
6 天以前 e38aca8134570513435c003c25dc05f1ae51c9c9
ruoyi-admin/src/test/java/com/battery/business/service/impl/TCollectorServiceImplTest.java
@@ -3,6 +3,7 @@
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;
@@ -24,12 +25,15 @@
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.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore;
import com.battery.device.api.discovery.DeviceDiscoveryCandidateStore.CollectorCandidate;
import com.battery.business.mapper.TDeviceAdapterMapper;
/**
 * 采集器 Service 单元测试。
 *
 * <p>采集器按 ADR-0064 不建独立设备模型:登记不再依赖发现候选(真实来源不产出采集器候选),
 * 由操作员手工登记来源身份,在线状态取所链接来源实例的连接结果。
 */
@ExtendWith(MockitoExtension.class)
class TCollectorServiceImplTest
@@ -37,20 +41,17 @@
    @Mock
    private TCollectorMapper collectorMapper;
    @Mock
    private TDeviceAdapterMapper adapterMapper;
    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))));
        collectorService = new TCollectorServiceImpl(collectorMapper, adapterMapper);
        collector = new TCollector();
        collector.setCollectorId(1L);
        collector.setCollectorCode("COL-001");
@@ -80,21 +81,52 @@
        }
    }
    /** 在线事实只有一个来源:所链接来源实例的连接结果;登记表里的存储列不再充当这个事实。 */
    @Test
    void registeredCollectorCodeSqlShouldIgnoreDeletedRows() throws IOException
    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 selectStart = mapperXml.indexOf("<select id=\"selectRegisteredCollectorCodes\"");
            assertTrue(selectStart >= 0);
            int selectEnd = mapperXml.indexOf("</select>", selectStart);
            String selectSql = mapperXml.substring(selectStart, selectEnd);
            assertTrue(selectSql.contains("where del_flag = '0'"));
            int fragmentStart = mapperXml.indexOf("<sql id=\"derivedOnlineStatus\"");
            assertTrue(fragmentStart >= 0, "在线状态的派生表达式应当是一个共用片段");
            String fragment = mapperXml.substring(fragmentStart, mapperXml.indexOf("</sql>", 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("<include refid=\"derivedOnlineStatus\" />", -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<TCollector> captor = ArgumentCaptor.forClass(TCollector.class);
        verify(collectorMapper).insertCollector(captor.capture());
        assertNull(captor.getValue().getIpAddress());
        assertNull(captor.getValue().getMacAddress());
    }
    @Test
@@ -117,6 +149,55 @@
        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<TCollector> captor = ArgumentCaptor.forClass(TCollector.class);
        verify(collectorMapper).insertCollector(captor.capture());
        assertEquals(Long.valueOf(3L), captor.getValue().getAdapterId());
    }
    @Test
@@ -148,36 +229,6 @@
    }
    @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<TCollector> 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 offlineCollectorCanBeRegisteredWithoutChangingOnlineFact()
    {
        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, false, now, now.plusSeconds(60))));
        when(collectorMapper.insertCollector(any(TCollector.class))).thenReturn(1);
        assertEquals(1, collectorService.insertCollector(collector));
        verify(collectorMapper).updateRegisteredObservation("COL-001", "OFFLINE");
    }
    @Test
    void insertCollectorShouldRejectDuplicateCode()
    {
        collector.setCollectorId(null);
@@ -203,6 +254,27 @@
        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<TCollector> 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
@@ -256,14 +328,6 @@
        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