package cn.iocoder.yudao.module.mes.service.energy; import cn.iocoder.yudao.module.mes.controller.admin.energy.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.energy.EnergyRecordDO; import cn.iocoder.yudao.module.mes.dal.mysql.energy.EnergyRecordMapper; import jakarta.validation.Validation; import org.apache.ibatis.session.Configuration; 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 java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class EnergyRecordServiceTest { @Mock private org.springframework.context.ApplicationEventPublisher applicationEventPublisher; @Mock private EnergyRecordMapper mapper; @InjectMocks private EnergyRecordService service; @Test void changedDateChecksBothDaysWithOriginalTenant() { var req = request(); req.setId(77L); var old = new EnergyRecordDO(); old.setId(77L); old.setRecordDate(req.getRecordDate().minusDays(1)); when(mapper.selectById(77L)).thenReturn(old); when(mapper.updateById(any(EnergyRecordDO.class))).thenReturn(1); var previousTenant = cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder.getTenantId(); try { cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder.setTenantId(42L); service.update(req); var event = org.mockito.ArgumentCaptor.forClass(EnergyRecordChangedEvent.class); verify(applicationEventPublisher).publishEvent(event.capture()); assertEquals(42L, event.getValue().tenantId()); assertEquals(java.util.Set.of(old.getRecordDate(), req.getRecordDate()), event.getValue().dates()); } finally { cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder.setTenantId(previousTenant); } } @Test void calculatesMoneyWithDecimalRoundingAndClearsRemark() { EnergyRecordSaveReqVO req = request(); req.setQuantity(new BigDecimal("12.345")); req.setUnitPrice(new BigDecimal("0.6789")); EnergyRecordDO record = EnergyRecordService.toRecord(req); assertEquals(new BigDecimal("8.38"), record.getAmount()); assertEquals("燕窝生产区", record.getUsageArea()); assertEquals("", record.getRemark()); req.setUnitPrice(BigDecimal.ZERO); assertEquals(new BigDecimal("0.00"), EnergyRecordService.toRecord(req).getAmount()); } @Test void createIgnoresClientIdAndUpdateRequiresExistingRecord() { EnergyRecordSaveReqVO req = request(); req.setId(77L); doAnswer(invocation -> { EnergyRecordDO record = invocation.getArgument(0); assertNull(record.getId()); record.setId(9L); return 1; }).when(mapper).insert(any(EnergyRecordDO.class)); assertEquals(9L, service.create(req)); assertThrows(RuntimeException.class, () -> service.update(req)); verify(mapper, never()).updateById(any(EnergyRecordDO.class)); req.setId(null); assertThrows(RuntimeException.class, () -> service.update(req)); } @Test void deleteMissingRecordDoesNotExecuteDelete() { assertThrows(RuntimeException.class, () -> service.delete(8L)); verify(mapper, never()).deleteById(any(Long.class)); } @Test void aggregatesSeparateUnitsAndMergesDatesAreasAndCosts() { EnergyAnalysisRespVO result = EnergyRecordService.aggregate(List.of( row("WATER", "2026-09-01", "燕窝生产区", "10", "30", 1), row("WATER", "2026-09-01", "原浆生产区", "5", "15", 2), row("ELECTRICITY", "2026-09-01", "燕窝生产区", "100", "80", 1), row("WATER", "2026-09-02", "燕窝生产区", "2", "0", 1))); assertEquals(new BigDecimal("125"), result.getTotalAmount()); assertEquals(5, result.getRecordCount()); assertEquals(new BigDecimal("17"), result.getSummary().get(0).getQuantity()); assertEquals(new BigDecimal("100"), result.getSummary().get(1).getQuantity()); assertEquals(BigDecimal.ZERO, result.getSummary().get(2).getQuantity()); assertEquals(3, result.getTrend().size()); assertEquals(new BigDecimal("15"), result.getTrend().get(0).getQuantity()); assertEquals(3, result.getAreas().size()); assertEquals(new BigDecimal("12"), result.getAreas().get(0).getQuantity()); } @Test void emptyAnalysisReturnsZeroTotalsAndAllThreeEnergies() { EnergyAnalysisRespVO result = EnergyRecordService.aggregate(List.of()); assertEquals(3, result.getSummary().size()); assertEquals(BigDecimal.ZERO, result.getTotalAmount()); assertTrue(result.getTrend().isEmpty()); assertTrue(result.getAreas().isEmpty()); } @Test void rejectsInvalidTypeNegativePriceZeroQuantityFutureDateAndExcessPrecision() { try (var factory = Validation.buildDefaultValidatorFactory()) { var validator = factory.getValidator(); assertTrue(validator.validate(request()).isEmpty()); EnergyRecordSaveReqVO req = request(); req.setEnergyType("GAS"); req.setUnitPrice(new BigDecimal("-1")); req.setQuantity(BigDecimal.ZERO); req.setRecordDate(LocalDate.now().plusDays(1)); assertTrue(validator.validate(req).size() >= 4); req = request(); req.setQuantity(new BigDecimal("1.0001")); assertFalse(validator.validate(req).isEmpty()); EnergyAnalysisReqVO analysis = new EnergyAnalysisReqVO(); analysis.setStartDate(LocalDate.of(2026, 1, 1)); analysis.setEndDate(LocalDate.of(2025, 1, 1)); assertFalse(validator.validate(analysis).isEmpty()); analysis.setEndDate(LocalDate.of(2029, 1, 1)); assertFalse(validator.validate(analysis).isEmpty()); } } @Test void analysisSqlBindsFiltersAndSelectsDayOrMonthSafely() { Configuration config = new Configuration(); config.addMapper(EnergyRecordMapper.class); var statement = config.getMappedStatement(EnergyRecordMapper.class.getName() + ".selectAnalysis"); EnergyAnalysisReqVO req = new EnergyAnalysisReqVO(); req.setStartDate(LocalDate.of(2026, 1, 1)); req.setEndDate(LocalDate.of(2026, 1, 31)); String daily = statement.getBoundSql(Map.of("req", req)).getSql(); assertTrue(daily.contains("'%Y-%m-%d'")); assertTrue(daily.contains("deleted = 0")); req.setGroupBy("MONTH"); req.setEnergyType("STEAM"); req.setUsageArea("' OR 1=1 --"); req.setProductCategory("燕窝"); String monthly = statement.getBoundSql(Map.of("req", req)).getSql(); assertTrue(monthly.contains("'%Y-%m'")); assertTrue(monthly.contains("energy_type = ?")); assertTrue(monthly.contains("product_category = ?")); assertFalse(monthly.contains(req.getUsageArea())); } private static EnergyRecordSaveReqVO request() { EnergyRecordSaveReqVO req = new EnergyRecordSaveReqVO(); req.setRecordDate(LocalDate.now()); req.setEnergyType("WATER"); req.setUsageArea(" 燕窝生产区 "); req.setProductCategory("燕窝"); req.setUsagePurpose("设备清洗"); req.setQuantity(BigDecimal.ONE); req.setUnitPrice(BigDecimal.ONE); req.setRecorder("张三"); return req; } private static EnergyRecordMapper.Aggregate row(String type, String period, String area, String quantity, String amount, long count) { EnergyRecordMapper.Aggregate row = new EnergyRecordMapper.Aggregate(); row.setEnergyType(type); row.setPeriod(period); row.setUsageArea(area); row.setQuantity(new BigDecimal(quantity)); row.setAmount(new BigDecimal(amount)); row.setRecordCount(count); return row; } }