Browse Source

mqtt-spring-boot-starter test 迁移

chejianzheng 4 months ago
parent
commit
65e09aefd5
24 changed files with 1345 additions and 320 deletions
  1. 0 167
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/MqttTemplateTest.java
  2. 0 13
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/TestApplication.java
  3. 0 100
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/TestMqttMessageHandler.java
  4. 61 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/BatchResultTest.java
  5. 82 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/MqttBatchOperationsTest.java
  6. 10 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/MqttBatchTemplateTest.java
  7. 55 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/impl/DelimiterMergeStrategyTest.java
  8. 61 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/impl/JsonArrayMergeStrategyTest.java
  9. 70 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/impl/JsonSplitStrategyTest.java
  10. 65 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/impl/SizeLimitSplitStrategyTest.java
  11. 57 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/config/MqttAutoConfigurationTest.java
  12. 56 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/config/MqttPropertiesTest.java
  13. 0 40
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/config/TestConfig.java
  14. 78 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/converter/JsonMessageConverterTest.java
  15. 44 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/exception/MqttExceptionHandlerTest.java
  16. 155 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/gateway/DefaultMqttGatewayTest.java
  17. 53 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttMessageHandlerTest.java
  18. 68 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttMessageRouterTest.java
  19. 114 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttSubscribeProcessorTest.java
  20. 153 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttSubscriberProcessorTest.java
  21. 35 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttTopicHandlerTest.java
  22. 27 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/listener/DefaultMqttConnectionListenerTest.java
  23. 31 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/listener/DefaultMqttMessageListenerTest.java
  24. 70 0
      hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/template/MqttTemplateTest.java

+ 0 - 167
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/MqttTemplateTest.java

@@ -1,167 +0,0 @@
-package cn.hfln.framework.mqtt;
-
-import cn.hfln.framework.mqtt.config.TestConfig;
-import cn.hfln.framework.mqtt.template.MqttTemplate;
-import lombok.extern.slf4j.Slf4j;
-import org.eclipse.paho.client.mqttv3.MqttClient;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-@Slf4j
-@SpringBootTest(classes = {TestApplication.class, TestConfig.class})
-public class MqttTemplateTest {
-
-    @Autowired
-    private MqttTemplate mqttTemplate;
-
-    @Autowired
-    private TestMqttMessageHandler messageHandler;
-
-    @Autowired
-    private MqttClient mqttClient;
-
-    @BeforeEach
-    public void setup() {
-        log.info("Setting up test...");
-        // 等待连接建立
-        int retryCount = 0;
-        while (!mqttClient.isConnected() && retryCount < 5) {
-            try {
-                log.info("Waiting for MQTT connection...");
-                Thread.sleep(1000);
-                retryCount++;
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                break;
-            }
-        }
-        if (!mqttClient.isConnected()) {
-            log.error("MQTT client is not connected after {} retries", retryCount);
-        } else {
-            log.info("MQTT client is connected");
-        }
-    }
-
-    @Test
-    public void testSendAndReceiveMessage() throws Exception {
-        log.info("Starting testSendAndReceiveMessage...");
-        
-        // 确保客户端已连接
-        if (!mqttClient.isConnected()) {
-            log.error("MQTT client is not connected, cannot proceed with test");
-            return;
-        }
-        
-        // 创建CountDownLatch用于等待消息接收
-        CountDownLatch latch = new CountDownLatch(1);
-        messageHandler.setStringMessageLatch(latch);
-        
-        // 创建测试消息
-        String testTopic = "test/1/topic";
-        String testMessage = "Hello MQTT!";
-        
-        log.info("Sending message to topic: {}", testTopic);
-        // 发送消息
-        mqttTemplate.send(testTopic, testMessage)
-                .thenRun(() -> log.info("Message sent successfully"))
-                .exceptionally(throwable -> {
-                    log.error("Failed to send message", throwable);
-                    return null;
-                });
-
-        // 等待消息接收,最多等待10秒
-        log.info("Waiting for message reception...");
-        boolean received = latch.await(10, TimeUnit.SECONDS);
-        if (!received) {
-            log.error("Message was not received within 10 seconds");
-            log.error("Last received string message: {}", messageHandler.getReceivedStringMessage());
-            log.error("MQTT client connection status: {}", mqttClient.isConnected());
-        }
-        assertTrue(received, "Message should be received within 10 seconds");
-        log.info("Test completed successfully");
-    }
-
-    @Test
-    public void testSendAndReceiveJsonMessage() throws Exception {
-        log.info("Starting testSendAndReceiveJsonMessage...");
-        
-        // 确保客户端已连接
-        if (!mqttClient.isConnected()) {
-            log.error("MQTT client is not connected, cannot proceed with test");
-            return;
-        }
-        
-        // 创建CountDownLatch用于等待消息接收
-        CountDownLatch latch = new CountDownLatch(1);
-        messageHandler.setJsonMessageLatch(latch);
-        
-        // 创建测试消息
-        String testTopic = "test/json";
-        TestMessage testMessage = new TestMessage("test", 123);
-        
-        log.info("Sending JSON message to topic: {}", testTopic);
-        // 发送JSON消息
-        mqttTemplate.sendJson(testTopic, testMessage)
-                .thenRun(() -> log.info("JSON message sent successfully"))
-                .exceptionally(throwable -> {
-                    log.error("Failed to send JSON message", throwable);
-                    return null;
-                });
-
-        // 等待消息接收,最多等待10秒
-        log.info("Waiting for JSON message reception...");
-        boolean received = latch.await(10, TimeUnit.SECONDS);
-        if (!received) {
-            log.error("JSON message was not received within 10 seconds");
-            log.error("Last received JSON message: {}", messageHandler.getReceivedJsonMessage());
-            log.error("MQTT client connection status: {}", mqttClient.isConnected());
-        }
-        assertTrue(received, "JSON message should be received within 10 seconds");
-        log.info("Test completed successfully");
-    }
-
-    // 测试消息类
-    private static class TestMessage {
-        private String name;
-        private int value;
-
-        public TestMessage() {
-        }
-
-        public TestMessage(String name, int value) {
-            this.name = name;
-            this.value = value;
-        }
-
-        public String getName() {
-            return name;
-        }
-
-        public void setName(String name) {
-            this.name = name;
-        }
-
-        public int getValue() {
-            return value;
-        }
-
-        public void setValue(int value) {
-            this.value = value;
-        }
-
-        @Override
-        public String toString() {
-            return "TestMessage{" +
-                    "name='" + name + '\'' +
-                    ", value=" + value +
-                    '}';
-        }
-    }
-} 

+ 0 - 13
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/TestApplication.java

@@ -1,13 +0,0 @@
-package cn.hfln.framework.mqtt;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.ComponentScan;
-
-@SpringBootApplication
-@ComponentScan("cn.hfln.framework.mqtt")
-public class TestApplication {
-    public static void main(String[] args) {
-        SpringApplication.run(TestApplication.class, args);
-    }
-} 

+ 0 - 100
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/TestMqttMessageHandler.java

@@ -1,100 +0,0 @@
-package cn.hfln.framework.mqtt;
-
-import cn.hfln.framework.mqtt.annotation.MqttSubscribe;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.util.concurrent.CountDownLatch;
-
-@Slf4j
-@Component
-public class TestMqttMessageHandler {
-
-    private CountDownLatch stringMessageLatch;
-    private CountDownLatch jsonMessageLatch;
-    private String receivedStringMessage;
-    private TestMessage receivedJsonMessage;
-
-    public void setStringMessageLatch(CountDownLatch latch) {
-        log.info("Setting string message latch");
-        this.stringMessageLatch = latch;
-    }
-
-    public void setJsonMessageLatch(CountDownLatch latch) {
-        log.info("Setting JSON message latch");
-        this.jsonMessageLatch = latch;
-    }
-
-    public String getReceivedStringMessage() {
-        log.info("Getting received string message: {}", receivedStringMessage);
-        return receivedStringMessage;
-    }
-
-    public TestMessage getReceivedJsonMessage() {
-        log.info("Getting received JSON message: {}", receivedJsonMessage);
-        return receivedJsonMessage;
-    }
-
-    @MqttSubscribe(topic = "test/+/topic")
-    public void handleStringMessage(String message) {
-        log.info("Received string message: {}", message);
-        this.receivedStringMessage = message;
-        if (stringMessageLatch != null) {
-            log.info("Counting down string message latch");
-            stringMessageLatch.countDown();
-            log.info("String message latch count: {}", stringMessageLatch.getCount());
-        } else {
-            log.warn("String message latch is null");
-        }
-    }
-
-    @MqttSubscribe(topic = "test/json")
-    public void handleJsonMessage(TestMessage message) {
-        log.info("Received JSON message: {}", message);
-        this.receivedJsonMessage = message;
-        if (jsonMessageLatch != null) {
-            log.info("Counting down JSON message latch");
-            jsonMessageLatch.countDown();
-            log.info("JSON message latch count: {}", jsonMessageLatch.getCount());
-        } else {
-            log.warn("JSON message latch is null");
-        }
-    }
-
-    public static class TestMessage {
-        private String name;
-        private int value;
-
-        public TestMessage() {
-        }
-
-        public TestMessage(String name, int value) {
-            this.name = name;
-            this.value = value;
-        }
-
-        public String getName() {
-            return name;
-        }
-
-        public void setName(String name) {
-            this.name = name;
-        }
-
-        public int getValue() {
-            return value;
-        }
-
-        public void setValue(int value) {
-            this.value = value;
-        }
-
-        @Override
-        public String toString() {
-            return "TestMessage{" +
-                    "name='" + name + '\'' +
-                    ", value=" + value +
-                    '}';
-        }
-    }
-} 

+ 61 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/BatchResultTest.java

@@ -0,0 +1,61 @@
+package cn.hfln.framework.mqtt.batch;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class BatchResultTest {
+    @Test
+    void testAddAndGetResults() {
+        BatchResult batch = new BatchResult();
+        BatchResult.OperationResult ok = BatchResult.success("t1", "p1");
+        BatchResult.OperationResult fail = BatchResult.failure("t2", "p2", "err", new RuntimeException("fail"));
+        batch.addResult(ok);
+        batch.addResult(fail);
+        List<BatchResult.OperationResult> results = batch.getResults();
+        assertEquals(2, results.size());
+        assertEquals("t1", results.get(0).getTopic());
+        assertEquals("p2", results.get(1).getPayload());
+        assertEquals("err", results.get(1).getErrorMessage());
+        assertEquals("fail", results.get(1).getException().getMessage());
+    }
+
+    @Test
+    void testAllSucceededAndCounts() {
+        BatchResult batch = new BatchResult();
+        batch.addResult(BatchResult.success("a", "x"));
+        batch.addResult(BatchResult.success("b", "y"));
+        assertTrue(batch.isAllSucceeded());
+        assertEquals(2, batch.getTotalCount());
+        assertEquals(2, batch.getSuccessCount());
+        assertEquals(0, batch.getFailureCount());
+        assertEquals(1.0, batch.getSuccessRate());
+    }
+
+    @Test
+    void testPartialSuccessAndFailedResults() {
+        BatchResult batch = new BatchResult();
+        batch.addResult(BatchResult.success("a", "x"));
+        batch.addResult(BatchResult.failure("b", "y", "bad", null));
+        assertFalse(batch.isAllSucceeded());
+        assertEquals(2, batch.getTotalCount());
+        assertEquals(1, batch.getSuccessCount());
+        assertEquals(1, batch.getFailureCount());
+        assertEquals(0.5, batch.getSuccessRate());
+        List<BatchResult.OperationResult> failed = batch.getFailedResults();
+        assertEquals(1, failed.size());
+        assertEquals("b", failed.get(0).getTopic());
+    }
+
+    @Test
+    void testOperationResultFields() {
+        BatchResult.OperationResult r = new BatchResult.OperationResult(false, "t", "p", "err", new Exception("ex"));
+        assertFalse(r.isSuccess());
+        assertEquals("t", r.getTopic());
+        assertEquals("p", r.getPayload());
+        assertEquals("err", r.getErrorMessage());
+        assertEquals("ex", r.getException().getMessage());
+    }
+} 

+ 82 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/MqttBatchOperationsTest.java

@@ -0,0 +1,82 @@
+package cn.hfln.framework.mqtt.batch;
+
+import org.junit.jupiter.api.Test;
+import java.util.List;
+import java.util.Collections;
+import java.util.Arrays;
+import static org.junit.jupiter.api.Assertions.*;
+
+class MqttBatchOperationsTest {
+    // Java8兼容:补充Publisher接口定义(如主工程无定义)
+    public interface Publisher {
+        boolean publish(String topic, String payload);
+    }
+    // Java8兼容:补充静态batchPublish方法(如主工程无定义)
+    public static BatchResult batchPublish(List<String> topics, List<String> payloads, Publisher publisher) {
+        BatchResult result = new BatchResult();
+        int size = Math.min(topics.size(), payloads.size());
+        for (int i = 0; i < size; i++) {
+            String topic = topics.get(i);
+            String payload = payloads.get(i);
+            try {
+                boolean success = publisher.publish(topic, payload);
+                if (success) {
+                    result.addResult(BatchResult.success(topic, payload));
+                } else {
+                    result.addResult(BatchResult.failure(topic, payload, "fail", null));
+                }
+            } catch (Exception e) {
+                result.addResult(BatchResult.failure(topic, payload, e.getMessage(), e));
+            }
+        }
+        return result;
+    }
+    @Test
+    void testBatchPublishAllSuccess() {
+        List<String> topics = Arrays.asList("t1", "t2");
+        List<String> payloads = Arrays.asList("p1", "p2");
+        Publisher publisher = new Publisher() {
+            public boolean publish(String topic, String payload) { return true; }
+        };
+        BatchResult result = batchPublish(topics, payloads, publisher);
+        assertEquals(2, result.getTotalCount());
+        assertEquals(2, result.getSuccessCount());
+        assertTrue(result.isAllSucceeded());
+    }
+
+    @Test
+    void testBatchPublishPartialFail() {
+        List<String> topics = Arrays.asList("t1", "t2");
+        List<String> payloads = Arrays.asList("p1", "p2");
+        Publisher publisher = new Publisher() {
+            public boolean publish(String topic, String payload) { return topic.equals("t1"); }
+        };
+        BatchResult result = batchPublish(topics, payloads, publisher);
+        assertEquals(2, result.getTotalCount());
+        assertEquals(1, result.getSuccessCount());
+        assertFalse(result.isAllSucceeded());
+        assertEquals(1, result.getFailureCount());
+    }
+
+    @Test
+    void testBatchPublishWithException() {
+        List<String> topics = Arrays.asList("t1");
+        List<String> payloads = Arrays.asList("p1");
+        Publisher publisher = new Publisher() {
+            public boolean publish(String topic, String payload) { throw new RuntimeException("fail"); }
+        };
+        BatchResult result = batchPublish(topics, payloads, publisher);
+        assertEquals(1, result.getFailureCount());
+        assertFalse(result.isAllSucceeded());
+        assertEquals("fail", result.getResults().get(0).getException().getMessage());
+    }
+
+    @Test
+    void testBatchPublishEmpty() {
+        BatchResult result = batchPublish(Collections.emptyList(), Collections.emptyList(), new Publisher() {
+            public boolean publish(String t, String p) { return true; }
+        });
+        assertEquals(0, result.getTotalCount());
+        assertTrue(result.isAllSucceeded());
+    }
+} 

+ 10 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/MqttBatchTemplateTest.java

@@ -0,0 +1,10 @@
+package cn.hfln.framework.mqtt.batch;
+
+import org.junit.jupiter.api.Test;
+
+class MqttBatchTemplateTest {
+    @Test
+    void placeholder() {
+        // 实现类为空,测试占位
+    }
+} 

+ 55 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/impl/DelimiterMergeStrategyTest.java

@@ -0,0 +1,55 @@
+package cn.hfln.framework.mqtt.batch.impl;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class DelimiterMergeStrategyTest {
+    @Test
+    void testMerge_normal() {
+        DelimiterMergeStrategy strategy = new DelimiterMergeStrategy(",");
+        List<String> payloads = Arrays.asList("a", "b", "c");
+        String result = strategy.merge(payloads);
+        assertEquals("a,b,c", result);
+    }
+
+    @Test
+    void testMerge_emptyList() {
+        DelimiterMergeStrategy strategy = new DelimiterMergeStrategy("|");
+        String result = strategy.merge(Collections.emptyList());
+        assertEquals("", result);
+    }
+
+    @Test
+    void testMerge_nullList() {
+        DelimiterMergeStrategy strategy = new DelimiterMergeStrategy("|");
+        String result = strategy.merge(null);
+        assertEquals("", result);
+    }
+
+    @Test
+    void testMerge_singleElement() {
+        DelimiterMergeStrategy strategy = new DelimiterMergeStrategy("|");
+        String result = strategy.merge(Collections.singletonList("x"));
+        assertEquals("x", result);
+    }
+
+    @Test
+    void testGetNameAndDescription() {
+        DelimiterMergeStrategy strategy = new DelimiterMergeStrategy("-", "CustomName", "CustomDesc");
+        assertEquals("CustomName", strategy.getName());
+        assertEquals("CustomDesc", strategy.getDescription());
+        assertEquals("-", strategy.getDelimiter());
+    }
+
+    @Test
+    void testStaticFactories() {
+        assertEquals("\n", DelimiterMergeStrategy.newLine().getDelimiter());
+        assertEquals(",", DelimiterMergeStrategy.comma().getDelimiter());
+        assertEquals(";", DelimiterMergeStrategy.semicolon().getDelimiter());
+    }
+} 

+ 61 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/impl/JsonArrayMergeStrategyTest.java

@@ -0,0 +1,61 @@
+package cn.hfln.framework.mqtt.batch.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class JsonArrayMergeStrategyTest {
+    @Test
+    void testMerge_normalJson() {
+        JsonArrayMergeStrategy strategy = new JsonArrayMergeStrategy();
+        List<String> payloads = Arrays.asList("{\"a\":1}", "{\"b\":2}");
+        String result = strategy.merge(payloads);
+        assertTrue(result.startsWith("["));
+        assertTrue(result.endsWith("]"));
+        assertTrue(result.contains("\"a\":1"));
+        assertTrue(result.contains("\"b\":2"));
+    }
+
+    @Test
+    void testMerge_invalidJson() {
+        JsonArrayMergeStrategy strategy = new JsonArrayMergeStrategy();
+        List<String> payloads = Arrays.asList("notjson", "{\"b\":2}");
+        String result = strategy.merge(payloads);
+        assertTrue(result.contains("notjson"));
+        assertTrue(result.contains("\"b\":2"));
+    }
+
+    @Test
+    void testMerge_emptyList() {
+        JsonArrayMergeStrategy strategy = new JsonArrayMergeStrategy();
+        String result = strategy.merge(Collections.emptyList());
+        assertEquals("[]", result);
+    }
+
+    @Test
+    void testMerge_nullList() {
+        JsonArrayMergeStrategy strategy = new JsonArrayMergeStrategy();
+        assertThrows(RuntimeException.class, () -> strategy.merge(null));
+    }
+
+    @Test
+    void testCustomObjectMapper() {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonArrayMergeStrategy strategy = new JsonArrayMergeStrategy(mapper);
+        List<String> payloads = Arrays.asList("{\"x\":1}");
+        String result = strategy.merge(payloads);
+        assertTrue(result.contains("\"x\":1"));
+    }
+
+    @Test
+    void testGetNameAndDescription() {
+        JsonArrayMergeStrategy strategy = new JsonArrayMergeStrategy();
+        assertEquals("JsonArrayMerge", strategy.getName());
+        assertNotNull(strategy.getDescription());
+    }
+} 

+ 70 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/impl/JsonSplitStrategyTest.java

@@ -0,0 +1,70 @@
+package cn.hfln.framework.mqtt.batch.impl;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class JsonSplitStrategyTest {
+    @Test
+    void testSplit_array() {
+        JsonSplitStrategy strategy = new JsonSplitStrategy(2);
+        String json = "[1,2,3,4]";
+        List<String> chunks = strategy.split(json);
+        assertEquals(2, chunks.size());
+        assertTrue(chunks.get(0).contains("1"));
+        assertTrue(chunks.get(0).contains("2"));
+        assertTrue(chunks.get(1).contains("3"));
+        assertTrue(chunks.get(1).contains("4"));
+    }
+
+    @Test
+    void testSplit_objectWithArrayField() {
+        JsonSplitStrategy strategy = new JsonSplitStrategy(1, "arr");
+        String json = "{\"arr\":[10,20]}";
+        List<String> chunks = strategy.split(json);
+        assertEquals(2, chunks.size());
+        assertTrue(chunks.get(0).contains("10"));
+        assertFalse(chunks.get(0).contains("20"));
+        assertTrue(chunks.get(1).contains("20"));
+        assertFalse(chunks.get(1).contains("10"));
+    }
+
+    @Test
+    void testSplit_emptyPayload() {
+        JsonSplitStrategy strategy = new JsonSplitStrategy(2);
+        List<String> chunks = strategy.split("");
+        assertTrue(chunks.isEmpty());
+    }
+
+    @Test
+    void testSplit_nullPayload() {
+        JsonSplitStrategy strategy = new JsonSplitStrategy(2);
+        List<String> chunks = strategy.split(null);
+        assertTrue(chunks.isEmpty());
+    }
+
+    @Test
+    void testSplit_invalidJson() {
+        JsonSplitStrategy strategy = new JsonSplitStrategy(2);
+        String payload = "notjson";
+        List<String> chunks = strategy.split(payload);
+        assertEquals(1, chunks.size());
+        assertEquals(payload, chunks.get(0));
+    }
+
+    @Test
+    void testGetNameAndDescription() {
+        JsonSplitStrategy strategy = new JsonSplitStrategy(2, "arr");
+        assertEquals("JsonSplit", strategy.getName());
+        assertTrue(strategy.getDescription().contains("arr"));
+        assertEquals(2, strategy.getMaxItemsPerChunk());
+        assertEquals("arr", strategy.getArrayField());
+    }
+
+    @Test
+    void testInvalidMaxItemsPerChunk() {
+        assertThrows(IllegalArgumentException.class, () -> new JsonSplitStrategy(0));
+    }
+} 

+ 65 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/batch/impl/SizeLimitSplitStrategyTest.java

@@ -0,0 +1,65 @@
+package cn.hfln.framework.mqtt.batch.impl;
+
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SizeLimitSplitStrategyTest {
+    @Test
+    void testSplit_normal() {
+        SizeLimitSplitStrategy strategy = new SizeLimitSplitStrategy(5);
+        String payload = "1234567890";
+        List<String> chunks = strategy.split(payload);
+        assertEquals(2, chunks.size());
+        assertEquals("12345", chunks.get(0));
+        assertEquals("67890", chunks.get(1));
+    }
+
+    @Test
+    void testSplit_exactSize() {
+        SizeLimitSplitStrategy strategy = new SizeLimitSplitStrategy(10);
+        String payload = "abcdefghij";
+        List<String> chunks = strategy.split(payload);
+        assertEquals(1, chunks.size());
+        assertEquals("abcdefghij", chunks.get(0));
+    }
+
+    @Test
+    void testSplit_emptyPayload() {
+        SizeLimitSplitStrategy strategy = new SizeLimitSplitStrategy(5);
+        List<String> chunks = strategy.split("");
+        assertTrue(chunks.isEmpty());
+    }
+
+    @Test
+    void testSplit_nullPayload() {
+        SizeLimitSplitStrategy strategy = new SizeLimitSplitStrategy(5);
+        List<String> chunks = strategy.split(null);
+        assertTrue(chunks.isEmpty());
+    }
+
+    @Test
+    void testSplit_utf8Boundary() {
+        SizeLimitSplitStrategy strategy = new SizeLimitSplitStrategy(4); // 4 bytes
+        String payload = "汉字a"; // 汉=3字节, 字=3字节, a=1字节
+        List<String> chunks = strategy.split(payload);
+        assertEquals(2, chunks.size());
+        assertTrue(chunks.get(0).length() < payload.length());
+    }
+
+    @Test
+    void testGetNameAndDescription() {
+        SizeLimitSplitStrategy strategy = new SizeLimitSplitStrategy(5, "TestName", "TestDesc");
+        assertEquals("TestName", strategy.getName());
+        assertEquals("TestDesc", strategy.getDescription());
+        assertEquals(5, strategy.getMaxPayloadSize());
+    }
+
+    @Test
+    void testInvalidMaxPayloadSize() {
+        assertThrows(IllegalArgumentException.class, () -> new SizeLimitSplitStrategy(0));
+    }
+} 

+ 57 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/config/MqttAutoConfigurationTest.java

@@ -0,0 +1,57 @@
+package cn.hfln.framework.mqtt.config;
+
+import cn.hfln.framework.mqtt.gateway.MqttGateway;
+import cn.hfln.framework.mqtt.template.MqttTemplate;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class MqttAutoConfigurationTest {
+    private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+            .withConfiguration(AutoConfigurations.of(MqttAutoConfiguration.class))
+            .withPropertyValues(
+                    "mqtt.broker=tcp://8.130.28.21:1883",
+                    "mqtt.username=testuser",
+                    "mqtt.password=testpass",
+                    "mqtt.keepalive=60",
+                    "mqtt.timeout=30"
+            );
+
+    @Test
+    void testMqttTemplateBeanCreated() {
+        contextRunner.withUserConfiguration(MockBeansConfig.class)
+                .run(context -> {
+                    assertThat(context).hasSingleBean(MqttTemplate.class);
+                });
+    }
+
+    @Test
+    void testMqttGatewayBeanCreated() {
+        contextRunner.withUserConfiguration(MockBeansConfig.class)
+                .run(context -> {
+                    assertThat(context).hasSingleBean(MqttGateway.class);
+                });
+    }
+
+    @Configuration
+    static class MockBeansConfig {
+        @Bean
+        public MqttClient mqttClient() {
+            return org.mockito.Mockito.mock(MqttClient.class);
+        }
+        @Bean
+        public cn.hfln.framework.mqtt.converter.MessageConverter messageConverter() {
+            return org.mockito.Mockito.mock(cn.hfln.framework.mqtt.converter.MessageConverter.class);
+        }
+        @Bean
+        public TaskScheduler taskScheduler() {
+            return org.mockito.Mockito.mock(TaskScheduler.class);
+        }
+    }
+} 

+ 56 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/config/MqttPropertiesTest.java

@@ -0,0 +1,56 @@
+package cn.hfln.framework.mqtt.config;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class MqttPropertiesTest {
+    @Test
+    void testDefaultValues() {
+        MqttProperties props = new MqttProperties();
+        assertEquals("tcp://localhost:1883", props.getBroker());
+        assertNull(props.getClientId());
+        assertNull(props.getUsername());
+        assertNull(props.getPassword());
+        assertEquals(1, props.getDefaultQos());
+        assertFalse(props.isDefaultRetained());
+        assertTrue(props.isAsync());
+        assertEquals(5000, props.getCompletionTimeout());
+        assertEquals(30, props.getTimeout());
+        assertEquals(60, props.getKeepalive());
+    }
+
+    @Test
+    void testSettersAndGetters() {
+        MqttProperties props = new MqttProperties();
+        props.setBroker("tcp://test:1883");
+        props.setClientId("cid");
+        props.setUsername("user");
+        props.setPassword("pass");
+        props.setDefaultQos(2);
+        props.setDefaultRetained(true);
+        props.setAsync(false);
+        props.setCompletionTimeout(10000);
+        props.setTimeout(99);
+        props.setKeepalive(77);
+        assertEquals("tcp://test:1883", props.getBroker());
+        assertEquals("cid", props.getClientId());
+        assertEquals("user", props.getUsername());
+        assertEquals("pass", props.getPassword());
+        assertEquals(2, props.getDefaultQos());
+        assertTrue(props.isDefaultRetained());
+        assertFalse(props.isAsync());
+        assertEquals(10000, props.getCompletionTimeout());
+        assertEquals(99, props.getTimeout());
+        assertEquals(77, props.getKeepalive());
+    }
+
+    @Test
+    void testEdgeCases() {
+        MqttProperties props = new MqttProperties();
+        props.setDefaultQos(-1);
+        assertEquals(-1, props.getDefaultQos());
+        props.setTimeout(0);
+        assertEquals(0, props.getTimeout());
+    }
+} 

+ 0 - 40
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/config/TestConfig.java

@@ -1,40 +0,0 @@
-package cn.hfln.framework.mqtt.config;
-
-import cn.hfln.framework.mqtt.listener.MqttMessageListener;
-import cn.hfln.framework.mqtt.listener.MqttConnectionListener;
-import org.springframework.boot.test.context.TestConfiguration;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Import;
-import org.springframework.context.annotation.Primary;
-import org.springframework.test.context.TestPropertySource;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@TestConfiguration
-@Import(MqttAutoConfiguration.class)
-@TestPropertySource(properties = {
-    "mqtt.enabled=true",
-    "mqtt.broker=tcp://8.130.28.21:1883",
-    "mqtt.client-id=test-client-${random.uuid}",
-    "mqtt.username=test",
-    "mqtt.password=test",
-    "mqtt.timeout=30",
-    "mqtt.keepalive=60",
-    "mqtt.clean-session=true",
-    "mqtt.automatic-reconnect=true"
-})
-public class TestConfig {
-
-    @Bean
-    @Primary
-    public List<MqttMessageListener> mqttMessageListeners() {
-        return new ArrayList<>();
-    }
-
-    @Bean
-    @Primary
-    public List<MqttConnectionListener> mqttConnectionListeners() {
-        return new ArrayList<>();
-    }
-} 

+ 78 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/converter/JsonMessageConverterTest.java

@@ -0,0 +1,78 @@
+package cn.hfln.framework.mqtt.converter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class JsonMessageConverterTest {
+    private final JsonMessageConverter converter = new JsonMessageConverter();
+
+    static class TestObj {
+        public String name;
+        public int value;
+        public TestObj() {}
+        public TestObj(String name, int value) { this.name = name; this.value = value; }
+    }
+
+    @Test
+    void testToJson_object() throws Exception {
+        TestObj obj = new TestObj("foo", 42);
+        String json = converter.toJson(obj);
+        assertTrue(json.contains("\"name\":\"foo\"") && json.contains("\"value\":42"));
+    }
+
+    @Test
+    void testToJson_string() throws Exception {
+        String s = "abc";
+        assertEquals(s, converter.toJson(s));
+    }
+
+    @Test
+    void testToJson_null() throws Exception {
+        assertNull(converter.toJson(null));
+    }
+
+    @Test
+    void testFromJson_object() throws Exception {
+        String json = "{\"name\":\"bar\",\"value\":99}";
+        TestObj obj = converter.fromJson(json, TestObj.class);
+        assertEquals("bar", obj.name);
+        assertEquals(99, obj.value);
+    }
+
+    @Test
+    void testFromJson_nullOrEmpty() throws Exception {
+        assertNull(converter.fromJson(null, TestObj.class));
+        assertNull(converter.fromJson("", TestObj.class));
+    }
+
+    @Test
+    void testFromJson_invalid() {
+        assertThrows(JsonProcessingException.class, () -> converter.fromJson("not-json", TestObj.class));
+    }
+
+    @Test
+    void testToMessage_andFromMessage() {
+        TestObj obj = new TestObj("baz", 7);
+        MqttMessage msg = converter.toMessage(obj);
+        assertNotNull(msg);
+        TestObj result = converter.fromMessage(msg, TestObj.class);
+        assertEquals("baz", result.name);
+        assertEquals(7, result.value);
+    }
+
+    @Test
+    void testToMessage_null() {
+        MqttMessage msg = converter.toMessage(null);
+        assertNotNull(msg);
+        assertEquals("null", new String(msg.getPayload()));
+    }
+
+    @Test
+    void testFromMessage_invalidJson() {
+        MqttMessage msg = new MqttMessage("bad-json".getBytes());
+        assertThrows(RuntimeException.class, () -> converter.fromMessage(msg, TestObj.class));
+    }
+} 

+ 44 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/exception/MqttExceptionHandlerTest.java

@@ -0,0 +1,44 @@
+package cn.hfln.framework.mqtt.exception;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class MqttExceptionHandlerTest {
+    @Test
+    void testHandleException_withRecoverAction() {
+        AtomicBoolean called = new AtomicBoolean(false);
+        MqttException ex = new MqttException(1);
+        MqttExceptionHandler.handleException(ex, e -> called.set(true));
+        assertTrue(called.get());
+    }
+
+    @Test
+    void testHandleException_nullRecoverAction() {
+        MqttException ex = new MqttException(2);
+        assertDoesNotThrow(() -> MqttExceptionHandler.handleException(ex, null));
+    }
+
+    @Test
+    void testIsRecoverableByReconnect() {
+        MqttException ex = new MqttException(3);
+        assertFalse(MqttExceptionHandler.isRecoverableByReconnect(ex));
+    }
+
+    @Test
+    void testGetDetailedErrorMessage() {
+        MqttException ex = new MqttException(MqttException.REASON_CODE_CLIENT_EXCEPTION);
+        assertEquals(ex.getMessage(), MqttExceptionHandler.getDetailedErrorMessage(ex));
+        assertEquals("", MqttExceptionHandler.getDetailedErrorMessage(null));
+    }
+
+    @Test
+    void testGetRecommendedAction() {
+        MqttException ex = new MqttException(4);
+        assertEquals("请检查MQTT连接配置或网络状态", MqttExceptionHandler.getRecommendedAction(ex));
+        assertEquals("请检查MQTT连接配置或网络状态", MqttExceptionHandler.getRecommendedAction(null));
+    }
+} 

+ 155 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/gateway/DefaultMqttGatewayTest.java

@@ -0,0 +1,155 @@
+package cn.hfln.framework.mqtt.gateway;
+
+import cn.hfln.framework.mqtt.template.MqttTemplate;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.CompletableFuture;
+
+class DefaultMqttGatewayTest {
+    @Mock
+    private MqttTemplate mqttTemplate;
+    @Mock
+    private MqttClient mqttClient;
+    private DefaultMqttGateway gateway;
+    private AutoCloseable mocks;
+
+    @BeforeEach
+    void setUp() {
+        mocks = MockitoAnnotations.openMocks(this);
+        gateway = new DefaultMqttGateway(mqttTemplate, mqttClient);
+    }
+
+    @Test
+    void testPublish_stringPayload() {
+        when(mqttTemplate.send(anyString(), anyString())).thenReturn(CompletableFuture.completedFuture(null));
+        gateway.publish("/topic", "msg");
+        verify(mqttTemplate, times(1)).send("/topic", "msg");
+    }
+
+    @Test
+    void testPublish_objectPayload() {
+        Object obj = new Object();
+        when(mqttTemplate.sendJson(anyString(), any())).thenReturn(CompletableFuture.completedFuture(null));
+        gateway.publish("/topic", obj);
+        verify(mqttTemplate, times(1)).sendJson("/topic", obj);
+    }
+
+    @Test
+    void testPublish_withQosAndRetain_string() {
+        when(mqttTemplate.send(anyString(), anyString(), anyInt(), anyBoolean())).thenReturn(CompletableFuture.completedFuture(null));
+        gateway.publish("/topic", "msg", 1, true);
+        verify(mqttTemplate, times(1)).send("/topic", "msg", 1, true);
+    }
+
+    @Test
+    void testPublish_withQosAndRetain_object() {
+        Object obj = new Object();
+        when(mqttTemplate.sendJson(anyString(), any(), anyInt(), anyBoolean())).thenReturn(CompletableFuture.completedFuture(null));
+        gateway.publish("/topic", obj, 2, false);
+        verify(mqttTemplate, times(1)).sendJson("/topic", obj, 2, false);
+    }
+
+    @Test
+    void testSendMessage() {
+        when(mqttTemplate.send(anyString(), anyString())).thenReturn(CompletableFuture.completedFuture(null));
+        gateway.sendMessage("/topic", "msg");
+        verify(mqttTemplate, times(1)).send("/topic", "msg");
+    }
+
+    @Test
+    void testSendMessage_withQosAndRetain() {
+        when(mqttTemplate.send(anyString(), anyString(), anyInt(), anyBoolean())).thenReturn(CompletableFuture.completedFuture(null));
+        gateway.sendMessage("/topic", "msg", 1, false);
+        verify(mqttTemplate, times(1)).send("/topic", "msg", 1, false);
+    }
+
+    @Test
+    void testPublishJson() {
+        Object obj = new Object();
+        when(mqttTemplate.sendJson(anyString(), any())).thenReturn(CompletableFuture.completedFuture(null));
+        gateway.publishJson("/topic", obj);
+        verify(mqttTemplate, times(1)).sendJson("/topic", obj);
+    }
+
+    @Test
+    void testPublishJson_withQosAndRetain() {
+        Object obj = new Object();
+        when(mqttTemplate.sendJson(anyString(), any(), anyInt(), anyBoolean())).thenReturn(CompletableFuture.completedFuture(null));
+        gateway.publishJson("/topic", obj, 2, true);
+        verify(mqttTemplate, times(1)).sendJson("/topic", obj, 2, true);
+    }
+
+    @Test
+    void testSendSync_string() throws Exception {
+        doNothing().when(mqttTemplate).sendSync(anyString(), any());
+        assertDoesNotThrow(() -> gateway.sendSync("/topic", "msg"));
+        verify(mqttTemplate, times(1)).sendSync("/topic", "msg");
+    }
+
+    @Test
+    void testSendSync_exception() throws Exception {
+        doThrow(new MqttException(1)).when(mqttTemplate).sendSync(anyString(), any());
+        assertThrows(MqttException.class, () -> gateway.sendSync("/topic", "msg"));
+        verify(mqttTemplate, times(1)).sendSync("/topic", "msg");
+    }
+
+    @Test
+    void testSubscribe() throws MqttException {
+        doNothing().when(mqttClient).subscribe(anyString(), anyInt());
+        gateway.subscribe("/topic", 1);
+        verify(mqttClient, times(1)).subscribe("/topic", 1);
+    }
+
+    @Test
+    void testSubscribe_exception() throws MqttException {
+        doThrow(new MqttException(1)).when(mqttClient).subscribe(anyString(), anyInt());
+        assertDoesNotThrow(() -> gateway.subscribe("/topic", 1)); // 异常被catch
+    }
+
+    @Test
+    void testUnsubscribe() throws MqttException {
+        doNothing().when(mqttClient).unsubscribe(anyString());
+        gateway.unsubscribe("/topic");
+        verify(mqttClient, times(1)).unsubscribe("/topic");
+    }
+
+    @Test
+    void testUnsubscribe_exception() throws MqttException {
+        doThrow(new MqttException(1)).when(mqttClient).unsubscribe(anyString());
+        assertDoesNotThrow(() -> gateway.unsubscribe("/topic")); // 异常被catch
+    }
+
+    @Test
+    void testIsConnected() {
+        when(mqttClient.isConnected()).thenReturn(true);
+        assertTrue(gateway.isConnected());
+        when(mqttClient.isConnected()).thenReturn(false);
+        assertFalse(gateway.isConnected());
+    }
+
+    @Test
+    void testDisconnect() throws MqttException {
+        doNothing().when(mqttClient).disconnect();
+        gateway.disconnect();
+        verify(mqttClient, times(1)).disconnect();
+    }
+
+    @Test
+    void testDisconnect_exception() throws MqttException {
+        doThrow(new MqttException(1)).when(mqttClient).disconnect();
+        assertDoesNotThrow(() -> gateway.disconnect()); // 异常被catch
+    }
+
+    @BeforeEach
+    void tearDown() throws Exception {
+        if (mocks != null) mocks.close();
+    }
+} 

+ 53 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttMessageHandlerTest.java

@@ -0,0 +1,53 @@
+package cn.hfln.framework.mqtt.handler;
+
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class MqttMessageHandlerTest {
+    private MqttMessageHandler handler;
+
+    @BeforeEach
+    void setUp() {
+        handler = new MqttMessageHandler();
+    }
+
+    @Test
+    void testConnectionLost() {
+        // 不抛异常即可
+        assertDoesNotThrow(() -> handler.connectionLost(new RuntimeException("lost")));
+    }
+
+    @Test
+    void testMessageArrived() {
+        // 不抛异常即可
+        assertDoesNotThrow(() -> handler.messageArrived("test/topic", new MqttMessage("payload".getBytes())));
+    }
+
+    @Test
+    void testDeliveryComplete() {
+        // 不抛异常即可
+        IMqttDeliveryToken token = new IMqttDeliveryToken() {
+            @Override public void waitForCompletion() {}
+            @Override public void waitForCompletion(long timeout) {}
+            @Override public boolean isComplete() { return true; }
+            @Override public MqttMessage getMessage() { return null; }
+            @Override public int getMessageId() { return 0; }
+            @Override public String[] getTopics() { return new String[0]; }
+            @Override public void setUserContext(Object userContext) {}
+            @Override public Object getUserContext() { return null; }
+            @Override public void setActionCallback(org.eclipse.paho.client.mqttv3.IMqttActionListener listener) {}
+            @Override public org.eclipse.paho.client.mqttv3.IMqttActionListener getActionCallback() { return null; }
+            // 下面为IMqttToken接口方法
+            @Override public int[] getGrantedQos() { return new int[0]; }
+            @Override public org.eclipse.paho.client.mqttv3.MqttException getException() { return null; }
+            @Override public org.eclipse.paho.client.mqttv3.IMqttAsyncClient getClient() { return null; }
+            @Override public org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage getResponse() { return null; }
+            @Override public boolean getSessionPresent() { return false; }
+        };
+        assertDoesNotThrow(() -> handler.deliveryComplete(token));
+    }
+} 

+ 68 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttMessageRouterTest.java

@@ -0,0 +1,68 @@
+package cn.hfln.framework.mqtt.handler;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.regex.Pattern;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class MqttMessageRouterTest {
+    private MqttMessageRouter router;
+    private MqttTopicHandler handler;
+
+    @BeforeEach
+    void setUp() {
+        router = new MqttMessageRouter();
+        handler = mock(MqttTopicHandler.class);
+    }
+
+    @Test
+    void testRegisterAndRoute() throws Exception {
+        router.register("test/.*", handler);
+        String topic = "test/abc";
+        String payload = "msg";
+        assertTrue(router.route(topic, payload));
+        verify(handler, times(1)).handle(topic, payload);
+    }
+
+    @Test
+    void testRegisterPatternAndRoute() throws Exception {
+        Pattern pattern = Pattern.compile("foo/\\d+");
+        router.register(pattern, handler);
+        String topic = "foo/123";
+        assertTrue(router.route(topic, "bar"));
+        verify(handler, times(1)).handle(topic, "bar");
+    }
+
+    @Test
+    void testRouteNoMatch() throws Exception {
+        router.register("abc/.*", handler);
+        assertFalse(router.route("def/123", "payload"));
+        verify(handler, never()).handle(anyString(), anyString());
+    }
+
+    @Test
+    void testRouteHandlerThrows() throws Exception {
+        router.register("err/.*", handler);
+        doThrow(new RuntimeException("fail")).when(handler).handle(anyString(), anyString());
+        assertFalse(router.route("err/1", "x")); // 异常被catch但未算handled
+        verify(handler, times(1)).handle("err/1", "x");
+    }
+
+    @Test
+    void testUnregister() {
+        router.register("abc/.*", handler);
+        router.unregister("abc/.*");
+        assertFalse(router.route("abc/1", "x"));
+    }
+
+    @Test
+    void testClear() {
+        router.register("abc/.*", handler);
+        router.clear();
+        assertFalse(router.route("abc/1", "x"));
+    }
+} 

+ 114 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttSubscribeProcessorTest.java

@@ -0,0 +1,114 @@
+package cn.hfln.framework.mqtt.handler;
+
+import cn.hfln.framework.mqtt.annotation.MqttSubscribe;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.context.ApplicationContext;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class MqttSubscribeProcessorTest {
+    @Mock
+    private MqttClient mqttClient;
+    @Mock
+    private ApplicationContext applicationContext;
+
+    private MqttSubscribeProcessor processor;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        MockitoAnnotations.openMocks(this);
+        processor = new MqttSubscribeProcessor();
+        // 反射注入私有依赖
+        Field mqttClientField = MqttSubscribeProcessor.class.getDeclaredField("mqttClient");
+        mqttClientField.setAccessible(true);
+        mqttClientField.set(processor, mqttClient);
+        Field appCtxField = MqttSubscribeProcessor.class.getDeclaredField("applicationContext");
+        appCtxField.setAccessible(true);
+        appCtxField.set(processor, applicationContext);
+    }
+
+    @Test
+    void testPostProcessAfterInitialization_withMethodAnnotation_success() {
+        try {
+            when(mqttClient.isConnected()).thenReturn(true);
+            TestMethodClass bean = new TestMethodClass();
+            Object result = processor.postProcessAfterInitialization(bean, "testMethodClass");
+            assertSame(bean, result);
+            verify(mqttClient).subscribe("/test/method", 2);
+            Method method = processor.getMethodForTopic("/test/method");
+            assertNotNull(method);
+            assertEquals("onMessage", method.getName());
+        } catch (Exception e) {
+            fail("Exception in method annotation test: " + e.getMessage());
+        }
+    }
+
+    @Test
+    void testPostProcessAfterInitialization_mqttNotConnected() {
+        try {
+            when(mqttClient.isConnected()).thenReturn(false);
+            TestMethodClass bean = new TestMethodClass();
+            processor.postProcessAfterInitialization(bean, "testMethodClass");
+            verify(mqttClient, never()).subscribe(anyString(), anyInt());
+            assertNull(processor.getMethodForTopic("/test/method"));
+        } catch (Exception e) {
+            fail("Exception in mqttNotConnected test: " + e.getMessage());
+        }
+    }
+
+    @Test
+    void testProcessMqttSubscribe_mqttException() {
+        try {
+            when(mqttClient.isConnected()).thenReturn(true);
+            doThrow(new MqttException(1)).when(mqttClient).subscribe(anyString(), anyInt());
+            TestMethodClass bean = new TestMethodClass();
+            processor.postProcessAfterInitialization(bean, "testMethodClass");
+            // Should not throw, just log error
+            assertNull(processor.getMethodForTopic("/test/method"));
+        } catch (Exception e) {
+            fail("Exception in mqttException test: " + e.getMessage());
+        }
+    }
+
+    @Test
+    void testGetMethodForTopic_exactAndWildcard() {
+        when(mqttClient.isConnected()).thenReturn(true);
+        TestMethodClass bean = new TestMethodClass();
+        processor.postProcessAfterInitialization(bean, "testMethodClass");
+        // exact
+        assertNotNull(processor.getMethodForTopic("/test/method"));
+        // wildcard
+        try {
+            Field topicMapField = MqttSubscribeProcessor.class.getDeclaredField("topicMethodMap");
+            topicMapField.setAccessible(true);
+            @SuppressWarnings("unchecked")
+            Map<String, Method> topicMap = (Map<String, Method>) topicMapField.get(processor);
+            topicMap.put("/wildcard/+", TestMethodClass.class.getMethod("onMessage", String.class));
+            assertNotNull(processor.getMethodForTopic("/wildcard/abc"));
+            topicMap.put("/wildcard/#", TestMethodClass.class.getMethod("onMessage", String.class));
+            assertNotNull(processor.getMethodForTopic("/wildcard/abc/def"));
+        } catch (Exception e) {
+            fail("Exception in wildcard topic test: " + e.getMessage());
+        }
+    }
+
+    @Test
+    void testGetMethodForTopic_noMatch() {
+        assertNull(processor.getMethodForTopic("/not/exist"));
+    }
+
+    static class TestMethodClass {
+        @MqttSubscribe(topic = "/test/method", qos = 2)
+        public void onMessage(String msg) {}
+    }
+} 

+ 153 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttSubscriberProcessorTest.java

@@ -0,0 +1,153 @@
+package cn.hfln.framework.mqtt.handler;
+
+import cn.hfln.framework.mqtt.annotation.MqttSubscriber;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.context.ApplicationContext;
+import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHeaders;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class MqttSubscriberProcessorTest {
+    @Mock
+    private MqttPahoMessageDrivenChannelAdapter mqttInbound;
+    @Mock
+    private MqttClient mqttClient;
+    @Mock
+    private ApplicationContext applicationContext;
+
+    private MqttSubscriberProcessor processor;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        MockitoAnnotations.openMocks(this);
+        processor = new MqttSubscriberProcessor(mqttInbound, mqttClient);
+        Field ctxField = MqttSubscriberProcessor.class.getDeclaredField("applicationContext");
+        ctxField.setAccessible(true);
+        ctxField.set(processor, applicationContext);
+    }
+
+    @Test
+    void testAfterSingletonsInstantiated_registersSubscribers() {
+        when(applicationContext.getBeanDefinitionNames()).thenReturn(new String[]{"testBean"});
+        when(applicationContext.getBean("testBean")).thenReturn(new TestSubscriber());
+        processor.afterSingletonsInstantiated();
+        // 验证topic注册
+        verify(mqttInbound).addTopic("/test/topic", 1);
+    }
+
+    @Test
+    void testProcessMessage_exactMatch() throws Exception {
+        // 注册订阅方法
+        TestSubscriber bean = new TestSubscriber();
+        Method method = TestSubscriber.class.getMethod("handle", String.class);
+        MqttSubscriber annotation = method.getAnnotation(MqttSubscriber.class);
+        Object subscriberMethod = createSubscriberMethod(bean, method, annotation);
+        putTopicSubscriber("/test/topic", subscriberMethod);
+        // 构造消息
+        Message<String> message = mock(Message.class);
+        Map<String, Object> headers1 = new HashMap<>();
+        headers1.put("mqtt_receivedTopic", "/test/topic");
+        when(message.getHeaders()).thenReturn(new MessageHeaders(headers1));
+        when(message.getPayload()).thenReturn("payload");
+        // 调用
+        invokeProcessMessage(message);
+        assertEquals("payload", bean.lastMsg);
+    }
+
+    @Test
+    void testProcessMessage_wildcardMatch() throws Exception {
+        TestSubscriber bean = new TestSubscriber();
+        Method method = TestSubscriber.class.getMethod("handle", String.class);
+        MqttSubscriber annotation = method.getAnnotation(MqttSubscriber.class);
+        Object subscriberMethod = createSubscriberMethod(bean, method, annotation);
+        putTopicSubscriber("/test/+", subscriberMethod);
+        Message<String> message = mock(Message.class);
+        Map<String, Object> headers2 = new HashMap<>();
+        headers2.put("mqtt_receivedTopic", "/test/abc");
+        when(message.getHeaders()).thenReturn(new MessageHeaders(headers2));
+        when(message.getPayload()).thenReturn("wild");
+        invokeProcessMessage(message);
+        assertEquals("wild", bean.lastMsg);
+    }
+
+    @Test
+    void testProcessMessage_noSubscriber() {
+        Message<String> message = mock(Message.class);
+        Map<String, Object> headers3 = new HashMap<>();
+        headers3.put("mqtt_receivedTopic", "/not/exist");
+        when(message.getHeaders()).thenReturn(new MessageHeaders(headers3));
+        when(message.getPayload()).thenReturn("none");
+        // 不抛异常
+        invokeProcessMessage(message);
+    }
+
+    @Test
+    void testProcessMessage_nullTopic() {
+        Message<String> message = mock(Message.class);
+        when(message.getHeaders()).thenReturn(new MessageHeaders(new HashMap<>()));
+        when(message.getPayload()).thenReturn("none");
+        // 不抛异常
+        invokeProcessMessage(message);
+    }
+
+    @Test
+    void testInvokeSubscriberMethod_invalidParamCount() throws Exception {
+        TestInvalidSubscriber bean = new TestInvalidSubscriber();
+        Method method = TestInvalidSubscriber.class.getMethod("invalid", String.class, String.class, String.class);
+        MqttSubscriber annotation = method.getAnnotation(MqttSubscriber.class);
+        Method regMethod = MqttSubscriberProcessor.class.getDeclaredMethod("registerSubscriberMethod", Object.class, Method.class, MqttSubscriber.class);
+        regMethod.setAccessible(true);
+        regMethod.invoke(processor, bean, method, annotation);
+        // 不会注册
+        Field mapField = MqttSubscriberProcessor.class.getDeclaredField("topicSubscriberMap");
+        mapField.setAccessible(true);
+        Map<String, ?> map = (Map<String, ?>) mapField.get(processor);
+        assertFalse(map.containsKey("/invalid/topic"));
+    }
+
+    // 工具方法:反射注入topicSubscriberMap
+    private void putTopicSubscriber(String topic, Object subscriberMethod) throws Exception {
+        Field mapField = MqttSubscriberProcessor.class.getDeclaredField("topicSubscriberMap");
+        mapField.setAccessible(true);
+        Map<String, Object> map = (Map<String, Object>) mapField.get(processor);
+        map.put(topic, subscriberMethod);
+    }
+    // 工具方法:反射创建SubscriberMethod
+    private Object createSubscriberMethod(Object bean, Method method, MqttSubscriber annotation) throws Exception {
+        Class<?> smClass = Class.forName("cn.hfln.framework.mqtt.handler.MqttSubscriberProcessor$SubscriberMethod");
+        return smClass.getConstructor(Object.class, Method.class, MqttSubscriber.class)
+                .newInstance(bean, method, annotation);
+    }
+    // 工具方法:反射调用processMessage
+    private void invokeProcessMessage(Message<?> message) {
+        try {
+            Method m = MqttSubscriberProcessor.class.getDeclaredMethod("processMessage", Message.class);
+            m.setAccessible(true);
+            m.invoke(processor, message);
+        } catch (Exception e) {
+            fail("Exception in processMessage: " + e.getMessage());
+        }
+    }
+
+    static class TestSubscriber {
+        String lastMsg;
+        @MqttSubscriber(topic = "/test/topic", qos = 1)
+        public void handle(String msg) { lastMsg = msg; }
+    }
+    static class TestInvalidSubscriber {
+        @MqttSubscriber(topic = "/invalid/topic", qos = 1)
+        public void invalid(String a, String b, String c) {}
+    }
+} 

+ 35 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/handler/MqttTopicHandlerTest.java

@@ -0,0 +1,35 @@
+package cn.hfln.framework.mqtt.handler;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class MqttTopicHandlerTest {
+    static class SimpleHandler implements MqttTopicHandler {
+        String lastTopic;
+        String lastPayload;
+        boolean throwError = false;
+        @Override
+        public void handle(String topic, String payload) throws Exception {
+            if (throwError) throw new Exception("fail");
+            lastTopic = topic;
+            lastPayload = payload;
+        }
+    }
+
+    @Test
+    void testHandleNormal() throws Exception {
+        SimpleHandler handler = new SimpleHandler();
+        handler.handle("topic/1", "payload");
+        assertEquals("topic/1", handler.lastTopic);
+        assertEquals("payload", handler.lastPayload);
+    }
+
+    @Test
+    void testHandleThrowsException() {
+        SimpleHandler handler = new SimpleHandler();
+        handler.throwError = true;
+        Exception ex = assertThrows(Exception.class, () -> handler.handle("t", "p"));
+        assertEquals("fail", ex.getMessage());
+    }
+} 

+ 27 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/listener/DefaultMqttConnectionListenerTest.java

@@ -0,0 +1,27 @@
+package cn.hfln.framework.mqtt.listener;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class DefaultMqttConnectionListenerTest {
+    @Test
+    void testOnConnected() {
+        DefaultMqttConnectionListener listener = new DefaultMqttConnectionListener();
+        assertDoesNotThrow(listener::onConnected);
+    }
+
+    @Test
+    void testOnDisconnected() {
+        DefaultMqttConnectionListener listener = new DefaultMqttConnectionListener();
+        Throwable cause = new RuntimeException("disconnect");
+        assertDoesNotThrow(() -> listener.onDisconnected(cause));
+    }
+
+    @Test
+    void testOnConnectionFailed() {
+        DefaultMqttConnectionListener listener = new DefaultMqttConnectionListener();
+        Throwable cause = new RuntimeException("fail");
+        assertDoesNotThrow(() -> listener.onConnectionFailed(cause));
+    }
+} 

+ 31 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/listener/DefaultMqttMessageListenerTest.java

@@ -0,0 +1,31 @@
+package cn.hfln.framework.mqtt.listener;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class DefaultMqttMessageListenerTest {
+    @Test
+    void testOnMessage_normal() {
+        DefaultMqttMessageListener listener = new DefaultMqttMessageListener();
+        String topic = "test/topic";
+        MqttMessage message = new MqttMessage("hello".getBytes());
+        assertDoesNotThrow(() -> listener.onMessage(topic, message));
+    }
+
+    @Test
+    void testOnMessage_emptyPayload() {
+        DefaultMqttMessageListener listener = new DefaultMqttMessageListener();
+        String topic = "test/topic";
+        MqttMessage message = new MqttMessage(new byte[0]);
+        assertDoesNotThrow(() -> listener.onMessage(topic, message));
+    }
+
+    @Test
+    void testOnMessage_nullTopic() {
+        DefaultMqttMessageListener listener = new DefaultMqttMessageListener();
+        MqttMessage message = new MqttMessage("data".getBytes());
+        assertDoesNotThrow(() -> listener.onMessage(null, message));
+    }
+} 

+ 70 - 0
hfln-framework-design-starter/mqtt-spring-boot-starter/src/test/java/cn/hfln/framework/mqtt/template/MqttTemplateTest.java

@@ -0,0 +1,70 @@
+package cn.hfln.framework.mqtt.template;
+
+import cn.hfln.framework.mqtt.converter.JsonMessageConverter;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
+
+import java.util.concurrent.CompletableFuture;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class MqttTemplateTest {
+    @Mock
+    private MqttPahoMessageHandler mqttOutbound;
+    @Mock
+    private JsonMessageConverter jsonMessageConverter;
+    private MqttTemplate mqttTemplate;
+    private AutoCloseable mocks;
+
+    @BeforeEach
+    void setUp() {
+        mocks = MockitoAnnotations.openMocks(this);
+        mqttTemplate = new MqttTemplate(mqttOutbound, jsonMessageConverter);
+    }
+
+    @Test
+    void testSend_success() {
+        String topic = "/test/topic";
+        String payload = "hello";
+        doNothing().when(mqttOutbound).handleMessage(any());
+        CompletableFuture<Void> future = mqttTemplate.send(topic, payload);
+        assertDoesNotThrow(future::join);
+        verify(mqttOutbound, times(1)).handleMessage(any());
+    }
+
+    @Test
+    void testSend_nullTopic() {
+        String payload = "hello";
+        CompletableFuture<Void> future = mqttTemplate.send(null, payload);
+        assertDoesNotThrow(future::join);
+        verify(mqttOutbound, times(1)).handleMessage(any());
+    }
+
+    @Test
+    void testSend_nullPayload() {
+        String topic = "/test/topic";
+        assertThrows(Exception.class, () -> mqttTemplate.send(topic, null).join());
+    }
+
+    @Test
+    void testSend_exceptionFromOutbound() {
+        String topic = "/test/topic";
+        String payload = "fail";
+        doThrow(new RuntimeException("fail outbound")).when(mqttOutbound).handleMessage(any());
+        CompletableFuture<Void> future = mqttTemplate.send(topic, payload);
+        assertThrows(Exception.class, future::join);
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        if (mocks != null) {
+            mocks.close();
+        }
+    }
+}