| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |
| | | } |