package com.ruoyi.system.service.impl; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.times; import java.math.BigDecimal; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.time.Instant; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.dao.DuplicateKeyException; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.system.domain.TTest; import com.ruoyi.system.domain.TTestBattery; import com.ruoyi.system.domain.TTestChannelBinding; import com.ruoyi.system.domain.TTestCollectorChannel; import com.ruoyi.system.domain.TTestFieldSnapshot; import com.ruoyi.system.domain.TTestPlanStep; import com.ruoyi.system.domain.TTestWarningThreshold; import com.ruoyi.system.domain.TDetectionChannel; import com.ruoyi.system.mapper.TTestMapper; import com.ruoyi.system.service.ISharedFieldSnapshotService; /** 测试配置服务的业务契约测试。 */ @ExtendWith(MockitoExtension.class) class TTestServiceImplTest { @Mock private TTestMapper testMapper; @Mock private ISharedFieldSnapshotService sharedFieldSnapshotService; @InjectMocks private TTestServiceImpl testService; @Test void createShouldReturnIdentifierAndInitializeIndependentStatuses() { TTest test = validTest(); mockEnabledOptions(); mockGroupSnapshot(); when(testMapper.insertTest(any(TTest.class))).thenAnswer(invocation -> { invocation.getArgument(0).setTestId(10L); return 1; }); doAnswer(invocation -> { invocation.getArgument(0).setRelationId(20L); return 1; }).when(testMapper).insertTestBattery(any(TTestBattery.class)); assertEquals(10L, testService.createTest(test)); assertEquals("WAITING", test.getTestStatus()); assertEquals("NOT_STARTED", test.getReceptionStatus()); assertEquals(7L, test.getCreatorId()); verify(testMapper).insertTestPlanStep(any(TTestPlanStep.class)); verify(testMapper).insertTestChannelBinding(any(TTestChannelBinding.class)); verify(testMapper, times(3)).insertTestCollectorChannel(any(TTestCollectorChannel.class)); verify(sharedFieldSnapshotService).saveForTest(eq(10L), any()); verify(testMapper).insertTestThreshold(any(TTestWarningThreshold.class)); verify(testMapper).insertTestAudit(anyLong(), any(), anyLong(), any(), isNull(), any()); } @Test void listShouldAggregateBatteryNumbersOutsideDatabaseQuery() { TTest first = new TTest(); first.setTestId(10L); TTest second = new TTest(); second.setTestId(11L); TTestBattery firstBattery = new TTestBattery(); firstBattery.setTestId(10L); firstBattery.setBatteryNo("BAT-001"); TTestBattery secondBattery = new TTestBattery(); secondBattery.setTestId(10L); secondBattery.setBatteryNo("BAT-002"); when(testMapper.selectTestList(any(TTest.class))).thenReturn(java.util.List.of(first, second)); when(testMapper.selectBatteryNosByTestIds(java.util.List.of(10L, 11L))) .thenReturn(java.util.List.of(firstBattery, secondBattery)); java.util.List result = testService.selectTestList(new TTest()); assertEquals("BAT-001、BAT-002", result.get(0).getBatteryNo()); assertEquals("", result.get(1).getBatteryNo()); } @Test void createShouldRejectMissingCollector() { TTest test = validTest(); test.getBatteries().get(0).setCollectorId(null); mockEnabledPeopleAndType(); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-001")).thenReturn(1); when(testMapper.countRegisteredChannel(100L)).thenReturn(1); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("采集器不能为空", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void createShouldRejectMissingRequiredCollectorChannel() { TTest test = validTest(); test.getBatteries().get(0).getCollectorChannels().remove(0); mockEnabledOptions(); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("每个电池号必须绑定主温度、正电位主通道和负电位主通道", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void createShouldAllowPlanStepsToBeConfiguredLater() { TTest test = validTest(); test.setPlanSteps(Collections.emptyList()); mockEnabledOptions(); mockGroupSnapshot(); when(testMapper.insertTest(any(TTest.class))).thenAnswer(invocation -> { invocation.getArgument(0).setTestId(10L); return 1; }); doAnswer(invocation -> { invocation.getArgument(0).setRelationId(20L); return 1; }).when(testMapper).insertTestBattery(any(TTestBattery.class)); assertEquals(10L, testService.createTest(test)); verify(testMapper, never()).insertTestPlanStep(any(TTestPlanStep.class)); } @Test void createShouldRejectBatteryOutsideSelectedGroup() { TTest test = validTest(); mockEnabledPeopleAndType(); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-001")).thenReturn(0); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("电池号不属于所选启用电池组", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void createShouldRejectDisabledCreator() { TTest test = validTest(); when(testMapper.countUserEnabled(7L)).thenReturn(0); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("创建人不存在或已停用", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void createShouldRejectDisabledPrincipal() { TTest test = validTest(); when(testMapper.countUserEnabled(7L)).thenReturn(1); when(testMapper.countUserEnabled(9L)).thenReturn(0); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("测试负责人不存在或已停用", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void createShouldRejectDuplicatePrimaryChannelBinding() { TTest test = validTest(); TTestBattery duplicate = validBattery("BAT-002", 2L, 100L); test.setBatteries(java.util.List.of(test.getBatteries().get(0), duplicate)); mockEnabledPeopleAndType(); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-001")).thenReturn(1); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-002")).thenReturn(1); when(testMapper.countRegisteredChannel(100L)).thenReturn(1); when(testMapper.countCollectorEnabled(8L)).thenReturn(1); when(testMapper.selectCollectorChannelCount(8L)).thenReturn(4); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("同一检测柜通道不可重复绑定", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void createShouldRejectDuplicateCollectorChannelMapping() { TTest test = validTest(); TTestBattery second = validBattery("BAT-002", 2L, 101L); test.setBatteries(java.util.List.of(test.getBatteries().get(0), second)); mockEnabledPeopleAndType(); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-001")).thenReturn(1); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-002")).thenReturn(1); when(testMapper.countRegisteredChannel(100L)).thenReturn(1); when(testMapper.countRegisteredChannel(101L)).thenReturn(1); when(testMapper.countCollectorEnabled(8L)).thenReturn(1); when(testMapper.selectCollectorChannelCount(8L)).thenReturn(4); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("同一采集器通道不可重复映射", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void createShouldRejectCaseInsensitiveDuplicateCollectorMeasurement() { TTest test = validTest(); test.getBatteries().get(0).getCollectorChannels() .add(collectorChannel("primary_temperature", "主温度重复", "S-04")); mockEnabledOptions(); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("同一电池号的采集器测量项不可重复", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void createShouldRejectBatteriesFromDifferentGroups() { TTest test = validTest(); TTestBattery otherGroup = validBattery("BAT-002", 2L, 101L); otherGroup.setGroupId(2L); test.setBatteries(java.util.List.of(test.getBatteries().get(0), otherGroup)); mockEnabledPeopleAndType(); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-001")).thenReturn(1); when(testMapper.countRegisteredChannel(100L)).thenReturn(1); when(testMapper.countCollectorEnabled(8L)).thenReturn(1); when(testMapper.selectCollectorChannelCount(8L)).thenReturn(4); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("同一测试只能选择一个电池组", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void updateShouldRejectConfigurationAfterReceptionStarted() { TTest request = validTest(); request.setTestId(10L); TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("RECEIVING"); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); ServiceException exception = assertThrows(ServiceException.class, () -> testService.updateTest(request)); assertEquals("开始接收后不能修改测试配置", exception.getMessage()); verify(testMapper, never()).updateTest(any(TTest.class)); } @Test void createShouldRejectCollectorChannelOutsideRegisteredRange() { TTest test = validTest(); test.getBatteries().get(0).getCollectorChannels().get(2).setChannelCode("S-05"); mockEnabledPeopleAndType(); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-001")).thenReturn(1); when(testMapper.countRegisteredChannel(100L)).thenReturn(1); when(testMapper.countCollectorEnabled(8L)).thenReturn(1); when(testMapper.selectCollectorChannelCount(8L)).thenReturn(4); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("采集器通道不存在或超出登记范围", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void updateShouldReplaceConfigurationBeforeMonitoringAndAppendAudit() { TTest request = validTest(); request.setTestId(10L); request.setMonitorTimeoutMinutes(45); TTest current = validTest(); current.setTestId(10L); current.setCreatorId(7L); current.setReceptionStatus("NOT_STARTED"); current.setMonitorTimeoutMinutes(30); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); when(testMapper.selectTestBatteries(10L)).thenReturn(current.getBatteries()); when(sharedFieldSnapshotService.selectByTestId(10L)).thenReturn(java.util.List.of()); when(sharedFieldSnapshotService.resolveForUpdate(1L, 1L, java.util.List.of())) .thenReturn(java.util.List.of()); mockEnabledOptions(); when(testMapper.updateTest(request)).thenReturn(1); doAnswer(invocation -> { invocation.getArgument(0).setRelationId(21L); return 1; }).when(testMapper).insertTestBattery(any(TTestBattery.class)); assertEquals(1, testService.updateTest(request)); assertEquals(45, request.getMonitorTimeoutMinutes()); verify(testMapper).deleteTestBindingsByTestId(10L); verify(testMapper).deleteTestPlanStepsByTestId(10L); verify(testMapper).insertTestAudit(anyLong(), any(), anyLong(), any(), any(), any()); } @Test void updateSameGroupShouldPreserveOriginalFieldSnapshot() { TTest request = validTest(); request.setTestId(10L); TTest current = validTest(); current.setTestId(10L); current.setCreatorId(7L); current.setReceptionStatus("NOT_STARTED"); TTestFieldSnapshot originalSnapshot = new TTestFieldSnapshot(); originalSnapshot.setFieldDefinitionId(11L); originalSnapshot.setFieldName("额定容量"); originalSnapshot.setFieldType("number"); originalSnapshot.setFieldValue("72"); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); when(testMapper.selectTestBatteries(10L)).thenReturn(current.getBatteries()); when(sharedFieldSnapshotService.selectByTestId(10L)).thenReturn(java.util.List.of(originalSnapshot)); when(sharedFieldSnapshotService.resolveForUpdate(1L, 1L, java.util.List.of(originalSnapshot))) .thenReturn(java.util.List.of(originalSnapshot)); mockEnabledOptions(); when(testMapper.updateTest(request)).thenReturn(1); doAnswer(invocation -> { invocation.getArgument(0).setRelationId(21L); return 1; }).when(testMapper).insertTestBattery(any(TTestBattery.class)); assertEquals(1, testService.updateTest(request)); verify(sharedFieldSnapshotService).resolveForUpdate(1L, 1L, java.util.List.of(originalSnapshot)); verify(sharedFieldSnapshotService).saveForTest(eq(10L), org.mockito.ArgumentMatchers.argThat(snapshots -> snapshots.size() == 1 && "72".equals(snapshots.get(0).getFieldValue()))); } @Test void deleteBeforeMonitoringShouldAppendAudit() { TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("NOT_STARTED"); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); when(testMapper.deleteTestById(10L, "operator")).thenReturn(1); assertEquals(1, testService.deleteTestByIds(new Long[] { 10L }, 7L, "operator")); verify(testMapper).insertTestAudit(eq(10L), eq("TEST_DELETED"), eq(7L), eq("operator"), any(), isNull()); } @Test void startReceptionShouldOpenAllSourceIntervalsAtomically() { TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("NOT_STARTED"); current.getBatteries().get(0).getBinding().setBindingId(30L); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); TDetectionChannel channel = new TDetectionChannel(); channel.setChannelId(100L); channel.setOnlineStatus("ONLINE"); channel.setLastSeenAtUtc(Instant.parse("2026-08-29T12:00:00Z")); when(testMapper.selectReceptionChannelForUpdate(100L)).thenReturn(channel); when(testMapper.countCollectorEnabled(8L)).thenReturn(1); when(testMapper.selectCollectorChannelCount(8L)).thenReturn(4); when(testMapper.collectorOnline(8L)).thenReturn(true); when(testMapper.updateReceptionStarted(eq(10L), any(), anyBoolean(), eq("operator"))).thenReturn(1); assertEquals(1, testService.startReception(10L, 7L, "operator")); verify(testMapper).insertChannelLease(eq(100L), eq(10L), eq(30L), eq(7L), any()); verify(testMapper, times(3)).insertCollectorChannelLease(eq(8L), any(), eq(10L), eq(30L), eq(7L), any()); verify(testMapper).insertTestAudit(eq(10L), eq("RECEPTION_STARTED"), eq(7L), eq("operator"), isNull(), any()); } @Test void startReceptionShouldMarkRunningCabinetAttachmentWithoutControlCommand() { TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("NOT_STARTED"); current.getBatteries().get(0).getBinding().setBindingId(30L); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); TDetectionChannel channel = new TDetectionChannel(); channel.setChannelId(100L); channel.setOnlineStatus("ONLINE"); channel.setWorkStatus("WORKING"); channel.setLastSeenAtUtc(Instant.parse("2026-08-29T12:00:00Z")); when(testMapper.selectReceptionChannelForUpdate(100L)).thenReturn(channel); when(testMapper.countCollectorEnabled(8L)).thenReturn(1); when(testMapper.selectCollectorChannelCount(8L)).thenReturn(4); when(testMapper.collectorOnline(8L)).thenReturn(true); when(testMapper.updateReceptionStarted(eq(10L), any(), anyBoolean(), eq("operator"))).thenReturn(1); assertEquals(1, testService.startReception(10L, 7L, "operator")); verify(testMapper).insertTestAudit(eq(10L), eq("RECEPTION_STARTED"), eq(7L), eq("operator"), isNull(), org.mockito.ArgumentMatchers.argThat(value -> value != null && value.contains("runtimeAttach") && value.contains("true"))); } @Test void startReceptionShouldRollbackWhenDatabaseLeaseIsTaken() { TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("NOT_STARTED"); current.getBatteries().get(0).getBinding().setBindingId(30L); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); TDetectionChannel channel = new TDetectionChannel(); channel.setChannelId(100L); channel.setOnlineStatus("ONLINE"); channel.setLastSeenAtUtc(Instant.parse("2026-08-29T12:00:00Z")); when(testMapper.selectReceptionChannelForUpdate(100L)).thenReturn(channel); when(testMapper.countCollectorEnabled(8L)).thenReturn(1); when(testMapper.selectCollectorChannelCount(8L)).thenReturn(4); when(testMapper.collectorOnline(8L)).thenReturn(true); when(testMapper.insertChannelLease(any(), any(), any(), any(), any())) .thenThrow(new DuplicateKeyException("lease already exists")); ServiceException exception = assertThrows(ServiceException.class, () -> testService.startReception(10L, 7L, "operator")); assertEquals("数据来源已被其他测试占用,请刷新后重试", exception.getMessage()); verify(testMapper, never()).updateReceptionStarted(any(), any(), anyBoolean(), any()); verify(testMapper, never()).insertTestAudit(anyLong(), any(), anyLong(), any(), any(), any()); } @Test void startReceptionShouldRejectOfflineSourceWithoutWritingAnything() { TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("NOT_STARTED"); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); TDetectionChannel channel = new TDetectionChannel(); channel.setChannelId(100L); channel.setOnlineStatus("OFFLINE"); when(testMapper.selectReceptionChannelForUpdate(100L)).thenReturn(channel); ServiceException exception = assertThrows(ServiceException.class, () -> testService.startReception(10L, 7L, "operator")); assertEquals("检测柜通道 100 当前离线或不可观测", exception.getMessage()); verify(testMapper, never()).insertChannelLease(any(), any(), any(), any(), any()); verify(testMapper, never()).updateReceptionStarted(any(), any(), anyBoolean(), any()); } @Test void startReceptionShouldRejectIncompleteCollectorMappingsBeforeWriting() { TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("NOT_STARTED"); current.getBatteries().get(0).getBinding().setBindingId(30L); current.getBatteries().get(0).setCollectorChannels(Collections.emptyList()); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); TDetectionChannel channel = new TDetectionChannel(); channel.setChannelId(100L); channel.setOnlineStatus("ONLINE"); channel.setLastSeenAtUtc(Instant.parse("2026-08-29T12:00:00Z")); when(testMapper.selectReceptionChannelForUpdate(100L)).thenReturn(channel); when(testMapper.countCollectorEnabled(8L)).thenReturn(1); when(testMapper.collectorOnline(8L)).thenReturn(true); when(testMapper.selectCollectorChannelCount(8L)).thenReturn(4); ServiceException exception = assertThrows(ServiceException.class, () -> testService.startReception(10L, 7L, "operator")); assertEquals("每个电池号必须绑定主温度、正电位主通道和负电位主通道", exception.getMessage()); verify(testMapper, never()).insertChannelLease(any(), any(), any(), any(), any()); verify(testMapper, never()).updateReceptionStarted(any(), any(), anyBoolean(), any()); } @Test void startReceptionShouldRejectMissingOperatorExplicitly() { ServiceException exception = assertThrows(ServiceException.class, () -> testService.startReception(10L, null, "operator")); assertEquals("操作人不能为空", exception.getMessage()); verify(testMapper, never()).selectTestByIdForUpdate(anyLong()); } @Test void pauseReceptionShouldStopPollingAndAppendAudit() { TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("RECEIVING"); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); when(testMapper.updateReceptionPaused(eq(10L), eq("operator"), any())).thenReturn(1); assertEquals(1, testService.pauseReception(10L, 7L, "operator")); verify(testMapper).updateReceptionPaused(eq(10L), eq("operator"), any()); verify(testMapper).insertTestAudit(eq(10L), eq("RECEPTION_PAUSED"), eq(7L), eq("operator"), isNull(), org.mockito.ArgumentMatchers.argThat(value -> value != null && value.contains("STOPPED"))); } @Test void resumeReceptionShouldRestartPollingAndAppendAudit() { TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("PAUSED"); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); when(testMapper.updateReceptionResumed(eq(10L), eq("operator"), any())).thenReturn(1); assertEquals(1, testService.resumeReception(10L, 7L, "operator")); verify(testMapper).updateReceptionResumed(eq(10L), eq("operator"), any()); verify(testMapper).insertTestAudit(eq(10L), eq("RECEPTION_RESUMED"), eq(7L), eq("operator"), isNull(), org.mockito.ArgumentMatchers.argThat(value -> value != null && value.contains("RESUMED"))); } @Test void stopTestShouldTerminateAndReleaseLeases() { TTest current = validTest(); current.setTestId(10L); current.setReceptionStatus("PAUSED"); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); when(testMapper.updateReceptionStopped(eq(10L), eq("operator"), any())).thenReturn(1); assertEquals(1, testService.stopTest(10L, 7L, "operator")); verify(testMapper).updateReceptionStopped(eq(10L), eq("operator"), any()); verify(testMapper).deleteChannelLeasesByTestId(10L); verify(testMapper).deleteCollectorChannelLeasesByTestId(10L); verify(testMapper).insertTestAudit(eq(10L), eq("TEST_STOPPED"), eq(7L), eq("operator"), isNull(), org.mockito.ArgumentMatchers.argThat(value -> value != null && value.contains("TERMINATED"))); } private void mockEnabledOptions() { mockEnabledPeopleAndType(); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-001")).thenReturn(1); when(testMapper.countRegisteredChannel(100L)).thenReturn(1); when(testMapper.countCollectorEnabled(8L)).thenReturn(1); when(testMapper.selectCollectorChannelCount(8L)).thenReturn(4); } private void mockEnabledPeopleAndType() { when(testMapper.countUserEnabled(7L)).thenReturn(1); when(testMapper.countUserEnabled(9L)).thenReturn(1); when(testMapper.countTestTypeEnabled(3L)).thenReturn(1); } private void mockGroupSnapshot() { TTestFieldSnapshot snapshot = new TTestFieldSnapshot(); snapshot.setFieldDefinitionId(11L); snapshot.setFieldName("额定容量"); snapshot.setFieldType("number"); snapshot.setFieldValue("72"); when(sharedFieldSnapshotService.createForBatteryGroup(1L)).thenReturn(java.util.List.of(snapshot)); } private TTest validTest() { TTestPlanStep step = new TTestPlanStep(); step.setCycleNo(1); step.setStepNo(1); step.setStepType("CONSTANT_CURRENT_CHARGE"); step.setStepName("恒流充电"); step.setSortOrder(1); LinkedHashMap parameters = new LinkedHashMap<>(); parameters.put("currentA", new BigDecimal("1.5")); step.setParameters(parameters); TTestWarningThreshold threshold = new TTestWarningThreshold(); threshold.setMetricCode("VOLTAGE"); threshold.setMinValue(new BigDecimal("2.5")); threshold.setMaxValue(new BigDecimal("4.2")); threshold.setUnit("V"); threshold.setSeverity("WARNING"); TTest test = new TTest(); test.setTestName("低温循环测试"); test.setCreatorId(7L); test.setOperatorId(7L); test.setCreateBy("creator"); test.setUpdateBy("creator"); test.setPrincipalId(9L); test.setTestTypeId(3L); test.setTestPurpose("验证低温循环性能"); test.setMonitorTimeoutMinutes(30); test.setBatteries(Collections.singletonList(validBattery("BAT-001", 1L, 100L))); test.setPlanSteps(Collections.singletonList(step)); test.setThresholds(Collections.singletonList(threshold)); return test; } private TTestBattery validBattery(String batteryNo, Long relationId, Long channelId) { TTestChannelBinding binding = new TTestChannelBinding(); binding.setChannelId(channelId); binding.setExpectedBarcode("CODE-" + batteryNo); TTestBattery battery = new TTestBattery(); battery.setRelationId(relationId); battery.setGroupId(1L); battery.setBatteryNo(batteryNo); battery.setCollectorId(8L); battery.setBinding(binding); battery.setCollectorChannels(new java.util.ArrayList<>(java.util.List.of( collectorChannel("PRIMARY_TEMPERATURE", "主温度", "S-01"), collectorChannel("PRIMARY_POSITIVE_POTENTIAL", "正电位主通道", "S-02"), collectorChannel("PRIMARY_NEGATIVE_POTENTIAL", "负电位主通道", "S-03")))); battery.setFields(Collections.emptyList()); return battery; } private TTestCollectorChannel collectorChannel(String measurementCode, String measurementName, String channelCode) { TTestCollectorChannel mapping = new TTestCollectorChannel(); mapping.setMeasurementCode(measurementCode); mapping.setMeasurementName(measurementName); mapping.setChannelCode(channelCode); return mapping; } }