Commit 7661c7165c88c57039baee1d0db96a40de4cde31

Authored by puhui999
1 parent fb35807e

feat:【IoT 物联网】场景联动规则匹配器单元测试

Showing 13 changed files with 2575 additions and 260 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java
... ... @@ -2,18 +2,11 @@ package cn.iocoder.yudao.module.iot.service.rule.scene;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.ListUtil;
5   -import cn.hutool.core.map.MapUtil;
6   -import cn.hutool.core.text.CharPool;
7   -import cn.hutool.core.util.NumberUtil;
8 5 import cn.hutool.core.util.ObjUtil;
9   -import cn.hutool.core.util.StrUtil;
10 6 import cn.hutool.extra.spring.SpringUtil;
11 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
12 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
13   -import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
14 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
15   -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
16   -import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
17 10 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
18 11 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
19 12 import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
... ... @@ -23,7 +16,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
23 16 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
24 17 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
25 18 import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper;
26   -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
27 19 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
28 20 import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
29 21 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
... ... @@ -36,12 +28,9 @@ import org.springframework.stereotype.Service;
36 28 import org.springframework.validation.annotation.Validated;
37 29  
38 30 import java.util.Collection;
39   -import java.util.HashMap;
40 31 import java.util.List;
41   -import java.util.Map;
42 32  
43 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
44   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
45 34 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
46 35 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NOT_EXISTS;
47 36  
... ... @@ -353,65 +342,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
353 342 }
354 343 }
355 344  
356   - // TODO @puhui999:下面还需要么?
357   - /**
358   - * 判断触发器的条件参数是否匹配
359   - *
360   - * @param message 设备消息
361   - * @param condition 触发条件
362   - * @param sceneRule 规则场景(用于日志,无其它作用)
363   - * @param trigger 触发器(用于日志,无其它作用)
364   - * @return 是否匹配
365   - */
366   - @SuppressWarnings({"unchecked", "DataFlowIssue"})
367   - private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
368   - IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) {
369   - // 1.1 校验操作符是否合法
370   - IotSceneRuleConditionOperatorEnum operator =
371   - IotSceneRuleConditionOperatorEnum.operatorOf(condition.getOperator());
372   - if (operator == null) {
373   - log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]",
374   - sceneRule.getId(), trigger, condition.getOperator());
375   - return false;
376   - }
377   - // 1.2 校验消息是否包含对应的值
378   - String messageValue = MapUtil.getStr((Map<String, Object>) message.getData(), condition.getIdentifier());
379   - if (messageValue == null) {
380   - return false;
381   - }
382   -
383   - // 2.1 构建 Spring 表达式的变量
384   - Map<String, Object> springExpressionVariables = new HashMap<>();
385   - try {
386   - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue);
387   - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam());
388   - List<String> parameterValues = StrUtil.splitTrim(condition.getParam(), CharPool.COMMA);
389   - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues);
390   - // 特殊:解决数字的比较。因为 Spring 是基于它的 compareTo 方法,对数字的比较存在问题!
391   - if (ObjectUtils.equalsAny(operator, IotSceneRuleConditionOperatorEnum.BETWEEN,
392   - IotSceneRuleConditionOperatorEnum.NOT_BETWEEN,
393   - IotSceneRuleConditionOperatorEnum.GREATER_THAN,
394   - IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS,
395   - IotSceneRuleConditionOperatorEnum.LESS_THAN,
396   - IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS)
397   - && NumberUtil.isNumber(messageValue)
398   - && NumberUtils.isAllNumber(parameterValues)) {
399   - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE,
400   - NumberUtil.parseDouble(messageValue));
401   - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
402   - NumberUtil.parseDouble(condition.getParam()));
403   - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
404   - convertList(parameterValues, NumberUtil::parseDouble));
405   - }
406   - // 2.2 计算 Spring 表达式
407   - return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables);
408   - } catch (Exception e) {
409   - log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]",
410   - message, sceneRule.getId(), trigger, operator, springExpressionVariables, e);
411   - return false;
412   - }
413   - }
414   -
415 345 /**
416 346 * 执行规则场景的动作
417 347 *
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java deleted
1   -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
2   -
3   -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5   -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
6   -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
7   -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.*;
8   -import org.junit.jupiter.api.BeforeEach;
9   -import org.junit.jupiter.api.Test;
10   -
11   -import java.util.Arrays;
12   -import java.util.HashMap;
13   -import java.util.List;
14   -
15   -import static org.junit.jupiter.api.Assertions.*;
16   -
17   -/**
18   - * IoT 场景规则触发器匹配器测试类
19   - *
20   - * @author HUIHUI
21   - */
22   -public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest {
23   -
24   - // TODO @puhui999:public 都加下哈;
25   -
26   - private IotSceneRuleMatcherManager matcherManager;
27   -
28   - @BeforeEach
29   - void setUp() {
30   - // 创建所有匹配器实例
31   - List<IotSceneRuleMatcher> matchers = Arrays.asList(
32   - new DeviceStateUpdateTriggerMatcher(),
33   - new DevicePropertyPostTriggerMatcher(),
34   - new DeviceEventPostTriggerMatcher(),
35   - new DeviceServiceInvokeTriggerMatcher(),
36   - new TimerTriggerMatcher()
37   - );
38   -
39   - // 初始化匹配器管理器
40   - matcherManager = new IotSceneRuleMatcherManager(matchers);
41   - }
42   -
43   - @Test
44   - void testDeviceStateUpdateTriggerMatcher() {
45   - // 1. 准备测试数据
46   - IotDeviceMessage message = IotDeviceMessage.builder()
47   - .requestId("test-001")
48   - .method("thing.state.update") // TODO @puhui999:这里的枚举;
49   - .data(1) // 在线状态 TODO @puhui999:这里的枚举;
50   - .build();
51   -
52   - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
53   - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
54   - trigger.setOperator("="); // TODO @puhui999:这里的枚举;下面也是类似;
55   - trigger.setValue("1");
56   -
57   - // 2. 执行测试
58   - boolean matched = matcherManager.isMatched(message, trigger);
59   -
60   - // 3. 验证结果
61   - assertTrue(matched, "设备状态更新触发器应该匹配");
62   - }
63   -
64   - @Test
65   - void testDevicePropertyPostTriggerMatcher() {
66   - // 1. 准备测试数据
67   - HashMap<String, Object> params = new HashMap<>();
68   - IotDeviceMessage message = IotDeviceMessage.builder()
69   - .requestId("test-002")
70   - .method("thing.property.post")
71   - .data(25.5) // 温度值
72   - .params(params)
73   - .build();
74   - // 模拟标识符
75   - params.put("identifier", "temperature");
76   -
77   - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
78   - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
79   - trigger.setIdentifier("temperature");
80   - trigger.setOperator(">");
81   - trigger.setValue("20");
82   -
83   - // 2. 执行测试
84   - boolean matched = matcherManager.isMatched(message, trigger);
85   -
86   - // 3. 验证结果
87   - assertTrue(matched, "设备属性上报触发器应该匹配");
88   - }
89   -
90   - @Test
91   - void testDeviceEventPostTriggerMatcher() {
92   - // 1. 准备测试数据
93   - HashMap<String, Object> params = new HashMap<>();
94   - IotDeviceMessage message = IotDeviceMessage.builder()
95   - .requestId("test-003")
96   - .method("thing.event.post")
97   - .data("alarm_data")
98   - .params(params)
99   - .build();
100   - // 模拟标识符
101   - params.put("identifier", "high_temperature_alarm");
102   -
103   - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
104   - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType());
105   - trigger.setIdentifier("high_temperature_alarm");
106   -
107   - // 2. 执行测试
108   - boolean matched = matcherManager.isMatched(message, trigger);
109   -
110   - // 3. 验证结果
111   - assertTrue(matched, "设备事件上报触发器应该匹配");
112   - }
113   -
114   - @Test
115   - void testDeviceServiceInvokeTriggerMatcher() {
116   - // 1. 准备测试数据
117   - HashMap<String, Object> params = new HashMap<>();
118   - IotDeviceMessage message = IotDeviceMessage.builder()
119   - .requestId("test-004")
120   - .method("thing.service.invoke")
121   - .msg("alarm_data")
122   - .params(params)
123   - .build();
124   - // 模拟标识符
125   - params.put("identifier", "restart_device");
126   -
127   - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
128   - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
129   - trigger.setIdentifier("restart_device");
130   -
131   - // 2. 执行测试
132   - boolean matched = matcherManager.isMatched(message, trigger);
133   -
134   - // 3. 验证结果
135   - assertTrue(matched, "设备服务调用触发器应该匹配");
136   - }
137   -
138   - @Test
139   - void testTimerTriggerMatcher() {
140   - // 1. 准备测试数据
141   - IotDeviceMessage message = IotDeviceMessage.builder()
142   - .requestId("test-005")
143   - .method("timer.trigger") // 定时触发器不依赖具体消息方法
144   - .build();
145   -
146   - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
147   - trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
148   - trigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点
149   -
150   - // 2. 执行测试
151   - boolean matched = matcherManager.isMatched(message, trigger);
152   -
153   - // 3. 验证结果
154   - assertTrue(matched, "定时触发器应该匹配");
155   - }
156   -
157   - @Test
158   - void testInvalidTriggerType() {
159   - // 1. 准备测试数据
160   - IotDeviceMessage message = IotDeviceMessage.builder()
161   - .requestId("test-006")
162   - .method("unknown.method")
163   - .build();
164   -
165   - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
166   - trigger.setType(999); // 无效的触发器类型
167   -
168   - // 2. 执行测试
169   - boolean matched = matcherManager.isMatched(message, trigger);
170   -
171   - // 3. 验证结果
172   - assertFalse(matched, "无效的触发器类型应该不匹配");
173   - }
174   -
175   - @Test
176   - void testGetSupportedTriggerTypes() {
177   - // 1. 执行测试
178   - var supportedTypes = matcherManager.getSupportedTriggerTypes();
179   -
180   - // 2. 验证结果
181   - assertNotNull(supportedTypes);
182   - assertEquals(5, supportedTypes.size());
183   - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE));
184   - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST));
185   - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST));
186   - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE));
187   - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER));
188   - }
189   -
190   -}
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
  7 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
  8 +import org.junit.jupiter.api.BeforeEach;
  9 +import org.junit.jupiter.api.Test;
  10 +
  11 +import java.time.LocalDateTime;
  12 +import java.time.ZoneOffset;
  13 +
  14 +import static org.junit.jupiter.api.Assertions.*;
  15 +
  16 +/**
  17 + * {@link CurrentTimeConditionMatcher} 的单元测试类
  18 + *
  19 + * @author HUIHUI
  20 + */
  21 +public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest {
  22 +
  23 + private CurrentTimeConditionMatcher matcher;
  24 +
  25 + @BeforeEach
  26 + public void setUp() {
  27 + matcher = new CurrentTimeConditionMatcher();
  28 + }
  29 +
  30 + @Test
  31 + public void testGetSupportedConditionType() {
  32 + // when & then
  33 + assertEquals(IotSceneRuleConditionTypeEnum.CURRENT_TIME, matcher.getSupportedConditionType());
  34 + }
  35 +
  36 + @Test
  37 + public void testGetPriority() {
  38 + // when & then
  39 + assertEquals(40, matcher.getPriority());
  40 + }
  41 +
  42 + @Test
  43 + public void testIsEnabled() {
  44 + // when & then
  45 + assertTrue(matcher.isEnabled());
  46 + }
  47 +
  48 + // ========== 时间戳条件测试 ==========
  49 +
  50 + @Test
  51 + public void testIsMatched_DateTimeGreaterThan_Success() {
  52 + // given
  53 + IotDeviceMessage message = new IotDeviceMessage();
  54 + long pastTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8"));
  55 + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
  56 + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(),
  57 + String.valueOf(pastTimestamp)
  58 + );
  59 +
  60 + // when
  61 + boolean result = matcher.isMatched(message, condition);
  62 +
  63 + // then
  64 + assertTrue(result);
  65 + }
  66 +
  67 + @Test
  68 + public void testIsMatched_DateTimeGreaterThan_Failure() {
  69 + // given
  70 + IotDeviceMessage message = new IotDeviceMessage();
  71 + long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
  72 + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
  73 + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(),
  74 + String.valueOf(futureTimestamp)
  75 + );
  76 +
  77 + // when
  78 + boolean result = matcher.isMatched(message, condition);
  79 +
  80 + // then
  81 + assertFalse(result);
  82 + }
  83 +
  84 + @Test
  85 + public void testIsMatched_DateTimeLessThan_Success() {
  86 + // given
  87 + IotDeviceMessage message = new IotDeviceMessage();
  88 + long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
  89 + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
  90 + IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN.getOperator(),
  91 + String.valueOf(futureTimestamp)
  92 + );
  93 +
  94 + // when
  95 + boolean result = matcher.isMatched(message, condition);
  96 +
  97 + // then
  98 + assertTrue(result);
  99 + }
  100 +
  101 + @Test
  102 + public void testIsMatched_DateTimeBetween_Success() {
  103 + // given
  104 + IotDeviceMessage message = new IotDeviceMessage();
  105 + long startTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8"));
  106 + long endTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
  107 + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
  108 + IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(),
  109 + startTimestamp + "," + endTimestamp
  110 + );
  111 +
  112 + // when
  113 + boolean result = matcher.isMatched(message, condition);
  114 +
  115 + // then
  116 + assertTrue(result);
  117 + }
  118 +
  119 + @Test
  120 + public void testIsMatched_DateTimeBetween_Failure() {
  121 + // given
  122 + IotDeviceMessage message = new IotDeviceMessage();
  123 + long startTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
  124 + long endTimestamp = LocalDateTime.now().plusHours(2).toEpochSecond(ZoneOffset.of("+8"));
  125 + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
  126 + IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(),
  127 + startTimestamp + "," + endTimestamp
  128 + );
  129 +
  130 + // when
  131 + boolean result = matcher.isMatched(message, condition);
  132 +
  133 + // then
  134 + assertFalse(result);
  135 + }
  136 +
  137 + // ========== 当日时间条件测试 ==========
  138 +
  139 + @Test
  140 + public void testIsMatched_TimeGreaterThan_EarlyMorning() {
  141 + // given
  142 + IotDeviceMessage message = new IotDeviceMessage();
  143 + IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
  144 + IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(),
  145 + "06:00:00" // 早上6点
  146 + );
  147 +
  148 + // when
  149 + boolean result = matcher.isMatched(message, condition);
  150 +
  151 + // then
  152 + // 结果取决于当前时间,如果当前时间大于6点则为true
  153 + assertNotNull(result);
  154 + }
  155 +
  156 + @Test
  157 + public void testIsMatched_TimeLessThan_LateNight() {
  158 + // given
  159 + IotDeviceMessage message = new IotDeviceMessage();
  160 + IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
  161 + IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN.getOperator(),
  162 + "23:59:59" // 晚上11点59分59秒
  163 + );
  164 +
  165 + // when
  166 + boolean result = matcher.isMatched(message, condition);
  167 +
  168 + // then
  169 + // 大部分情况下应该为true,除非在午夜前1秒运行测试
  170 + assertNotNull(result);
  171 + }
  172 +
  173 + @Test
  174 + public void testIsMatched_TimeBetween_AllDay() {
  175 + // given
  176 + IotDeviceMessage message = new IotDeviceMessage();
  177 + IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
  178 + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(),
  179 + "00:00:00,23:59:59" // 全天
  180 + );
  181 +
  182 + // when
  183 + boolean result = matcher.isMatched(message, condition);
  184 +
  185 + // then
  186 + assertTrue(result); // 全天范围应该总是匹配
  187 + }
  188 +
  189 + @Test
  190 + public void testIsMatched_TimeBetween_WorkingHours() {
  191 + // given
  192 + IotDeviceMessage message = new IotDeviceMessage();
  193 + IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
  194 + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(),
  195 + "09:00:00,17:00:00" // 工作时间
  196 + );
  197 +
  198 + // when
  199 + boolean result = matcher.isMatched(message, condition);
  200 +
  201 + // then
  202 + // 结果取决于当前时间是否在工作时间内
  203 + assertNotNull(result);
  204 + }
  205 +
  206 + // ========== 异常情况测试 ==========
  207 +
  208 + @Test
  209 + public void testIsMatched_NullCondition() {
  210 + // given
  211 + IotDeviceMessage message = new IotDeviceMessage();
  212 +
  213 + // when
  214 + boolean result = matcher.isMatched(message, null);
  215 +
  216 + // then
  217 + assertFalse(result);
  218 + }
  219 +
  220 + @Test
  221 + public void testIsMatched_NullConditionType() {
  222 + // given
  223 + IotDeviceMessage message = new IotDeviceMessage();
  224 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  225 + condition.setType(null);
  226 +
  227 + // when
  228 + boolean result = matcher.isMatched(message, condition);
  229 +
  230 + // then
  231 + assertFalse(result);
  232 + }
  233 +
  234 + @Test
  235 + public void testIsMatched_InvalidOperator() {
  236 + // given
  237 + IotDeviceMessage message = new IotDeviceMessage();
  238 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  239 + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType());
  240 + condition.setOperator("invalid_operator");
  241 + condition.setParam("12:00:00");
  242 +
  243 + // when
  244 + boolean result = matcher.isMatched(message, condition);
  245 +
  246 + // then
  247 + assertFalse(result);
  248 + }
  249 +
  250 + @Test
  251 + public void testIsMatched_InvalidTimeFormat() {
  252 + // given
  253 + IotDeviceMessage message = new IotDeviceMessage();
  254 + IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
  255 + IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(),
  256 + "invalid-time-format"
  257 + );
  258 +
  259 + // when
  260 + boolean result = matcher.isMatched(message, condition);
  261 +
  262 + // then
  263 + assertFalse(result);
  264 + }
  265 +
  266 + @Test
  267 + public void testIsMatched_InvalidTimestampFormat() {
  268 + // given
  269 + IotDeviceMessage message = new IotDeviceMessage();
  270 + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
  271 + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(),
  272 + "invalid-timestamp"
  273 + );
  274 +
  275 + // when
  276 + boolean result = matcher.isMatched(message, condition);
  277 +
  278 + // then
  279 + assertFalse(result);
  280 + }
  281 +
  282 + @Test
  283 + public void testIsMatched_InvalidBetweenFormat() {
  284 + // given
  285 + IotDeviceMessage message = new IotDeviceMessage();
  286 + IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
  287 + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(),
  288 + "09:00:00" // 缺少结束时间
  289 + );
  290 +
  291 + // when
  292 + boolean result = matcher.isMatched(message, condition);
  293 +
  294 + // then
  295 + assertFalse(result);
  296 + }
  297 +
  298 + // ========== 辅助方法 ==========
  299 +
  300 + /**
  301 + * 创建日期时间条件
  302 + */
  303 + private IotSceneRuleDO.TriggerCondition createDateTimeCondition(String operator, String param) {
  304 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  305 + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType());
  306 + condition.setOperator(operator);
  307 + condition.setParam(param);
  308 + return condition;
  309 + }
  310 +
  311 + /**
  312 + * 创建当日时间条件
  313 + */
  314 + private IotSceneRuleDO.TriggerCondition createTimeCondition(String operator, String param) {
  315 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  316 + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType());
  317 + condition.setOperator(operator);
  318 + condition.setParam(param);
  319 + return condition;
  320 + }
  321 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  5 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  7 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
  9 +import org.junit.jupiter.api.BeforeEach;
  10 +import org.junit.jupiter.api.Test;
  11 +
  12 +import java.util.HashMap;
  13 +import java.util.Map;
  14 +
  15 +import static org.junit.jupiter.api.Assertions.*;
  16 +
  17 +/**
  18 + * {@link DevicePropertyConditionMatcher} 的单元测试类
  19 + *
  20 + * @author HUIHUI
  21 + */
  22 +public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
  23 +
  24 + private DevicePropertyConditionMatcher matcher;
  25 +
  26 + @BeforeEach
  27 + public void setUp() {
  28 + matcher = new DevicePropertyConditionMatcher();
  29 + }
  30 +
  31 + @Test
  32 + public void testGetSupportedConditionType() {
  33 + // when & then
  34 + assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY, matcher.getSupportedConditionType());
  35 + }
  36 +
  37 + @Test
  38 + public void testGetPriority() {
  39 + // when & then
  40 + assertEquals(20, matcher.getPriority());
  41 + }
  42 +
  43 + @Test
  44 + public void testIsEnabled() {
  45 + // when & then
  46 + assertTrue(matcher.isEnabled());
  47 + }
  48 +
  49 + @Test
  50 + public void testIsMatched_Success_TemperatureEquals() {
  51 + // given
  52 + Map<String, Object> properties = MapUtil.of("temperature", 25.5);
  53 + IotDeviceMessage message = createDeviceMessage(properties);
  54 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  55 + "temperature",
  56 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  57 + "25.5"
  58 + );
  59 +
  60 + // when
  61 + boolean result = matcher.isMatched(message, condition);
  62 +
  63 + // then
  64 + assertTrue(result);
  65 + }
  66 +
  67 + @Test
  68 + public void testIsMatched_Success_HumidityGreaterThan() {
  69 + // given
  70 + Map<String, Object> properties = MapUtil.of("humidity", 75);
  71 + IotDeviceMessage message = createDeviceMessage(properties);
  72 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  73 + "humidity",
  74 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  75 + "70"
  76 + );
  77 +
  78 + // when
  79 + boolean result = matcher.isMatched(message, condition);
  80 +
  81 + // then
  82 + assertTrue(result);
  83 + }
  84 +
  85 + @Test
  86 + public void testIsMatched_Success_PressureLessThan() {
  87 + // given
  88 + Map<String, Object> properties = MapUtil.of("pressure", 1010.5);
  89 + IotDeviceMessage message = createDeviceMessage(properties);
  90 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  91 + "pressure",
  92 + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
  93 + "1020"
  94 + );
  95 +
  96 + // when
  97 + boolean result = matcher.isMatched(message, condition);
  98 +
  99 + // then
  100 + assertTrue(result);
  101 + }
  102 +
  103 + @Test
  104 + public void testIsMatched_Success_StatusNotEquals() {
  105 + // given
  106 + Map<String, Object> properties = MapUtil.of("status", "active");
  107 + IotDeviceMessage message = createDeviceMessage(properties);
  108 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  109 + "status",
  110 + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
  111 + "inactive"
  112 + );
  113 +
  114 + // when
  115 + boolean result = matcher.isMatched(message, condition);
  116 +
  117 + // then
  118 + assertTrue(result);
  119 + }
  120 +
  121 + @Test
  122 + public void testIsMatched_Failure_PropertyMismatch() {
  123 + // given
  124 + Map<String, Object> properties = MapUtil.of("temperature", 15.0);
  125 + IotDeviceMessage message = createDeviceMessage(properties);
  126 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  127 + "temperature",
  128 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  129 + "20"
  130 + );
  131 +
  132 + // when
  133 + boolean result = matcher.isMatched(message, condition);
  134 +
  135 + // then
  136 + assertFalse(result);
  137 + }
  138 +
  139 + @Test
  140 + public void testIsMatched_Failure_PropertyNotFound() {
  141 + // given
  142 + Map<String, Object> properties = MapUtil.of("temperature", 25.5);
  143 + IotDeviceMessage message = createDeviceMessage(properties);
  144 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  145 + "humidity", // 不存在的属性
  146 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  147 + "50"
  148 + );
  149 +
  150 + // when
  151 + boolean result = matcher.isMatched(message, condition);
  152 +
  153 + // then
  154 + assertFalse(result);
  155 + }
  156 +
  157 + @Test
  158 + public void testIsMatched_Failure_NullCondition() {
  159 + // given
  160 + Map<String, Object> properties = MapUtil.of("temperature", 25.5);
  161 + IotDeviceMessage message = createDeviceMessage(properties);
  162 +
  163 + // when
  164 + boolean result = matcher.isMatched(message, null);
  165 +
  166 + // then
  167 + assertFalse(result);
  168 + }
  169 +
  170 + @Test
  171 + public void testIsMatched_Failure_NullConditionType() {
  172 + // given
  173 + Map<String, Object> properties = MapUtil.of("temperature", 25.5);
  174 + IotDeviceMessage message = createDeviceMessage(properties);
  175 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  176 + condition.setType(null);
  177 +
  178 + // when
  179 + boolean result = matcher.isMatched(message, condition);
  180 +
  181 + // then
  182 + assertFalse(result);
  183 + }
  184 +
  185 + @Test
  186 + public void testIsMatched_Failure_MissingIdentifier() {
  187 + // given
  188 + Map<String, Object> properties = MapUtil.of("temperature", 25.5);
  189 + IotDeviceMessage message = createDeviceMessage(properties);
  190 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  191 + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
  192 + condition.setIdentifier(null); // 缺少标识符
  193 + condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator());
  194 + condition.setParam("20");
  195 +
  196 + // when
  197 + boolean result = matcher.isMatched(message, condition);
  198 +
  199 + // then
  200 + assertFalse(result);
  201 + }
  202 +
  203 + @Test
  204 + public void testIsMatched_Failure_MissingOperator() {
  205 + // given
  206 + Map<String, Object> properties = MapUtil.of("temperature", 25.5);
  207 + IotDeviceMessage message = createDeviceMessage(properties);
  208 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  209 + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
  210 + condition.setIdentifier("temperature");
  211 + condition.setOperator(null); // 缺少操作符
  212 + condition.setParam("20");
  213 +
  214 + // when
  215 + boolean result = matcher.isMatched(message, condition);
  216 +
  217 + // then
  218 + assertFalse(result);
  219 + }
  220 +
  221 + @Test
  222 + public void testIsMatched_Failure_MissingParam() {
  223 + // given
  224 + Map<String, Object> properties = MapUtil.of("temperature", 25.5);
  225 + IotDeviceMessage message = createDeviceMessage(properties);
  226 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  227 + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
  228 + condition.setIdentifier("temperature");
  229 + condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator());
  230 + condition.setParam(null); // 缺少参数
  231 +
  232 + // when
  233 + boolean result = matcher.isMatched(message, condition);
  234 +
  235 + // then
  236 + assertFalse(result);
  237 + }
  238 +
  239 + @Test
  240 + public void testIsMatched_Failure_NullMessage() {
  241 + // given
  242 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  243 + "temperature",
  244 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  245 + "20"
  246 + );
  247 +
  248 + // when
  249 + boolean result = matcher.isMatched(null, condition);
  250 +
  251 + // then
  252 + assertFalse(result);
  253 + }
  254 +
  255 + @Test
  256 + public void testIsMatched_Failure_NullDeviceProperties() {
  257 + // given
  258 + IotDeviceMessage message = new IotDeviceMessage();
  259 + message.setParams(null);
  260 +
  261 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  262 + "temperature",
  263 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  264 + "20"
  265 + );
  266 +
  267 + // when
  268 + boolean result = matcher.isMatched(message, condition);
  269 +
  270 + // then
  271 + assertFalse(result);
  272 + }
  273 +
  274 + @Test
  275 + public void testIsMatched_Success_GreaterThanOrEquals() {
  276 + // given
  277 + Map<String, Object> properties = MapUtil.of("voltage", 12.0);
  278 + IotDeviceMessage message = createDeviceMessage(properties);
  279 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  280 + "voltage",
  281 + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(),
  282 + "12.0"
  283 + );
  284 +
  285 + // when
  286 + boolean result = matcher.isMatched(message, condition);
  287 +
  288 + // then
  289 + assertTrue(result);
  290 + }
  291 +
  292 + @Test
  293 + public void testIsMatched_Success_LessThanOrEquals() {
  294 + // given
  295 + Map<String, Object> properties = MapUtil.of("current", 2.5);
  296 + IotDeviceMessage message = createDeviceMessage(properties);
  297 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  298 + "current",
  299 + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(),
  300 + "3.0"
  301 + );
  302 +
  303 + // when
  304 + boolean result = matcher.isMatched(message, condition);
  305 +
  306 + // then
  307 + assertTrue(result);
  308 + }
  309 +
  310 + @Test
  311 + public void testIsMatched_Success_StringProperty() {
  312 + // given
  313 + Map<String, Object> properties = MapUtil.of("mode", "auto");
  314 + IotDeviceMessage message = createDeviceMessage(properties);
  315 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  316 + "mode",
  317 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  318 + "auto"
  319 + );
  320 +
  321 + // when
  322 + boolean result = matcher.isMatched(message, condition);
  323 +
  324 + // then
  325 + assertTrue(result);
  326 + }
  327 +
  328 + @Test
  329 + public void testIsMatched_Success_BooleanProperty() {
  330 + // given
  331 + Map<String, Object> properties = MapUtil.of("enabled", true);
  332 + IotDeviceMessage message = createDeviceMessage(properties);
  333 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  334 + "enabled",
  335 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  336 + "true"
  337 + );
  338 +
  339 + // when
  340 + boolean result = matcher.isMatched(message, condition);
  341 +
  342 + // then
  343 + assertTrue(result);
  344 + }
  345 +
  346 + @Test
  347 + public void testIsMatched_Success_MultipleProperties() {
  348 + // given
  349 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  350 + .put("temperature", 25.5)
  351 + .put("humidity", 60)
  352 + .put("status", "active")
  353 + .put("enabled", true)
  354 + .build();
  355 + IotDeviceMessage message = createDeviceMessage(properties);
  356 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  357 + "humidity",
  358 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  359 + "60"
  360 + );
  361 +
  362 + // when
  363 + boolean result = matcher.isMatched(message, condition);
  364 +
  365 + // then
  366 + assertTrue(result);
  367 + }
  368 +
  369 + // ========== 辅助方法 ==========
  370 +
  371 + /**
  372 + * 创建设备消息
  373 + */
  374 + private IotDeviceMessage createDeviceMessage(Map<String, Object> properties) {
  375 + IotDeviceMessage message = new IotDeviceMessage();
  376 + message.setParams(properties);
  377 + return message;
  378 + }
  379 +
  380 + /**
  381 + * 创建有效的条件
  382 + */
  383 + private IotSceneRuleDO.TriggerCondition createValidCondition(String identifier, String operator, String param) {
  384 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  385 + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
  386 + condition.setIdentifier(identifier);
  387 + condition.setOperator(operator);
  388 + condition.setParam(param);
  389 + return condition;
  390 + }
  391 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
  5 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  7 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
  9 +import org.junit.jupiter.api.BeforeEach;
  10 +import org.junit.jupiter.api.Test;
  11 +
  12 +import static org.junit.jupiter.api.Assertions.*;
  13 +
  14 +/**
  15 + * {@link DeviceStateConditionMatcher} 的单元测试类
  16 + *
  17 + * @author HUIHUI
  18 + */
  19 +public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest {
  20 +
  21 + private DeviceStateConditionMatcher matcher;
  22 +
  23 + @BeforeEach
  24 + public void setUp() {
  25 + matcher = new DeviceStateConditionMatcher();
  26 + }
  27 +
  28 + @Test
  29 + public void testGetSupportedConditionType() {
  30 + // when & then
  31 + assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_STATE, matcher.getSupportedConditionType());
  32 + }
  33 +
  34 + @Test
  35 + public void testGetPriority() {
  36 + // when & then
  37 + assertEquals(30, matcher.getPriority());
  38 + }
  39 +
  40 + @Test
  41 + public void testIsEnabled() {
  42 + // when & then
  43 + assertTrue(matcher.isEnabled());
  44 + }
  45 +
  46 + @Test
  47 + public void testIsMatched_Success_OnlineState() {
  48 + // given
  49 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
  50 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  51 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  52 + IotDeviceStateEnum.ONLINE.getState().toString()
  53 + );
  54 +
  55 + // when
  56 + boolean result = matcher.isMatched(message, condition);
  57 +
  58 + // then
  59 + assertTrue(result);
  60 + }
  61 +
  62 + @Test
  63 + public void testIsMatched_Success_OfflineState() {
  64 + // given
  65 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState());
  66 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  67 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  68 + IotDeviceStateEnum.OFFLINE.getState().toString()
  69 + );
  70 +
  71 + // when
  72 + boolean result = matcher.isMatched(message, condition);
  73 +
  74 + // then
  75 + assertTrue(result);
  76 + }
  77 +
  78 + @Test
  79 + public void testIsMatched_Success_InactiveState() {
  80 + // given
  81 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState());
  82 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  83 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  84 + IotDeviceStateEnum.INACTIVE.getState().toString()
  85 + );
  86 +
  87 + // when
  88 + boolean result = matcher.isMatched(message, condition);
  89 +
  90 + // then
  91 + assertTrue(result);
  92 + }
  93 +
  94 + @Test
  95 + public void testIsMatched_Failure_StateMismatch() {
  96 + // given
  97 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
  98 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  99 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  100 + IotDeviceStateEnum.OFFLINE.getState().toString()
  101 + );
  102 +
  103 + // when
  104 + boolean result = matcher.isMatched(message, condition);
  105 +
  106 + // then
  107 + assertFalse(result);
  108 + }
  109 +
  110 + @Test
  111 + public void testIsMatched_Success_NotEqualsOperator() {
  112 + // given
  113 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
  114 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  115 + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
  116 + IotDeviceStateEnum.OFFLINE.getState().toString()
  117 + );
  118 +
  119 + // when
  120 + boolean result = matcher.isMatched(message, condition);
  121 +
  122 + // then
  123 + assertTrue(result);
  124 + }
  125 +
  126 + @Test
  127 + public void testIsMatched_Success_GreaterThanOperator() {
  128 + // given
  129 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState()); // 2
  130 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  131 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  132 + IotDeviceStateEnum.ONLINE.getState().toString() // 1
  133 + );
  134 +
  135 + // when
  136 + boolean result = matcher.isMatched(message, condition);
  137 +
  138 + // then
  139 + assertTrue(result);
  140 + }
  141 +
  142 + @Test
  143 + public void testIsMatched_Success_LessThanOperator() {
  144 + // given
  145 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState()); // 0
  146 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  147 + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
  148 + IotDeviceStateEnum.ONLINE.getState().toString() // 1
  149 + );
  150 +
  151 + // when
  152 + boolean result = matcher.isMatched(message, condition);
  153 +
  154 + // then
  155 + assertTrue(result);
  156 + }
  157 +
  158 + @Test
  159 + public void testIsMatched_Failure_NullCondition() {
  160 + // given
  161 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
  162 +
  163 + // when
  164 + boolean result = matcher.isMatched(message, null);
  165 +
  166 + // then
  167 + assertFalse(result);
  168 + }
  169 +
  170 + @Test
  171 + public void testIsMatched_Failure_NullConditionType() {
  172 + // given
  173 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
  174 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  175 + condition.setType(null);
  176 +
  177 + // when
  178 + boolean result = matcher.isMatched(message, condition);
  179 +
  180 + // then
  181 + assertFalse(result);
  182 + }
  183 +
  184 + @Test
  185 + public void testIsMatched_Failure_MissingOperator() {
  186 + // given
  187 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
  188 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  189 + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType());
  190 + condition.setOperator(null);
  191 + condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString());
  192 +
  193 + // when
  194 + boolean result = matcher.isMatched(message, condition);
  195 +
  196 + // then
  197 + assertFalse(result);
  198 + }
  199 +
  200 + @Test
  201 + public void testIsMatched_Failure_MissingParam() {
  202 + // given
  203 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
  204 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  205 + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType());
  206 + condition.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
  207 + condition.setParam(null);
  208 +
  209 + // when
  210 + boolean result = matcher.isMatched(message, condition);
  211 +
  212 + // then
  213 + assertFalse(result);
  214 + }
  215 +
  216 + @Test
  217 + public void testIsMatched_Failure_NullMessage() {
  218 + // given
  219 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  220 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  221 + IotDeviceStateEnum.ONLINE.getState().toString()
  222 + );
  223 +
  224 + // when
  225 + boolean result = matcher.isMatched(null, condition);
  226 +
  227 + // then
  228 + assertFalse(result);
  229 + }
  230 +
  231 + @Test
  232 + public void testIsMatched_Failure_NullDeviceState() {
  233 + // given
  234 + IotDeviceMessage message = new IotDeviceMessage();
  235 + message.setParams(null);
  236 +
  237 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  238 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  239 + IotDeviceStateEnum.ONLINE.getState().toString()
  240 + );
  241 +
  242 + // when
  243 + boolean result = matcher.isMatched(message, condition);
  244 +
  245 + // then
  246 + assertFalse(result);
  247 + }
  248 +
  249 + @Test
  250 + public void testIsMatched_Success_GreaterThanOrEqualsOperator() {
  251 + // given
  252 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1
  253 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  254 + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(),
  255 + IotDeviceStateEnum.ONLINE.getState().toString() // 1
  256 + );
  257 +
  258 + // when
  259 + boolean result = matcher.isMatched(message, condition);
  260 +
  261 + // then
  262 + assertTrue(result);
  263 + }
  264 +
  265 + @Test
  266 + public void testIsMatched_Success_LessThanOrEqualsOperator() {
  267 + // given
  268 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1
  269 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  270 + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(),
  271 + IotDeviceStateEnum.OFFLINE.getState().toString() // 2
  272 + );
  273 +
  274 + // when
  275 + boolean result = matcher.isMatched(message, condition);
  276 +
  277 + // then
  278 + assertTrue(result);
  279 + }
  280 +
  281 + @Test
  282 + public void testIsMatched_Failure_InvalidOperator() {
  283 + // given
  284 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
  285 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  286 + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType());
  287 + condition.setOperator("invalid_operator");
  288 + condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString());
  289 +
  290 + // when
  291 + boolean result = matcher.isMatched(message, condition);
  292 +
  293 + // then
  294 + assertFalse(result);
  295 + }
  296 +
  297 + @Test
  298 + public void testIsMatched_Failure_InvalidParamFormat() {
  299 + // given
  300 + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
  301 + IotSceneRuleDO.TriggerCondition condition = createValidCondition(
  302 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  303 + "invalid_state_value"
  304 + );
  305 +
  306 + // when
  307 + boolean result = matcher.isMatched(message, condition);
  308 +
  309 + // then
  310 + assertFalse(result);
  311 + }
  312 +
  313 + // ========== 辅助方法 ==========
  314 +
  315 + /**
  316 + * 创建设备消息
  317 + */
  318 + private IotDeviceMessage createDeviceMessage(Integer deviceState) {
  319 + IotDeviceMessage message = new IotDeviceMessage();
  320 + message.setParams(deviceState);
  321 + return message;
  322 + }
  323 +
  324 + /**
  325 + * 创建有效的条件
  326 + */
  327 + private IotSceneRuleDO.TriggerCondition createValidCondition(String operator, String param) {
  328 + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
  329 + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType());
  330 + condition.setOperator(operator);
  331 + condition.setParam(param);
  332 + return condition;
  333 + }
  334 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  6 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  9 +import org.junit.jupiter.api.BeforeEach;
  10 +import org.junit.jupiter.api.Test;
  11 +
  12 +import java.util.HashMap;
  13 +import java.util.Map;
  14 +
  15 +import static org.junit.jupiter.api.Assertions.*;
  16 +
  17 +/**
  18 + * {@link DeviceEventPostTriggerMatcher} 的单元测试类
  19 + *
  20 + * @author HUIHUI
  21 + */
  22 +public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest {
  23 +
  24 + private DeviceEventPostTriggerMatcher matcher;
  25 +
  26 + @BeforeEach
  27 + public void setUp() {
  28 + matcher = new DeviceEventPostTriggerMatcher();
  29 + }
  30 +
  31 + @Test
  32 + public void testGetSupportedTriggerType() {
  33 + // when & then
  34 + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST, matcher.getSupportedTriggerType());
  35 + }
  36 +
  37 + @Test
  38 + public void testGetPriority() {
  39 + // when & then
  40 + assertEquals(30, matcher.getPriority());
  41 + }
  42 +
  43 + @Test
  44 + public void testIsEnabled() {
  45 + // when & then
  46 + assertTrue(matcher.isEnabled());
  47 + }
  48 +
  49 + @Test
  50 + public void testIsMatched_Success_AlarmEvent() {
  51 + // given
  52 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  53 + .put("identifier", "alarm")
  54 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  55 + .put("level", "high")
  56 + .put("message", "Temperature too high")
  57 + .build())
  58 + .build();
  59 + IotDeviceMessage message = createEventPostMessage(eventParams);
  60 + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
  61 +
  62 + // when
  63 + boolean result = matcher.isMatched(message, trigger);
  64 +
  65 + // then
  66 + assertTrue(result);
  67 + }
  68 +
  69 + @Test
  70 + public void testIsMatched_Success_ErrorEvent() {
  71 + // given
  72 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  73 + .put("identifier", "error")
  74 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  75 + .put("code", 500)
  76 + .put("description", "System error")
  77 + .build())
  78 + .build();
  79 + IotDeviceMessage message = createEventPostMessage(eventParams);
  80 + IotSceneRuleDO.Trigger trigger = createValidTrigger("error");
  81 +
  82 + // when
  83 + boolean result = matcher.isMatched(message, trigger);
  84 +
  85 + // then
  86 + assertTrue(result);
  87 + }
  88 +
  89 + @Test
  90 + public void testIsMatched_Success_InfoEvent() {
  91 + // given
  92 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  93 + .put("identifier", "info")
  94 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  95 + .put("status", "normal")
  96 + .put("timestamp", System.currentTimeMillis())
  97 + .build())
  98 + .build();
  99 + IotDeviceMessage message = createEventPostMessage(eventParams);
  100 + IotSceneRuleDO.Trigger trigger = createValidTrigger("info");
  101 +
  102 + // when
  103 + boolean result = matcher.isMatched(message, trigger);
  104 +
  105 + // then
  106 + assertTrue(result);
  107 + }
  108 +
  109 + @Test
  110 + public void testIsMatched_Failure_EventIdentifierMismatch() {
  111 + // given
  112 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  113 + .put("identifier", "alarm")
  114 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  115 + .put("level", "high")
  116 + .build())
  117 + .build();
  118 + IotDeviceMessage message = createEventPostMessage(eventParams);
  119 + IotSceneRuleDO.Trigger trigger = createValidTrigger("error"); // 不匹配的事件标识符
  120 +
  121 + // when
  122 + boolean result = matcher.isMatched(message, trigger);
  123 +
  124 + // then
  125 + assertFalse(result);
  126 + }
  127 +
  128 + @Test
  129 + public void testIsMatched_Failure_WrongMessageMethod() {
  130 + // given
  131 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  132 + .put("identifier", "alarm")
  133 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  134 + .put("level", "high")
  135 + .build())
  136 + .build();
  137 + IotDeviceMessage message = new IotDeviceMessage();
  138 + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法
  139 + message.setParams(eventParams);
  140 +
  141 + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
  142 +
  143 + // when
  144 + boolean result = matcher.isMatched(message, trigger);
  145 +
  146 + // then
  147 + assertFalse(result);
  148 + }
  149 +
  150 + @Test
  151 + public void testIsMatched_Failure_MissingIdentifier() {
  152 + // given
  153 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  154 + .put("identifier", "alarm")
  155 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  156 + .put("level", "high")
  157 + .build())
  158 + .build();
  159 + IotDeviceMessage message = createEventPostMessage(eventParams);
  160 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  161 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType());
  162 + trigger.setIdentifier(null); // 缺少标识符
  163 +
  164 + // when
  165 + boolean result = matcher.isMatched(message, trigger);
  166 +
  167 + // then
  168 + assertFalse(result);
  169 + }
  170 +
  171 + @Test
  172 + public void testIsMatched_Failure_NullMessageParams() {
  173 + // given
  174 + IotDeviceMessage message = new IotDeviceMessage();
  175 + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod());
  176 + message.setParams(null);
  177 +
  178 + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
  179 +
  180 + // when
  181 + boolean result = matcher.isMatched(message, trigger);
  182 +
  183 + // then
  184 + assertFalse(result);
  185 + }
  186 +
  187 + @Test
  188 + public void testIsMatched_Failure_InvalidMessageParams() {
  189 + // given
  190 + IotDeviceMessage message = new IotDeviceMessage();
  191 + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod());
  192 + message.setParams("invalid-params"); // 不是 Map 类型
  193 +
  194 + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
  195 +
  196 + // when
  197 + boolean result = matcher.isMatched(message, trigger);
  198 +
  199 + // then
  200 + assertFalse(result);
  201 + }
  202 +
  203 + @Test
  204 + public void testIsMatched_Failure_MissingEventIdentifierInParams() {
  205 + // given
  206 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  207 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  208 + .put("level", "high")
  209 + .build()) // 缺少 identifier 字段
  210 + .build();
  211 + IotDeviceMessage message = createEventPostMessage(eventParams);
  212 + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
  213 +
  214 + // when
  215 + boolean result = matcher.isMatched(message, trigger);
  216 +
  217 + // then
  218 + assertFalse(result);
  219 + }
  220 +
  221 + @Test
  222 + public void testIsMatched_Failure_NullTrigger() {
  223 + // given
  224 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  225 + .put("identifier", "alarm")
  226 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  227 + .put("level", "high")
  228 + .build())
  229 + .build();
  230 + IotDeviceMessage message = createEventPostMessage(eventParams);
  231 +
  232 + // when
  233 + boolean result = matcher.isMatched(message, null);
  234 +
  235 + // then
  236 + assertFalse(result);
  237 + }
  238 +
  239 + @Test
  240 + public void testIsMatched_Failure_NullTriggerType() {
  241 + // given
  242 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  243 + .put("identifier", "alarm")
  244 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  245 + .put("level", "high")
  246 + .build())
  247 + .build();
  248 + IotDeviceMessage message = createEventPostMessage(eventParams);
  249 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  250 + trigger.setType(null);
  251 + trigger.setIdentifier("alarm");
  252 +
  253 + // when
  254 + boolean result = matcher.isMatched(message, trigger);
  255 +
  256 + // then
  257 + assertFalse(result);
  258 + }
  259 +
  260 + @Test
  261 + public void testIsMatched_Success_ComplexEventValue() {
  262 + // given
  263 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  264 + .put("identifier", "maintenance")
  265 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  266 + .put("type", "scheduled")
  267 + .put("duration", 120)
  268 + .put("components", new String[]{"motor", "sensor"})
  269 + .put("priority", "medium")
  270 + .build())
  271 + .build();
  272 + IotDeviceMessage message = createEventPostMessage(eventParams);
  273 + IotSceneRuleDO.Trigger trigger = createValidTrigger("maintenance");
  274 +
  275 + // when
  276 + boolean result = matcher.isMatched(message, trigger);
  277 +
  278 + // then
  279 + assertTrue(result);
  280 + }
  281 +
  282 + @Test
  283 + public void testIsMatched_Success_EmptyEventValue() {
  284 + // given
  285 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  286 + .put("identifier", "heartbeat")
  287 + .put("value", MapUtil.of()) // 空的事件值
  288 + .build();
  289 + IotDeviceMessage message = createEventPostMessage(eventParams);
  290 + IotSceneRuleDO.Trigger trigger = createValidTrigger("heartbeat");
  291 +
  292 + // when
  293 + boolean result = matcher.isMatched(message, trigger);
  294 +
  295 + // then
  296 + assertTrue(result);
  297 + }
  298 +
  299 + @Test
  300 + public void testIsMatched_Success_CaseInsensitiveIdentifier() {
  301 + // given
  302 + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
  303 + .put("identifier", "ALARM") // 大写
  304 + .put("value", MapUtil.builder(new HashMap<String, Object>())
  305 + .put("level", "high")
  306 + .build())
  307 + .build();
  308 + IotDeviceMessage message = createEventPostMessage(eventParams);
  309 + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); // 小写
  310 +
  311 + // when
  312 + boolean result = matcher.isMatched(message, trigger);
  313 +
  314 + // then
  315 + // 根据实际实现,这里可能需要调整期望结果
  316 + // 如果实现是大小写敏感的,则应该为 false
  317 + assertFalse(result);
  318 + }
  319 +
  320 + // ========== 辅助方法 ==========
  321 +
  322 + /**
  323 + * 创建事件上报消息
  324 + */
  325 + private IotDeviceMessage createEventPostMessage(Map<String, Object> eventParams) {
  326 + IotDeviceMessage message = new IotDeviceMessage();
  327 + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod());
  328 + message.setParams(eventParams);
  329 + return message;
  330 + }
  331 +
  332 + /**
  333 + * 创建有效的触发器
  334 + */
  335 + private IotSceneRuleDO.Trigger createValidTrigger(String identifier) {
  336 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  337 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType());
  338 + trigger.setIdentifier(identifier);
  339 + return trigger;
  340 + }
  341 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  6 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
  9 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  10 +import org.junit.jupiter.api.BeforeEach;
  11 +import org.junit.jupiter.api.Test;
  12 +
  13 +import java.util.HashMap;
  14 +import java.util.Map;
  15 +
  16 +import static org.junit.jupiter.api.Assertions.*;
  17 +
  18 +/**
  19 + * {@link DevicePropertyPostTriggerMatcher} 的单元测试类
  20 + *
  21 + * @author HUIHUI
  22 + */
  23 +public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest {
  24 +
  25 + private DevicePropertyPostTriggerMatcher matcher;
  26 +
  27 + @BeforeEach
  28 + public void setUp() {
  29 + matcher = new DevicePropertyPostTriggerMatcher();
  30 + }
  31 +
  32 + @Test
  33 + public void testGetSupportedTriggerType() {
  34 + // when & then
  35 + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST, matcher.getSupportedTriggerType());
  36 + }
  37 +
  38 + @Test
  39 + public void testGetPriority() {
  40 + // when & then
  41 + assertEquals(20, matcher.getPriority());
  42 + }
  43 +
  44 + @Test
  45 + public void testIsEnabled() {
  46 + // when & then
  47 + assertTrue(matcher.isEnabled());
  48 + }
  49 +
  50 + @Test
  51 + public void testIsMatched_Success_TemperatureProperty() {
  52 + // given
  53 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  54 + .put("temperature", 25.5)
  55 + .build();
  56 + IotDeviceMessage message = createPropertyPostMessage(properties);
  57 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  58 + "temperature",
  59 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  60 + "20"
  61 + );
  62 +
  63 + // when
  64 + boolean result = matcher.isMatched(message, trigger);
  65 +
  66 + // then
  67 + assertTrue(result);
  68 + }
  69 +
  70 + @Test
  71 + public void testIsMatched_Success_HumidityProperty() {
  72 + // given
  73 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  74 + .put("humidity", 60)
  75 + .build();
  76 + IotDeviceMessage message = createPropertyPostMessage(properties);
  77 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  78 + "humidity",
  79 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  80 + "60"
  81 + );
  82 +
  83 + // when
  84 + boolean result = matcher.isMatched(message, trigger);
  85 +
  86 + // then
  87 + assertTrue(result);
  88 + }
  89 +
  90 + @Test
  91 + public void testIsMatched_Failure_PropertyMismatch() {
  92 + // given
  93 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  94 + .put("temperature", 15.0)
  95 + .build();
  96 + IotDeviceMessage message = createPropertyPostMessage(properties);
  97 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  98 + "temperature",
  99 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  100 + "20"
  101 + );
  102 +
  103 + // when
  104 + boolean result = matcher.isMatched(message, trigger);
  105 +
  106 + // then
  107 + assertFalse(result);
  108 + }
  109 +
  110 + @Test
  111 + public void testIsMatched_Failure_PropertyNotFound() {
  112 + // given
  113 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  114 + .put("temperature", 25.5)
  115 + .build();
  116 + IotDeviceMessage message = createPropertyPostMessage(properties);
  117 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  118 + "humidity", // 不存在的属性
  119 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  120 + "50"
  121 + );
  122 +
  123 + // when
  124 + boolean result = matcher.isMatched(message, trigger);
  125 +
  126 + // then
  127 + assertFalse(result);
  128 + }
  129 +
  130 + @Test
  131 + public void testIsMatched_Failure_WrongMessageMethod() {
  132 + // given
  133 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  134 + .put("temperature", 25.5)
  135 + .build();
  136 + IotDeviceMessage message = new IotDeviceMessage();
  137 + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
  138 + message.setParams(properties);
  139 +
  140 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  141 + "temperature",
  142 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  143 + "20"
  144 + );
  145 +
  146 + // when
  147 + boolean result = matcher.isMatched(message, trigger);
  148 +
  149 + // then
  150 + assertFalse(result);
  151 + }
  152 +
  153 + @Test
  154 + public void testIsMatched_Failure_MissingIdentifier() {
  155 + // given
  156 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  157 + .put("temperature", 25.5)
  158 + .build();
  159 + IotDeviceMessage message = createPropertyPostMessage(properties);
  160 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  161 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
  162 + trigger.setIdentifier(null); // 缺少标识符
  163 + trigger.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator());
  164 + trigger.setValue("20");
  165 +
  166 + // when
  167 + boolean result = matcher.isMatched(message, trigger);
  168 +
  169 + // then
  170 + assertFalse(result);
  171 + }
  172 +
  173 + @Test
  174 + public void testIsMatched_Failure_NullMessageParams() {
  175 + // given
  176 + IotDeviceMessage message = new IotDeviceMessage();
  177 + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
  178 + message.setParams(null);
  179 +
  180 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  181 + "temperature",
  182 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  183 + "20"
  184 + );
  185 +
  186 + // when
  187 + boolean result = matcher.isMatched(message, trigger);
  188 +
  189 + // then
  190 + assertFalse(result);
  191 + }
  192 +
  193 + @Test
  194 + public void testIsMatched_Failure_InvalidMessageParams() {
  195 + // given
  196 + IotDeviceMessage message = new IotDeviceMessage();
  197 + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
  198 + message.setParams("invalid-params"); // 不是 Map 类型
  199 +
  200 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  201 + "temperature",
  202 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  203 + "20"
  204 + );
  205 +
  206 + // when
  207 + boolean result = matcher.isMatched(message, trigger);
  208 +
  209 + // then
  210 + assertFalse(result);
  211 + }
  212 +
  213 + @Test
  214 + public void testIsMatched_Success_LessThanOperator() {
  215 + // given
  216 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  217 + .put("temperature", 15.0)
  218 + .build();
  219 + IotDeviceMessage message = createPropertyPostMessage(properties);
  220 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  221 + "temperature",
  222 + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
  223 + "20"
  224 + );
  225 +
  226 + // when
  227 + boolean result = matcher.isMatched(message, trigger);
  228 +
  229 + // then
  230 + assertTrue(result);
  231 + }
  232 +
  233 + @Test
  234 + public void testIsMatched_Success_NotEqualsOperator() {
  235 + // given
  236 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  237 + .put("status", "active")
  238 + .build();
  239 + IotDeviceMessage message = createPropertyPostMessage(properties);
  240 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  241 + "status",
  242 + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
  243 + "inactive"
  244 + );
  245 +
  246 + // when
  247 + boolean result = matcher.isMatched(message, trigger);
  248 +
  249 + // then
  250 + assertTrue(result);
  251 + }
  252 +
  253 + @Test
  254 + public void testIsMatched_Success_MultipleProperties() {
  255 + // given
  256 + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
  257 + .put("temperature", 25.5)
  258 + .put("humidity", 60)
  259 + .put("status", "active")
  260 + .build();
  261 + IotDeviceMessage message = createPropertyPostMessage(properties);
  262 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  263 + "humidity",
  264 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  265 + "60"
  266 + );
  267 +
  268 + // when
  269 + boolean result = matcher.isMatched(message, trigger);
  270 +
  271 + // then
  272 + assertTrue(result);
  273 + }
  274 +
  275 + // ========== 辅助方法 ==========
  276 +
  277 + /**
  278 + * 创建属性上报消息
  279 + */
  280 + private IotDeviceMessage createPropertyPostMessage(Map<String, Object> properties) {
  281 + IotDeviceMessage message = new IotDeviceMessage();
  282 + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
  283 + message.setParams(properties);
  284 + return message;
  285 + }
  286 +
  287 + /**
  288 + * 创建有效的触发器
  289 + */
  290 + private IotSceneRuleDO.Trigger createValidTrigger(String identifier, String operator, String value) {
  291 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  292 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
  293 + trigger.setIdentifier(identifier);
  294 + trigger.setOperator(operator);
  295 + trigger.setValue(value);
  296 + return trigger;
  297 + }
  298 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  6 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  9 +import org.junit.jupiter.api.BeforeEach;
  10 +import org.junit.jupiter.api.Test;
  11 +
  12 +import java.util.HashMap;
  13 +import java.util.Map;
  14 +
  15 +import static org.junit.jupiter.api.Assertions.*;
  16 +
  17 +/**
  18 + * {@link DeviceServiceInvokeTriggerMatcher} 的单元测试类
  19 + *
  20 + * @author HUIHUI
  21 + */
  22 +public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest {
  23 +
  24 + private DeviceServiceInvokeTriggerMatcher matcher;
  25 +
  26 + @BeforeEach
  27 + public void setUp() {
  28 + matcher = new DeviceServiceInvokeTriggerMatcher();
  29 + }
  30 +
  31 + @Test
  32 + public void testGetSupportedTriggerType() {
  33 + // when & then
  34 + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE, matcher.getSupportedTriggerType());
  35 + }
  36 +
  37 + @Test
  38 + public void testGetPriority() {
  39 + // when & then
  40 + assertEquals(40, matcher.getPriority());
  41 + }
  42 +
  43 + @Test
  44 + public void testIsEnabled() {
  45 + // when & then
  46 + assertTrue(matcher.isEnabled());
  47 + }
  48 +
  49 + @Test
  50 + public void testIsMatched_Success_RestartService() {
  51 + // given
  52 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  53 + .put("identifier", "restart")
  54 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  55 + .put("mode", "soft")
  56 + .build())
  57 + .build();
  58 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  59 + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
  60 +
  61 + // when
  62 + boolean result = matcher.isMatched(message, trigger);
  63 +
  64 + // then
  65 + assertTrue(result);
  66 + }
  67 +
  68 + @Test
  69 + public void testIsMatched_Success_ConfigService() {
  70 + // given
  71 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  72 + .put("identifier", "config")
  73 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  74 + .put("interval", 30)
  75 + .put("enabled", true)
  76 + .put("threshold", 75.5)
  77 + .build())
  78 + .build();
  79 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  80 + IotSceneRuleDO.Trigger trigger = createValidTrigger("config");
  81 +
  82 + // when
  83 + boolean result = matcher.isMatched(message, trigger);
  84 +
  85 + // then
  86 + assertTrue(result);
  87 + }
  88 +
  89 + @Test
  90 + public void testIsMatched_Success_UpdateService() {
  91 + // given
  92 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  93 + .put("identifier", "update")
  94 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  95 + .put("version", "1.2.3")
  96 + .put("url", "http://example.com/firmware.bin")
  97 + .build())
  98 + .build();
  99 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  100 + IotSceneRuleDO.Trigger trigger = createValidTrigger("update");
  101 +
  102 + // when
  103 + boolean result = matcher.isMatched(message, trigger);
  104 +
  105 + // then
  106 + assertTrue(result);
  107 + }
  108 +
  109 + @Test
  110 + public void testIsMatched_Failure_ServiceIdentifierMismatch() {
  111 + // given
  112 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  113 + .put("identifier", "restart")
  114 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  115 + .put("mode", "soft")
  116 + .build())
  117 + .build();
  118 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  119 + IotSceneRuleDO.Trigger trigger = createValidTrigger("config"); // 不匹配的服务标识符
  120 +
  121 + // when
  122 + boolean result = matcher.isMatched(message, trigger);
  123 +
  124 + // then
  125 + assertFalse(result);
  126 + }
  127 +
  128 + @Test
  129 + public void testIsMatched_Failure_WrongMessageMethod() {
  130 + // given
  131 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  132 + .put("identifier", "restart")
  133 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  134 + .put("mode", "soft")
  135 + .build())
  136 + .build();
  137 + IotDeviceMessage message = new IotDeviceMessage();
  138 + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法
  139 + message.setParams(serviceParams);
  140 +
  141 + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
  142 +
  143 + // when
  144 + boolean result = matcher.isMatched(message, trigger);
  145 +
  146 + // then
  147 + assertFalse(result);
  148 + }
  149 +
  150 + @Test
  151 + public void testIsMatched_Failure_MissingIdentifier() {
  152 + // given
  153 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  154 + .put("identifier", "restart")
  155 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  156 + .put("mode", "soft")
  157 + .build())
  158 + .build();
  159 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  160 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  161 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
  162 + trigger.setIdentifier(null); // 缺少标识符
  163 +
  164 + // when
  165 + boolean result = matcher.isMatched(message, trigger);
  166 +
  167 + // then
  168 + assertFalse(result);
  169 + }
  170 +
  171 + @Test
  172 + public void testIsMatched_Failure_NullMessageParams() {
  173 + // given
  174 + IotDeviceMessage message = new IotDeviceMessage();
  175 + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod());
  176 + message.setParams(null);
  177 +
  178 + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
  179 +
  180 + // when
  181 + boolean result = matcher.isMatched(message, trigger);
  182 +
  183 + // then
  184 + assertFalse(result);
  185 + }
  186 +
  187 + @Test
  188 + public void testIsMatched_Failure_InvalidMessageParams() {
  189 + // given
  190 + IotDeviceMessage message = new IotDeviceMessage();
  191 + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod());
  192 + message.setParams("invalid-params"); // 不是 Map 类型
  193 +
  194 + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
  195 +
  196 + // when
  197 + boolean result = matcher.isMatched(message, trigger);
  198 +
  199 + // then
  200 + assertFalse(result);
  201 + }
  202 +
  203 + @Test
  204 + public void testIsMatched_Failure_MissingServiceIdentifierInParams() {
  205 + // given
  206 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  207 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  208 + .put("mode", "soft")
  209 + .build()) // 缺少 identifier 字段
  210 + .build();
  211 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  212 + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
  213 +
  214 + // when
  215 + boolean result = matcher.isMatched(message, trigger);
  216 +
  217 + // then
  218 + assertFalse(result);
  219 + }
  220 +
  221 + @Test
  222 + public void testIsMatched_Failure_NullTrigger() {
  223 + // given
  224 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  225 + .put("identifier", "restart")
  226 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  227 + .put("mode", "soft")
  228 + .build())
  229 + .build();
  230 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  231 +
  232 + // when
  233 + boolean result = matcher.isMatched(message, null);
  234 +
  235 + // then
  236 + assertFalse(result);
  237 + }
  238 +
  239 + @Test
  240 + public void testIsMatched_Failure_NullTriggerType() {
  241 + // given
  242 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  243 + .put("identifier", "restart")
  244 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  245 + .put("mode", "soft")
  246 + .build())
  247 + .build();
  248 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  249 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  250 + trigger.setType(null);
  251 + trigger.setIdentifier("restart");
  252 +
  253 + // when
  254 + boolean result = matcher.isMatched(message, trigger);
  255 +
  256 + // then
  257 + assertFalse(result);
  258 + }
  259 +
  260 + @Test
  261 + public void testIsMatched_Success_EmptyInputData() {
  262 + // given
  263 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  264 + .put("identifier", "ping")
  265 + .put("inputData", MapUtil.of()) // 空的输入数据
  266 + .build();
  267 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  268 + IotSceneRuleDO.Trigger trigger = createValidTrigger("ping");
  269 +
  270 + // when
  271 + boolean result = matcher.isMatched(message, trigger);
  272 +
  273 + // then
  274 + assertTrue(result);
  275 + }
  276 +
  277 + @Test
  278 + public void testIsMatched_Success_NoInputData() {
  279 + // given
  280 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  281 + .put("identifier", "status")
  282 + // 没有 inputData 字段
  283 + .build();
  284 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  285 + IotSceneRuleDO.Trigger trigger = createValidTrigger("status");
  286 +
  287 + // when
  288 + boolean result = matcher.isMatched(message, trigger);
  289 +
  290 + // then
  291 + assertTrue(result);
  292 + }
  293 +
  294 + @Test
  295 + public void testIsMatched_Success_ComplexInputData() {
  296 + // given
  297 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  298 + .put("identifier", "calibrate")
  299 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  300 + .put("sensors", new String[]{"temperature", "humidity", "pressure"})
  301 + .put("precision", 0.01)
  302 + .put("duration", 300)
  303 + .put("autoSave", true)
  304 + .put("config", MapUtil.builder(new HashMap<String, Object>())
  305 + .put("mode", "auto")
  306 + .put("level", "high")
  307 + .build())
  308 + .build())
  309 + .build();
  310 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  311 + IotSceneRuleDO.Trigger trigger = createValidTrigger("calibrate");
  312 +
  313 + // when
  314 + boolean result = matcher.isMatched(message, trigger);
  315 +
  316 + // then
  317 + assertTrue(result);
  318 + }
  319 +
  320 + @Test
  321 + public void testIsMatched_Success_CaseInsensitiveIdentifier() {
  322 + // given
  323 + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
  324 + .put("identifier", "RESTART") // 大写
  325 + .put("inputData", MapUtil.builder(new HashMap<String, Object>())
  326 + .put("mode", "soft")
  327 + .build())
  328 + .build();
  329 + IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
  330 + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); // 小写
  331 +
  332 + // when
  333 + boolean result = matcher.isMatched(message, trigger);
  334 +
  335 + // then
  336 + // 根据实际实现,这里可能需要调整期望结果
  337 + // 如果实现是大小写敏感的,则应该为 false
  338 + assertFalse(result);
  339 + }
  340 +
  341 + // ========== 辅助方法 ==========
  342 +
  343 + /**
  344 + * 创建服务调用消息
  345 + */
  346 + private IotDeviceMessage createServiceInvokeMessage(Map<String, Object> serviceParams) {
  347 + IotDeviceMessage message = new IotDeviceMessage();
  348 + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod());
  349 + message.setParams(serviceParams);
  350 + return message;
  351 + }
  352 +
  353 + /**
  354 + * 创建有效的触发器
  355 + */
  356 + private IotSceneRuleDO.Trigger createValidTrigger(String identifier) {
  357 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  358 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
  359 + trigger.setIdentifier(identifier);
  360 + return trigger;
  361 + }
  362 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
  6 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
  9 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  10 +import org.junit.jupiter.api.BeforeEach;
  11 +import org.junit.jupiter.api.Test;
  12 +
  13 +import static org.junit.jupiter.api.Assertions.*;
  14 +
  15 +/**
  16 + * {@link DeviceStateUpdateTriggerMatcher} 的单元测试类
  17 + *
  18 + * @author HUIHUI
  19 + */
  20 +public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest {
  21 +
  22 + private DeviceStateUpdateTriggerMatcher matcher;
  23 +
  24 + @BeforeEach
  25 + public void setUp() {
  26 + matcher = new DeviceStateUpdateTriggerMatcher();
  27 + }
  28 +
  29 + @Test
  30 + public void testGetSupportedTriggerType() {
  31 + // when & then
  32 + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE, matcher.getSupportedTriggerType());
  33 + }
  34 +
  35 + @Test
  36 + public void testGetPriority() {
  37 + // when & then
  38 + assertEquals(10, matcher.getPriority());
  39 + }
  40 +
  41 + @Test
  42 + public void testIsEnabled() {
  43 + // when & then
  44 + assertTrue(matcher.isEnabled());
  45 + }
  46 +
  47 + @Test
  48 + public void testIsMatched_Success_OnlineState() {
  49 + // given
  50 + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
  51 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  52 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  53 + IotDeviceStateEnum.ONLINE.getState().toString()
  54 + );
  55 +
  56 + // when
  57 + boolean result = matcher.isMatched(message, trigger);
  58 +
  59 + // then
  60 + assertTrue(result);
  61 + }
  62 +
  63 + @Test
  64 + public void testIsMatched_Success_OfflineState() {
  65 + // given
  66 + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.OFFLINE.getState());
  67 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  68 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  69 + IotDeviceStateEnum.OFFLINE.getState().toString()
  70 + );
  71 +
  72 + // when
  73 + boolean result = matcher.isMatched(message, trigger);
  74 +
  75 + // then
  76 + assertTrue(result);
  77 + }
  78 +
  79 + @Test
  80 + public void testIsMatched_Failure_StateMismatch() {
  81 + // given
  82 + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
  83 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  84 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  85 + IotDeviceStateEnum.OFFLINE.getState().toString()
  86 + );
  87 +
  88 + // when
  89 + boolean result = matcher.isMatched(message, trigger);
  90 +
  91 + // then
  92 + assertFalse(result);
  93 + }
  94 +
  95 + @Test
  96 + public void testIsMatched_Failure_NullTrigger() {
  97 + // given
  98 + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
  99 +
  100 + // when
  101 + boolean result = matcher.isMatched(message, null);
  102 +
  103 + // then
  104 + assertFalse(result);
  105 + }
  106 +
  107 + @Test
  108 + public void testIsMatched_Failure_NullTriggerType() {
  109 + // given
  110 + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
  111 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  112 + trigger.setType(null);
  113 +
  114 + // when
  115 + boolean result = matcher.isMatched(message, trigger);
  116 +
  117 + // then
  118 + assertFalse(result);
  119 + }
  120 +
  121 + @Test
  122 + public void testIsMatched_Failure_WrongMessageMethod() {
  123 + // given
  124 + IotDeviceMessage message = new IotDeviceMessage();
  125 + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
  126 + message.setParams(IotDeviceStateEnum.ONLINE.getState());
  127 +
  128 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  129 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  130 + IotDeviceStateEnum.ONLINE.getState().toString()
  131 + );
  132 +
  133 + // when
  134 + boolean result = matcher.isMatched(message, trigger);
  135 +
  136 + // then
  137 + assertFalse(result);
  138 + }
  139 +
  140 + @Test
  141 + public void testIsMatched_Failure_MissingOperator() {
  142 + // given
  143 + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
  144 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  145 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
  146 + trigger.setOperator(null);
  147 + trigger.setValue(IotDeviceStateEnum.ONLINE.getState().toString());
  148 +
  149 + // when
  150 + boolean result = matcher.isMatched(message, trigger);
  151 +
  152 + // then
  153 + assertFalse(result);
  154 + }
  155 +
  156 + @Test
  157 + public void testIsMatched_Failure_MissingValue() {
  158 + // given
  159 + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
  160 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  161 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
  162 + trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
  163 + trigger.setValue(null);
  164 +
  165 + // when
  166 + boolean result = matcher.isMatched(message, trigger);
  167 +
  168 + // then
  169 + assertFalse(result);
  170 + }
  171 +
  172 + @Test
  173 + public void testIsMatched_Failure_NullMessageParams() {
  174 + // given
  175 + IotDeviceMessage message = new IotDeviceMessage();
  176 + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
  177 + message.setParams(null);
  178 +
  179 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  180 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  181 + IotDeviceStateEnum.ONLINE.getState().toString()
  182 + );
  183 +
  184 + // when
  185 + boolean result = matcher.isMatched(message, trigger);
  186 +
  187 + // then
  188 + assertFalse(result);
  189 + }
  190 +
  191 + @Test
  192 + public void testIsMatched_Success_GreaterThanOperator() {
  193 + // given
  194 + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
  195 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  196 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
  197 + IotDeviceStateEnum.INACTIVE.getState().toString()
  198 + );
  199 +
  200 + // when
  201 + boolean result = matcher.isMatched(message, trigger);
  202 +
  203 + // then
  204 + assertTrue(result);
  205 + }
  206 +
  207 + @Test
  208 + public void testIsMatched_Success_NotEqualsOperator() {
  209 + // given
  210 + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
  211 + IotSceneRuleDO.Trigger trigger = createValidTrigger(
  212 + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
  213 + IotDeviceStateEnum.OFFLINE.getState().toString()
  214 + );
  215 +
  216 + // when
  217 + boolean result = matcher.isMatched(message, trigger);
  218 +
  219 + // then
  220 + assertTrue(result);
  221 + }
  222 +
  223 + // ========== 辅助方法 ==========
  224 +
  225 + /**
  226 + * 创建设备状态更新消息
  227 + */
  228 + private IotDeviceMessage createStateUpdateMessage(Integer state) {
  229 + IotDeviceMessage message = new IotDeviceMessage();
  230 + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
  231 + message.setParams(state);
  232 + return message;
  233 + }
  234 +
  235 + /**
  236 + * 创建有效的触发器
  237 + */
  238 + private IotSceneRuleDO.Trigger createValidTrigger(String operator, String value) {
  239 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  240 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
  241 + trigger.setOperator(operator);
  242 + trigger.setValue(value);
  243 + return trigger;
  244 + }
  245 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  7 +import org.junit.jupiter.api.BeforeEach;
  8 +import org.junit.jupiter.api.Test;
  9 +
  10 +import static org.junit.jupiter.api.Assertions.*;
  11 +
  12 +/**
  13 + * {@link TimerTriggerMatcher} 的单元测试类
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +public class TimerTriggerMatcherTest extends BaseMockitoUnitTest {
  18 +
  19 + private TimerTriggerMatcher matcher;
  20 +
  21 + @BeforeEach
  22 + public void setUp() {
  23 + matcher = new TimerTriggerMatcher();
  24 + }
  25 +
  26 + @Test
  27 + public void testGetSupportedTriggerType() {
  28 + // when & then
  29 + assertEquals(IotSceneRuleTriggerTypeEnum.TIMER, matcher.getSupportedTriggerType());
  30 + }
  31 +
  32 + @Test
  33 + public void testGetPriority() {
  34 + // when & then
  35 + assertEquals(50, matcher.getPriority());
  36 + }
  37 +
  38 + @Test
  39 + public void testIsEnabled() {
  40 + // when & then
  41 + assertTrue(matcher.isEnabled());
  42 + }
  43 +
  44 + @Test
  45 + public void testIsMatched_Success_ValidCronExpression() {
  46 + // given
  47 + IotDeviceMessage message = new IotDeviceMessage();
  48 + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * ?"); // 每天中午12点
  49 +
  50 + // when
  51 + boolean result = matcher.isMatched(message, trigger);
  52 +
  53 + // then
  54 + assertTrue(result);
  55 + }
  56 +
  57 + @Test
  58 + public void testIsMatched_Success_EveryMinuteCron() {
  59 + // given
  60 + IotDeviceMessage message = new IotDeviceMessage();
  61 + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 * * * * ?"); // 每分钟
  62 +
  63 + // when
  64 + boolean result = matcher.isMatched(message, trigger);
  65 +
  66 + // then
  67 + assertTrue(result);
  68 + }
  69 +
  70 + @Test
  71 + public void testIsMatched_Success_WeekdaysCron() {
  72 + // given
  73 + IotDeviceMessage message = new IotDeviceMessage();
  74 + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9 ? * MON-FRI"); // 工作日上午9点
  75 +
  76 + // when
  77 + boolean result = matcher.isMatched(message, trigger);
  78 +
  79 + // then
  80 + assertTrue(result);
  81 + }
  82 +
  83 + @Test
  84 + public void testIsMatched_Failure_InvalidCronExpression() {
  85 + // given
  86 + IotDeviceMessage message = new IotDeviceMessage();
  87 + IotSceneRuleDO.Trigger trigger = createValidTrigger("invalid-cron-expression");
  88 +
  89 + // when
  90 + boolean result = matcher.isMatched(message, trigger);
  91 +
  92 + // then
  93 + assertFalse(result);
  94 + }
  95 +
  96 + @Test
  97 + public void testIsMatched_Failure_EmptyCronExpression() {
  98 + // given
  99 + IotDeviceMessage message = new IotDeviceMessage();
  100 + IotSceneRuleDO.Trigger trigger = createValidTrigger("");
  101 +
  102 + // when
  103 + boolean result = matcher.isMatched(message, trigger);
  104 +
  105 + // then
  106 + assertFalse(result);
  107 + }
  108 +
  109 + @Test
  110 + public void testIsMatched_Failure_NullCronExpression() {
  111 + // given
  112 + IotDeviceMessage message = new IotDeviceMessage();
  113 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  114 + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
  115 + trigger.setCronExpression(null);
  116 +
  117 + // when
  118 + boolean result = matcher.isMatched(message, trigger);
  119 +
  120 + // then
  121 + assertFalse(result);
  122 + }
  123 +
  124 + @Test
  125 + public void testIsMatched_Failure_NullTrigger() {
  126 + // given
  127 + IotDeviceMessage message = new IotDeviceMessage();
  128 +
  129 + // when
  130 + boolean result = matcher.isMatched(message, null);
  131 +
  132 + // then
  133 + assertFalse(result);
  134 + }
  135 +
  136 + @Test
  137 + public void testIsMatched_Failure_NullTriggerType() {
  138 + // given
  139 + IotDeviceMessage message = new IotDeviceMessage();
  140 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  141 + trigger.setType(null);
  142 + trigger.setCronExpression("0 0 12 * * ?");
  143 +
  144 + // when
  145 + boolean result = matcher.isMatched(message, trigger);
  146 +
  147 + // then
  148 + assertFalse(result);
  149 + }
  150 +
  151 + @Test
  152 + public void testIsMatched_Success_ComplexCronExpression() {
  153 + // given
  154 + IotDeviceMessage message = new IotDeviceMessage();
  155 + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 15 10 ? * 6#3"); // 每月第三个星期五上午10:15
  156 +
  157 + // when
  158 + boolean result = matcher.isMatched(message, trigger);
  159 +
  160 + // then
  161 + assertTrue(result);
  162 + }
  163 +
  164 + @Test
  165 + public void testIsMatched_Failure_IncorrectCronFormat() {
  166 + // given
  167 + IotDeviceMessage message = new IotDeviceMessage();
  168 + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * *"); // 缺少字段
  169 +
  170 + // when
  171 + boolean result = matcher.isMatched(message, trigger);
  172 +
  173 + // then
  174 + assertFalse(result);
  175 + }
  176 +
  177 + @Test
  178 + public void testIsMatched_Success_SpecificDateCron() {
  179 + // given
  180 + IotDeviceMessage message = new IotDeviceMessage();
  181 + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 0 1 1 ? 2025"); // 2025年1月1日午夜
  182 +
  183 + // when
  184 + boolean result = matcher.isMatched(message, trigger);
  185 +
  186 + // then
  187 + assertTrue(result);
  188 + }
  189 +
  190 + @Test
  191 + public void testIsMatched_Success_EverySecondCron() {
  192 + // given
  193 + IotDeviceMessage message = new IotDeviceMessage();
  194 + IotSceneRuleDO.Trigger trigger = createValidTrigger("* * * * * ?"); // 每秒
  195 +
  196 + // when
  197 + boolean result = matcher.isMatched(message, trigger);
  198 +
  199 + // then
  200 + assertTrue(result);
  201 + }
  202 +
  203 + @Test
  204 + public void testIsMatched_Failure_InvalidCharactersCron() {
  205 + // given
  206 + IotDeviceMessage message = new IotDeviceMessage();
  207 + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * @ #"); // 包含无效字符
  208 +
  209 + // when
  210 + boolean result = matcher.isMatched(message, trigger);
  211 +
  212 + // then
  213 + assertFalse(result);
  214 + }
  215 +
  216 + @Test
  217 + public void testIsMatched_Success_RangeCron() {
  218 + // given
  219 + IotDeviceMessage message = new IotDeviceMessage();
  220 + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9-17 * * MON-FRI"); // 工作日9-17点
  221 +
  222 + // when
  223 + boolean result = matcher.isMatched(message, trigger);
  224 +
  225 + // then
  226 + assertTrue(result);
  227 + }
  228 +
  229 + // ========== 辅助方法 ==========
  230 +
  231 + /**
  232 + * 创建有效的定时触发器
  233 + */
  234 + private IotSceneRuleDO.Trigger createValidTrigger(String cronExpression) {
  235 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  236 + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
  237 + trigger.setCronExpression(cronExpression);
  238 + return trigger;
  239 + }
  240 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml
... ... @@ -20,6 +20,15 @@ mybatis-plus:
20 20 lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
21 21 type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject
22 22  
  23 +# 日志配置
  24 +logging:
  25 + level:
  26 + cn.iocoder.yudao.module.iot.service.rule.scene.matcher: DEBUG
  27 + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager: INFO
  28 + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition: DEBUG
  29 + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger: DEBUG
  30 + root: WARN
  31 +
23 32 --- #################### 定时任务相关配置 ####################
24 33  
25 34 --- #################### 配置中心相关配置 ####################
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml
1 1 <configuration>
2 2 <!-- 引用 Spring Boot 的 logback 基础配置 -->
3 3 <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
  4 +
  5 + <!-- 控制台输出 -->
  6 + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  7 + <encoder>
  8 + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
  9 + <charset>UTF-8</charset>
  10 + </encoder>
  11 + </appender>
  12 +
  13 + <!-- 设置特定包的日志级别 -->
  14 + <logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher" level="DEBUG" additivity="false">
  15 + <appender-ref ref="CONSOLE"/>
  16 + </logger>
  17 +
  18 + <!-- 设置 IotSceneRuleMatcherManager 的日志级别 -->
  19 + <logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager" level="INFO"
  20 + additivity="false">
  21 + <appender-ref ref="CONSOLE"/>
  22 + </logger>
  23 +
  24 + <!-- 设置所有匹配器的日志级别 -->
  25 + <logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition" level="DEBUG" additivity="false">
  26 + <appender-ref ref="CONSOLE"/>
  27 + </logger>
  28 +
  29 + <logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger" level="DEBUG" additivity="false">
  30 + <appender-ref ref="CONSOLE"/>
  31 + </logger>
  32 +
  33 + <!-- 根日志级别 -->
  34 + <root level="WARN">
  35 + <appender-ref ref="CONSOLE"/>
  36 + </root>
4 37 </configuration>
... ...
yudao-server/src/main/resources/application-local.yaml
... ... @@ -185,6 +185,7 @@ logging:
185 185 cn.iocoder.yudao.module.erp.dal.mysql: debug
186 186 cn.iocoder.yudao.module.iot.dal.mysql: debug
187 187 cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG
  188 + cn.iocoder.yudao.module.iot.service.rule: debug
188 189 cn.iocoder.yudao.module.ai.dal.mysql: debug
189 190 org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
190 191  
... ...