package cn.iocoder.yudao.module.mes.service.energy;
|
|
import cn.iocoder.yudao.module.mes.dal.dataobject.energy.*;
|
import cn.iocoder.yudao.module.mes.dal.mysql.energy.*;
|
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
|
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
import org.apache.ibatis.session.Configuration;
|
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.mockito.*;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.util.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
import static org.mockito.ArgumentMatchers.*;
|
import static org.mockito.Mockito.*;
|
|
@ExtendWith(MockitoExtension.class)
|
class EnergyAlertServiceTest {
|
@Mock EnergyAlertRuleMapper ruleMapper;
|
@Mock EnergyAlertLogMapper logMapper;
|
@Mock EnergyRecordMapper recordMapper;
|
@Mock AdminUserService userService;
|
@Mock NotifyMessageSendApi notifyApi;
|
@InjectMocks EnergyAlertService service;
|
final LocalDate date = LocalDate.of(2026, 10, 10);
|
|
private EnergyAlertRuleDO rule() {
|
var rule = new EnergyAlertRuleDO(); rule.setId(1L); rule.setEnabled(true); rule.setName("燕窝每日用水");
|
rule.setEnergyType("WATER"); rule.setMetric("QUANTITY"); rule.setUsageArea("燕窝生产区");
|
rule.setProductCategory("燕窝"); rule.setThreshold(new BigDecimal("100")); rule.setRecipientIds("7"); return rule;
|
}
|
private void prepare(EnergyAlertRuleDO rule, String actual) {
|
when(ruleMapper.selectList(any(Wrapper.class))).thenReturn(List.of(rule));
|
when(ruleMapper.lock(1L)).thenReturn(rule);
|
when(recordMapper.selectDailyValue(date, rule)).thenReturn(new BigDecimal(actual));
|
}
|
private void user() {
|
AdminUserDO user = new AdminUserDO(); user.setId(7L); user.setNickname("能耗管理员");
|
when(userService.getUser(7L)).thenReturn(user);
|
}
|
@Test void equalThresholdDoesNotNotify() {
|
prepare(rule(), "100.000"); assertEquals(0, service.check(date).get("sent"));
|
verifyNoInteractions(notifyApi, logMapper);
|
assertFalse(EnergyAlertService.exceeds(BigDecimal.ZERO, BigDecimal.ONE));
|
}
|
@Test void successCreatesMessageSnapshotAndRepeatedCheckDoesNotSendAgain() {
|
prepare(rule(), "101"); user(); when(notifyApi.sendSingleMessageToAdmin(any())).thenReturn(33L);
|
assertEquals(1, service.check(date).get("sent"));
|
ArgumentCaptor<EnergyAlertLogDO> history = ArgumentCaptor.forClass(EnergyAlertLogDO.class);
|
verify(logMapper).insert(history.capture());
|
var stored = history.getValue(); stored.setId(9L);
|
assertEquals("SENT", stored.getStatus()); assertEquals(33L, stored.getMessageId());
|
assertEquals(new BigDecimal("101"), stored.getActualValue());
|
ArgumentCaptor<NotifySendSingleToUserReqDTO> message = ArgumentCaptor.forClass(NotifySendSingleToUserReqDTO.class);
|
verify(notifyApi).sendSingleMessageToAdmin(message.capture());
|
assertEquals("m³", message.getValue().getTemplateParams().get("unit"));
|
assertEquals("2026-10-10", message.getValue().getTemplateParams().get("date"));
|
when(logMapper.selectOne(any(Wrapper.class))).thenReturn(stored);
|
assertEquals(0, service.check(date).get("sent"));
|
verify(notifyApi, times(1)).sendSingleMessageToAdmin(any());
|
}
|
@Test void disabledTemplateIsFailureAndLaterCheckRetriesSameHistory() {
|
prepare(rule(), "101"); user();
|
when(notifyApi.sendSingleMessageToAdmin(any())).thenReturn(null).thenReturn(44L);
|
assertEquals(1, service.check(date).get("failed"));
|
ArgumentCaptor<EnergyAlertLogDO> history = ArgumentCaptor.forClass(EnergyAlertLogDO.class);
|
verify(logMapper).insert(history.capture()); var stored = history.getValue();
|
assertEquals("FAILED", stored.getStatus()); assertNull(stored.getMessageId()); stored.setId(9L);
|
when(logMapper.selectOne(any(Wrapper.class))).thenReturn(stored);
|
assertEquals(1, service.check(date).get("sent"));
|
verify(logMapper).updateById(stored); assertEquals("SENT", stored.getStatus()); assertEquals("", stored.getErrorMessage());
|
}
|
@Test void removedOrDisabledRuleIsSkippedAfterLock() {
|
var rule = rule(); when(ruleMapper.selectList(any(Wrapper.class))).thenReturn(List.of(rule));
|
rule.setEnabled(false); when(ruleMapper.lock(1L)).thenReturn(rule);
|
assertEquals(0, service.check(date).get("sent")); verifyNoInteractions(recordMapper, notifyApi);
|
}
|
@Test void oneFailedRecipientDoesNotPreventOthersAndMoneyHasYuanUnit() {
|
var rule = rule(); rule.setRecipientIds("7,8"); rule.setMetric("AMOUNT"); prepare(rule, "150.55"); user();
|
AdminUserDO second = new AdminUserDO(); second.setNickname("主管"); when(userService.getUser(8L)).thenReturn(second);
|
when(notifyApi.sendSingleMessageToAdmin(any())).thenThrow(new IllegalStateException("test failure")).thenReturn(77L);
|
var result = service.check(date); assertEquals(1, result.get("sent")); assertEquals(1, result.get("failed"));
|
ArgumentCaptor<NotifySendSingleToUserReqDTO> message = ArgumentCaptor.forClass(NotifySendSingleToUserReqDTO.class);
|
verify(notifyApi, times(2)).sendSingleMessageToAdmin(message.capture());
|
assertEquals("元", message.getValue().getTemplateParams().get("unit"));
|
verify(logMapper, times(2)).insert(any(EnergyAlertLogDO.class));
|
}
|
@Test void dailySqlHasExactScopeFiltersAndBoundParameters() {
|
Configuration configuration = new Configuration(); configuration.addMapper(EnergyRecordMapper.class);
|
var rule = rule(); rule.setUsageArea("' OR 1=1 --"); rule.setMetric("AMOUNT");
|
String sql = configuration.getMappedStatement(EnergyRecordMapper.class.getName()+".selectDailyValue")
|
.getBoundSql(Map.of("date", date, "rule", rule)).getSql();
|
assertTrue(sql.contains("SUM( amount")); assertTrue(sql.contains("usage_area=?"));
|
assertTrue(sql.contains("deleted=0")); assertFalse(sql.contains(rule.getUsageArea()));
|
}
|
@Test void monthlyCostUsesFirstDayAndDeduplicatesDifferentDaysInSameMonth() {
|
var rule = rule(); rule.setMetric("MONTH_AMOUNT");
|
when(ruleMapper.selectList(any(Wrapper.class))).thenReturn(List.of(rule));
|
when(ruleMapper.lock(1L)).thenReturn(rule);
|
when(recordMapper.selectDailyValue(date.withDayOfMonth(1), rule)).thenReturn(new BigDecimal("150.55"));
|
user(); when(notifyApi.sendSingleMessageToAdmin(any())).thenReturn(55L);
|
assertEquals(1, service.check(date).get("sent"));
|
var history = ArgumentCaptor.forClass(EnergyAlertLogDO.class); verify(logMapper).insert(history.capture());
|
var stored = history.getValue(); stored.setId(9L);
|
assertEquals("MONTH", stored.getPeriodType()); assertEquals(date.withDayOfMonth(1), stored.getRecordDate());
|
var message = ArgumentCaptor.forClass(NotifySendSingleToUserReqDTO.class); verify(notifyApi).sendSingleMessageToAdmin(message.capture());
|
assertEquals("energy_monthly_warning", message.getValue().getTemplateCode());
|
assertEquals("2026-10", message.getValue().getTemplateParams().get("date"));
|
assertEquals("元", message.getValue().getTemplateParams().get("unit"));
|
when(logMapper.selectOne(any(Wrapper.class))).thenReturn(stored);
|
assertEquals(0, service.check(date.plusDays(1)).get("sent"));
|
verify(notifyApi, times(1)).sendSingleMessageToAdmin(any());
|
}
|
@Test void monthlyQuantitySqlUsesWholeCalendarMonthAndCostMetricSelectsAmount() {
|
var config = new Configuration(); config.addMapper(EnergyRecordMapper.class);
|
var statement = config.getMappedStatement(EnergyRecordMapper.class.getName() + ".selectDailyValue");
|
var rule = rule(); rule.setMetric("MONTH_QUANTITY");
|
String sql = statement.getBoundSql(Map.of("date", LocalDate.of(2028,2,1), "rule", rule)).getSql();
|
assertTrue(sql.contains("SUM( quantity")); assertTrue(sql.contains("DATE_ADD(?, INTERVAL 1 MONTH)"));
|
assertTrue(sql.contains("record_date >= ?")); assertTrue(sql.contains("record_date < DATE_ADD"));
|
rule.setMetric("MONTH_AMOUNT");
|
assertTrue(statement.getBoundSql(Map.of("date", LocalDate.of(2026,12,1), "rule", rule)).getSql().contains("SUM( amount"));
|
}
|
}
|