From db2eb9412ddc4ff687eb4c9b862053f49f9aec7b Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 26 八月 2026 13:16:42 +0800
Subject: [PATCH] feat(device): 完善 BTS 只读协议与模拟器

---
 ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsXmlCodecTest.java |   86 +++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsXmlCodecTest.java b/ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsXmlCodecTest.java
index 8313e7e..2699acd 100644
--- a/ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsXmlCodecTest.java
+++ b/ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsXmlCodecTest.java
@@ -3,14 +3,18 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.math.BigDecimal;
 import java.time.Instant;
 import java.time.ZoneId;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import org.junit.jupiter.api.Test;
 import com.ruoyi.device.model.ChannelIdentity;
 import com.ruoyi.device.model.DeviceWorkStatus;
 import com.ruoyi.device.model.HistoryPage;
+import com.ruoyi.device.model.HistoryAvailability;
 import com.ruoyi.device.model.RealtimeSnapshot;
 
 class BtsXmlCodecTest
@@ -29,6 +33,19 @@
         assertEquals(1, channels.size());
         assertEquals(new ChannelIdentity("lab-bts", 22, 1, 2, 3), channels.get(0).identity());
         assertEquals("192.168.1.35", channels.get(0).serverIp());
+    }
+
+    @Test
+    void rejectsInvalidDiscoveryAvailability()
+    {
+        String xml = "<bts><cmd>getdevinfo_resp</cmd><middle count=\"1\">"
+                + "<channel ip=\"192.168.1.35\" devtype=\"22\" devid=\"1\" subdevid=\"2\" Channelid=\"3\">maybe</channel>"
+                + "</middle></bts>";
+
+        BtsProtocolException exception = assertThrows(BtsProtocolException.class,
+                () -> codec.parseDeviceInfo(xml));
+
+        assertEquals(BtsProtocolException.Reason.INVALID_VALUE, exception.reason());
     }
 
     @Test
@@ -63,12 +80,14 @@
     void parsesHistoryPageAndHongKongDeviceTime()
     {
         ChannelIdentity channel = new ChannelIdentity("lab-bts", 24, 1, 1, 1);
-        String xml = "<bts><cmd>download_resp</cmd><download testid=\"42\"/><list count=\"1\">"
+        String xml = "<bts><cmd>download_resp</cmd>"
+                + "<download devtype=\"24\" devid=\"1\" subdevid=\"1\" chlid=\"1\" testid=\"42\"/>"
+                + "<list count=\"1\">"
                 + "<data seqid=\"7\" stepid=\"1\" cycleid=\"2\" steptype=\"cc\" testtime=\"1000\" "
                 + "atime=\"2026-08-25 18.00.00\" volt=\"3.5\" curr=\"1.2\" cap=\"0.1\" eng=\"0.35\" T1=\"25\"/>"
                 + "</list></bts>";
 
-        HistoryPage page = codec.parseHistory(xml, channel, 7);
+        HistoryPage page = codec.parseHistory(xml, channel, "42", 7);
 
         assertEquals("42", page.btsTestId());
         assertEquals(8, page.nextPosition());
@@ -76,11 +95,72 @@
     }
 
     @Test
+    void rejectsHistoryResponseForDifferentChannelOrTest()
+    {
+        ChannelIdentity requested = new ChannelIdentity("lab-bts", 24, 1, 1, 1);
+        String xml = "<bts><cmd>download_resp</cmd>"
+                + "<download devtype=\"24\" devid=\"1\" subdevid=\"1\" chlid=\"2\" testid=\"99\"/>"
+                + "<list count=\"0\"/></bts>";
+
+        BtsProtocolException exception = assertThrows(BtsProtocolException.class,
+                () -> codec.parseHistory(xml, requested, "42", 1));
+
+        assertEquals(BtsProtocolException.Reason.IDENTITY_MISMATCH, exception.reason());
+    }
+
+    @Test
+    void buildsAndParsesHistoryAvailabilityForMultipleChannels()
+    {
+        ChannelIdentity first = new ChannelIdentity("lab-bts", 22, 3, 2, 8);
+        ChannelIdentity second = new ChannelIdentity("lab-bts", 22, 3, 1, 6);
+
+        String request = codec.inquireHistoryRequest(Set.of(first, second));
+        String response = "<bts><cmd>inquiredf_resp</cmd><list count=\"2\">"
+                + "<chl devtype=\"22\" devid=\"3\" subdevid=\"2\" chlid=\"8\" testid=\"11\" count=\"10\">true</chl>"
+                + "<chl devtype=\"22\" devid=\"3\" subdevid=\"1\" chlid=\"6\" testid=\"10\" count=\"11\">false</chl>"
+                + "</list></bts>";
+
+        List<HistoryAvailability> result = codec.parseHistoryAvailability(response);
+
+        assertTrue(request.contains("<cmd>inquiredf</cmd>"));
+        assertTrue(request.contains("devtype=\"22\" devid=\"3\" subdevid=\"2\" chlid=\"8\" testid=\"0\""));
+        assertTrue(request.contains("devtype=\"22\" devid=\"3\" subdevid=\"1\" chlid=\"6\" testid=\"0\""));
+        assertEquals(new HistoryAvailability(first, "11", 10, true), result.get(0));
+        assertEquals(new HistoryAvailability(second, "10", 11, false), result.get(1));
+    }
+
+    @Test
     void rejectsIllegalNumericData()
     {
         String xml = "<bts><cmd>inquire_resp</cmd><list count=\"1\"><inquire dev=\"22-1-1-1-0\" current=\"bad\"/></list></bts>";
 
-        assertThrows(BtsProtocolException.class, () -> codec.parseSnapshots(xml, Instant.EPOCH));
+        BtsProtocolException exception = assertThrows(BtsProtocolException.class,
+                () -> codec.parseSnapshots(xml, Instant.EPOCH));
+
+        assertEquals(BtsProtocolException.Reason.INVALID_VALUE, exception.reason());
+    }
+
+    @Test
+    void classifiesWrongCommandAndMalformedXml()
+    {
+        BtsProtocolException wrongCommand = assertThrows(BtsProtocolException.class,
+                () -> codec.parseSnapshots("<bts><cmd>download_resp</cmd></bts>", Instant.EPOCH));
+        BtsProtocolException malformed = assertThrows(BtsProtocolException.class,
+                () -> codec.parseSnapshots("<bts><cmd>inquire_resp</cmd>", Instant.EPOCH));
+
+        assertEquals(BtsProtocolException.Reason.UNEXPECTED_COMMAND, wrongCommand.reason());
+        assertEquals(BtsProtocolException.Reason.MALFORMED_XML, malformed.reason());
+    }
+
+    @Test
+    void reportsMissingRequiredField()
+    {
+        String xml = "<bts><cmd>inquire_resp</cmd><list count=\"1\"><inquire workstatus=\"working\"/></list></bts>";
+
+        BtsProtocolException exception = assertThrows(BtsProtocolException.class,
+                () -> codec.parseSnapshots(xml, Instant.EPOCH));
+
+        assertEquals(BtsProtocolException.Reason.MISSING_FIELD, exception.reason());
     }
 
     @Test

--
Gitblit v1.7.1