【同步】BOOT 和 CLOUD 的功能

This commit is contained in:
YunaiV
2025-10-02 17:51:49 +08:00
parent ec3a391981
commit f02c004736
90 changed files with 3143 additions and 1365 deletions

View File

@@ -0,0 +1,162 @@
package cn.iocoder.yudao.module.iot.service.rule.data.action;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkTcpConfig;
import cn.iocoder.yudao.module.iot.service.rule.data.action.tcp.IotTcpClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* {@link IotTcpDataRuleAction} 的单元测试
*
* @author HUIHUI
*/
class IotTcpDataRuleActionTest {
private IotTcpDataRuleAction tcpDataRuleAction;
@Mock
private IotTcpClient mockTcpClient;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
tcpDataRuleAction = new IotTcpDataRuleAction();
}
@Test
public void testGetType() {
// 准备参数
Integer expectedType = 2; // 数据接收类型枚举中 TCP 类型的值
// 调用方法
Integer actualType = tcpDataRuleAction.getType();
// 断言结果
assertEquals(expectedType, actualType);
}
// TODO @puhui999_ 后面是小写哈,单测的命名规则。
@Test
public void testInitProducer_Success() throws Exception {
// 准备参数
IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
config.setHost("localhost");
config.setPort(8080);
config.setDataFormat("JSON");
config.setSsl(false);
// 调用方法 & 断言结果
// 此测试需要实际的 TCP 连接,在单元测试中可能不可用
// 目前我们只验证配置是否有效
assertNotNull(config.getHost());
assertTrue(config.getPort() > 0 && config.getPort() <= 65535);
}
@Test
public void testInitProducer_InvalidHost() {
// 准备参数
IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
config.setHost("");
config.setPort(8080);
// 调用方法 & 断言结果
IotTcpDataRuleAction action = new IotTcpDataRuleAction();
// 测试验证逻辑(通常在 initProducer 方法中)
assertThrows(IllegalArgumentException.class, () -> {
if (config.getHost() == null || config.getHost().trim().isEmpty()) {
throw new IllegalArgumentException("TCP 服务器地址不能为空");
}
});
}
@Test
public void testInitProducer_InvalidPort() {
// 准备参数
IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
config.setHost("localhost");
config.setPort(-1);
// 调用方法 & 断言结果
assertThrows(IllegalArgumentException.class, () -> {
if (config.getPort() == null || config.getPort() <= 0 || config.getPort() > 65535) {
throw new IllegalArgumentException("TCP 服务器端口无效");
}
});
}
@Test
public void testCloseProducer() throws Exception {
// 准备参数
IotTcpClient client = mock(IotTcpClient.class);
// 调用方法
tcpDataRuleAction.closeProducer(client);
// 断言结果
verify(client, times(1)).close();
}
@Test
public void testExecute_WithValidConfig() {
// 准备参数
IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.report",
"{\"temperature\": 25.5, \"humidity\": 60}");
IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
config.setHost("localhost");
config.setPort(8080);
config.setDataFormat("JSON");
// 调用方法 & 断言结果
// 通常这需要实际的 TCP 连接
// 在单元测试中,我们只验证输入参数
assertNotNull(message);
assertNotNull(config);
assertNotNull(config.getHost());
assertTrue(config.getPort() > 0);
}
@Test
public void testConfig_DefaultValues() {
// 准备参数
IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
// 调用方法 & 断言结果
// 验证默认值
assertEquals("JSON", config.getDataFormat());
assertEquals(5000, config.getConnectTimeoutMs());
assertEquals(10000, config.getReadTimeoutMs());
assertEquals(false, config.getSsl());
assertEquals(30000L, config.getHeartbeatIntervalMs());
assertEquals(5000L, config.getReconnectIntervalMs());
assertEquals(3, config.getMaxReconnectAttempts());
}
@Test
public void testMessageSerialization() {
// 准备参数
IotDeviceMessage message = IotDeviceMessage.builder()
.deviceId(123L)
.method("thing.property.report")
.params("{\"temperature\": 25.5}")
.build();
// 调用方法
String json = JsonUtils.toJsonString(message);
// 断言结果
assertNotNull(json);
assertTrue(json.contains("\"deviceId\":123"));
assertTrue(json.contains("\"method\":\"thing.property.report\""));
assertTrue(json.contains("\"temperature\":25.5"));
}
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* Matcher 测试基类
* 提供通用的 Spring 测试配置
*
* @author HUIHUI
*/
@SpringJUnitConfig
public abstract class IotBaseConditionMatcherTest {
/**
* 注入一下 SpringUtil解析 EL 表达式时需要
* {@link SpringExpressionUtils#parseExpression}
*/
@Configuration
static class TestConfig {
@Bean
public SpringUtil springUtil() {
return new SpringUtil();
}
}
}

View File

@@ -1,12 +1,12 @@
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
@@ -16,14 +16,18 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link CurrentTimeConditionMatcher} 的单元测试
* {@link IotCurrentTimeConditionMatcher} 的单元测试
*
* @author HUIHUI
*/
public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest {
public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherTest {
@InjectMocks
private CurrentTimeConditionMatcher matcher;
private IotCurrentTimeConditionMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotCurrentTimeConditionMatcher();
}
@Test
public void testGetSupportedConditionType() {

View File

@@ -1,30 +1,32 @@
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import java.util.HashMap;
import java.util.Map;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link DevicePropertyConditionMatcher} 的单元测试
* {@link IotDevicePropertyConditionMatcher} 的单元测试
*
* @author HUIHUI
*/
public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
public class IotDevicePropertyConditionMatcherTest extends IotBaseConditionMatcherTest {
@InjectMocks
private DevicePropertyConditionMatcher matcher;
private IotDevicePropertyConditionMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDevicePropertyConditionMatcher();
}
@Test
public void testGetSupportedConditionType() {
@@ -41,27 +43,17 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
int result = matcher.getPriority();
// 断言
assertEquals(20, result);
}
@Test
public void testIsEnabled() {
// 调用
boolean result = matcher.isEnabled();
// 断言
assertTrue(result);
assertEquals(25, result); // 修正实际返回值是 25
}
@Test
public void testMatches_temperatureEquals_success() {
// 准备参数
String propertyName = "temperature";
// 准备参数创建属性上报消息
String propertyIdentifier = "temperature";
Double propertyValue = 25.5;
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyName,
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
String.valueOf(propertyValue)
);
@@ -75,14 +67,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_humidityGreaterThan_success() {
// 准备参数
String propertyName = "humidity";
// 准备参数创建属性上报消息
String propertyIdentifier = "humidity";
Integer propertyValue = 75;
Integer compareValue = 70;
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyName,
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(compareValue)
);
@@ -96,14 +87,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_pressureLessThan_success() {
// 准备参数
String propertyName = "pressure";
// 准备参数创建属性上报消息
String propertyIdentifier = "pressure";
Double propertyValue = 1010.5;
Integer compareValue = 1020;
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyName,
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
String.valueOf(compareValue)
);
@@ -117,14 +107,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_statusNotEquals_success() {
// 准备参数
String propertyName = "status";
// 准备参数创建属性上报消息
String propertyIdentifier = "status";
String propertyValue = "active";
String compareValue = "inactive";
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyName,
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
compareValue
);
@@ -138,14 +127,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_propertyMismatch_fail() {
// 准备参数
String propertyName = "temperature";
// 准备参数创建属性上报消息值不满足条件
String propertyIdentifier = "temperature";
Double propertyValue = 15.0;
Integer compareValue = 20;
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyName,
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(compareValue)
);
@@ -158,14 +146,16 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
}
@Test
public void testMatches_propertyNotFound_fail() {
// 准备参数
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
IotDeviceMessage message = createDeviceMessage(properties);
public void testMatches_identifierMismatch_fail() {
// 准备参数标识符不匹配
String messageIdentifier = "temperature";
String conditionIdentifier = "humidity";
Double propertyValue = 25.5;
IotDeviceMessage message = createPropertyPostMessage(messageIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
randomString(), // 随机不存在的属性名
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
"50"
conditionIdentifier,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
String.valueOf(propertyValue)
);
// 调用
@@ -178,8 +168,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_nullCondition_fail() {
// 准备参数
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
// 调用
boolean result = matcher.matches(message, null);
@@ -191,8 +180,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_nullConditionType_fail() {
// 准备参数
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(null);
@@ -206,8 +194,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_missingIdentifier_fail() {
// 准备参数
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
condition.setIdentifier(null); // 缺少标识符
@@ -224,8 +211,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_missingOperator_fail() {
// 准备参数
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
condition.setIdentifier("temperature");
@@ -242,8 +228,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_missingParam_fail() {
// 准备参数
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
condition.setIdentifier("temperature");
@@ -275,7 +260,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_nullDeviceProperties_fail() {
// 准备参数
// 准备参数消息的 params null
IotDeviceMessage message = new IotDeviceMessage();
message.setParams(null);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
@@ -292,14 +277,79 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
}
@Test
public void testMatches_voltageGreaterThanOrEquals_success() {
// 准备参数
String propertyName = "voltage";
Double propertyValue = 12.0;
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
IotDeviceMessage message = createDeviceMessage(properties);
public void testMatches_propertiesStructure_success() {
// 测试使用 properties 结构的消息真实的属性上报场景
String identifier = "temperature";
Double propertyValue = 25.5;
IotDeviceMessage message = createPropertyPostMessageWithProperties(identifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyName,
identifier,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
"20"
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言修复后的实现应该能正确从 properties 中提取属性值
assertTrue(result);
}
@Test
public void testMatches_simpleValueMessage_success() {
// 测试简单值消息params 直接是属性值
Double propertyValue = 25.5;
IotDeviceMessage message = createSimpleValueMessage(propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
"any", // 对于简单值消息标识符匹配会被跳过
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
"20"
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言修复后的实现应该能处理简单值消息
// 但由于标识符匹配失败结果为 false
assertFalse(result);
}
@Test
public void testMatches_valueFieldStructure_success() {
// 测试使用 value 字段的消息结构
String identifier = "temperature";
Double propertyValue = 25.5;
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod("thing.event.post");
Map<String, Object> params = new HashMap<>();
params.put("identifier", identifier);
params.put("value", propertyValue);
message.setParams(params);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
identifier,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
"20"
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言修复后的实现应该能从 value 字段提取属性值
assertTrue(result);
}
@Test
public void testMatches_voltageGreaterThanOrEquals_success() {
// 准备参数创建属性上报消息
String propertyIdentifier = "voltage";
Double propertyValue = 12.0;
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(),
String.valueOf(propertyValue)
);
@@ -313,14 +363,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_currentLessThanOrEquals_success() {
// 准备参数
String propertyName = "current";
// 准备参数创建属性上报消息
String propertyIdentifier = "current";
Double propertyValue = 2.5;
Double compareValue = 3.0;
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyName,
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(),
String.valueOf(compareValue)
);
@@ -334,13 +383,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_stringProperty_success() {
// 准备参数
String propertyName = "mode";
// 准备参数创建属性上报消息
String propertyIdentifier = "mode";
String propertyValue = "auto";
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyName,
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
propertyValue
);
@@ -354,13 +402,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
@Test
public void testMatches_booleanProperty_success() {
// 准备参数
String propertyName = "enabled";
// 准备参数创建属性上报消息
String propertyIdentifier = "enabled";
Boolean propertyValue = true;
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
IotDeviceMessage message = createDeviceMessage(properties);
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyName,
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
String.valueOf(propertyValue)
);
@@ -372,40 +419,61 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
assertTrue(result);
}
@Test
public void testMatches_multipleProperties_success() {
// 准备参数
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put("temperature", 25.5)
.put("humidity", 60)
.put("status", "active")
.put("enabled", true)
.build();
IotDeviceMessage message = createDeviceMessage(properties);
String targetProperty = "humidity";
Integer targetValue = 60;
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
targetProperty,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
String.valueOf(targetValue)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
// ========== 辅助方法 ==========
/**
* 创建设备消息
* 创建设备消息用于测试
*
* 支持的消息格式
* 1. 直接属性值params 直接是属性值适用于简单消息
* 2. 标识符+params 包含 identifier 和对应的属性值
* 3. properties 结构params.properties[identifier] = value
* 4. data 结构params.data[identifier] = value
* 5. value 字段params.value = value
*/
private IotDeviceMessage createDeviceMessage(Map<String, Object> properties) {
private IotDeviceMessage createPropertyPostMessage(String identifier, Object value) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setParams(properties);
message.setMethod("thing.event.post"); // 使用事件上报方法
// 创建符合修复后逻辑的 params 结构
Map<String, Object> params = new HashMap<>();
params.put("identifier", identifier);
// 直接将属性值放在标识符对应的字段中
params.put(identifier, value);
message.setParams(params);
return message;
}
/**
* 创建使用 properties 结构的消息模拟真实的属性上报消息
*/
private IotDeviceMessage createPropertyPostMessageWithProperties(String identifier, Object value) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod("thing.property.post"); // 属性上报方法
Map<String, Object> properties = new HashMap<>();
properties.put(identifier, value);
Map<String, Object> params = new HashMap<>();
params.put("properties", properties);
message.setParams(params);
return message;
}
/**
* 创建简单值消息params 直接是属性值
*/
private IotDeviceMessage createSimpleValueMessage(Object value) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod("thing.property.post");
// 直接将属性值作为 params
message.setParams(value);
return message;
}

View File

@@ -1,27 +1,31 @@
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link DeviceStateConditionMatcher} 的单元测试
* {@link IotDeviceStateConditionMatcher} 的单元测试
*
* @author HUIHUI
*/
public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest {
public class IotDeviceStateConditionMatcherTest extends IotBaseConditionMatcherTest {
@InjectMocks
private DeviceStateConditionMatcher matcher;
private IotDeviceStateConditionMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDeviceStateConditionMatcher();
}
@Test
public void testGetSupportedConditionType() {

View File

@@ -1,13 +1,13 @@
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import java.util.HashMap;
import java.util.Map;
@@ -18,14 +18,18 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link DeviceEventPostTriggerMatcher} 的单元测试
* {@link IotDeviceEventPostTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest {
public class IotDeviceEventPostTriggerMatcherTest extends IotBaseConditionMatcherTest {
@InjectMocks
private DeviceEventPostTriggerMatcher matcher;
private IotDeviceEventPostTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDeviceEventPostTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {

View File

@@ -1,14 +1,14 @@
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import java.util.HashMap;
import java.util.Map;
@@ -20,14 +20,18 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link DevicePropertyPostTriggerMatcher} 的单元测试
* {@link IotDevicePropertyPostTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest {
public class IotDevicePropertyPostTriggerMatcherTest extends IotBaseConditionMatcherTest {
@InjectMocks
private DevicePropertyPostTriggerMatcher matcher;
private IotDevicePropertyPostTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDevicePropertyPostTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {

View File

@@ -1,13 +1,13 @@
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import java.util.HashMap;
import java.util.Map;
@@ -18,14 +18,18 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link DeviceServiceInvokeTriggerMatcher} 的单元测试
* {@link IotDeviceServiceInvokeTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest {
public class IotDeviceServiceInvokeTriggerMatcherTest extends IotBaseConditionMatcherTest {
@InjectMocks
private DeviceServiceInvokeTriggerMatcher matcher;
private IotDeviceServiceInvokeTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDeviceServiceInvokeTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {

View File

@@ -1,27 +1,31 @@
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link DeviceStateUpdateTriggerMatcher} 的单元测试
* {@link IotDeviceStateUpdateTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest {
public class IotDeviceStateUpdateTriggerMatcherTest extends IotBaseConditionMatcherTest {
@InjectMocks
private DeviceStateUpdateTriggerMatcher matcher;
private IotDeviceStateUpdateTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDeviceStateUpdateTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {

View File

@@ -1,25 +1,29 @@
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link TimerTriggerMatcher} 的单元测试
* {@link IotTimerTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class TimerTriggerMatcherTest extends BaseMockitoUnitTest {
public class IotTimerTriggerMatcherTest extends IotBaseConditionMatcherTest {
@InjectMocks
private TimerTriggerMatcher matcher;
private IotTimerTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotTimerTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {

View File

@@ -0,0 +1,126 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.timer;
import cn.hutool.core.collection.ListUtil;
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.framework.job.core.IotSchedulerManager;
import cn.iocoder.yudao.module.iot.job.rule.IotSceneRuleJob;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.quartz.SchedulerException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* {@link IotSceneRuleTimerHandler} 的单元测试类
*
* @author HUIHUI
*/
@ExtendWith(MockitoExtension.class)
public class IotSceneRuleTimerHandlerTest {
@Mock
private IotSchedulerManager schedulerManager;
@InjectMocks
private IotSceneRuleTimerHandler timerHandler;
@BeforeEach
void setUp() {
// 重置 Mock 对象
reset(schedulerManager);
}
@Test
public void testRegisterTimerTriggers_success() throws SchedulerException {
// 准备参数
Long sceneRuleId = 1L;
IotSceneRuleDO sceneRule = new IotSceneRuleDO();
sceneRule.setId(sceneRuleId);
sceneRule.setStatus(0); // 0 表示启用
// 创建定时触发器
IotSceneRuleDO.Trigger timerTrigger = new IotSceneRuleDO.Trigger();
timerTrigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
timerTrigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点
sceneRule.setTriggers(ListUtil.toList(timerTrigger));
// 调用
timerHandler.registerTimerTriggers(sceneRule);
// 验证
verify(schedulerManager, times(1)).addOrUpdateJob(
eq(IotSceneRuleJob.class),
eq("iot_scene_rule_timer_" + sceneRuleId),
eq("0 0 12 * * ?"),
eq(IotSceneRuleJob.buildJobDataMap(sceneRuleId))
);
}
@Test
public void testRegisterTimerTriggers_noTimerTrigger() throws SchedulerException {
// 准备参数 - 没有定时触发器
IotSceneRuleDO sceneRule = new IotSceneRuleDO();
sceneRule.setStatus(0); // 0 表示启用
// 创建非定时触发器
IotSceneRuleDO.Trigger deviceTrigger = new IotSceneRuleDO.Trigger();
deviceTrigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
sceneRule.setTriggers(ListUtil.toList(deviceTrigger));
// 调用
timerHandler.registerTimerTriggers(sceneRule);
// 验证 - 不应该调用调度器
verify(schedulerManager, never()).addOrUpdateJob(any(), any(), any(), any());
}
@Test
public void testRegisterTimerTriggers_emptyCronExpression() throws SchedulerException {
// 准备参数 - CRON 表达式为空
Long sceneRuleId = 2L;
IotSceneRuleDO sceneRule = new IotSceneRuleDO();
sceneRule.setId(sceneRuleId);
sceneRule.setStatus(0); // 0 表示启用
// 创建定时触发器但没有 CRON 表达式
IotSceneRuleDO.Trigger timerTrigger = new IotSceneRuleDO.Trigger();
timerTrigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
timerTrigger.setCronExpression(""); // 空的 CRON 表达式
sceneRule.setTriggers(ListUtil.toList(timerTrigger));
// 调用
timerHandler.registerTimerTriggers(sceneRule);
// 验证 - 不应该调用调度器
verify(schedulerManager, never()).addOrUpdateJob(any(), any(), any(), any());
}
@Test
public void testUnregisterTimerTriggers_success() throws SchedulerException {
// 准备参数
Long sceneRuleId = 3L;
// 调用
timerHandler.unregisterTimerTriggers(sceneRuleId);
// 验证
verify(schedulerManager, times(1)).deleteJob("iot_scene_rule_timer_" + sceneRuleId);
}
@Test
public void testPauseTimerTriggers_success() throws SchedulerException {
// 准备参数
Long sceneRuleId = 4L;
// 调用
timerHandler.pauseTimerTriggers(sceneRuleId);
// 验证
verify(schedulerManager, times(1)).pauseJob("iot_scene_rule_timer_" + sceneRuleId);
}
}