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.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 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 com.ruoyi.common.exception.ServiceException; import com.ruoyi.system.domain.TTest; import com.ruoyi.system.domain.TBatteryGroup; import com.ruoyi.system.domain.TBatteryGroupRelationField; 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.mapper.TTestMapper; import com.ruoyi.system.service.ITBatteryGroupService; /** 测试配置服务的业务契约测试。 */ @ExtendWith(MockitoExtension.class) class TTestServiceImplTest { @Mock private TTestMapper testMapper; @Mock private ITBatteryGroupService batteryGroupService; @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(testMapper).insertTestFieldSnapshot(any()); verify(testMapper).insertTestThreshold(any(TTestWarningThreshold.class)); verify(testMapper).insertTestAudit(anyLong(), any(), anyLong(), any(), isNull(), any()); } @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 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); mockEnabledOptions(); mockGroupSnapshot(); 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(testMapper.selectTestFieldSnapshots(10L)).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(testMapper).insertTestFieldSnapshot(org.mockito.ArgumentMatchers.argThat(snapshot -> Long.valueOf(11L).equals(snapshot.getFieldDefinitionId()) && "额定容量".equals(snapshot.getFieldName()) && "number".equals(snapshot.getFieldType()) && "72".equals(snapshot.getFieldValue()))); verify(batteryGroupService, never()).selectBatteryGroupById(anyLong()); } @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()); } 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() { TBatteryGroupRelationField field = new TBatteryGroupRelationField(); field.setBatteryGroupFieldId(11L); field.setFieldName("额定容量"); field.setFieldType("number"); field.setFieldValue("72"); TBatteryGroup group = new TBatteryGroup(); group.setGroupId(1L); group.setTemplateFields(java.util.List.of(field)); when(batteryGroupService.selectBatteryGroupById(1L)).thenReturn(group); } 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; } }