|
@@ -0,0 +1,260 @@
|
|
|
|
+package com.hfln.device.infrastructure.mqtt;
|
|
|
|
+
|
|
|
|
+import cn.hfln.framework.mqtt.annotation.MqttSubscriber;
|
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
|
+import org.springframework.boot.test.context.SpringBootTest;
|
|
|
|
+import org.springframework.boot.test.context.TestConfiguration;
|
|
|
|
+import org.springframework.context.annotation.Bean;
|
|
|
|
+import org.springframework.messaging.Message;
|
|
|
|
+import org.springframework.messaging.support.GenericMessage;
|
|
|
|
+import org.springframework.test.context.ContextConfiguration;
|
|
|
|
+
|
|
|
|
+import java.util.HashMap;
|
|
|
|
+import java.util.Map;
|
|
|
|
+import java.util.concurrent.CountDownLatch;
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
+
|
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @MqttSubscriber注解功能测试
|
|
|
|
+ *
|
|
|
|
+ * 测试目标:
|
|
|
|
+ * 1. 验证@MqttSubscriber注解是否能正常工作
|
|
|
|
+ * 2. 测试消息路由和处理机制
|
|
|
|
+ * 3. 确认没有重复消费问题
|
|
|
|
+ */
|
|
|
|
+@SpringBootTest(classes = {MqttSubscriberFunctionalTest.TestConfig.class})
|
|
|
|
+@ContextConfiguration
|
|
|
|
+public class MqttSubscriberFunctionalTest {
|
|
|
|
+
|
|
|
|
+ @TestConfiguration
|
|
|
|
+ static class TestConfig {
|
|
|
|
+
|
|
|
|
+ @Bean
|
|
|
|
+ public TestMqttSubscriber testMqttSubscriber() {
|
|
|
|
+ return new TestMqttSubscriber();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 测试用的MQTT订阅者
|
|
|
|
+ */
|
|
|
|
+ public static class TestMqttSubscriber {
|
|
|
|
+
|
|
|
|
+ private final CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
+ private volatile String lastReceivedTopic;
|
|
|
|
+ private volatile String lastReceivedPayload;
|
|
|
|
+ private volatile boolean messageReceived = false;
|
|
|
|
+
|
|
|
|
+ @MqttSubscriber(topic = "/test/device/+/login", qos = 1, desc = "测试设备登录")
|
|
|
|
+ public void handleTestDeviceLogin(String topic, Message<?> message) {
|
|
|
|
+ System.out.println("收到MQTT消息 - Topic: " + topic + ", Payload: " + message.getPayload());
|
|
|
|
+
|
|
|
|
+ this.lastReceivedTopic = topic;
|
|
|
|
+ this.lastReceivedPayload = message.getPayload().toString();
|
|
|
|
+ this.messageReceived = true;
|
|
|
|
+
|
|
|
|
+ latch.countDown();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @MqttSubscriber(topic = "/test/device/+/keepalive", qos = 0, desc = "测试设备心跳")
|
|
|
|
+ public void handleTestDeviceKeepAlive(String topic, Message<?> message) {
|
|
|
|
+ System.out.println("收到心跳消息 - Topic: " + topic);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @MqttSubscriber(topic = "/test/app/+/command", qos = 1, desc = "测试应用命令")
|
|
|
|
+ public void handleTestAppCommand(String topic, Message<?> message) {
|
|
|
|
+ System.out.println("收到应用命令 - Topic: " + topic + ", Payload: " + message.getPayload());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public boolean waitForMessage(long timeoutSeconds) throws InterruptedException {
|
|
|
|
+ return latch.await(timeoutSeconds, TimeUnit.SECONDS);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public String getLastReceivedTopic() {
|
|
|
|
+ return lastReceivedTopic;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public String getLastReceivedPayload() {
|
|
|
|
+ return lastReceivedPayload;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public boolean isMessageReceived() {
|
|
|
|
+ return messageReceived;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Test
|
|
|
|
+ public void testMqttSubscriberAnnotationBasicFunctionality() throws Exception {
|
|
|
|
+ System.out.println("=== 测试@MqttSubscriber注解基础功能 ===");
|
|
|
|
+
|
|
|
|
+ // 这个测试验证@MqttSubscriber注解能否被正确识别和处理
|
|
|
|
+ TestMqttSubscriber subscriber = new TestMqttSubscriber();
|
|
|
|
+
|
|
|
|
+ // 验证订阅者对象创建成功
|
|
|
|
+ assertNotNull(subscriber, "@MqttSubscriber对象应该能正常创建");
|
|
|
|
+
|
|
|
|
+ // 模拟消息处理
|
|
|
|
+ Map<String, Object> headers = new HashMap<>();
|
|
|
|
+ headers.put("mqtt_receivedTopic", "/test/device/test001/login");
|
|
|
|
+
|
|
|
|
+ String testPayload = "{\"device_info\":{\"deviceid\":\"test001\",\"firmware\":\"v1.0.0\"}}";
|
|
|
|
+ Message<String> testMessage = new GenericMessage<>(testPayload, headers);
|
|
|
|
+
|
|
|
|
+ // 直接调用处理方法(模拟MQTT框架的调用)
|
|
|
|
+ subscriber.handleTestDeviceLogin("/test/device/test001/login", testMessage);
|
|
|
|
+
|
|
|
|
+ // 验证消息处理结果
|
|
|
|
+ assertTrue(subscriber.isMessageReceived(), "消息应该被正确接收");
|
|
|
|
+ assertEquals("/test/device/test001/login", subscriber.getLastReceivedTopic(), "主题应该匹配");
|
|
|
|
+ assertEquals(testPayload, subscriber.getLastReceivedPayload(), "载荷应该匹配");
|
|
|
|
+
|
|
|
|
+ System.out.println("✓ @MqttSubscriber注解基础功能测试通过");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Test
|
|
|
|
+ public void testMqttSubscriberMultipleTopicPatterns() throws Exception {
|
|
|
|
+ System.out.println("=== 测试@MqttSubscriber多主题模式 ===");
|
|
|
|
+
|
|
|
|
+ TestMqttSubscriber subscriber = new TestMqttSubscriber();
|
|
|
|
+
|
|
|
|
+ // 测试不同的主题模式
|
|
|
|
+ String[] testTopics = {
|
|
|
|
+ "/test/device/abc123/login",
|
|
|
|
+ "/test/device/xyz789/keepalive",
|
|
|
|
+ "/test/app/admin/command"
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ for (String topic : testTopics) {
|
|
|
|
+ Message<String> message = new GenericMessage<>("test payload");
|
|
|
|
+
|
|
|
|
+ // 根据主题调用相应的处理方法
|
|
|
|
+ if (topic.contains("/login")) {
|
|
|
|
+ assertDoesNotThrow(() -> {
|
|
|
|
+ subscriber.handleTestDeviceLogin(topic, message);
|
|
|
|
+ }, "登录消息处理不应该抛出异常");
|
|
|
|
+ } else if (topic.contains("/keepalive")) {
|
|
|
|
+ assertDoesNotThrow(() -> {
|
|
|
|
+ subscriber.handleTestDeviceKeepAlive(topic, message);
|
|
|
|
+ }, "心跳消息处理不应该抛出异常");
|
|
|
|
+ } else if (topic.contains("/command")) {
|
|
|
|
+ assertDoesNotThrow(() -> {
|
|
|
|
+ subscriber.handleTestAppCommand(topic, message);
|
|
|
|
+ }, "命令消息处理不应该抛出异常");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ System.out.println("✓ 多主题模式测试通过");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Test
|
|
|
|
+ public void testMqttSubscriberErrorHandling() throws Exception {
|
|
|
|
+ System.out.println("=== 测试@MqttSubscriber错误处理 ===");
|
|
|
|
+
|
|
|
|
+ TestMqttSubscriber subscriber = new TestMqttSubscriber();
|
|
|
|
+
|
|
|
|
+ // 测试null消息
|
|
|
|
+ assertDoesNotThrow(() -> {
|
|
|
|
+ subscriber.handleTestDeviceLogin("/test/device/test001/login", null);
|
|
|
|
+ }, "null消息不应该导致异常");
|
|
|
|
+
|
|
|
|
+ // 测试空载荷
|
|
|
|
+ Message<String> emptyMessage = new GenericMessage<>("");
|
|
|
|
+ assertDoesNotThrow(() -> {
|
|
|
|
+ subscriber.handleTestDeviceLogin("/test/device/test001/login", emptyMessage);
|
|
|
|
+ }, "空载荷消息不应该导致异常");
|
|
|
|
+
|
|
|
|
+ // 测试无效JSON载荷
|
|
|
|
+ Message<String> invalidJsonMessage = new GenericMessage<>("invalid json{[}");
|
|
|
|
+ assertDoesNotThrow(() -> {
|
|
|
|
+ subscriber.handleTestDeviceLogin("/test/device/test001/login", invalidJsonMessage);
|
|
|
|
+ }, "无效JSON载荷不应该导致异常");
|
|
|
|
+
|
|
|
|
+ System.out.println("✓ 错误处理测试通过");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Test
|
|
|
|
+ public void testMqttSubscriberPerformance() throws Exception {
|
|
|
|
+ System.out.println("=== 测试@MqttSubscriber性能 ===");
|
|
|
|
+
|
|
|
|
+ TestMqttSubscriber subscriber = new TestMqttSubscriber();
|
|
|
|
+
|
|
|
|
+ // 性能测试:处理大量消息
|
|
|
|
+ int messageCount = 1000;
|
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
|
+
|
|
|
|
+ for (int i = 0; i < messageCount; i++) {
|
|
|
|
+ String topic = "/test/device/perf" + i + "/login";
|
|
|
|
+ String payload = "{\"device_info\":{\"deviceid\":\"perf" + i + "\"}}";
|
|
|
|
+ Message<String> message = new GenericMessage<>(payload);
|
|
|
|
+
|
|
|
|
+ subscriber.handleTestDeviceLogin(topic, message);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ long endTime = System.currentTimeMillis();
|
|
|
|
+ long duration = endTime - startTime;
|
|
|
|
+
|
|
|
|
+ System.out.println("处理 " + messageCount + " 条消息耗时: " + duration + "ms");
|
|
|
|
+ System.out.println("平均每条消息耗时: " + (duration * 1.0 / messageCount) + "ms");
|
|
|
|
+
|
|
|
|
+ // 性能要求:平均每条消息处理时间应该小于10ms
|
|
|
|
+ assertTrue(duration < messageCount * 10, "性能测试:平均处理时间应该小于10ms/条");
|
|
|
|
+
|
|
|
|
+ System.out.println("✓ 性能测试通过");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Test
|
|
|
|
+ public void testMqttSubscriberConfiguration() throws Exception {
|
|
|
|
+ System.out.println("=== 测试@MqttSubscriber配置 ===");
|
|
|
|
+
|
|
|
|
+ // 这个测试验证注解的配置参数是否正确
|
|
|
|
+ TestMqttSubscriber subscriber = new TestMqttSubscriber();
|
|
|
|
+
|
|
|
|
+ // 通过反射检查方法上的注解
|
|
|
|
+ Class<?> subscriberClass = subscriber.getClass();
|
|
|
|
+
|
|
|
|
+ // 检查登录处理方法的注解
|
|
|
|
+ try {
|
|
|
|
+ java.lang.reflect.Method loginMethod = subscriberClass.getMethod("handleTestDeviceLogin", String.class, Message.class);
|
|
|
|
+ MqttSubscriber annotation = loginMethod.getAnnotation(MqttSubscriber.class);
|
|
|
|
+
|
|
|
|
+ assertNotNull(annotation, "应该能找到@MqttSubscriber注解");
|
|
|
|
+ assertEquals("/test/device/+/login", annotation.topic(), "主题配置应该正确");
|
|
|
|
+ assertEquals(1, annotation.qos(), "QoS配置应该正确");
|
|
|
|
+ assertEquals("测试设备登录", annotation.desc(), "描述配置应该正确");
|
|
|
|
+
|
|
|
|
+ } catch (NoSuchMethodException e) {
|
|
|
|
+ fail("应该能找到handleTestDeviceLogin方法");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ System.out.println("✓ 配置测试通过");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Test
|
|
|
|
+ public void testMqttSubscriberIsolation() throws Exception {
|
|
|
|
+ System.out.println("=== 测试@MqttSubscriber消息隔离 ===");
|
|
|
|
+
|
|
|
|
+ TestMqttSubscriber subscriber = new TestMqttSubscriber();
|
|
|
|
+
|
|
|
|
+ // 验证不同的订阅方法相互独立
|
|
|
|
+ String loginTopic = "/test/device/test001/login";
|
|
|
|
+ String keepaliveTopic = "/test/device/test001/keepalive";
|
|
|
|
+ String commandTopic = "/test/app/admin/command";
|
|
|
|
+
|
|
|
|
+ Message<String> message = new GenericMessage<>("test");
|
|
|
|
+
|
|
|
|
+ // 调用登录处理方法
|
|
|
|
+ subscriber.handleTestDeviceLogin(loginTopic, message);
|
|
|
|
+ assertTrue(subscriber.isMessageReceived(), "登录消息应该被处理");
|
|
|
|
+
|
|
|
|
+ // 调用其他方法不应该影响登录消息的状态
|
|
|
|
+ subscriber.handleTestDeviceKeepAlive(keepaliveTopic, message);
|
|
|
|
+ subscriber.handleTestAppCommand(commandTopic, message);
|
|
|
|
+
|
|
|
|
+ // 验证消息状态仍然正确
|
|
|
|
+ assertEquals(loginTopic, subscriber.getLastReceivedTopic(), "最后接收的主题应该仍是登录主题");
|
|
|
|
+
|
|
|
|
+ System.out.println("✓ 消息隔离测试通过");
|
|
|
|
+ }
|
|
|
|
+}
|