From e38aca8134570513435c003c25dc05f1ae51c9c9 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期四, 08 十月 2026 13:56:28 +0800
Subject: [PATCH] 实现 Ticket04:采集器辅助测量与登记入口,拆除开发模拟器

---
 ruoyi-admin/src/test/java/com/battery/business/service/impl/TTestLiveServiceImplTest.java |  213 ++++++++++++++++++++++++++++++++---------------------
 1 files changed, 128 insertions(+), 85 deletions(-)

diff --git a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TTestLiveServiceImplTest.java b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TTestLiveServiceImplTest.java
index c216676..3178319 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TTestLiveServiceImplTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TTestLiveServiceImplTest.java
@@ -61,7 +61,7 @@
         var mapper = mock(TTestMapper.class); when(mapper.selectCollectorCode(7L)).thenReturn("collector-7");
         var adapter = mock(TestDataObservationAdapter.class);
         var service = new TTestLiveServiceImpl(tests,
-                new com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore(), adapter, mapper);
+                new com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore(), adapter, mapper, null);
         test.setReceptionBoundaryAtUtc(java.util.Date.from(now));
         for (Instant deviceTime : List.of(now.minusSeconds(1), now)) {
             var measurement = new TestDataObservationAdapter.CollectorMeasurement("S-01", 1L, deviceTime, deviceTime.toString(),
@@ -77,68 +77,74 @@
     @Test
     void currentTemperatureAlarmIsIndependentOfHistoricalTodoHandling() throws Exception
     {
-        try (var simulator = new com.battery.device.simulator.BtsDevelopmentSimulator(0)) {
-            TTest test = test("RECEIVING");
-            var battery = test.getBatteries().get(0); battery.setCollectorId(7L);
-            var primary = new com.battery.business.domain.TTestCollectorChannel();
-            primary.setChannelCode("S-01"); primary.setMeasurementCode("PRIMARY_TEMPERATURE");
-            var secondary = new com.battery.business.domain.TTestCollectorChannel();
-            secondary.setChannelCode("S-03"); secondary.setMeasurementCode("SECONDARY_TEMPERATURE_2");
-            battery.setCollectorChannels(List.of(primary, secondary));
-            var rule = new com.battery.business.domain.TTestWarningThreshold();
-            rule.setThresholdId(11L); rule.setMetricCode("TEMPERATURE");
-            rule.setMinValue(new BigDecimal("8")); rule.setMaxValue(new BigDecimal("33"));
-            test.setThresholds(List.of(rule));
-            var tests = mock(ITTestService.class); when(tests.selectTestById(10L)).thenReturn(test);
-            var mapper = mock(TTestMapper.class); when(mapper.selectCollectorCode(7L)).thenReturn("dev-sim-collector-1");
-            var service = new TTestLiveServiceImpl(tests,
-                    new com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore(), simulator, mapper);
-            simulator.configureCollectorChannel(1, "S-01", List.of(
-                    new com.battery.device.simulator.BtsSimulatorStateStore.CollectorMetricState("temperature", new BigDecimal("38"), "℃")));
-            var alarms = service.current(10L).exceptions().stream().filter(item -> "THRESHOLD".equals(item.get("exceptionType"))).toList();
-            assertEquals(1, alarms.size());
-            assertEquals(new BigDecimal("38"), alarms.get(0).get("value"));
-            assertEquals("7:S-01", alarms.get(0).get("sourceIdentity"));
-            assertFalse(alarms.get(0).containsKey("exceptionId"));
+        TTest test = test("RECEIVING");
+        var battery = test.getBatteries().get(0); battery.setCollectorId(7L);
+        var primary = new com.battery.business.domain.TTestCollectorChannel();
+        primary.setChannelCode("S-01"); primary.setMeasurementCode("PRIMARY_TEMPERATURE");
+        var secondary = new com.battery.business.domain.TTestCollectorChannel();
+        secondary.setChannelCode("S-03"); secondary.setMeasurementCode("SECONDARY_TEMPERATURE_2");
+        battery.setCollectorChannels(List.of(primary, secondary));
+        var rule = new com.battery.business.domain.TTestWarningThreshold();
+        rule.setThresholdId(11L); rule.setMetricCode("TEMPERATURE");
+        rule.setMinValue(new BigDecimal("8")); rule.setMaxValue(new BigDecimal("33"));
+        test.setThresholds(List.of(rule));
+        var tests = mock(ITTestService.class); when(tests.selectTestById(10L)).thenReturn(test);
+        var mapper = mock(TTestMapper.class); when(mapper.selectCollectorCode(7L)).thenReturn("dev-sim-collector-1");
+        var adapter = mock(TestDataObservationAdapter.class);
+        Instant now = Instant.now();
+        // 采集器来源交出主、辅助两路测量:只有绑定了的 S-01 主温度进预警。
+        when(adapter.latestCollector(org.mockito.ArgumentMatchers.nullable(String.class),
+                org.mockito.ArgumentMatchers.eq("dev-sim-collector-1"))).thenReturn(collectorRealtime(now, "38"));
+        var service = new TTestLiveServiceImpl(tests,
+                new com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore(), adapter, mapper, null);
+        var alarms = service.current(10L).exceptions().stream().filter(item -> "THRESHOLD".equals(item.get("exceptionType"))).toList();
+        assertEquals(1, alarms.size());
+        assertEquals(new BigDecimal("38"), alarms.get(0).get("value"));
+        assertEquals("7:S-01", alarms.get(0).get("sourceIdentity"));
+        assertFalse(alarms.get(0).containsKey("exceptionId"));
 
-            var history = new TTestWarningException(); history.setExceptionStatus("OPEN");
-            history.setExceptionType("THRESHOLD"); history.setMetricCode("temperature");
-            history.setAbnormalValue(new BigDecimal("38"));
-            when(mapper.selectWarningExceptions(10L, "OPEN")).thenReturn(List.of(history));
-            simulator.configureCollectorChannel(1, "S-01", List.of(
-                    new com.battery.device.simulator.BtsSimulatorStateStore.CollectorMetricState("temperature", new BigDecimal("25"), "℃")));
-            assertTrue(service.current(10L).exceptions().stream().noneMatch(item -> "THRESHOLD".equals(item.get("exceptionType"))));
-            assertEquals("OPEN", history.getExceptionStatus());
-        }
+        var history = new TTestWarningException(); history.setExceptionStatus("OPEN");
+        history.setExceptionType("THRESHOLD"); history.setMetricCode("temperature");
+        history.setAbnormalValue(new BigDecimal("38"));
+        when(mapper.selectWarningExceptions(10L, "OPEN")).thenReturn(List.of(history));
+        when(adapter.latestCollector(org.mockito.ArgumentMatchers.nullable(String.class),
+                org.mockito.ArgumentMatchers.eq("dev-sim-collector-1"))).thenReturn(collectorRealtime(now, "25"));
+        assertTrue(service.current(10L).exceptions().stream().noneMatch(item -> "THRESHOLD".equals(item.get("exceptionType"))));
+        assertEquals("OPEN", history.getExceptionStatus());
+    }
+
+    private static TestDataObservationAdapter.CollectorRealtime collectorRealtime(Instant now, String temperature)
+    {
+        return new TestDataObservationAdapter.CollectorRealtime("dev-sim-collector-1", true, 1000,
+                List.of(new TestDataObservationAdapter.CollectorMeasurement("S-01", 1L, now, now.toString(), now,
+                        "TEMPERATURE", new BigDecimal(temperature), "℃")), null);
     }
 
     @Test
-    void consoleReadsConfiguredSimulatorPlanAtCurrentChannelPosition() throws Exception
+    void consoleReadsConfiguredPlanAtCurrentChannelPosition() throws Exception
     {
-        try (var simulator = new com.battery.device.simulator.BtsDevelopmentSimulator(0)) {
-            var test = test("RECEIVING");
-            var binding = test.getBatteries().get(0).getBinding();
-            binding.setDevtype(22); binding.setSubdevid(1);
-            simulator.configureChannel(1, 1, new com.battery.device.simulator.BtsSimulatorStateStore.ChannelConfiguration(
-                    BigDecimal.ONE, new BigDecimal("4.125"), BigDecimal.ONE, BigDecimal.ONE, 2, 2, 3, 120, 30,
-                    List.of(new com.battery.device.simulator.BtsSimulatorStateStore.SimulatorPlanStep("充电", "cc", 60, 4.2),
-                            new com.battery.device.simulator.BtsSimulatorStateStore.SimulatorPlanStep("放电", "dc", 90, 3.0))));
-            simulator.updateChannel(1, 1, true, DeviceWorkStatus.WORKING);
-            var tests = mock(ITTestService.class);
-            when(tests.selectTestById(10L)).thenReturn(test);
-            var service = new TTestLiveServiceImpl(tests,
-                    new com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore(), simulator);
-            var plan = service.current(10L).testPlan();
-            assertEquals(2, plan.currentCycleNo());
-            assertEquals(2, plan.currentStepNo());
-            assertEquals(3, plan.plannedCycleCount());
-            assertEquals(List.of(1, 2), plan.rows().stream().map(row -> row.cyclePosition()).toList());
-            assertEquals("放电", plan.rows().get(1).stepName());
-            assertEquals(90d, plan.rows().get(1).durationSeconds());
-            assertEquals("进行中", plan.rows().get(1).executionStatus());
-            simulator.updateChannel(1, 1, false, DeviceWorkStatus.WORKING);
-            org.junit.jupiter.api.Assertions.assertNull(service.current(10L).testPlan());
-        }
+        var test = test("RECEIVING");
+        var binding = test.getBatteries().get(0).getBinding();
+        binding.setDevtype(22); binding.setSubdevid(1);
+        var tests = mock(ITTestService.class);
+        when(tests.selectTestById(10L)).thenReturn(test);
+        var adapter = mock(TestDataObservationAdapter.class);
+        // 来源交出它自己的方案:当前循环 2、当前工步 2、计划 3 个循环,工步行按循环位置排列。
+        when(adapter.testPlan(any(ChannelIdentity.class))).thenReturn(new TestDataObservationAdapter.TestPlan(2, 2, 3,
+                List.of(new TestDataObservationAdapter.PlanRow("STEP", 1, 1, "充电", 60, 4.2, "已完成", null, null),
+                        new TestDataObservationAdapter.PlanRow("STEP", 2, 2, "放电", 90, 3.0, "进行中", null, null))));
+        var service = new TTestLiveServiceImpl(tests,
+                new com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore(), adapter, null, null);
+        var plan = service.current(10L).testPlan();
+        assertEquals(2, plan.currentCycleNo());
+        assertEquals(2, plan.currentStepNo());
+        assertEquals(3, plan.plannedCycleCount());
+        assertEquals(List.of(1, 2), plan.rows().stream().map(row -> row.cyclePosition()).toList());
+        assertEquals("放电", plan.rows().get(1).stepName());
+        assertEquals(90d, plan.rows().get(1).durationSeconds());
+        assertEquals("进行中", plan.rows().get(1).executionStatus());
+        when(adapter.testPlan(any(ChannelIdentity.class))).thenReturn(null);
+        org.junit.jupiter.api.Assertions.assertNull(service.current(10L).testPlan());
     }
 
     @Test
@@ -212,36 +218,73 @@
         assertTrue(alarms.stream().anyMatch(item -> "CABINET_CHANNEL".equals(item.get("sourceType"))));
     }
 
+    /**
+     * 非接收状态只读来源可达性:状态取自所链接来源实例的连接结果,且**不请求**任何测量,
+     * 否则暂停期间仍会产生新观测。
+     */
     @Test
     void nonReceivingConsoleReadsCollectorStatusWithoutRequestingMeasurements() throws Exception
     {
-        try (var simulator = new com.battery.device.simulator.BtsDevelopmentSimulator(0))
+        TTest test = test("RECEIVING");
+        test.getBatteries().get(0).setCollectorId(7L);
+        var tests = mock(ITTestService.class);
+        when(tests.selectTestById(10L)).thenReturn(test);
+        var mapper = mock(TTestMapper.class);
+        when(mapper.selectCollectorCode(7L)).thenReturn("dev-sim-collector-1");
+        var adapter = mock(TestDataObservationAdapter.class);
+        var adapters = mock(com.battery.business.mapper.TDeviceAdapterMapper.class);
+        var source = new com.battery.business.domain.TDeviceAdapter();
+        source.setConnectionStatus("ONLINE");
+        when(adapters.selectDeviceAdapterByCode("dev-sim")).thenReturn(source);
+        var service = new TTestLiveServiceImpl(tests,
+                new com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore(), adapter, mapper, adapters);
+        test.setReceptionStatus("RECEIVING");
+        var receiving = service.current(10L).sources().stream()
+                .filter(item -> item.sourceType().equals("COLLECTOR")).findFirst().orElseThrow();
+        assertEquals("ONLINE", receiving.deviceStatus());
+        verify(adapter).latestCollector(org.mockito.ArgumentMatchers.eq("dev-sim"),
+                org.mockito.ArgumentMatchers.eq("dev-sim-collector-1"));
+        org.mockito.Mockito.clearInvocations(adapter);
+        for (String status : List.of("PAUSED", "ENDED", "NOT_STARTED"))
         {
-            TTest test = test("RECEIVING");
-            test.getBatteries().get(0).setCollectorId(7L);
-            var tests = mock(ITTestService.class);
-            when(tests.selectTestById(10L)).thenReturn(test);
-            var mapper = mock(TTestMapper.class);
-            when(mapper.selectCollectorCode(7L)).thenReturn("dev-sim-collector-1");
-            var candidates = new com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore();
-            Instant now = Instant.now();
-            candidates.replaceCollectors("dev-sim", List.of(new DeviceDiscoveryCandidateStore.CollectorCandidate(
-                    "dev-sim", "dev-sim-collector-1", "127.0.1.2", null, "模拟采集器", null, null,
-                    4, true, now, now.plusSeconds(30))));
-            var service = new TTestLiveServiceImpl(tests, candidates, simulator, mapper);
-            service.current(10L);
-            var received = simulator.snapshot().recentRequests();
-            assertTrue(received.stream().anyMatch(item -> item.command().equals("collector-realtime")));
-            for (String status : List.of("PAUSED", "ENDED", "NOT_STARTED"))
-            {
-                test.setReceptionStatus(status);
-                var source = service.current(10L).sources().stream()
-                        .filter(item -> item.sourceType().equals("COLLECTOR")).findFirst().orElseThrow();
-                assertEquals("ONLINE", source.deviceStatus());
-                assertTrue(source.latestSnapshot().isEmpty());
-                assertEquals(received, simulator.snapshot().recentRequests());
-            }
+            test.setReceptionStatus(status);
+            var paused = service.current(10L).sources().stream()
+                    .filter(item -> item.sourceType().equals("COLLECTOR")).findFirst().orElseThrow();
+            assertEquals("ONLINE", paused.deviceStatus());
+            assertTrue(paused.latestSnapshot().isEmpty());
         }
+        verify(adapter, never()).latestCollector(org.mockito.ArgumentMatchers.anyString(),
+                org.mockito.ArgumentMatchers.anyString());
+    }
+
+    /** 来源不提供辅助测量(新威实测无辅助数据)时如实标注,并且不因此生成离线异常。 */
+    @Test
+    void collectorWithoutAuxiliaryMeasurementsStaysOnlineAndIsMarkedUnavailable() throws Exception
+    {
+        TTest test = test("RECEIVING");
+        test.getBatteries().get(0).setCollectorId(7L);
+        var tests = mock(ITTestService.class);
+        when(tests.selectTestById(10L)).thenReturn(test);
+        var mapper = mock(TTestMapper.class);
+        when(mapper.selectCollectorCode(7L)).thenReturn("neware76-collector");
+        var adapter = mock(TestDataObservationAdapter.class);
+        when(adapter.latestCollector(org.mockito.ArgumentMatchers.anyString(),
+                org.mockito.ArgumentMatchers.anyString())).thenReturn(null);
+        var adapters = mock(com.battery.business.mapper.TDeviceAdapterMapper.class);
+        var source = new com.battery.business.domain.TDeviceAdapter();
+        source.setConnectionStatus("ONLINE");
+        when(adapters.selectDeviceAdapterByCode("dev-sim")).thenReturn(source);
+        var service = new TTestLiveServiceImpl(tests,
+                new com.battery.device.api.discovery.InMemoryDeviceDiscoveryCandidateStore(), adapter, mapper, adapters);
+
+        var collector = service.current(10L).sources().stream()
+                .filter(item -> item.sourceType().equals("COLLECTOR")).findFirst().orElseThrow();
+
+        assertEquals("ONLINE", collector.deviceStatus());
+        assertEquals(Boolean.FALSE, collector.latestSnapshot().get("auxiliaryDataAvailable"));
+        assertFalse(collector.latestSnapshot().containsKey("measurements"));
+        assertTrue(service.current(10L).exceptions().stream()
+                .noneMatch(item -> "COLLECTOR".equals(item.get("sourceType"))));
     }
 
     @Test
@@ -386,7 +429,7 @@
                 "dev-sim", 1L, "dev-sim", 1, 1, "127.0.0.2", "mac", "cabinet", "vendor", "model",
                 List.of(channel), now, now.plusSeconds(60));
         when(candidates.snapshot(any(Instant.class))).thenReturn(
-                new DeviceDiscoveryCandidateStore.CandidateSnapshot(List.of(cabinet), List.of()));
+                new DeviceDiscoveryCandidateStore.CandidateSnapshot(List.of(cabinet)));
         var plan = new TestDataObservationAdapter.TestPlan(1, 1, 1, List.of());
         when(simulator.testPlan(any(ChannelIdentity.class))).thenReturn(plan);
         if (mapper != null && test.getBatteries().get(0).getCollectorId() != null) {
@@ -464,7 +507,7 @@
         private CurrentLiveService(ITTestService tests, DeviceDiscoveryCandidateStore candidates,
                 TestDataObservationAdapter simulator, TTestMapper mapper)
         {
-            super(tests, candidates, simulator, mapper);
+            super(tests, candidates, simulator, mapper, null);
         }
     }
 

--
Gitblit v1.7.1