第一次提交
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
yudao-ui-admin CI / build (14.x) (push) Has been cancelled
yudao-ui-admin CI / build (16.x) (push) Has been cancelled

This commit is contained in:
2025-11-12 14:58:39 +08:00
commit 0cc7d05f55
6053 changed files with 615352 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
package cn.iocoder.yudao.module.iot.service.rule.action.databridge;
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.IotDataSinkDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.*;
import cn.iocoder.yudao.module.iot.service.rule.data.action.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* {@link IotDataRuleAction} 实现类的单元测试
*
* @author HUIHUI
*/
@Disabled // 默认禁用,需要手动启用测试
@Slf4j
public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest {
private IotDeviceMessage message;
@Mock
private RestTemplate restTemplate;
@InjectMocks
private IotHttpDataSinkAction httpDataBridgeExecute;
@BeforeEach
public void setUp() {
// TODO @芋艿:@puhui999需要调整下
// 创建共享的测试消息
//message = IotDeviceMessage.builder().messageId("TEST-001").reportTime(LocalDateTime.now())
// .productKey("testProduct").deviceName("testDevice")
// .type("property").identifier("temperature").data("{\"value\": 60}")
// .build();
}
@Test
public void testKafkaMQDataBridge() throws Exception {
// 1. 创建执行器实例
IotKafkaDataRuleAction action = new IotKafkaDataRuleAction();
// 2. 创建配置
IotDataSinkKafkaConfig config = new IotDataSinkKafkaConfig()
.setBootstrapServers("127.0.0.1:9092")
.setTopic("test-topic")
.setSsl(false)
.setUsername(null)
.setPassword(null);
// 3. 执行测试并验证缓存
executeAndVerifyCache(action, config, "KafkaMQ");
}
@Test
public void testRabbitMQDataBridge() throws Exception {
// 1. 创建执行器实例
IotRabbitMQDataRuleAction action = new IotRabbitMQDataRuleAction();
// 2. 创建配置
IotDataSinkRabbitMQConfig config = new IotDataSinkRabbitMQConfig()
.setHost("localhost")
.setPort(5672)
.setVirtualHost("/")
.setUsername("admin")
.setPassword("123456")
.setExchange("test-exchange")
.setRoutingKey("test-key")
.setQueue("test-queue");
// 3. 执行测试并验证缓存
executeAndVerifyCache(action, config, "RabbitMQ");
}
@Test
public void testRedisDataBridge() throws Exception {
// 1. 创建执行器实例
IotRedisRuleAction action = new IotRedisRuleAction();
// 2. 创建配置 - 测试 Stream 数据结构
IotDataSinkRedisConfig config = new IotDataSinkRedisConfig();
config.setHost("127.0.0.1");
config.setPort(6379);
config.setDatabase(0);
config.setPassword("123456");
config.setTopic("test-stream");
config.setDataStructure(1); // Stream 类型
// 3. 执行测试并验证缓存
executeAndVerifyCache(action, config, "Redis");
}
@Test
public void testRocketMQDataBridge() throws Exception {
// 1. 创建执行器实例
IotRocketMQDataRuleAction action = new IotRocketMQDataRuleAction();
// 2. 创建配置
IotDataSinkRocketMQConfig config = new IotDataSinkRocketMQConfig()
.setNameServer("127.0.0.1:9876")
.setGroup("test-group")
.setTopic("test-topic")
.setTags("test-tag");
// 3. 执行测试并验证缓存
executeAndVerifyCache(action, config, "RocketMQ");
}
@Test
public void testHttpDataBridge() throws Exception {
// 1. 配置 RestTemplate mock 返回成功响应
when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(), any(Class.class)))
.thenReturn(new ResponseEntity<>("Success", HttpStatus.OK));
// 2. 创建配置
IotDataSinkHttpConfig config = new IotDataSinkHttpConfig()
.setUrl("https://doc.iocoder.cn/").setMethod(HttpMethod.GET.name());
// 3. 执行测试
log.info("[testHttpDataBridge][执行HTTP数据桥接测试]");
httpDataBridgeExecute.execute(message, new IotDataSinkDO()
.setType(httpDataBridgeExecute.getType()).setConfig(config));
}
/**
* 执行测试并验证缓存的通用方法
*
* @param action 执行器实例
* @param config 配置对象
* @param type MQ 类型
* @throws Exception 如果执行过程中发生异常
*/
private void executeAndVerifyCache(IotDataRuleAction action, IotAbstractDataSinkConfig config, String type)
throws Exception {
log.info("[test{}DataBridge][第一次执行,应该会创建新的 producer]", type);
action.execute(message, new IotDataSinkDO().setType(action.getType()).setConfig(config));
log.info("[test{}DataBridge][第二次执行,应该会复用缓存的 producer]", type);
action.execute(message, new IotDataSinkDO().setType(action.getType()).setConfig(config));
}
}

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,211 @@
package cn.iocoder.yudao.module.iot.service.rule.scene;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
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.framework.job.core.IotSchedulerManager;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
/**
* {@link IotSceneRuleServiceImpl} 的简化单元测试类
* 使用 Mockito 进行纯单元测试,不依赖 Spring 容器
*
* @author 芋道源码
*/
public class IotSceneRuleServiceSimpleTest extends BaseMockitoUnitTest {
@InjectMocks
private IotSceneRuleServiceImpl sceneRuleService;
@Mock
private IotSceneRuleMapper sceneRuleMapper;
@Mock
private List<IotSceneRuleAction> sceneRuleActions;
@Mock
private IotSchedulerManager schedulerManager;
@Mock
private IotProductService productService;
@Mock
private IotDeviceService deviceService;
@Test
public void testCreateScene_Rule_success() {
// 准备参数
IotSceneRuleSaveReqVO createReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> {
o.setId(null);
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class)));
o.setActions(Collections.singletonList(randomPojo(IotSceneRuleDO.Action.class)));
});
// Mock 行为
Long expectedId = randomLongId();
when(sceneRuleMapper.insert(any(IotSceneRuleDO.class))).thenAnswer(invocation -> {
IotSceneRuleDO sceneRule = invocation.getArgument(0);
sceneRule.setId(expectedId);
return 1;
});
// 调用
Long sceneRuleId = sceneRuleService.createSceneRule(createReqVO);
// 断言
assertEquals(expectedId, sceneRuleId);
verify(sceneRuleMapper, times(1)).insert(any(IotSceneRuleDO.class));
}
@Test
public void testUpdateScene_Rule_success() {
// 准备参数
Long id = randomLongId();
IotSceneRuleSaveReqVO updateReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> {
o.setId(id);
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class)));
o.setActions(Collections.singletonList(randomPojo(IotSceneRuleDO.Action.class)));
});
// Mock 行为
IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule);
when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
// 调用
assertDoesNotThrow(() -> sceneRuleService.updateSceneRule(updateReqVO));
// 验证
verify(sceneRuleMapper, times(1)).selectById(id);
verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class));
}
@Test
public void testDeleteSceneRule_success() {
// 准备参数
Long id = randomLongId();
// Mock 行为
IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule);
when(sceneRuleMapper.deleteById(id)).thenReturn(1);
// 调用
assertDoesNotThrow(() -> sceneRuleService.deleteSceneRule(id));
// 验证
verify(sceneRuleMapper, times(1)).selectById(id);
verify(sceneRuleMapper, times(1)).deleteById(id);
}
@Test
public void testGetSceneRule() {
// 准备参数
Long id = randomLongId();
IotSceneRuleDO expectedSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
// Mock 行为
when(sceneRuleMapper.selectById(id)).thenReturn(expectedSceneRule);
// 调用
IotSceneRuleDO result = sceneRuleService.getSceneRule(id);
// 断言
assertEquals(expectedSceneRule, result);
verify(sceneRuleMapper, times(1)).selectById(id);
}
@Test
public void testUpdateSceneRuleStatus_success() {
// 准备参数
Long id = randomLongId();
Integer status = CommonStatusEnum.DISABLE.getStatus();
// Mock 行为
IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> {
o.setId(id);
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule);
when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
// 调用
assertDoesNotThrow(() -> sceneRuleService.updateSceneRuleStatus(id, status));
// 验证
verify(sceneRuleMapper, times(1)).selectById(id);
verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class));
}
@Test
public void testExecuteSceneRuleByTimer_success() {
// 准备参数
Long id = randomLongId();
// Mock 行为
IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> {
o.setId(id);
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule);
// 调用
assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id));
// 验证
verify(sceneRuleMapper, times(1)).selectById(id);
}
@Test
public void testExecuteSceneRuleByTimer_notExists() {
// 准备参数
Long id = randomLongId();
// Mock 行为
when(sceneRuleMapper.selectById(id)).thenReturn(null);
// 调用 - 不存在的场景规则应该不会抛异常,只是记录日志
assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id));
// 验证
verify(sceneRuleMapper, times(1)).selectById(id);
}
@Test
public void testExecuteSceneRuleByTimer_disabled() {
// 准备参数
Long id = randomLongId();
// Mock 行为
IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> {
o.setId(id);
o.setStatus(CommonStatusEnum.DISABLE.getStatus());
});
when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule);
// 调用 - 禁用的场景规则应该不会执行,只是记录日志
assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id));
// 验证
verify(sceneRuleMapper, times(1)).selectById(id);
}
}

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

@@ -0,0 +1,342 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
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 java.time.LocalDateTime;
import java.time.ZoneOffset;
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 IotCurrentTimeConditionMatcher} 的单元测试
*
* @author HUIHUI
*/
public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherTest {
private IotCurrentTimeConditionMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotCurrentTimeConditionMatcher();
}
@Test
public void testGetSupportedConditionType() {
// 调用
IotSceneRuleConditionTypeEnum result = matcher.getSupportedConditionType();
// 断言
assertEquals(IotSceneRuleConditionTypeEnum.CURRENT_TIME, result);
}
@Test
public void testGetPriority() {
// 调用
int result = matcher.getPriority();
// 断言
assertEquals(40, result);
}
@Test
public void testIsEnabled() {
// 调用
boolean result = matcher.isEnabled();
// 断言
assertTrue(result);
}
// ========== 时间戳条件测试 ==========
@Test
public void testMatches_DateTimeGreaterThan_success() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
long pastTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8"));
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(),
String.valueOf(pastTimestamp)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_DateTimeGreaterThan_fail() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(),
String.valueOf(futureTimestamp)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_DateTimeLessThan_success() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN.getOperator(),
String.valueOf(futureTimestamp)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_DateTimeBetween_success() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
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
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_DateTimeBetween_fail() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
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
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
// ========== 当日时间条件测试 ==========
@Test
public void testMatches_TimeGreaterThan_earlyMorning() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(),
"06:00:00" // 早上6点
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
// 结果取决于当前时间如果当前时间大于6点则为true
assertNotNull(result);
}
@Test
public void testMatches_TimeLessThan_lateNight() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN.getOperator(),
"23:59:59" // 晚上11点59分59秒
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
// 大部分情况下应该为true除非在午夜前1秒运行测试
assertNotNull(result);
}
@Test
public void testMatches_TimeBetween_allDay() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(),
"00:00:00,23:59:59" // 全天
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result); // 全天范围应该总是匹配
}
@Test
public void testMatches_TimeBetween_workingHours() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(),
"09:00:00,17:00:00" // 工作时间
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
// 结果取决于当前时间是否在工作时间内
assertNotNull(result);
}
// ========== 异常情况测试 ==========
@Test
public void testMatches_nullCondition() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
// 调用
boolean result = matcher.matches(message, null);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullConditionType() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(null);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_invalidOperator() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType());
condition.setOperator(randomString()); // 随机无效操作符
condition.setParam("12:00:00");
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_invalidTimeFormat() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(),
randomString() // 随机无效时间格式
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_invalidTimestampFormat() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(),
randomString() // 随机无效时间戳格式
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_invalidBetweenFormat() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(),
"09:00:00" // 缺少结束时间
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
// ========== 辅助方法 ==========
/**
* 创建设备消息
*/
private IotDeviceMessage createDeviceMessage() {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
return message;
}
/**
* 创建日期时间条件
*/
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;
}
}

View File

@@ -0,0 +1,492 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
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 java.util.HashMap;
import java.util.Map;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link IotDevicePropertyConditionMatcher} 的单元测试
*
* @author HUIHUI
*/
public class IotDevicePropertyConditionMatcherTest extends IotBaseConditionMatcherTest {
private IotDevicePropertyConditionMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDevicePropertyConditionMatcher();
}
@Test
public void testGetSupportedConditionType() {
// 调用
IotSceneRuleConditionTypeEnum result = matcher.getSupportedConditionType();
// 断言
assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY, result);
}
@Test
public void testGetPriority() {
// 调用
int result = matcher.getPriority();
// 断言
assertEquals(25, result); // 修正:实际返回值是 25
}
@Test
public void testMatches_temperatureEquals_success() {
// 准备参数:创建属性上报消息
String propertyIdentifier = "temperature";
Double propertyValue = 25.5;
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
String.valueOf(propertyValue)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_humidityGreaterThan_success() {
// 准备参数:创建属性上报消息
String propertyIdentifier = "humidity";
Integer propertyValue = 75;
Integer compareValue = 70;
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(compareValue)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_pressureLessThan_success() {
// 准备参数:创建属性上报消息
String propertyIdentifier = "pressure";
Double propertyValue = 1010.5;
Integer compareValue = 1020;
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
String.valueOf(compareValue)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_statusNotEquals_success() {
// 准备参数:创建属性上报消息
String propertyIdentifier = "status";
String propertyValue = "active";
String compareValue = "inactive";
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
compareValue
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_propertyMismatch_fail() {
// 准备参数:创建属性上报消息,值不满足条件
String propertyIdentifier = "temperature";
Double propertyValue = 15.0;
Integer compareValue = 20;
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(compareValue)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_identifierMismatch_fail() {
// 准备参数:标识符不匹配
String messageIdentifier = "temperature";
String conditionIdentifier = "humidity";
Double propertyValue = 25.5;
IotDeviceMessage message = createPropertyPostMessage(messageIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
conditionIdentifier,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
String.valueOf(propertyValue)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullCondition_fail() {
// 准备参数
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
// 调用
boolean result = matcher.matches(message, null);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullConditionType_fail() {
// 准备参数
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(null);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_missingIdentifier_fail() {
// 准备参数
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
condition.setIdentifier(null); // 缺少标识符
condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator());
condition.setParam("20");
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_missingOperator_fail() {
// 准备参数
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
condition.setIdentifier("temperature");
condition.setOperator(null); // 缺少操作符
condition.setParam("20");
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_missingParam_fail() {
// 准备参数
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
condition.setIdentifier("temperature");
condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator());
condition.setParam(null); // 缺少参数
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullMessage_fail() {
// 准备参数
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
"temperature",
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
"20"
);
// 调用
boolean result = matcher.matches(null, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullDeviceProperties_fail() {
// 准备参数:消息的 params 为 null
IotDeviceMessage message = new IotDeviceMessage();
message.setParams(null);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
"temperature",
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
"20"
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_propertiesStructure_success() {
// 测试使用 properties 结构的消息(真实的属性上报场景)
String identifier = "temperature";
Double propertyValue = 25.5;
IotDeviceMessage message = createPropertyPostMessageWithProperties(identifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
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)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_currentLessThanOrEquals_success() {
// 准备参数:创建属性上报消息
String propertyIdentifier = "current";
Double propertyValue = 2.5;
Double compareValue = 3.0;
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(),
String.valueOf(compareValue)
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_stringProperty_success() {
// 准备参数:创建属性上报消息
String propertyIdentifier = "mode";
String propertyValue = "auto";
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
propertyValue
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_booleanProperty_success() {
// 准备参数:创建属性上报消息
String propertyIdentifier = "enabled";
Boolean propertyValue = true;
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
propertyIdentifier,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
String.valueOf(propertyValue)
);
// 调用
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 createPropertyPostMessage(String identifier, Object value) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
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;
}
/**
* 创建有效的条件
*/
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;
}
}

View File

@@ -0,0 +1,360 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
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 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 IotDeviceStateConditionMatcher} 的单元测试
*
* @author HUIHUI
*/
public class IotDeviceStateConditionMatcherTest extends IotBaseConditionMatcherTest {
private IotDeviceStateConditionMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDeviceStateConditionMatcher();
}
@Test
public void testGetSupportedConditionType() {
// 调用
IotSceneRuleConditionTypeEnum result = matcher.getSupportedConditionType();
// 断言
assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_STATE, result);
}
@Test
public void testGetPriority() {
// 调用
int result = matcher.getPriority();
// 断言
assertEquals(30, result);
}
@Test
public void testIsEnabled() {
// 调用
boolean result = matcher.isEnabled();
// 断言
assertTrue(result);
}
@Test
public void testMatches_onlineState_success() {
// 准备参数
IotDeviceStateEnum deviceState = IotDeviceStateEnum.ONLINE;
IotDeviceMessage message = createDeviceMessage(deviceState.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
deviceState.getState().toString()
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_offlineState_success() {
// 准备参数
IotDeviceStateEnum deviceState = IotDeviceStateEnum.OFFLINE;
IotDeviceMessage message = createDeviceMessage(deviceState.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
deviceState.getState().toString()
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_inactiveState_success() {
// 准备参数
IotDeviceStateEnum deviceState = IotDeviceStateEnum.INACTIVE;
IotDeviceMessage message = createDeviceMessage(deviceState.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
deviceState.getState().toString()
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_stateMismatch_fail() {
// 准备参数
IotDeviceStateEnum actualState = IotDeviceStateEnum.ONLINE;
IotDeviceStateEnum expectedState = IotDeviceStateEnum.OFFLINE;
IotDeviceMessage message = createDeviceMessage(actualState.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
expectedState.getState().toString()
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_notEqualsOperator_success() {
// 准备参数
IotDeviceStateEnum actualState = IotDeviceStateEnum.ONLINE;
IotDeviceStateEnum compareState = IotDeviceStateEnum.OFFLINE;
IotDeviceMessage message = createDeviceMessage(actualState.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
compareState.getState().toString()
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_greaterThanOperator_success() {
// 准备参数
IotDeviceStateEnum actualState = IotDeviceStateEnum.OFFLINE; // 状态值为 2
IotDeviceStateEnum compareState = IotDeviceStateEnum.ONLINE; // 状态值为 1
IotDeviceMessage message = createDeviceMessage(actualState.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
compareState.getState().toString()
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_lessThanOperator_success() {
// 准备参数
IotDeviceStateEnum actualState = IotDeviceStateEnum.INACTIVE; // 状态值为 0
IotDeviceStateEnum compareState = IotDeviceStateEnum.ONLINE; // 状态值为 1
IotDeviceMessage message = createDeviceMessage(actualState.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
compareState.getState().toString()
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_nullCondition_fail() {
// 准备参数
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
// 调用
boolean result = matcher.matches(message, null);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullConditionType_fail() {
// 准备参数
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(null);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_missingOperator_fail() {
// 准备参数
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());
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_missingParam_fail() {
// 准备参数
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);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullMessage_fail() {
// 准备参数
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
IotDeviceStateEnum.ONLINE.getState().toString()
);
// 调用
boolean result = matcher.matches(null, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullDeviceState_fail() {
// 准备参数
IotDeviceMessage message = new IotDeviceMessage();
message.setParams(null);
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
IotDeviceStateEnum.ONLINE.getState().toString()
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_greaterThanOrEqualsOperator_success() {
// 准备参数
IotDeviceStateEnum deviceState = IotDeviceStateEnum.ONLINE; // 状态值为 1
IotDeviceMessage message = createDeviceMessage(deviceState.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(),
deviceState.getState().toString() // 比较值也为 1
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_lessThanOrEqualsOperator_success() {
// 准备参数
IotDeviceStateEnum actualState = IotDeviceStateEnum.ONLINE; // 状态值为 1
IotDeviceStateEnum compareState = IotDeviceStateEnum.OFFLINE; // 状态值为 2
IotDeviceMessage message = createDeviceMessage(actualState.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(),
compareState.getState().toString()
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertTrue(result);
}
@Test
public void testMatches_invalidOperator_fail() {
// 准备参数
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType());
condition.setOperator(randomString()); // 随机无效操作符
condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString());
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
@Test
public void testMatches_invalidParamFormat_fail() {
// 准备参数
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
randomString() // 随机无效状态值
);
// 调用
boolean result = matcher.matches(message, condition);
// 断言
assertFalse(result);
}
// ========== 辅助方法 ==========
/**
* 创建设备消息
*/
private IotDeviceMessage createDeviceMessage(Integer deviceState) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
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;
}
}

View File

@@ -0,0 +1,380 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
import cn.hutool.core.map.MapUtil;
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 java.util.HashMap;
import java.util.Map;
import static cn.hutool.core.util.RandomUtil.randomInt;
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 IotDeviceEventPostTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class IotDeviceEventPostTriggerMatcherTest extends IotBaseConditionMatcherTest {
private IotDeviceEventPostTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDeviceEventPostTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {
// 准备参数
// 无需准备参数
// 调用
IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType();
// 断言
assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST, result);
}
@Test
public void testGetPriority_success() {
// 准备参数
// 无需准备参数
// 调用
int result = matcher.getPriority();
// 断言
assertEquals(30, result);
}
@Test
public void testIsEnabled_success() {
// 准备参数
// 无需准备参数
// 调用
boolean result = matcher.isEnabled();
// 断言
assertTrue(result);
}
@Test
public void testMatches_alarmEventSuccess() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("level", randomString())
.put("message", randomString())
.build())
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_errorEventSuccess() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("code", randomInt())
.put("description", randomString())
.build())
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_infoEventSuccess() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("status", randomString())
.put("timestamp", System.currentTimeMillis())
.build())
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_eventIdentifierMismatch() {
// 准备参数
String messageIdentifier = randomString();
String triggerIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", messageIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("level", randomString())
.build())
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(triggerIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_wrongMessageMethod() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("level", randomString())
.build())
.build();
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法
message.setParams(eventParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTriggerIdentifier() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("level", randomString())
.build())
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType());
trigger.setIdentifier(null); // 缺少标识符
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullMessageParams() {
// 准备参数
String eventIdentifier = randomString();
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod());
message.setParams(null);
IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_invalidMessageParams() {
// 准备参数
String eventIdentifier = randomString();
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod());
message.setParams(randomString()); // 不是 Map 类型
IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_missingEventIdentifierInParams() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("level", randomString())
.build()) // 缺少 identifier 字段
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTrigger() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("level", randomString())
.build())
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
// 调用
boolean result = matcher.matches(message, null);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTriggerType() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("level", randomString())
.build())
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
trigger.setType(null);
trigger.setIdentifier(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_complexEventValueSuccess() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("type", randomString())
.put("duration", randomInt())
.put("components", new String[]{randomString(), randomString()})
.put("priority", randomString())
.build())
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_emptyEventValueSuccess() {
// 准备参数
String eventIdentifier = randomString();
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.ofEntries()) // 空的事件值
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_caseSensitiveIdentifierMismatch() {
// 准备参数
String eventIdentifier = randomString().toUpperCase(); // 大写
String triggerIdentifier = eventIdentifier.toLowerCase(); // 小写
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", eventIdentifier)
.put("value", MapUtil.builder(new HashMap<String, Object>())
.put("level", randomString())
.build())
.build();
IotDeviceMessage message = createEventPostMessage(eventParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(triggerIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
// 根据实际实现,这里可能需要调整期望结果
// 如果实现是大小写敏感的,则应该为 false
assertFalse(result);
}
// ========== 辅助方法 ==========
/**
* 创建事件上报消息
*/
private IotDeviceMessage createEventPostMessage(Map<String, Object> eventParams) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
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;
}
}

View File

@@ -0,0 +1,344 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
import cn.hutool.core.map.MapUtil;
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 java.util.HashMap;
import java.util.Map;
import static cn.hutool.core.util.RandomUtil.randomDouble;
import static cn.hutool.core.util.RandomUtil.randomInt;
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 IotDevicePropertyPostTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class IotDevicePropertyPostTriggerMatcherTest extends IotBaseConditionMatcherTest {
private IotDevicePropertyPostTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDevicePropertyPostTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {
// 准备参数
// 无需准备参数
// 调用
IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType();
// 断言
assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST, result);
}
@Test
public void testGetPriority_success() {
// 准备参数
// 无需准备参数
// 调用
int result = matcher.getPriority();
// 断言
assertEquals(20, result);
}
@Test
public void testIsEnabled_success() {
// 准备参数
// 无需准备参数
// 调用
boolean result = matcher.isEnabled();
// 断言
assertTrue(result);
}
@Test
public void testMatches_numericPropertyGreaterThanSuccess() {
// 准备参数
String propertyName = randomString();
Double propertyValue = 25.5;
Integer compareValue = 20;
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put(propertyName, propertyValue)
.build();
IotDeviceMessage message = createPropertyPostMessage(properties);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
propertyName,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(compareValue)
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_integerPropertyEqualsSuccess() {
// 准备参数
String propertyName = randomString();
Integer propertyValue = randomInt();
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put(propertyName, propertyValue)
.build();
IotDeviceMessage message = createPropertyPostMessage(properties);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
propertyName,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
String.valueOf(propertyValue)
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_propertyValueNotMeetCondition() {
// 准备参数
String propertyName = randomString();
Double propertyValue = 15.0;
Integer compareValue = 20;
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put(propertyName, propertyValue)
.build();
IotDeviceMessage message = createPropertyPostMessage(properties);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
propertyName,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(compareValue)
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_propertyNotFound() {
// 准备参数
String existingProperty = randomString();
String missingProperty = randomString();
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put(existingProperty, randomDouble())
.build();
IotDeviceMessage message = createPropertyPostMessage(properties);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
missingProperty, // 不存在的属性
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(randomInt())
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_wrongMessageMethod() {
// 准备参数
String propertyName = randomString();
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put(propertyName, randomDouble())
.build();
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
message.setParams(properties);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
propertyName,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(randomInt())
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTriggerIdentifier() {
// 准备参数
String propertyName = randomString();
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put(propertyName, randomDouble())
.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(String.valueOf(randomInt()));
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullMessageParams() {
// 准备参数
String propertyName = randomString();
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
message.setParams(null);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
propertyName,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(randomInt())
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_invalidMessageParams() {
// 准备参数
String propertyName = randomString();
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
message.setParams(randomString()); // 不是 Map 类型
IotSceneRuleDO.Trigger trigger = createValidTrigger(
propertyName,
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
String.valueOf(randomInt())
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_lessThanOperatorSuccess() {
// 准备参数
String propertyName = randomString();
Double propertyValue = 15.0;
Integer compareValue = 20;
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put(propertyName, propertyValue)
.build();
IotDeviceMessage message = createPropertyPostMessage(properties);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
propertyName,
IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
String.valueOf(compareValue)
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_notEqualsOperatorSuccess() {
// 准备参数
String propertyName = randomString();
String propertyValue = randomString();
String compareValue = randomString();
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put(propertyName, propertyValue)
.build();
IotDeviceMessage message = createPropertyPostMessage(properties);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
propertyName,
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
compareValue
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_multiplePropertiesTargetPropertySuccess() {
// 准备参数
String targetProperty = randomString();
Integer targetValue = randomInt();
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
.put(randomString(), randomDouble())
.put(targetProperty, targetValue)
.put(randomString(), randomString())
.build();
IotDeviceMessage message = createPropertyPostMessage(properties);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
targetProperty,
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
String.valueOf(targetValue)
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
// ========== 辅助方法 ==========
/**
* 创建属性上报消息
*/
private IotDeviceMessage createPropertyPostMessage(Map<String, Object> properties) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
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;
}
}

View File

@@ -0,0 +1,402 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
import cn.hutool.core.map.MapUtil;
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 java.util.HashMap;
import java.util.Map;
import static cn.hutool.core.util.RandomUtil.*;
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 IotDeviceServiceInvokeTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class IotDeviceServiceInvokeTriggerMatcherTest extends IotBaseConditionMatcherTest {
private IotDeviceServiceInvokeTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDeviceServiceInvokeTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {
// 准备参数
// 无需准备参数
// 调用
IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType();
// 断言
assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE, result);
}
@Test
public void testGetPriority_success() {
// 准备参数
// 无需准备参数
// 调用
int result = matcher.getPriority();
// 断言
assertEquals(40, result);
}
@Test
public void testIsEnabled_success() {
// 准备参数
// 无需准备参数
// 调用
boolean result = matcher.isEnabled();
// 断言
assertTrue(result);
}
@Test
public void testMatches_serviceInvokeSuccess() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("mode", randomString())
.build())
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_configServiceSuccess() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("interval", randomInt())
.put("enabled", randomBoolean())
.put("threshold", randomDouble())
.build())
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_updateServiceSuccess() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("version", randomString())
.put("url", randomString())
.build())
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_serviceIdentifierMismatch() {
// 准备参数
String messageIdentifier = randomString();
String triggerIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", messageIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("mode", randomString())
.build())
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(triggerIdentifier); // 不匹配的服务标识符
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_wrongMessageMethod() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("mode", randomString())
.build())
.build();
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法
message.setParams(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTriggerIdentifier() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("mode", randomString())
.build())
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
trigger.setIdentifier(null); // 缺少标识符
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullMessageParams() {
// 准备参数
String serviceIdentifier = randomString();
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod());
message.setParams(null);
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_invalidMessageParams() {
// 准备参数
String serviceIdentifier = randomString();
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod());
message.setParams(randomString()); // 不是 Map 类型
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_missingServiceIdentifierInParams() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("mode", randomString())
.build()) // 缺少 identifier 字段
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTrigger() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("mode", randomString())
.build())
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
// 调用
boolean result = matcher.matches(message, null);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTriggerType() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("mode", randomString())
.build())
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
trigger.setType(null);
trigger.setIdentifier(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_emptyInputDataSuccess() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.ofEntries()) // 空的输入数据
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_noInputDataSuccess() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
// 没有 inputData 字段
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_complexInputDataSuccess() {
// 准备参数
String serviceIdentifier = randomString();
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("sensors", new String[]{randomString(), randomString(), randomString()})
.put("precision", randomDouble())
.put("duration", randomInt())
.put("autoSave", randomBoolean())
.put("config", MapUtil.builder(new HashMap<String, Object>())
.put("mode", randomString())
.put("level", randomString())
.build())
.build())
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_caseSensitiveIdentifierMismatch() {
// 准备参数
String serviceIdentifier = randomString().toUpperCase(); // 大写
String triggerIdentifier = serviceIdentifier.toLowerCase(); // 小写
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
.put("identifier", serviceIdentifier)
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
.put("mode", randomString())
.build())
.build();
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
IotSceneRuleDO.Trigger trigger = createValidTrigger(triggerIdentifier);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
// 根据实际实现,这里可能需要调整期望结果
// 如果实现是大小写敏感的,则应该为 false
assertFalse(result);
}
// ========== 辅助方法 ==========
/**
* 创建服务调用消息
*/
private IotDeviceMessage createServiceInvokeMessage(Map<String, Object> serviceParams) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
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;
}
}

View File

@@ -0,0 +1,266 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
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 static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link IotDeviceStateUpdateTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class IotDeviceStateUpdateTriggerMatcherTest extends IotBaseConditionMatcherTest {
private IotDeviceStateUpdateTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotDeviceStateUpdateTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {
// 准备参数
// 无需准备参数
// 调用
IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType();
// 断言
assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE, result);
}
@Test
public void testGetPriority_success() {
// 准备参数
// 无需准备参数
// 调用
int result = matcher.getPriority();
// 断言
assertEquals(10, result);
}
@Test
public void testIsEnabled_success() {
// 准备参数
// 无需准备参数
// 调用
boolean result = matcher.isEnabled();
// 断言
assertTrue(result);
}
@Test
public void testMatches_onlineStateSuccess() {
// 准备参数
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
IotSceneRuleDO.Trigger trigger = createValidTrigger(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
IotDeviceStateEnum.ONLINE.getState().toString()
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_offlineStateSuccess() {
// 准备参数
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.OFFLINE.getState());
IotSceneRuleDO.Trigger trigger = createValidTrigger(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
IotDeviceStateEnum.OFFLINE.getState().toString()
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_stateMismatch() {
// 准备参数
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
IotSceneRuleDO.Trigger trigger = createValidTrigger(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
IotDeviceStateEnum.OFFLINE.getState().toString()
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTrigger() {
// 准备参数
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
// 调用
boolean result = matcher.matches(message, null);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTriggerType() {
// 准备参数
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
trigger.setType(null);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_wrongMessageMethod() {
// 准备参数
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
message.setParams(IotDeviceStateEnum.ONLINE.getState());
IotSceneRuleDO.Trigger trigger = createValidTrigger(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
IotDeviceStateEnum.ONLINE.getState().toString()
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTriggerOperator() {
// 准备参数
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());
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTriggerValue() {
// 准备参数
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);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullMessageParams() {
// 准备参数
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
message.setParams(null);
IotSceneRuleDO.Trigger trigger = createValidTrigger(
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
IotDeviceStateEnum.ONLINE.getState().toString()
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_greaterThanOperatorSuccess() {
// 准备参数
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
IotSceneRuleDO.Trigger trigger = createValidTrigger(
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
IotDeviceStateEnum.INACTIVE.getState().toString()
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_notEqualsOperatorSuccess() {
// 准备参数
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
IotSceneRuleDO.Trigger trigger = createValidTrigger(
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
IotDeviceStateEnum.OFFLINE.getState().toString()
);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
// ========== 辅助方法 ==========
/**
* 创建设备状态更新消息
*/
private IotDeviceMessage createStateUpdateMessage(Integer state) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
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;
}
}

View File

@@ -0,0 +1,280 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
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 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 IotTimerTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
public class IotTimerTriggerMatcherTest extends IotBaseConditionMatcherTest {
private IotTimerTriggerMatcher matcher;
@BeforeEach
public void setUp() {
matcher = new IotTimerTriggerMatcher();
}
@Test
public void testGetSupportedTriggerType_success() {
// 准备参数
// 无需准备参数
// 调用
IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType();
// 断言
assertEquals(IotSceneRuleTriggerTypeEnum.TIMER, result);
}
@Test
public void testGetPriority_success() {
// 准备参数
// 无需准备参数
// 调用
int result = matcher.getPriority();
// 断言
assertEquals(50, result);
}
@Test
public void testIsEnabled_success() {
// 准备参数
// 无需准备参数
// 调用
boolean result = matcher.isEnabled();
// 断言
assertTrue(result);
}
@Test
public void testMatches_validCronExpressionSuccess() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = "0 0 12 * * ?"; // 每天中午12点
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_everyMinuteCronSuccess() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = "0 * * * * ?"; // 每分钟
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_weekdaysCronSuccess() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = "0 0 9 ? * MON-FRI"; // 工作日上午9点
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_invalidCronExpression() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = randomString(); // 随机无效的 cron 表达式
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_emptyCronExpression() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = ""; // 空的 cron 表达式
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullCronExpression() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
trigger.setCronExpression(null);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTrigger() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
// 调用
boolean result = matcher.matches(message, null);
// 断言
assertFalse(result);
}
@Test
public void testMatches_nullTriggerType() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
trigger.setType(null);
trigger.setCronExpression("0 0 12 * * ?");
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_complexCronExpressionSuccess() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = "0 15 10 ? * 6#3"; // 每月第三个星期五上午10:15
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_incorrectCronFormat() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = "0 0 12 * *"; // 缺少字段的 cron 表达式
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_specificDateCronSuccess() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = "0 0 0 1 1 ? 2025"; // 2025年1月1日午夜
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_everySecondCronSuccess() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = "* * * * * ?"; // 每秒执行
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
@Test
public void testMatches_invalidCharactersCron() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
String cronExpression = "0 0 12 * * @ #"; // 包含无效字符的 cron 表达式
IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression);
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertFalse(result);
}
@Test
public void testMatches_rangeCronSuccess() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9-17 * * MON-FRI"); // 工作日9-17点
// 调用
boolean result = matcher.matches(message, trigger);
// 断言
assertTrue(result);
}
// ========== 辅助方法 ==========
/**
* 创建设备消息
*/
private IotDeviceMessage createDeviceMessage() {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
return message;
}
/**
* 创建有效的定时触发器
*/
private IotSceneRuleDO.Trigger createValidTrigger(String cronExpression) {
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
trigger.setCronExpression(cronExpression);
return trigger;
}
}

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);
}
}

View File

@@ -0,0 +1,52 @@
spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
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
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
--- #################### 监控相关配置 ####################
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
base-package: cn.iocoder.yudao
tenant: # 多租户相关配置项
enable: true
xss:
enable: false
demo: false # 关闭演示模式

View File

@@ -0,0 +1,37 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 设置特定包的日志级别 -->
<logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<!-- 设置 IotSceneRuleMatcherManager 的日志级别 -->
<logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager" level="INFO"
additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<!-- 设置所有匹配器的日志级别 -->
<logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<!-- 根日志级别 -->
<root level="WARN">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@@ -0,0 +1,10 @@
DELETE FROM "iot_scene_rule";
DELETE FROM "iot_product";
DELETE FROM "iot_device";
DELETE FROM "iot_thing_model";
DELETE FROM "iot_device_data";
DELETE FROM "iot_alert_config";
DELETE FROM "iot_alert_record";
DELETE FROM "iot_ota_firmware";
DELETE FROM "iot_ota_task";
DELETE FROM "iot_ota_record";

View File

@@ -0,0 +1,182 @@
CREATE TABLE IF NOT EXISTS "iot_scene_rule" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(255) NOT NULL DEFAULT '',
"description" varchar(500) DEFAULT NULL,
"status" tinyint NOT NULL DEFAULT '0',
"triggers" text,
"actions" text,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL DEFAULT '0',
PRIMARY KEY ("id")
) COMMENT 'IoT 场景联动规则表';
CREATE TABLE IF NOT EXISTS "iot_product" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(255) NOT NULL DEFAULT '',
"product_key" varchar(100) NOT NULL DEFAULT '',
"protocol_type" tinyint NOT NULL DEFAULT '0',
"category_id" bigint DEFAULT NULL,
"description" varchar(500) DEFAULT NULL,
"data_format" tinyint NOT NULL DEFAULT '0',
"device_type" tinyint NOT NULL DEFAULT '0',
"net_type" tinyint NOT NULL DEFAULT '0',
"validate_type" tinyint NOT NULL DEFAULT '0',
"status" tinyint NOT NULL DEFAULT '0',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL DEFAULT '0',
PRIMARY KEY ("id")
) COMMENT 'IoT 产品表';
CREATE TABLE IF NOT EXISTS "iot_device" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"device_name" varchar(255) NOT NULL DEFAULT '',
"product_id" bigint NOT NULL,
"device_key" varchar(100) NOT NULL DEFAULT '',
"device_secret" varchar(100) NOT NULL DEFAULT '',
"nickname" varchar(255) DEFAULT NULL,
"status" tinyint NOT NULL DEFAULT '0',
"status_last_update_time" timestamp DEFAULT NULL,
"last_online_time" timestamp DEFAULT NULL,
"last_offline_time" timestamp DEFAULT NULL,
"active_time" timestamp DEFAULT NULL,
"ip" varchar(50) DEFAULT NULL,
"firmware_version" varchar(50) DEFAULT NULL,
"device_type" tinyint NOT NULL DEFAULT '0',
"gateway_id" bigint DEFAULT NULL,
"sub_device_count" int NOT NULL DEFAULT '0',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL DEFAULT '0',
PRIMARY KEY ("id")
) COMMENT 'IoT 设备表';
CREATE TABLE IF NOT EXISTS "iot_thing_model" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"product_id" bigint NOT NULL,
"identifier" varchar(100) NOT NULL DEFAULT '',
"name" varchar(255) NOT NULL DEFAULT '',
"description" varchar(500) DEFAULT NULL,
"type" tinyint NOT NULL DEFAULT '1',
"property" text,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL DEFAULT '0',
PRIMARY KEY ("id")
) COMMENT 'IoT 物模型表';
CREATE TABLE IF NOT EXISTS "iot_device_data" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"device_id" bigint NOT NULL,
"product_id" bigint NOT NULL,
"identifier" varchar(100) NOT NULL DEFAULT '',
"type" tinyint NOT NULL DEFAULT '1',
"data" text,
"ts" bigint NOT NULL DEFAULT '0',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("id")
) COMMENT 'IoT 设备数据表';
CREATE TABLE IF NOT EXISTS "iot_alert_config" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(255) NOT NULL DEFAULT '',
"product_id" bigint NOT NULL,
"device_id" bigint DEFAULT NULL,
"rule_id" bigint DEFAULT NULL,
"status" tinyint NOT NULL DEFAULT '0',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL DEFAULT '0',
PRIMARY KEY ("id")
) COMMENT 'IoT 告警配置表';
CREATE TABLE IF NOT EXISTS "iot_alert_record" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"alert_config_id" bigint NOT NULL,
"alert_name" varchar(255) NOT NULL DEFAULT '',
"product_id" bigint NOT NULL,
"device_id" bigint DEFAULT NULL,
"rule_id" bigint DEFAULT NULL,
"alert_data" text,
"alert_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deal_status" tinyint NOT NULL DEFAULT '0',
"deal_time" timestamp DEFAULT NULL,
"deal_user_id" bigint DEFAULT NULL,
"deal_remark" varchar(500) DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL DEFAULT '0',
PRIMARY KEY ("id")
) COMMENT 'IoT 告警记录表';
CREATE TABLE IF NOT EXISTS "iot_ota_firmware" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(255) NOT NULL DEFAULT '',
"product_id" bigint NOT NULL,
"version" varchar(50) NOT NULL DEFAULT '',
"description" varchar(500) DEFAULT NULL,
"file_url" varchar(500) DEFAULT NULL,
"file_size" bigint NOT NULL DEFAULT '0',
"status" tinyint NOT NULL DEFAULT '0',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL DEFAULT '0',
PRIMARY KEY ("id")
) COMMENT 'IoT OTA 固件表';
CREATE TABLE IF NOT EXISTS "iot_ota_task" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(255) NOT NULL DEFAULT '',
"firmware_id" bigint NOT NULL,
"product_id" bigint NOT NULL,
"upgrade_type" tinyint NOT NULL DEFAULT '0',
"status" tinyint NOT NULL DEFAULT '0',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL DEFAULT '0',
PRIMARY KEY ("id")
) COMMENT 'IoT OTA 升级任务表';
CREATE TABLE IF NOT EXISTS "iot_ota_record" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"task_id" bigint NOT NULL,
"firmware_id" bigint NOT NULL,
"device_id" bigint NOT NULL,
"status" tinyint NOT NULL DEFAULT '0',
"progress" int NOT NULL DEFAULT '0',
"error_msg" varchar(500) DEFAULT NULL,
"start_time" timestamp DEFAULT NULL,
"end_time" timestamp DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL DEFAULT '0',
PRIMARY KEY ("id")
) COMMENT 'IoT OTA 升级记录表';