| | |
| | | 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 |
| | | { |
| | |
| | | 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); |
| | |
| | | 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( |
| | |
| | | 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); |
| | | } |