From b78295e8da96c3e5e76befeef9582ce5273dbb74 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期二, 29 九月 2026 13:10:23 +0800
Subject: [PATCH] 按代码规范检查整改实体注释、Service Javadoc 与分层边界

---
 ruoyi-admin/src/test/java/com/battery/business/service/impl/TTestLiveServiceImplTest.java |  227 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 225 insertions(+), 2 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 34dc6f4..c216676 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
@@ -34,9 +34,216 @@
 import com.battery.business.service.ITTestService;
 import com.battery.business.service.ITestLiveService.LiveState;
 
-/** 实时流客户端断连时应结束连接而不触发全局异常处理。 */
+/** 验证当前测量、实时提醒与历史待办独立,以及实时流断连后的资源清理。 */
 class TTestLiveServiceImplTest
 {
+    @Test
+    void thresholdWarningsRespectDeviceTimeAndReceptionBoundary()
+    {
+        Instant now = Instant.parse("2026-09-05T12:00:00Z");
+        TTest test = test("RECEIVING");
+        test.setStartedAtUtc(java.util.Date.from(now));
+        test.setReceptionBoundaryAtUtc(java.util.Date.from(now));
+        var rule = new com.battery.business.domain.TTestWarningThreshold();
+        rule.setThresholdId(11L); rule.setMetricCode("VOLTAGE"); rule.setMaxValue(new BigDecimal("3"));
+        test.setThresholds(List.of(rule));
+        assertTrue(liveService(test, now, now.minusSeconds(1)).current(10L).exceptions().isEmpty());
+        assertEquals(1, liveService(test, now, now).current(10L).exceptions().size());
+        test.setReceptionBoundaryAtUtc(java.util.Date.from(now.plusSeconds(1)));
+        assertTrue(liveService(test, now, now).current(10L).exceptions().isEmpty());
+
+        var battery = test.getBatteries().get(0); battery.setCollectorId(7L); battery.setBinding(null);
+        var binding = new com.battery.business.domain.TTestCollectorChannel();
+        binding.setChannelCode("S-01"); binding.setMeasurementCode("PRIMARY_TEMPERATURE");
+        battery.setCollectorChannels(List.of(binding));
+        rule.setMetricCode("TEMPERATURE"); rule.setMaxValue(new BigDecimal("33"));
+        var tests = mock(ITTestService.class); when(tests.selectTestById(10L)).thenReturn(test);
+        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);
+        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(),
+                    now, "TEMPERATURE", new BigDecimal("38"), "℃");
+            when(adapter.latestCollector(org.mockito.ArgumentMatchers.nullable(String.class), org.mockito.ArgumentMatchers.eq("collector-7"))).thenReturn(new TestDataObservationAdapter.CollectorRealtime(
+                    "collector-7", true, 1000, List.of(measurement), null));
+            assertEquals(deviceTime.equals(now) ? 1 : 0, service.current(10L).exceptions().size());
+        }
+        test.setReceptionBoundaryAtUtc(java.util.Date.from(now.plusSeconds(1)));
+        assertTrue(service.current(10L).exceptions().isEmpty());
+    }
+
+    @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"));
+
+            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());
+        }
+    }
+
+    @Test
+    void consoleReadsConfiguredSimulatorPlanAtCurrentChannelPosition() 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());
+        }
+    }
+
+    @Test
+    void offlineHintIsIndependentOfHistoricalHandlingStatusAndSurvivesRefresh()
+    {
+        TTest test = test("RECEIVING");
+        var mapper = mock(TTestMapper.class);
+        TTestWarningException warning = new TTestWarningException();
+        warning.setExceptionId(21L);
+        warning.setExceptionType("DEVICE_OFFLINE");
+        warning.setSourceType("CABINET_CHANNEL");
+        warning.setSourceIdentity("dev-sim:1:1:0:1");
+        warning.setMetricCode("DEVICE_ONLINE");
+        warning.setExceptionStatus("OPEN");
+        when(mapper.selectWarningExceptions(10L, "OPEN")).thenReturn(List.of(warning));
+        var service = liveService(test, Instant.now(), false, DeviceWorkStatus.WORKING, mapper);
+
+        for (int attempt = 0; attempt < 2; attempt++) {
+            var alarms = service.current(10L).exceptions();
+            assertEquals(1, alarms.size());
+            assertFalse(alarms.get(0).containsKey("exceptionId"));
+            assertEquals("DEVICE_OFFLINE", alarms.get(0).get("exceptionType"));
+            assertFalse(alarms.get(0).get("message").toString().contains("处理"));
+        }
+        warning.setExceptionStatus("CLOSED");
+        when(mapper.selectWarningExceptions(10L, "OPEN")).thenReturn(List.of());
+        assertEquals(1, service.current(10L).exceptions().size());
+        test.setReceptionStatus("PAUSED");
+        assertTrue(service.current(10L).exceptions().isEmpty());
+        test.setReceptionStatus("ENDED");
+        assertTrue(service.current(10L).exceptions().isEmpty());
+    }
+
+    @Test
+    void recoveredSourcesHideOfflineHintsEvenWhenHistoryRemainsOpen()
+    {
+        TTest test = test("RECEIVING");
+        test.getBatteries().get(0).setCollectorId(7L);
+        var mapper = mock(TTestMapper.class);
+        var warnings = List.of("CABINET_CHANNEL", "COLLECTOR").stream().map(sourceType -> {
+            var warning = new TTestWarningException();
+            warning.setExceptionType("DEVICE_OFFLINE");
+            warning.setSourceType(sourceType);
+            warning.setSourceIdentity("COLLECTOR".equals(sourceType) ? "7" : "dev-sim:1:1:0:1");
+            warning.setExceptionStatus("OPEN");
+            return warning;
+        }).toList();
+        when(mapper.selectWarningExceptions(10L, "OPEN")).thenReturn(warnings);
+
+        for (boolean online : List.of(false, true, true, false)) {
+            var service = liveService(test, Instant.now(), online, DeviceWorkStatus.WORKING, mapper);
+            var state = service.current(10L);
+            assertTrue(state.sources().stream().allMatch(source -> (online ? "ONLINE" : "OFFLINE").equals(source.deviceStatus())));
+            assertEquals(online ? 0 : 2, state.exceptions().size());
+            assertTrue(warnings.stream().allMatch(warning -> "OPEN".equals(warning.getExceptionStatus())));
+        }
+    }
+
+    @Test
+    void receivingOfflineCabinetAndCollectorAreVisibleAsOpenAlarms()
+    {
+        TTest test = test("RECEIVING");
+        test.getBatteries().get(0).setCollectorId(7L);
+        var service = liveService(test, Instant.now(), false);
+
+        var alarms = service.current(10L).exceptions();
+
+        assertEquals(2, alarms.size());
+        assertTrue(alarms.stream().allMatch(item -> "OPEN".equals(item.get("status"))));
+        assertTrue(alarms.stream().anyMatch(item -> "COLLECTOR".equals(item.get("sourceType"))));
+        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 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
     void notStartedShouldNotExposePreTestDeviceSnapshot()
     {
@@ -153,6 +360,17 @@
     private static TTestLiveServiceImpl liveService(TTest test, Instant now, boolean online,
             DeviceWorkStatus workStatus, TTestMapper mapper)
     {
+        return liveService(test, now, online, workStatus, mapper, now);
+    }
+
+    private static TTestLiveServiceImpl liveService(TTest test, Instant now, Instant deviceTime)
+    {
+        return liveService(test, now, true, DeviceWorkStatus.WORKING, null, deviceTime);
+    }
+
+    private static TTestLiveServiceImpl liveService(TTest test, Instant now, boolean online,
+            DeviceWorkStatus workStatus, TTestMapper mapper, Instant deviceTime)
+    {
         DeviceDiscoveryCandidateStore candidates = mock(DeviceDiscoveryCandidateStore.class);
         TestDataObservationAdapter simulator = mock(TestDataObservationAdapter.class);
         ITTestService tests = mock(ITTestService.class);
@@ -161,7 +379,7 @@
         RealtimeSnapshot snapshot = new RealtimeSnapshot(identity, workStatus, 1, 1, "cc", null,
                 new BigDecimal("1.200"), new BigDecimal("3.700"), new BigDecimal("2.500"),
                 new BigDecimal("9.250"), 12L, 12L, "0", now,
-                java.util.Map.of());
+                java.util.Map.of(), com.battery.device.api.model.DeviceConnectionStatus.ONLINE, deviceTime, List.of(), null);
         DeviceDiscoveryCandidateStore.ChannelCandidate channel = new DeviceDiscoveryCandidateStore.ChannelCandidate(
                 identity, online, workStatus, now, snapshot);
         DeviceDiscoveryCandidateStore.CabinetCandidate cabinet = new DeviceDiscoveryCandidateStore.CabinetCandidate(
@@ -171,6 +389,11 @@
                 new DeviceDiscoveryCandidateStore.CandidateSnapshot(List.of(cabinet), List.of()));
         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) {
+            when(mapper.selectCollectorCode(7L)).thenReturn("collector-7");
+            when(simulator.latestCollector(org.mockito.ArgumentMatchers.nullable(String.class), org.mockito.ArgumentMatchers.eq("collector-7"))).thenReturn(
+                    new TestDataObservationAdapter.CollectorRealtime("collector-7", online, 1000, List.of(), null));
+        }
 
         return new CurrentLiveService(tests, candidates, simulator, mapper);
     }

--
Gitblit v1.7.1