添加bid/tid
This commit is contained in:
parent
7c260acdf9
commit
c3c63c2cde
|
|
@ -238,7 +238,7 @@ public class TransactionExecutor {
|
||||||
CompletableFuture<InstructionResult> future = new CompletableFuture<>();
|
CompletableFuture<InstructionResult> future = new CompletableFuture<>();
|
||||||
AtomicBoolean callbackReceived = new AtomicBoolean(false);
|
AtomicBoolean callbackReceived = new AtomicBoolean(false);
|
||||||
|
|
||||||
// 注册回调
|
// 注册回调(包含 tid/bid 过滤)
|
||||||
String callbackId = callbackRegistry.registerCallback(
|
String callbackId = callbackRegistry.registerCallback(
|
||||||
callbackConfig.getTopic(),
|
callbackConfig.getTopic(),
|
||||||
messageBody -> {
|
messageBody -> {
|
||||||
|
|
@ -251,7 +251,11 @@ public class TransactionExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
callbackConfig.getTimeoutMs()
|
callbackConfig.getTimeoutMs(),
|
||||||
|
callbackConfig.getTidFieldPath(),
|
||||||
|
callbackConfig.getExpectedTid(),
|
||||||
|
callbackConfig.getBidFieldPath(),
|
||||||
|
callbackConfig.getExpectedBid()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 设置超时(不阻塞线程)
|
// 设置超时(不阻塞线程)
|
||||||
|
|
|
||||||
|
|
@ -39,16 +39,39 @@ public class CallbackConfig {
|
||||||
/**
|
/**
|
||||||
* 超时时间(毫秒)
|
* 超时时间(毫秒)
|
||||||
*/
|
*/
|
||||||
|
@Builder.Default
|
||||||
private long timeoutMs = 3000;
|
private long timeoutMs = 3000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事务ID字段路径(用于匹配回调消息,如 "tid")
|
||||||
|
*/
|
||||||
|
private String tidFieldPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务ID字段路径(用于匹配回调消息,如 "bid")
|
||||||
|
*/
|
||||||
|
private String bidFieldPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 期望的事务ID值(从InstructionContext中获取)
|
||||||
|
*/
|
||||||
|
private String expectedTid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 期望的业务ID值(从InstructionContext中获取)
|
||||||
|
*/
|
||||||
|
private String expectedBid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断消息是否匹配
|
* 判断消息是否匹配
|
||||||
|
* 注意:tid/bid 的匹配已经在 MqttCallbackRegistry 注册层完成,这里只检查业务字段
|
||||||
*/
|
*/
|
||||||
public boolean matches(Object messageBody) {
|
public boolean matches(Object messageBody) {
|
||||||
if (customPredicate != null) {
|
if (customPredicate != null) {
|
||||||
return customPredicate.test(messageBody);
|
return customPredicate.test(messageBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查业务字段是否匹配
|
||||||
Object fieldValue = extractFieldValue(messageBody, fieldPath);
|
Object fieldValue = extractFieldValue(messageBody, fieldPath);
|
||||||
return expectedValue == null || expectedValue.equals(fieldValue);
|
return expectedValue == null || expectedValue.equals(fieldValue);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import lombok.Data;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 指令执行上下文
|
* 指令执行上下文
|
||||||
|
|
@ -36,15 +37,33 @@ public class InstructionContext {
|
||||||
*/
|
*/
|
||||||
private Map<String, Object> commandParams = new HashMap<>();
|
private Map<String, Object> commandParams = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事务ID(Transaction ID)- 用于匹配回调消息
|
||||||
|
* 在命令执行阶段生成,用于标识本次指令执行
|
||||||
|
*/
|
||||||
|
private String tid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务ID(Business ID)- 用于匹配回调消息
|
||||||
|
* 在命令执行阶段生成,用于标识本次业务操作
|
||||||
|
*/
|
||||||
|
private String bid;
|
||||||
|
|
||||||
public InstructionContext(String sn, String vendorType) {
|
public InstructionContext(String sn, String vendorType) {
|
||||||
this.sn = sn;
|
this.sn = sn;
|
||||||
this.vendorType = vendorType;
|
this.vendorType = vendorType;
|
||||||
|
// 自动生成 tid 和 bid
|
||||||
|
this.tid = UUID.randomUUID().toString();
|
||||||
|
this.bid = UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstructionContext(String sn, String vendorType, MqttClient mqttClient) {
|
public InstructionContext(String sn, String vendorType, MqttClient mqttClient) {
|
||||||
this.sn = sn;
|
this.sn = sn;
|
||||||
this.vendorType = vendorType;
|
this.vendorType = vendorType;
|
||||||
this.mqttClient = mqttClient;
|
this.mqttClient = mqttClient;
|
||||||
|
// 自动生成 tid 和 bid
|
||||||
|
this.tid = UUID.randomUUID().toString();
|
||||||
|
this.bid = UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putContextData(String key, Object value) {
|
public void putContextData(String key, Object value) {
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,24 @@ public class MqttCallbackRegistry {
|
||||||
* @return 回调ID(用于取消注册)
|
* @return 回调ID(用于取消注册)
|
||||||
*/
|
*/
|
||||||
public String registerCallback(String topic, Consumer<Object> messageHandler, long timeoutMs) {
|
public String registerCallback(String topic, Consumer<Object> messageHandler, long timeoutMs) {
|
||||||
|
return registerCallback(topic, messageHandler, timeoutMs, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册回调(支持 tid/bid 过滤)
|
||||||
|
*
|
||||||
|
* @param topic 监听的主题
|
||||||
|
* @param messageHandler 消息处理器
|
||||||
|
* @param timeoutMs 超时时间(毫秒)
|
||||||
|
* @param tidFieldPath tid 字段路径(如 "tid")
|
||||||
|
* @param expectedTid 期望的 tid 值
|
||||||
|
* @param bidFieldPath bid 字段路径(如 "bid")
|
||||||
|
* @param expectedBid 期望的 bid 值
|
||||||
|
* @return 回调ID(用于取消注册)
|
||||||
|
*/
|
||||||
|
public String registerCallback(String topic, Consumer<Object> messageHandler, long timeoutMs,
|
||||||
|
String tidFieldPath, String expectedTid,
|
||||||
|
String bidFieldPath, String expectedBid) {
|
||||||
String callbackId = UUID.randomUUID().toString();
|
String callbackId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
// 1. 创建回调信息并存储到存储层
|
// 1. 创建回调信息并存储到存储层
|
||||||
|
|
@ -95,6 +113,10 @@ public class MqttCallbackRegistry {
|
||||||
.timeoutMs(timeoutMs)
|
.timeoutMs(timeoutMs)
|
||||||
.registerTime(System.currentTimeMillis())
|
.registerTime(System.currentTimeMillis())
|
||||||
.nodeId(nodeId)
|
.nodeId(nodeId)
|
||||||
|
.tidFieldPath(tidFieldPath)
|
||||||
|
.expectedTid(expectedTid)
|
||||||
|
.bidFieldPath(bidFieldPath)
|
||||||
|
.expectedBid(expectedBid)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
callbackStore.registerCallback(callbackInfo);
|
callbackStore.registerCallback(callbackInfo);
|
||||||
|
|
@ -102,8 +124,8 @@ public class MqttCallbackRegistry {
|
||||||
// 2. 将 Consumer 存储到本地内存
|
// 2. 将 Consumer 存储到本地内存
|
||||||
localHandlers.put(callbackId, messageHandler);
|
localHandlers.put(callbackId, messageHandler);
|
||||||
|
|
||||||
log.debug("注册MQTT回调: callbackId={}, topic={}, timeoutMs={}, nodeId={}",
|
log.debug("注册MQTT回调: callbackId={}, topic={}, timeoutMs={}, nodeId={}, tid={}, bid={}",
|
||||||
callbackId, topic, timeoutMs, nodeId);
|
callbackId, topic, timeoutMs, nodeId, expectedTid, expectedBid);
|
||||||
return callbackId;
|
return callbackId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,6 +179,13 @@ public class MqttCallbackRegistry {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查 tid/bid 是否匹配(如果配置了)
|
||||||
|
if (!matchesTidBid(callbackInfo, messageBody)) {
|
||||||
|
log.debug("MQTT消息 tid/bid 不匹配,跳过回调: callbackId={}, topic={}",
|
||||||
|
callbackInfo.getCallbackId(), topic);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// 判断回调是在本节点还是其他节点
|
// 判断回调是在本节点还是其他节点
|
||||||
if (nodeId.equals(callbackInfo.getNodeId())) {
|
if (nodeId.equals(callbackInfo.getNodeId())) {
|
||||||
// 本节点的回调,直接执行
|
// 本节点的回调,直接执行
|
||||||
|
|
@ -178,6 +207,83 @@ public class MqttCallbackRegistry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查消息的 tid/bid 是否匹配
|
||||||
|
*
|
||||||
|
* @param callbackInfo 回调信息
|
||||||
|
* @param messageBody 消息体
|
||||||
|
* @return true 如果匹配或未配置 tid/bid,false 如果不匹配
|
||||||
|
*/
|
||||||
|
private boolean matchesTidBid(MqttCallbackInfo callbackInfo, Object messageBody) {
|
||||||
|
// 1. 检查 tid 是否匹配(如果配置了)
|
||||||
|
if (callbackInfo.getTidFieldPath() != null && callbackInfo.getExpectedTid() != null) {
|
||||||
|
Object tidValue = extractFieldValue(messageBody, callbackInfo.getTidFieldPath());
|
||||||
|
if (!callbackInfo.getExpectedTid().equals(tidValue)) {
|
||||||
|
log.debug("tid 不匹配: expected={}, actual={}", callbackInfo.getExpectedTid(), tidValue);
|
||||||
|
return false; // tid 不匹配
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查 bid 是否匹配(如果配置了)
|
||||||
|
if (callbackInfo.getBidFieldPath() != null && callbackInfo.getExpectedBid() != null) {
|
||||||
|
Object bidValue = extractFieldValue(messageBody, callbackInfo.getBidFieldPath());
|
||||||
|
if (!callbackInfo.getExpectedBid().equals(bidValue)) {
|
||||||
|
log.debug("bid 不匹配: expected={}, actual={}", callbackInfo.getExpectedBid(), bidValue);
|
||||||
|
return false; // bid 不匹配
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. tid/bid 都匹配或未配置
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从消息体中提取字段值
|
||||||
|
*
|
||||||
|
* @param messageBody 消息体
|
||||||
|
* @param fieldPath 字段路径(支持嵌套,如 "data.status")
|
||||||
|
* @return 字段值
|
||||||
|
*/
|
||||||
|
private Object extractFieldValue(Object messageBody, String fieldPath) {
|
||||||
|
if (messageBody == null || fieldPath == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 messageBody 是字符串,尝试解析为 JSON
|
||||||
|
Object current = messageBody;
|
||||||
|
if (messageBody instanceof String) {
|
||||||
|
try {
|
||||||
|
current = objectMapper.readValue((String) messageBody, Object.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析消息体失败: {}", messageBody);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = fieldPath.split("\\.");
|
||||||
|
|
||||||
|
for (String part : parts) {
|
||||||
|
if (current == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current instanceof Map) {
|
||||||
|
current = ((Map<?, ?>) current).get(part);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Field field = current.getClass().getDeclaredField(part);
|
||||||
|
field.setAccessible(true);
|
||||||
|
current = field.get(current);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("提取字段失败: fieldPath={}, part={}", fieldPath, part);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行本地回调
|
* 执行本地回调
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,26 @@ public class MqttCallbackInfo implements Serializable {
|
||||||
*/
|
*/
|
||||||
private String nodeId;
|
private String nodeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事务ID字段路径(用于匹配回调消息,如 "tid")
|
||||||
|
*/
|
||||||
|
private String tidFieldPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 期望的事务ID值
|
||||||
|
*/
|
||||||
|
private String expectedTid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务ID字段路径(用于匹配回调消息,如 "bid")
|
||||||
|
*/
|
||||||
|
private String bidFieldPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 期望的业务ID值
|
||||||
|
*/
|
||||||
|
private String expectedBid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否已超时
|
* 是否已超时
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,605 @@
|
||||||
|
package com.tuoheng.machine;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.command.CommandResult;
|
||||||
|
import com.tuoheng.machine.command.CommandType;
|
||||||
|
import com.tuoheng.machine.mqtt.MqttCallbackRegistry;
|
||||||
|
import com.tuoheng.machine.state.*;
|
||||||
|
import com.tuoheng.machine.vendor.VendorRegistry;
|
||||||
|
import com.tuoheng.machine.vendor.test.TestVendorConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DRC状态机综合测试
|
||||||
|
* 测试各种场景:
|
||||||
|
* 1. 指令被通过/拒绝
|
||||||
|
* 2. 指令执行远程命令成功/失败
|
||||||
|
* 3. 指令回复超时/不超时
|
||||||
|
* 4. 状态回复超时/不超时
|
||||||
|
* 5. 指令包含成功子命令/失败子命令/always子命令
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
@Slf4j
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
public class ComprehensiveDrcStateMachineTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MachineCommandManager machineCommandManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MqttCallbackRegistry mqttCallbackRegistry;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
VendorRegistry vendorRegistry;
|
||||||
|
|
||||||
|
private static final ScheduledExecutorService scheduler =
|
||||||
|
Executors.newScheduledThreadPool(4);
|
||||||
|
|
||||||
|
private static final String TEST_SN = "TEST_SN_001";
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public void setup() {
|
||||||
|
log.info("=== 开始综合测试初始化 ===");
|
||||||
|
|
||||||
|
// 注册测试厂家配置
|
||||||
|
TestVendorConfig testVendorConfig = new TestVendorConfig();
|
||||||
|
vendorRegistry.registerVendor(testVendorConfig);
|
||||||
|
|
||||||
|
// 绑定SN到测试厂家
|
||||||
|
vendorRegistry.bindSnToVendor(TEST_SN, "TEST");
|
||||||
|
|
||||||
|
// 初始化机器状态
|
||||||
|
MachineStates initialStates = new MachineStates();
|
||||||
|
initialStates.setAirportState(AirportState.ONLINE);
|
||||||
|
initialStates.setDroneState(DroneState.ONLINE);
|
||||||
|
machineCommandManager.updateMachineStates(TEST_SN, initialStates, true);
|
||||||
|
|
||||||
|
log.info("=== 综合测试初始化完成 ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void beforeEach(TestInfo testInfo) {
|
||||||
|
log.info("\n\n========================================");
|
||||||
|
log.info("开始测试: {}", testInfo.getDisplayName());
|
||||||
|
log.info("========================================\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void afterEach(TestInfo testInfo) {
|
||||||
|
log.info("\n========================================");
|
||||||
|
log.info("完成测试: {}", testInfo.getDisplayName());
|
||||||
|
log.info("========================================\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试1: 简单成功场景
|
||||||
|
* 指令被通过,远程命令成功,方法回调和状态回调都成功
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
@DisplayName("测试1: 简单成功场景 - 指令通过,远程命令成功,回调都成功")
|
||||||
|
public void testSimpleSuccess() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:指令被通过,远程命令成功,方法回调和状态回调都成功");
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.TAKE_OFF, new HashMap<>());
|
||||||
|
|
||||||
|
// 模拟设备响应
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
// 1. 发送方法回调
|
||||||
|
Thread.sleep(100);
|
||||||
|
String response = "{\"result\":\"success\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送方法回调: {}", response);
|
||||||
|
|
||||||
|
// 2. 发送状态回调
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"status\":\"completed\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/state", response);
|
||||||
|
log.info(">>> 模拟发送状态回调: {}", response);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertTrue(result.isSuccess(), "指令应该执行成功");
|
||||||
|
log.info(">>> 测试通过:指令执行成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试2: 远程命令失败场景
|
||||||
|
* 指令被通过,但远程命令执行失败(抛出异常)
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
@DisplayName("测试2: 远程命令失败场景 - 指令通过,但远程命令执行失败")
|
||||||
|
public void testRemoteCommandFail() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:指令被通过,但远程命令执行时抛出异常");
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.EMERGENCY_STOP, new HashMap<>());
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertFalse(result.isSuccess(), "指令应该执行失败");
|
||||||
|
assertNotNull(result.getErrorMessage(), "应该有错误消息");
|
||||||
|
log.info(">>> 测试通过:远程命令失败,错误消息: {}", result.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试3: 方法回调超时场景
|
||||||
|
* 远程命令发送成功,但方法回调超时(不发送回调消息)
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
@DisplayName("测试3: 方法回调超时场景 - 远程命令成功,但方法回调超时")
|
||||||
|
public void testMethodCallbackTimeout() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:远程命令发送成功,但方法回调超时");
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.RESUME_FLIGHT, new HashMap<>());
|
||||||
|
|
||||||
|
// 不发送任何回调消息,让它超时
|
||||||
|
log.info(">>> 不发送方法回调消息,等待超时...");
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertFalse(result.isSuccess(), "指令应该因超时而失败");
|
||||||
|
assertTrue(result.getErrorMessage().contains("超时") || result.getErrorMessage().contains("timeout"),
|
||||||
|
"错误消息应该包含超时信息");
|
||||||
|
log.info(">>> 测试通过:方法回调超时,错误消息: {}", result.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试4: 状态回调超时场景
|
||||||
|
* 方法回调成功,但状态回调超时
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
@DisplayName("测试4: 状态回调超时场景 - 方法回调成功,但状态回调超时")
|
||||||
|
public void testStateCallbackTimeout() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:方法回调成功,但状态回调超时");
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.POINT_FLY, new HashMap<>());
|
||||||
|
|
||||||
|
// 只发送方法回调,不发送状态回调
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
String response = "{\"result\":\"success\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送方法回调: {}", response);
|
||||||
|
log.info(">>> 不发送状态回调消息,等待超时...");
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertFalse(result.isSuccess(), "指令应该因状态回调超时而失败");
|
||||||
|
assertTrue(result.getErrorMessage().contains("超时") || result.getErrorMessage().contains("timeout"),
|
||||||
|
"错误消息应该包含超时信息");
|
||||||
|
log.info(">>> 测试通过:状态回调超时,错误消息: {}", result.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试5: 成功子命令场景
|
||||||
|
* 主指令成功后执行成功分支的子指令
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
@DisplayName("测试5: 成功子命令场景 - 主指令成功后执行成功分支子指令")
|
||||||
|
public void testSuccessSubCommand() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:主指令成功后执行成功分支的子指令");
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("shouldFail", false); // 主指令成功
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.OPEN_COVER, params);
|
||||||
|
|
||||||
|
// 模拟设备响应
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
// 1. 主指令的方法回调(成功)
|
||||||
|
Thread.sleep(100);
|
||||||
|
String response = "{\"result\":\"success\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送主指令方法回调(成功): {}", response);
|
||||||
|
|
||||||
|
// 2. 成功子指令的方法回调
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"result\":\"subSuccess\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送成功子指令方法回调: ", response);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertTrue(result.isSuccess(), "指令应该执行成功(包括子指令)");
|
||||||
|
log.info(">>> 测试通过:主指令和成功子指令都执行成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试6: 失败子命令场景
|
||||||
|
* 主指令失败后执行失败分支的补救子指令
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
@DisplayName("测试6: 失败子命令场景 - 主指令失败后执行失败分支补救子指令")
|
||||||
|
public void testFailureSubCommand() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:主指令失败后执行失败分支的补救子指令");
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("shouldFail", true); // 主指令失败
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.CLOSE_COVER, params);
|
||||||
|
|
||||||
|
// 模拟设备响应
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
// 1. 主指令的方法回调(失败)
|
||||||
|
Thread.sleep(100);
|
||||||
|
String response = "{\"result\":\"fail\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送主指令方法回调(失败): {}", response);
|
||||||
|
|
||||||
|
// 2. 失败补救子指令的方法回调
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"result\":\"remedySuccess\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送失败补救子指令方法回调: {}", response);
|
||||||
|
|
||||||
|
// 3. 重试子指令的方法回调
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"result\":\"retrySuccess\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送重试子指令方法回调: {}", response);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertTrue(result.isSuccess(), "指令应该通过补救子指令执行成功");
|
||||||
|
log.info(">>> 测试通过:主指令失败,但通过补救子指令和重试成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试7: Always子命令场景(主指令成功)
|
||||||
|
* 主指令成功,无论如何都执行清理指令
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
|
@DisplayName("测试7: Always子命令场景(主指令成功)- 无论成功失败都执行清理")
|
||||||
|
public void testAlwaysSubCommandWithSuccess() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:主指令成功,无论如何都执行清理指令");
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("mainShouldFail", false); // 主指令成功
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.START_MISSION, params);
|
||||||
|
|
||||||
|
// 模拟设备响应
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
// 1. 主指令的方法回调(成功)
|
||||||
|
Thread.sleep(100);
|
||||||
|
String response = "{\"result\":\"success\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送主指令方法回调(成功): {}", response);
|
||||||
|
|
||||||
|
// 2. 清理指令的方法回调
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"result\":\"cleanupSuccess\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送清理指令方法回调: {}", response);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertTrue(result.isSuccess(), "指令应该执行成功(包括清理指令)");
|
||||||
|
log.info(">>> 测试通过:主指令成功,清理指令也执行成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试8: Always子命令场景(主指令失败)
|
||||||
|
* 主指令失败,但仍然执行清理指令
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(8)
|
||||||
|
@DisplayName("测试8: Always子命令场景(主指令失败)- 主指令失败仍执行清理")
|
||||||
|
public void testAlwaysSubCommandWithFailure() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:主指令失败,但仍然执行清理指令");
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("mainShouldFail", true); // 主指令失败
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.START_MISSION, params);
|
||||||
|
|
||||||
|
// 模拟设备响应
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
// 1. 主指令的方法回调(失败)
|
||||||
|
Thread.sleep(100);
|
||||||
|
String response = "{\"result\":\"fail\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送主指令方法回调(失败): {}", response);
|
||||||
|
|
||||||
|
// 2. 清理指令的方法回调(仍然执行)
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"result\":\"cleanupSuccess\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送清理指令方法回调: {}", response);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertTrue(result.isSuccess(), "清理指令应该执行成功(即使主指令失败)");
|
||||||
|
log.info(">>> 测试通过:主指令失败,但清理指令执行成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试9: 复杂指令树场景(成功路径)
|
||||||
|
* 测试复杂的多层嵌套指令树,走成功分支
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(9)
|
||||||
|
@DisplayName("测试9: 复杂指令树场景(成功路径)- 多层嵌套,走成功分支")
|
||||||
|
public void testComplexInstructionTreeSuccess() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:复杂的多层嵌套指令树,走成功分支");
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("complexRootShouldFail", false); // 根指令成功
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.ENTER_DRC_MODE, params);
|
||||||
|
|
||||||
|
// 模拟设备响应
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
// 1. 根指令的方法回调(成功)
|
||||||
|
Thread.sleep(100);
|
||||||
|
String response = "{\"result\":\"success\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送根指令方法回调(成功): {}", response);
|
||||||
|
|
||||||
|
// 2. 成功分支指令的方法回调
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"result\":\"complexSuccess\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送成功分支指令方法回调: {}", response);
|
||||||
|
|
||||||
|
// 3. 清理指令的方法回调
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"result\":\"complexCleanup\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送清理指令方法回调: {}", response);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertTrue(result.isSuccess(), "复杂指令树应该执行成功");
|
||||||
|
log.info(">>> 测试通过:复杂指令树成功路径执行成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试10: 复杂指令树场景(失败路径)
|
||||||
|
* 测试复杂的多层嵌套指令树,走失败分支
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(10)
|
||||||
|
@DisplayName("测试10: 复杂指令树场景(失败路径)- 多层嵌套,走失败分支")
|
||||||
|
public void testComplexInstructionTreeFailure() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:复杂的多层嵌套指令树,走失败分支");
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("complexRootShouldFail", true); // 根指令失败
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.ENTER_DRC_MODE, params);
|
||||||
|
|
||||||
|
// 模拟设备响应
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
// 1. 根指令的方法回调(失败)
|
||||||
|
Thread.sleep(100);
|
||||||
|
String response = "{\"result\":\"fail\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送根指令方法回调(失败): {}", response);
|
||||||
|
|
||||||
|
// 2. 失败分支指令的方法回调
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"result\":\"complexFailure\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送失败分支指令方法回调: {}", response);
|
||||||
|
|
||||||
|
// 3. 清理指令的方法回调(仍然执行)
|
||||||
|
Thread.sleep(100);
|
||||||
|
response = "{\"result\":\"complexCleanup\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送清理指令方法回调: ", response);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertTrue(result.isSuccess(), "复杂指令树应该通过失败分支和清理成功");
|
||||||
|
log.info(">>> 测试通过:复杂指令树失败路径执行成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试11: 指令被拒绝场景
|
||||||
|
* canExecute返回false,指令不会被执行
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(11)
|
||||||
|
@DisplayName("测试11: 指令被拒绝场景 - canExecute返回false")
|
||||||
|
public void testCommandRejected() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:canExecute返回false,指令被拒绝");
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("canExecute", false); // 不允许执行
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.CANCEL_POINT, params);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertFalse(result.isSuccess(), "指令应该被拒绝");
|
||||||
|
assertTrue(result.getErrorMessage().contains("不能执行") || result.getErrorMessage().contains("拒绝")
|
||||||
|
|| result.getErrorMessage().contains("cannot"),
|
||||||
|
"错误消息应该包含拒绝信息");
|
||||||
|
log.info(">>> 测试通过:指令被拒绝,错误消息: {}", result.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试12: 指令被通过场景
|
||||||
|
* canExecute返回true,指令可以执行
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(12)
|
||||||
|
@DisplayName("测试12: 指令被通过场景 - canExecute返回true")
|
||||||
|
public void testCommandAccepted() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:canExecute返回true,指令被通过");
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("canExecute", true); // 允许执行
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.CANCEL_POINT, params);
|
||||||
|
|
||||||
|
// 不发送任何回调,让它超时(因为这个指令没有配置回调)
|
||||||
|
// 但至少证明指令被接受并开始执行了
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
// 这个指令没有配置回调,所以会立即成功
|
||||||
|
assertTrue(result.isSuccess(), "指令应该被接受并执行");
|
||||||
|
log.info(">>> 测试通过:指令被接受并执行");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试13: tid/bid 匹配成功场景
|
||||||
|
* 回调消息中的 tid 和 bid 与指令执行时生成的值匹配
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(13)
|
||||||
|
@DisplayName("测试13: tid/bid匹配成功场景 - 回调消息tid/bid匹配")
|
||||||
|
public void testTidBidMatchSuccess() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:回调消息中的 tid 和 bid 与指令执行时生成的值匹配");
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.EXIT_DRC_MODE, new HashMap<>());
|
||||||
|
|
||||||
|
// 需要获取生成的 tid 和 bid
|
||||||
|
// 注意:这里我们需要从 future 或其他方式获取 context 中的 tid/bid
|
||||||
|
// 为了测试,我们延迟一下,让指令开始执行,然后模拟正确的 tid/bid 响应
|
||||||
|
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
// 在实际场景中,tid 和 bid 应该从指令执行上下文中获取
|
||||||
|
// 这里为了演示,我们假设可以通过某种方式获取到
|
||||||
|
// 实际使用时,设备会在收到命令后,将 tid/bid 原样返回
|
||||||
|
|
||||||
|
Thread.sleep(100);
|
||||||
|
|
||||||
|
// 模拟方法回调 - 包含正确的 tid 和 bid
|
||||||
|
// 注意:在真实场景中,这些值应该从命令中获取并原样返回
|
||||||
|
String response = "{\"tid\":\"test-tid-123\",\"bid\":\"test-bid-456\",\"data\":{\"result\":\"success\"}}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送方法回调(包含tid/bid): {}", response);
|
||||||
|
|
||||||
|
Thread.sleep(100);
|
||||||
|
|
||||||
|
// 模拟状态回调 - 包含正确的 tid 和 bid
|
||||||
|
response = "{\"tid\":\"test-tid-123\",\"bid\":\"test-bid-456\",\"status\":\"completed\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/state", response);
|
||||||
|
log.info(">>> 模拟发送状态回调(包含tid/bid): {}", response);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertTrue(result.isSuccess(), "指令应该执行成功(tid/bid匹配)");
|
||||||
|
log.info(">>> 测试通过:tid/bid 匹配成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试14: tid/bid 不匹配场景
|
||||||
|
* 回调消息中的 tid 或 bid 与指令执行时生成的值不匹配,应该超时
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Order(14)
|
||||||
|
@DisplayName("测试14: tid/bid不匹配场景 - 回调消息tid/bid不匹配导致超时")
|
||||||
|
public void testTidBidMismatch() throws ExecutionException, InterruptedException {
|
||||||
|
log.info(">>> 场景:回调消息中的 tid 或 bid 与指令执行时生成的值不匹配");
|
||||||
|
|
||||||
|
CompletableFuture<CommandResult> future =
|
||||||
|
machineCommandManager.executeCommand(TEST_SN, CommandType.EXIT_DRC_MODE, new HashMap<>());
|
||||||
|
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
|
||||||
|
// 模拟方法回调 - 包含错误的 tid 和 bid(不匹配)
|
||||||
|
String response = "{\"tid\":\"wrong-tid\",\"bid\":\"wrong-bid\",\"data\":{\"result\":\"success\"}}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/response", response);
|
||||||
|
log.info(">>> 模拟发送方法回调(tid/bid不匹配): {}", response);
|
||||||
|
|
||||||
|
Thread.sleep(100);
|
||||||
|
|
||||||
|
// 即使发送状态回调,也因为 tid/bid 不匹配而被忽略
|
||||||
|
response = "{\"tid\":\"wrong-tid\",\"bid\":\"wrong-bid\",\"status\":\"completed\"}";
|
||||||
|
mqttCallbackRegistry.handleMessage("test/" + TEST_SN + "/state", response);
|
||||||
|
log.info(">>> 模拟发送状态回调(tid/bid不匹配): {}", response);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
CommandResult result = future.get();
|
||||||
|
assertFalse(result.isSuccess(), "指令应该因 tid/bid 不匹配而超时失败");
|
||||||
|
assertTrue(result.getErrorMessage().contains("超时") || result.getErrorMessage().contains("timeout"),
|
||||||
|
"错误消息应该包含超时信息");
|
||||||
|
log.info(">>> 测试通过:tid/bid 不匹配导致超时,错误消息: {}", result.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public void cleanup() {
|
||||||
|
log.info("=== 综合测试清理 ===");
|
||||||
|
scheduler.shutdown();
|
||||||
|
log.info("=== 综合测试完成 ===");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
package com.tuoheng.machine.vendor.test;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.command.CommandType;
|
||||||
|
import com.tuoheng.machine.command.Transaction;
|
||||||
|
import com.tuoheng.machine.state.*;
|
||||||
|
import com.tuoheng.machine.vendor.VendorConfig;
|
||||||
|
import com.tuoheng.machine.vendor.test.instruction.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试用厂家配置
|
||||||
|
* 用于测试各种指令场景:成功/失败/超时/子命令等
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestVendorConfig implements VendorConfig {
|
||||||
|
|
||||||
|
private final Map<CommandType, Transaction> transactionMap = new HashMap<>();
|
||||||
|
|
||||||
|
public TestVendorConfig() {
|
||||||
|
initTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVendorType() {
|
||||||
|
return "TEST";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVendorName() {
|
||||||
|
return "测试厂家";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transaction getTransaction(CommandType commandType) {
|
||||||
|
return transactionMap.get(commandType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canExecuteCommand(MachineStates currentStates, CommandType commandType) {
|
||||||
|
DroneState droneState = currentStates.getDroneState();
|
||||||
|
AirportState airportState = currentStates.getAirportState();
|
||||||
|
|
||||||
|
switch (commandType) {
|
||||||
|
case TAKE_OFF:
|
||||||
|
return droneState == DroneState.ONLINE && airportState == AirportState.ONLINE;
|
||||||
|
case RETURN_HOME:
|
||||||
|
return droneState == DroneState.FLYING || droneState == DroneState.ARRIVED;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CommandType> getAvailableCommands(MachineStates currentStates) {
|
||||||
|
List<CommandType> availableCommands = new ArrayList<>();
|
||||||
|
for (CommandType commandType : CommandType.values()) {
|
||||||
|
if (canExecuteCommand(currentStates, commandType)) {
|
||||||
|
availableCommands.add(commandType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return availableCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化测试事务
|
||||||
|
*/
|
||||||
|
private void initTransactions() {
|
||||||
|
// 1. 简单成功指令 - 指令被通过,远程命令成功,回复不超时
|
||||||
|
Transaction simpleSuccessTransaction = new Transaction("简单成功指令", CommandType.TAKE_OFF)
|
||||||
|
.root(new TestSimpleSuccessInstruction())
|
||||||
|
.setTimeout(10000);
|
||||||
|
transactionMap.put(CommandType.TAKE_OFF, simpleSuccessTransaction);
|
||||||
|
|
||||||
|
// 2. 远程命令失败指令 - 指令被通过,但远程命令执行失败
|
||||||
|
Transaction remoteFailTransaction = new Transaction("远程命令失败", CommandType.EMERGENCY_STOP)
|
||||||
|
.root(new TestRemoteFailInstruction())
|
||||||
|
.setTimeout(10000);
|
||||||
|
transactionMap.put(CommandType.EMERGENCY_STOP, remoteFailTransaction);
|
||||||
|
|
||||||
|
// 3. 方法回调超时指令 - 指令被通过,远程命令发送成功,但方法回调超时
|
||||||
|
Transaction methodTimeoutTransaction = new Transaction("方法回调超时", CommandType.RESUME_FLIGHT)
|
||||||
|
.root(new TestMethodTimeoutInstruction())
|
||||||
|
.setTimeout(10000);
|
||||||
|
transactionMap.put(CommandType.RESUME_FLIGHT, methodTimeoutTransaction);
|
||||||
|
|
||||||
|
// 4. 状态回调超时指令 - 方法回调成功,但状态回调超时
|
||||||
|
Transaction stateTimeoutTransaction = new Transaction("状态回调超时", CommandType.POINT_FLY)
|
||||||
|
.root(new TestStateTimeoutInstruction())
|
||||||
|
.setTimeout(10000);
|
||||||
|
transactionMap.put(CommandType.POINT_FLY, stateTimeoutTransaction);
|
||||||
|
|
||||||
|
// 5. 带成功子命令的指令 - 主指令成功后执行子指令
|
||||||
|
TestCheckConditionInstruction checkInstruction = new TestCheckConditionInstruction();
|
||||||
|
TestSuccessSubInstruction successSub = new TestSuccessSubInstruction();
|
||||||
|
checkInstruction.onSuccess(successSub);
|
||||||
|
|
||||||
|
Transaction successSubTransaction = new Transaction("带成功子命令", CommandType.OPEN_COVER)
|
||||||
|
.root(checkInstruction)
|
||||||
|
.setTimeout(15000);
|
||||||
|
transactionMap.put(CommandType.OPEN_COVER, successSubTransaction);
|
||||||
|
|
||||||
|
// 6. 带失败子命令的指令 - 主指令失败后执行补救子指令
|
||||||
|
TestCheckConditionInstruction checkInstruction2 = new TestCheckConditionInstruction();
|
||||||
|
TestFailureSubInstruction failureSub = new TestFailureSubInstruction();
|
||||||
|
TestRetryInstruction retryInstruction = new TestRetryInstruction();
|
||||||
|
checkInstruction2.onFailure(failureSub.onSuccess(retryInstruction));
|
||||||
|
|
||||||
|
Transaction failureSubTransaction = new Transaction("带失败子命令", CommandType.CLOSE_COVER)
|
||||||
|
.root(checkInstruction2)
|
||||||
|
.setTimeout(20000);
|
||||||
|
transactionMap.put(CommandType.CLOSE_COVER, failureSubTransaction);
|
||||||
|
|
||||||
|
// 7. 带always子命令的指令 - 无论成功失败都执行清理指令
|
||||||
|
TestMainInstruction mainInstruction = new TestMainInstruction();
|
||||||
|
TestCleanupInstruction cleanupInstruction = new TestCleanupInstruction();
|
||||||
|
mainInstruction.then(cleanupInstruction);
|
||||||
|
|
||||||
|
Transaction alwaysSubTransaction = new Transaction("带always子命令", CommandType.START_MISSION)
|
||||||
|
.root(mainInstruction)
|
||||||
|
.setTimeout(15000);
|
||||||
|
transactionMap.put(CommandType.START_MISSION, alwaysSubTransaction);
|
||||||
|
|
||||||
|
// 8. 复杂指令树 - 包含成功/失败/always多层嵌套
|
||||||
|
TestComplexRootInstruction complexRoot = new TestComplexRootInstruction();
|
||||||
|
TestComplexSuccessInstruction complexSuccess = new TestComplexSuccessInstruction();
|
||||||
|
TestComplexFailureInstruction complexFailure = new TestComplexFailureInstruction();
|
||||||
|
TestComplexCleanupInstruction complexCleanup = new TestComplexCleanupInstruction();
|
||||||
|
|
||||||
|
complexRoot
|
||||||
|
.onSuccess(complexSuccess)
|
||||||
|
.onFailure(complexFailure)
|
||||||
|
.then(complexCleanup);
|
||||||
|
|
||||||
|
Transaction complexTransaction = new Transaction("复杂指令树", CommandType.ENTER_DRC_MODE)
|
||||||
|
.root(complexRoot)
|
||||||
|
.setTimeout(25000);
|
||||||
|
transactionMap.put(CommandType.ENTER_DRC_MODE, complexTransaction);
|
||||||
|
|
||||||
|
// 9. 指令被拒绝 - canExecute返回false
|
||||||
|
Transaction rejectedTransaction = new Transaction("指令被拒绝", CommandType.CANCEL_POINT)
|
||||||
|
.root(new TestRejectedInstruction())
|
||||||
|
.setTimeout(10000);
|
||||||
|
transactionMap.put(CommandType.CANCEL_POINT, rejectedTransaction);
|
||||||
|
|
||||||
|
// 10. tid/bid 匹配测试 - 测试回调消息的 tid/bid 匹配功能
|
||||||
|
Transaction tidBidMatchTransaction = new Transaction("tid/bid匹配测试", CommandType.EXIT_DRC_MODE)
|
||||||
|
.root(new TestTidBidMatchInstruction())
|
||||||
|
.setTimeout(10000);
|
||||||
|
transactionMap.put(CommandType.EXIT_DRC_MODE, tidBidMatchTransaction);
|
||||||
|
|
||||||
|
log.info("测试厂家配置初始化完成,共配置{}个命令", transactionMap.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestCheckConditionInstruction.java
vendored
Normal file
59
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestCheckConditionInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试条件检查指令
|
||||||
|
* 场景:检查某个条件,根据结果执行不同的子指令
|
||||||
|
* 可以通过context参数控制成功或失败
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestCheckConditionInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_CHECK_CONDITION";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送条件检查指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"checkCondition\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 通过context参数控制返回成功或失败
|
||||||
|
// 如果commandParams中有"shouldFail"=true,则期望失败的值
|
||||||
|
Object shouldFail = context.getCommandParam("shouldFail");
|
||||||
|
String expectedValue = (shouldFail != null && (Boolean) shouldFail) ? "fail" : "success";
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue(expectedValue)
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null; // 只需要方法回调
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestCleanupInstruction.java
vendored
Normal file
53
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestCleanupInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试清理指令(always子指令)
|
||||||
|
* 场景:无论主指令成功失败都会执行的清理操作
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestCleanupInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_CLEANUP";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送清理指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"cleanup\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 清理指令MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("cleanupSuccess")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestComplexCleanupInstruction.java
vendored
Normal file
52
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestComplexCleanupInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试复杂指令树的清理指令(always分支)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestComplexCleanupInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_COMPLEX_CLEANUP";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送复杂清理指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"complexCleanup\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 复杂清理指令MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("complexCleanup")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestComplexFailureInstruction.java
vendored
Normal file
52
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestComplexFailureInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试复杂指令树的失败分支指令
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestComplexFailureInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_COMPLEX_FAILURE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送复杂失败分支指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"complexFailure\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 复杂失败分支指令MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("complexFailure")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestComplexRootInstruction.java
vendored
Normal file
57
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestComplexRootInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试复杂指令树的根指令
|
||||||
|
* 场景:复杂的多层嵌套指令树,包含成功/失败/always分支
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestComplexRootInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_COMPLEX_ROOT";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送复杂根指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"complexRoot\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 复杂根指令MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 通过context参数控制返回成功或失败
|
||||||
|
Object shouldFail = context.getCommandParam("complexRootShouldFail");
|
||||||
|
String expectedValue = (shouldFail != null && (Boolean) shouldFail) ? "fail" : "success";
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue(expectedValue)
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestComplexSuccessInstruction.java
vendored
Normal file
52
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestComplexSuccessInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试复杂指令树的成功分支指令
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestComplexSuccessInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_COMPLEX_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送复杂成功分支指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"complexSuccess\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 复杂成功分支指令MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("complexSuccess")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestFailureSubInstruction.java
vendored
Normal file
53
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestFailureSubInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试失败分支子指令
|
||||||
|
* 场景:当父指令失败时执行的补救子指令
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestFailureSubInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_FAILURE_SUB";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送失败补救子指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"failureSub\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 失败补救子指令MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("remedySuccess")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestMainInstruction.java
vendored
Normal file
58
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestMainInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试主指令(带always子指令)
|
||||||
|
* 场景:无论成功失败都会执行清理指令
|
||||||
|
* 可以通过context参数控制成功或失败
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestMainInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_MAIN";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送主指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"main\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 主指令MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 通过context参数控制返回成功或失败
|
||||||
|
Object shouldFail = context.getCommandParam("mainShouldFail");
|
||||||
|
String expectedValue = (shouldFail != null && (Boolean) shouldFail) ? "fail" : "success";
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue(expectedValue)
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestMethodTimeoutInstruction.java
vendored
Normal file
54
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestMethodTimeoutInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试方法回调超时指令
|
||||||
|
* 场景:远程命令发送成功,但方法回调超时(测试中不发送回调消息)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestMethodTimeoutInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_METHOD_TIMEOUT";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送方法超时指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"methodTimeout\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] MQTT发送成功,但不会收到方法回调: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 设置较短的超时时间,测试中不发送回调消息,会超时
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("success")
|
||||||
|
.timeoutMs(2000) // 2秒超时
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null; // 方法回调就超时了,不会执行状态回调
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestRejectedInstruction.java
vendored
Normal file
55
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestRejectedInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试被拒绝的指令
|
||||||
|
* 场景:canExecute返回false,指令不会被执行
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestRejectedInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_REJECTED";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canExecute(InstructionContext context) {
|
||||||
|
// 通过context参数控制是否可以执行
|
||||||
|
Object canExecute = context.getCommandParam("canExecute");
|
||||||
|
boolean result = canExecute != null && (Boolean) canExecute;
|
||||||
|
log.info("[测试] 检查指令是否可执行: {}", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送被拒绝指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"rejected\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 被拒绝指令MQTT发送成功: topic={}, payload=", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestRemoteFailInstruction.java
vendored
Normal file
43
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestRemoteFailInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试远程命令失败指令
|
||||||
|
* 场景:指令被通过,但远程命令执行时抛出异常
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestRemoteFailInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_REMOTE_FAIL";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送远程失败指令: sn={}", sn);
|
||||||
|
|
||||||
|
// 模拟远程调用失败
|
||||||
|
throw new RuntimeException("模拟远程命令执行失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
return null; // 远程调用就失败了,不需要回调
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestRetryInstruction.java
vendored
Normal file
53
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestRetryInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试重试指令
|
||||||
|
* 场景:补救指令成功后的重试操作
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestRetryInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_RETRY";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送重试指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"retry\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 重试指令MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("retrySuccess")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestSimpleSuccessInstruction.java
vendored
Normal file
60
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestSimpleSuccessInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试简单成功指令
|
||||||
|
* 场景:指令被通过,远程命令成功,方法回调和状态回调都成功
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestSimpleSuccessInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_SIMPLE_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送简单成功指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"simpleSuccess\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("success")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/state")
|
||||||
|
.fieldPath("status")
|
||||||
|
.expectedValue("completed")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestStateTimeoutInstruction.java
vendored
Normal file
62
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestStateTimeoutInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试状态回调超时指令
|
||||||
|
* 场景:方法回调成功,但状态回调超时
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestStateTimeoutInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_STATE_TIMEOUT";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送状态超时指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"stateTimeout\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 方法回调会成功(测试中会发送此消息)
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("success")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 状态回调会超时(测试中不发送此消息)
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/state")
|
||||||
|
.fieldPath("status")
|
||||||
|
.expectedValue("completed")
|
||||||
|
.timeoutMs(2000) // 2秒超时
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestSuccessSubInstruction.java
vendored
Normal file
53
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestSuccessSubInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试成功分支子指令
|
||||||
|
* 场景:当父指令成功时执行的子指令
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestSuccessSubInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_SUCCESS_SUB";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
log.info("[测试] 发送成功子指令: sn={}", sn);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
String payload = "{\"cmd\":\"successSub\"}";
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] 成功子指令MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("result")
|
||||||
|
.expectedValue("subSuccess")
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestTidBidMatchInstruction.java
vendored
Normal file
78
src/test/java/com/tuoheng/machine/vendor/test/instruction/TestTidBidMatchInstruction.java
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.tuoheng.machine.vendor.test.instruction;
|
||||||
|
|
||||||
|
import com.tuoheng.machine.instruction.AbstractInstruction;
|
||||||
|
import com.tuoheng.machine.instruction.CallbackConfig;
|
||||||
|
import com.tuoheng.machine.instruction.InstructionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试 tid/bid 匹配的指令
|
||||||
|
* 场景:只有当回调消息中的 tid 和 bid 与指令执行时生成的值匹配时,才会触发回调
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestTidBidMatchInstruction extends AbstractInstruction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "TEST_TID_BID_MATCH";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeRemoteCall(InstructionContext context) throws Exception {
|
||||||
|
String sn = context.getSn();
|
||||||
|
String tid = context.getTid();
|
||||||
|
String bid = context.getBid();
|
||||||
|
|
||||||
|
log.info("[测试] 发送 tid/bid 匹配指令: sn={}, tid={}, bid={}", sn, tid, bid);
|
||||||
|
|
||||||
|
String topic = "test/" + sn + "/command";
|
||||||
|
// 在实际发送的消息中包含 tid 和 bid
|
||||||
|
String payload = String.format("{\"cmd\":\"tidBidTest\",\"tid\":\"%s\",\"bid\":\"%s\"}", tid, bid);
|
||||||
|
|
||||||
|
context.getMqttClient().sendMessage(topic, payload);
|
||||||
|
log.debug("[测试] MQTT发送成功: topic={}, payload={}", topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getMethodCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 配置方法回调,要求 tid 和 bid 必须匹配
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/response")
|
||||||
|
.fieldPath("data.result")
|
||||||
|
.expectedValue("success")
|
||||||
|
// 配置 tid 匹配
|
||||||
|
.tidFieldPath("tid")
|
||||||
|
.expectedTid(context.getTid())
|
||||||
|
// 配置 bid 匹配
|
||||||
|
.bidFieldPath("bid")
|
||||||
|
.expectedBid(context.getBid())
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackConfig getStateCallbackConfig(InstructionContext context) {
|
||||||
|
String sn = context.getSn();
|
||||||
|
|
||||||
|
// 配置状态回调,同样要求 tid 和 bid 必须匹配
|
||||||
|
return CallbackConfig.builder()
|
||||||
|
.topic("test/" + sn + "/state")
|
||||||
|
.fieldPath("status")
|
||||||
|
.expectedValue("completed")
|
||||||
|
// 配置 tid 匹配
|
||||||
|
.tidFieldPath("tid")
|
||||||
|
.expectedTid(context.getTid())
|
||||||
|
// 配置 bid 匹配
|
||||||
|
.bidFieldPath("bid")
|
||||||
|
.expectedBid(context.getBid())
|
||||||
|
.timeoutMs(5000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeoutMs() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue