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 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.TTestBattery; import com.ruoyi.system.domain.TTestChannelBinding; import com.ruoyi.system.domain.TTestPlanStep; import com.ruoyi.system.domain.TTestWarningThreshold; import com.ruoyi.system.mapper.TTestMapper; /** 测试配置服务的业务契约测试。 */ @ExtendWith(MockitoExtension.class) class TTestServiceImplTest { @Mock private TTestMapper testMapper; @InjectMocks private TTestServiceImpl testService; @Test void createShouldReturnIdentifierAndInitializeIndependentStatuses() { TTest test = validTest(); mockEnabledOptions(); 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_MONITORED", test.getMonitoringStatus()); assertEquals(7L, test.getCreatorId()); verify(testMapper).insertTestPlanStep(any(TTestPlanStep.class)); verify(testMapper).insertTestChannelBinding(any(TTestChannelBinding.class)); verify(testMapper).insertTestThreshold(any(TTestWarningThreshold.class)); verify(testMapper).insertTestAudit(anyLong(), any(), anyLong(), any(), isNull(), any()); } @Test void createShouldAllowMissingCollector() { TTest test = validTest(); test.getBatteries().get(0).setCollectorId(null); mockEnabledPeopleAndType(); when(testMapper.countBatteryInEnabledGroup(1L, "BAT-001")).thenReturn(1); when(testMapper.countRegisteredChannel(100L)).thenReturn(1); 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()).countCollectorEnabled(anyLong()); } @Test void createShouldAllowPlanStepsToBeConfiguredLater() { TTest test = validTest(); test.setPlanSteps(Collections.emptyList()); mockEnabledOptions(); 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); 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); ServiceException exception = assertThrows(ServiceException.class, () -> testService.createTest(test)); assertEquals("同一测试只能选择一个电池组", exception.getMessage()); verify(testMapper, never()).insertTest(any(TTest.class)); } @Test void updateShouldRejectConfigurationAfterMonitoringStarted() { TTest request = validTest(); request.setTestId(10L); TTest current = validTest(); current.setTestId(10L); current.setMonitoringStatus("MONITORING"); 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 updateShouldReplaceConfigurationBeforeMonitoringAndAppendAudit() { TTest request = validTest(); request.setTestId(10L); request.setMonitorTimeoutMinutes(45); TTest current = validTest(); current.setTestId(10L); current.setCreatorId(7L); current.setMonitoringStatus("NOT_MONITORED"); current.setMonitorTimeoutMinutes(30); when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); 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 deleteBeforeMonitoringShouldAppendAudit() { TTest current = validTest(); current.setTestId(10L); current.setMonitoringStatus("NOT_MONITORED"); 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); } private void mockEnabledPeopleAndType() { when(testMapper.countUserEnabled(7L)).thenReturn(1); when(testMapper.countUserEnabled(9L)).thenReturn(1); when(testMapper.countTestTypeEnabled(3L)).thenReturn(1); } 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.setFields(Collections.emptyList()); return battery; } }