添加bid/tid
This commit is contained in:
parent
7c260acdf9
commit
c3c63c2cde
|
|
@ -238,7 +238,7 @@ public class TransactionExecutor {
|
|||
CompletableFuture<InstructionResult> future = new CompletableFuture<>();
|
||||
AtomicBoolean callbackReceived = new AtomicBoolean(false);
|
||||
|
||||
// 注册回调
|
||||
// 注册回调(包含 tid/bid 过滤)
|
||||
String callbackId = callbackRegistry.registerCallback(
|
||||
callbackConfig.getTopic(),
|
||||
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;
|
||||
|
||||
/**
|
||||
* 事务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) {
|
||||
if (customPredicate != null) {
|
||||
return customPredicate.test(messageBody);
|
||||
}
|
||||
|
||||
// 检查业务字段是否匹配
|
||||
Object fieldValue = extractFieldValue(messageBody, fieldPath);
|
||||
return expectedValue == null || expectedValue.equals(fieldValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import lombok.Data;
|
|||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 指令执行上下文
|
||||
|
|
@ -36,15 +37,33 @@ public class InstructionContext {
|
|||
*/
|
||||
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) {
|
||||
this.sn = sn;
|
||||
this.vendorType = vendorType;
|
||||
// 自动生成 tid 和 bid
|
||||
this.tid = UUID.randomUUID().toString();
|
||||
this.bid = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public InstructionContext(String sn, String vendorType, MqttClient mqttClient) {
|
||||
this.sn = sn;
|
||||
this.vendorType = vendorType;
|
||||
this.mqttClient = mqttClient;
|
||||
// 自动生成 tid 和 bid
|
||||
this.tid = UUID.randomUUID().toString();
|
||||
this.bid = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public void putContextData(String key, Object value) {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,24 @@ public class MqttCallbackRegistry {
|
|||
* @return 回调ID(用于取消注册)
|
||||
*/
|
||||
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();
|
||||
|
||||
// 1. 创建回调信息并存储到存储层
|
||||
|
|
@ -95,6 +113,10 @@ public class MqttCallbackRegistry {
|
|||
.timeoutMs(timeoutMs)
|
||||
.registerTime(System.currentTimeMillis())
|
||||
.nodeId(nodeId)
|
||||
.tidFieldPath(tidFieldPath)
|
||||
.expectedTid(expectedTid)
|
||||
.bidFieldPath(bidFieldPath)
|
||||
.expectedBid(expectedBid)
|
||||
.build();
|
||||
|
||||
callbackStore.registerCallback(callbackInfo);
|
||||
|
|
@ -102,8 +124,8 @@ public class MqttCallbackRegistry {
|
|||
// 2. 将 Consumer 存储到本地内存
|
||||
localHandlers.put(callbackId, messageHandler);
|
||||
|
||||
log.debug("注册MQTT回调: callbackId={}, topic={}, timeoutMs={}, nodeId={}",
|
||||
callbackId, topic, timeoutMs, nodeId);
|
||||
log.debug("注册MQTT回调: callbackId={}, topic={}, timeoutMs={}, nodeId={}, tid={}, bid={}",
|
||||
callbackId, topic, timeoutMs, nodeId, expectedTid, expectedBid);
|
||||
return callbackId;
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +179,13 @@ public class MqttCallbackRegistry {
|
|||
continue;
|
||||
}
|
||||
|
||||
// 检查 tid/bid 是否匹配(如果配置了)
|
||||
if (!matchesTidBid(callbackInfo, messageBody)) {
|
||||
log.debug("MQTT消息 tid/bid 不匹配,跳过回调: callbackId={}, topic={}",
|
||||
callbackInfo.getCallbackId(), topic);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 判断回调是在本节点还是其他节点
|
||||
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;
|
||||
|
||||
/**
|
||||
* 事务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