diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java index ee56310fba..ba48afc5c2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java +++ b/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; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.text.CharPool; -import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 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; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; @@ -36,12 +28,9 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NOT_EXISTS; @@ -353,65 +342,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { } } - // TODO @puhui999:下面还需要么? - /** - * 判断触发器的条件参数是否匹配 - * - * @param message 设备消息 - * @param condition 触发条件 - * @param sceneRule 规则场景(用于日志,无其它作用) - * @param trigger 触发器(用于日志,无其它作用) - * @return 是否匹配 - */ - @SuppressWarnings({"unchecked", "DataFlowIssue"}) - private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, - IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { - // 1.1 校验操作符是否合法 - IotSceneRuleConditionOperatorEnum operator = - IotSceneRuleConditionOperatorEnum.operatorOf(condition.getOperator()); - if (operator == null) { - log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]", - sceneRule.getId(), trigger, condition.getOperator()); - return false; - } - // 1.2 校验消息是否包含对应的值 - String messageValue = MapUtil.getStr((Map) message.getData(), condition.getIdentifier()); - if (messageValue == null) { - return false; - } - - // 2.1 构建 Spring 表达式的变量 - Map springExpressionVariables = new HashMap<>(); - try { - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam()); - List parameterValues = StrUtil.splitTrim(condition.getParam(), CharPool.COMMA); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues); - // 特殊:解决数字的比较。因为 Spring 是基于它的 compareTo 方法,对数字的比较存在问题! - if (ObjectUtils.equalsAny(operator, IotSceneRuleConditionOperatorEnum.BETWEEN, - IotSceneRuleConditionOperatorEnum.NOT_BETWEEN, - IotSceneRuleConditionOperatorEnum.GREATER_THAN, - IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS, - IotSceneRuleConditionOperatorEnum.LESS_THAN, - IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS) - && NumberUtil.isNumber(messageValue) - && NumberUtils.isAllNumber(parameterValues)) { - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, - NumberUtil.parseDouble(messageValue)); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, - NumberUtil.parseDouble(condition.getParam())); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, - convertList(parameterValues, NumberUtil::parseDouble)); - } - // 2.2 计算 Spring 表达式 - return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables); - } catch (Exception e) { - log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]", - message, sceneRule.getId(), trigger, operator, springExpressionVariables, e); - return false; - } - } - /** * 执行规则场景的动作 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java deleted file mode 100644 index f97fd5f3c2..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * IoT 场景规则触发器匹配器测试类 - * - * @author HUIHUI - */ -public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { - - // TODO @puhui999:public 都加下哈; - - private IotSceneRuleMatcherManager matcherManager; - - @BeforeEach - void setUp() { - // 创建所有匹配器实例 - List matchers = Arrays.asList( - new DeviceStateUpdateTriggerMatcher(), - new DevicePropertyPostTriggerMatcher(), - new DeviceEventPostTriggerMatcher(), - new DeviceServiceInvokeTriggerMatcher(), - new TimerTriggerMatcher() - ); - - // 初始化匹配器管理器 - matcherManager = new IotSceneRuleMatcherManager(matchers); - } - - @Test - void testDeviceStateUpdateTriggerMatcher() { - // 1. 准备测试数据 - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-001") - .method("thing.state.update") // TODO @puhui999:这里的枚举; - .data(1) // 在线状态 TODO @puhui999:这里的枚举; - .build(); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); - trigger.setOperator("="); // TODO @puhui999:这里的枚举;下面也是类似; - trigger.setValue("1"); - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "设备状态更新触发器应该匹配"); - } - - @Test - void testDevicePropertyPostTriggerMatcher() { - // 1. 准备测试数据 - HashMap params = new HashMap<>(); - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-002") - .method("thing.property.post") - .data(25.5) // 温度值 - .params(params) - .build(); - // 模拟标识符 - params.put("identifier", "temperature"); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); - trigger.setIdentifier("temperature"); - trigger.setOperator(">"); - trigger.setValue("20"); - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "设备属性上报触发器应该匹配"); - } - - @Test - void testDeviceEventPostTriggerMatcher() { - // 1. 准备测试数据 - HashMap params = new HashMap<>(); - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-003") - .method("thing.event.post") - .data("alarm_data") - .params(params) - .build(); - // 模拟标识符 - params.put("identifier", "high_temperature_alarm"); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); - trigger.setIdentifier("high_temperature_alarm"); - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "设备事件上报触发器应该匹配"); - } - - @Test - void testDeviceServiceInvokeTriggerMatcher() { - // 1. 准备测试数据 - HashMap params = new HashMap<>(); - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-004") - .method("thing.service.invoke") - .msg("alarm_data") - .params(params) - .build(); - // 模拟标识符 - params.put("identifier", "restart_device"); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); - trigger.setIdentifier("restart_device"); - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "设备服务调用触发器应该匹配"); - } - - @Test - void testTimerTriggerMatcher() { - // 1. 准备测试数据 - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-005") - .method("timer.trigger") // 定时触发器不依赖具体消息方法 - .build(); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); - trigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点 - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "定时触发器应该匹配"); - } - - @Test - void testInvalidTriggerType() { - // 1. 准备测试数据 - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-006") - .method("unknown.method") - .build(); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(999); // 无效的触发器类型 - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertFalse(matched, "无效的触发器类型应该不匹配"); - } - - @Test - void testGetSupportedTriggerTypes() { - // 1. 执行测试 - var supportedTypes = matcherManager.getSupportedTriggerTypes(); - - // 2. 验证结果 - assertNotNull(supportedTypes); - assertEquals(5, supportedTypes.size()); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE)); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST)); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST)); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE)); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER)); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java new file mode 100644 index 0000000000..88e948ea0f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java @@ -0,0 +1,321 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link CurrentTimeConditionMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest { + + private CurrentTimeConditionMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new CurrentTimeConditionMatcher(); + } + + @Test + public void testGetSupportedConditionType() { + // when & then + assertEquals(IotSceneRuleConditionTypeEnum.CURRENT_TIME, matcher.getSupportedConditionType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(40, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + // ========== 时间戳条件测试 ========== + + @Test + public void testIsMatched_DateTimeGreaterThan_Success() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long pastTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), + String.valueOf(pastTimestamp) + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_DateTimeGreaterThan_Failure() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), + String.valueOf(futureTimestamp) + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_DateTimeLessThan_Success() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN.getOperator(), + String.valueOf(futureTimestamp) + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_DateTimeBetween_Success() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long startTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8")); + long endTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(), + startTimestamp + "," + endTimestamp + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_DateTimeBetween_Failure() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long startTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); + long endTimestamp = LocalDateTime.now().plusHours(2).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(), + startTimestamp + "," + endTimestamp + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + // ========== 当日时间条件测试 ========== + + @Test + public void testIsMatched_TimeGreaterThan_EarlyMorning() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(), + "06:00:00" // 早上6点 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + // 结果取决于当前时间,如果当前时间大于6点则为true + assertNotNull(result); + } + + @Test + public void testIsMatched_TimeLessThan_LateNight() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN.getOperator(), + "23:59:59" // 晚上11点59分59秒 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + // 大部分情况下应该为true,除非在午夜前1秒运行测试 + assertNotNull(result); + } + + @Test + public void testIsMatched_TimeBetween_AllDay() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), + "00:00:00,23:59:59" // 全天 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); // 全天范围应该总是匹配 + } + + @Test + public void testIsMatched_TimeBetween_WorkingHours() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), + "09:00:00,17:00:00" // 工作时间 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + // 结果取决于当前时间是否在工作时间内 + assertNotNull(result); + } + + // ========== 异常情况测试 ========== + + @Test + public void testIsMatched_NullCondition() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_NullConditionType() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(null); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_InvalidOperator() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); + condition.setOperator("invalid_operator"); + condition.setParam("12:00:00"); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_InvalidTimeFormat() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(), + "invalid-time-format" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_InvalidTimestampFormat() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), + "invalid-timestamp" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_InvalidBetweenFormat() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), + "09:00:00" // 缺少结束时间 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建日期时间条件 + */ + private IotSceneRuleDO.TriggerCondition createDateTimeCondition(String operator, String param) { + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); + condition.setOperator(operator); + condition.setParam(param); + return condition; + } + + /** + * 创建当日时间条件 + */ + private IotSceneRuleDO.TriggerCondition createTimeCondition(String operator, String param) { + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); + condition.setOperator(operator); + condition.setParam(param); + return condition; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java new file mode 100644 index 0000000000..209893d1c8 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java @@ -0,0 +1,391 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DevicePropertyConditionMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { + + private DevicePropertyConditionMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DevicePropertyConditionMatcher(); + } + + @Test + public void testGetSupportedConditionType() { + // when & then + assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY, matcher.getSupportedConditionType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(20, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_TemperatureEquals() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "temperature", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "25.5" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_HumidityGreaterThan() { + // given + Map properties = MapUtil.of("humidity", 75); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "humidity", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "70" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_PressureLessThan() { + // given + Map properties = MapUtil.of("pressure", 1010.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "pressure", + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), + "1020" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_StatusNotEquals() { + // given + Map properties = MapUtil.of("status", "active"); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "status", + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), + "inactive" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_PropertyMismatch() { + // given + Map properties = MapUtil.of("temperature", 15.0); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_PropertyNotFound() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "humidity", // 不存在的属性 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "50" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullCondition() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullConditionType() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(null); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingIdentifier() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); + condition.setIdentifier(null); // 缺少标识符 + condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); + condition.setParam("20"); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingOperator() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); + condition.setIdentifier("temperature"); + condition.setOperator(null); // 缺少操作符 + condition.setParam("20"); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingParam() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); + condition.setIdentifier("temperature"); + condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); + condition.setParam(null); // 缺少参数 + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessage() { + // given + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(null, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullDeviceProperties() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(null); + + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_GreaterThanOrEquals() { + // given + Map properties = MapUtil.of("voltage", 12.0); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "voltage", + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), + "12.0" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_LessThanOrEquals() { + // given + Map properties = MapUtil.of("current", 2.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "current", + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), + "3.0" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_StringProperty() { + // given + Map properties = MapUtil.of("mode", "auto"); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "mode", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "auto" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_BooleanProperty() { + // given + Map properties = MapUtil.of("enabled", true); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "enabled", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "true" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_MultipleProperties() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .put("humidity", 60) + .put("status", "active") + .put("enabled", true) + .build(); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "humidity", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "60" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建设备消息 + */ + private IotDeviceMessage createDeviceMessage(Map properties) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(properties); + return message; + } + + /** + * 创建有效的条件 + */ + private IotSceneRuleDO.TriggerCondition createValidCondition(String identifier, String operator, String param) { + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); + condition.setIdentifier(identifier); + condition.setOperator(operator); + condition.setParam(param); + return condition; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java new file mode 100644 index 0000000000..8eaf3c4af5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java @@ -0,0 +1,334 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DeviceStateConditionMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest { + + private DeviceStateConditionMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DeviceStateConditionMatcher(); + } + + @Test + public void testGetSupportedConditionType() { + // when & then + assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_STATE, matcher.getSupportedConditionType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(30, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_OnlineState() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_OfflineState() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_InactiveState() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.INACTIVE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_StateMismatch() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_NotEqualsOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_GreaterThanOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState()); // 2 + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() // 1 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_LessThanOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState()); // 0 + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() // 1 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_NullCondition() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullConditionType() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(null); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); + condition.setOperator(null); + condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString()); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingParam() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); + condition.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator()); + condition.setParam(null); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessage() { + // given + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(null, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullDeviceState() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(null); + + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_GreaterThanOrEqualsOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1 + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() // 1 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_LessThanOrEqualsOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1 + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() // 2 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_InvalidOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); + condition.setOperator("invalid_operator"); + condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString()); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_InvalidParamFormat() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "invalid_state_value" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建设备消息 + */ + private IotDeviceMessage createDeviceMessage(Integer deviceState) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(deviceState); + return message; + } + + /** + * 创建有效的条件 + */ + private IotSceneRuleDO.TriggerCondition createValidCondition(String operator, String param) { + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); + condition.setOperator(operator); + condition.setParam(param); + return condition; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java new file mode 100644 index 0000000000..acba2332c1 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java @@ -0,0 +1,341 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DeviceEventPostTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest { + + private DeviceEventPostTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DeviceEventPostTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(30, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_AlarmEvent() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .put("message", "Temperature too high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_ErrorEvent() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "error") + .put("value", MapUtil.builder(new HashMap()) + .put("code", 500) + .put("description", "System error") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("error"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_InfoEvent() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "info") + .put("value", MapUtil.builder(new HashMap()) + .put("status", "normal") + .put("timestamp", System.currentTimeMillis()) + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("info"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_EventIdentifierMismatch() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("error"); // 不匹配的事件标识符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_WrongMessageMethod() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法 + message.setParams(eventParams); + + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingIdentifier() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); + trigger.setIdentifier(null); // 缺少标识符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); + message.setParams(null); + + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_InvalidMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); + message.setParams("invalid-params"); // 不是 Map 类型 + + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingEventIdentifierInParams() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) // 缺少 identifier 字段 + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTrigger() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTriggerType() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(null); + trigger.setIdentifier("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_ComplexEventValue() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "maintenance") + .put("value", MapUtil.builder(new HashMap()) + .put("type", "scheduled") + .put("duration", 120) + .put("components", new String[]{"motor", "sensor"}) + .put("priority", "medium") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("maintenance"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_EmptyEventValue() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "heartbeat") + .put("value", MapUtil.of()) // 空的事件值 + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("heartbeat"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_CaseInsensitiveIdentifier() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "ALARM") // 大写 + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); // 小写 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + // 根据实际实现,这里可能需要调整期望结果 + // 如果实现是大小写敏感的,则应该为 false + assertFalse(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建事件上报消息 + */ + private IotDeviceMessage createEventPostMessage(Map eventParams) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); + message.setParams(eventParams); + return message; + } + + /** + * 创建有效的触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String identifier) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); + trigger.setIdentifier(identifier); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java new file mode 100644 index 0000000000..0744c9a272 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java @@ -0,0 +1,298 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DevicePropertyPostTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest { + + private DevicePropertyPostTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DevicePropertyPostTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(20, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_TemperatureProperty() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_HumidityProperty() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("humidity", 60) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "humidity", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "60" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_PropertyMismatch() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 15.0) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_PropertyNotFound() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "humidity", // 不存在的属性 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "50" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_WrongMessageMethod() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .build(); + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); + message.setParams(properties); + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingIdentifier() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); + trigger.setIdentifier(null); // 缺少标识符 + trigger.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); + trigger.setValue("20"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); + message.setParams(null); + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_InvalidMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); + message.setParams("invalid-params"); // 不是 Map 类型 + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_LessThanOperator() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 15.0) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_NotEqualsOperator() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("status", "active") + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "status", + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), + "inactive" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_MultipleProperties() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .put("humidity", 60) + .put("status", "active") + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "humidity", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "60" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建属性上报消息 + */ + private IotDeviceMessage createPropertyPostMessage(Map properties) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); + message.setParams(properties); + return message; + } + + /** + * 创建有效的触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String identifier, String operator, String value) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); + trigger.setIdentifier(identifier); + trigger.setOperator(operator); + trigger.setValue(value); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java new file mode 100644 index 0000000000..addb1c5277 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java @@ -0,0 +1,362 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DeviceServiceInvokeTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest { + + private DeviceServiceInvokeTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DeviceServiceInvokeTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(40, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_RestartService() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_ConfigService() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "config") + .put("inputData", MapUtil.builder(new HashMap()) + .put("interval", 30) + .put("enabled", true) + .put("threshold", 75.5) + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("config"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_UpdateService() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "update") + .put("inputData", MapUtil.builder(new HashMap()) + .put("version", "1.2.3") + .put("url", "http://example.com/firmware.bin") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("update"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_ServiceIdentifierMismatch() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("config"); // 不匹配的服务标识符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_WrongMessageMethod() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法 + message.setParams(serviceParams); + + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingIdentifier() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); + trigger.setIdentifier(null); // 缺少标识符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); + message.setParams(null); + + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_InvalidMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); + message.setParams("invalid-params"); // 不是 Map 类型 + + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingServiceIdentifierInParams() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) // 缺少 identifier 字段 + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTrigger() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTriggerType() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(null); + trigger.setIdentifier("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_EmptyInputData() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "ping") + .put("inputData", MapUtil.of()) // 空的输入数据 + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("ping"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_NoInputData() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "status") + // 没有 inputData 字段 + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("status"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_ComplexInputData() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "calibrate") + .put("inputData", MapUtil.builder(new HashMap()) + .put("sensors", new String[]{"temperature", "humidity", "pressure"}) + .put("precision", 0.01) + .put("duration", 300) + .put("autoSave", true) + .put("config", MapUtil.builder(new HashMap()) + .put("mode", "auto") + .put("level", "high") + .build()) + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("calibrate"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_CaseInsensitiveIdentifier() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "RESTART") // 大写 + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); // 小写 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + // 根据实际实现,这里可能需要调整期望结果 + // 如果实现是大小写敏感的,则应该为 false + assertFalse(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建服务调用消息 + */ + private IotDeviceMessage createServiceInvokeMessage(Map serviceParams) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); + message.setParams(serviceParams); + return message; + } + + /** + * 创建有效的触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String identifier) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); + trigger.setIdentifier(identifier); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java new file mode 100644 index 0000000000..2f101b2b08 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java @@ -0,0 +1,245 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DeviceStateUpdateTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest { + + private DeviceStateUpdateTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DeviceStateUpdateTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(10, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_OnlineState() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_OfflineState() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.OFFLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_StateMismatch() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTrigger() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTriggerType() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(null); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_WrongMessageMethod() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); + message.setParams(IotDeviceStateEnum.ONLINE.getState()); + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingOperator() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); + trigger.setOperator(null); + trigger.setValue(IotDeviceStateEnum.ONLINE.getState().toString()); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingValue() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); + trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator()); + trigger.setValue(null); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); + message.setParams(null); + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_GreaterThanOperator() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + IotDeviceStateEnum.INACTIVE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_NotEqualsOperator() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建设备状态更新消息 + */ + private IotDeviceMessage createStateUpdateMessage(Integer state) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); + message.setParams(state); + return message; + } + + /** + * 创建有效的触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String operator, String value) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); + trigger.setOperator(operator); + trigger.setValue(value); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java new file mode 100644 index 0000000000..13fe587e14 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java @@ -0,0 +1,240 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link TimerTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class TimerTriggerMatcherTest extends BaseMockitoUnitTest { + + private TimerTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new TimerTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.TIMER, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(50, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_ValidCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * ?"); // 每天中午12点 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_EveryMinuteCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 * * * * ?"); // 每分钟 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_WeekdaysCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9 ? * MON-FRI"); // 工作日上午9点 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_InvalidCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("invalid-cron-expression"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_EmptyCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger(""); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); + trigger.setCronExpression(null); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTrigger() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTriggerType() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(null); + trigger.setCronExpression("0 0 12 * * ?"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_ComplexCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 15 10 ? * 6#3"); // 每月第三个星期五上午10:15 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_IncorrectCronFormat() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * *"); // 缺少字段 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_SpecificDateCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 0 1 1 ? 2025"); // 2025年1月1日午夜 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_EverySecondCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("* * * * * ?"); // 每秒 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_InvalidCharactersCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * @ #"); // 包含无效字符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_RangeCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9-17 * * MON-FRI"); // 工作日9-17点 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建有效的定时触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String cronExpression) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); + trigger.setCronExpression(cronExpression); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml index 7eecc88a4b..3966a274d4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml @@ -20,6 +20,15 @@ mybatis-plus: lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject +# 日志配置 +logging: + level: + cn.iocoder.yudao.module.iot.service.rule.scene.matcher: DEBUG + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager: INFO + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition: DEBUG + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger: DEBUG + root: WARN + --- #################### 定时任务相关配置 #################### --- #################### 配置中心相关配置 #################### diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml index 1d071e4799..b68931dc1c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml @@ -1,4 +1,37 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index b12c0bbd04..6d97229e9b 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -185,6 +185,7 @@ logging: cn.iocoder.yudao.module.erp.dal.mysql: debug cn.iocoder.yudao.module.iot.dal.mysql: debug cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG + cn.iocoder.yudao.module.iot.service.rule: debug cn.iocoder.yudao.module.ai.dal.mysql: debug org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示