云
2 天以前 657698fd2fb32794932474648dbaee9e284e38c8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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"));
    }
}