From f9e7ed27f6ae71f6b2d89c40b422284a7e5411d2 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 26 八月 2026 13:29:53 +0800
Subject: [PATCH] fix(device): 完善 BTS 状态与模拟器契约
---
ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java | 21 ++++++----
ruoyi-device/src/main/java/com/ruoyi/device/model/DeviceWorkStatus.java | 2 +
ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsXmlCodecTest.java | 32 ++++++++++++++++
ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java | 8 ++--
ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java | 25 ++++++++++++
5 files changed, 75 insertions(+), 13 deletions(-)
diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java
index d0be888..8860b8f 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java
@@ -104,7 +104,7 @@
public List<DetectedChannel> parseDeviceInfo(String xml)
{
Document document = parse(xml, "getdevinfo_resp");
- NodeList nodes = document.getElementsByTagName("channel");
+ NodeList nodes = firstElement(document, "middle").getElementsByTagName("channel");
List<DetectedChannel> channels = new ArrayList<>(nodes.getLength());
for (int i = 0; i < nodes.getLength(); i++)
{
@@ -121,7 +121,7 @@
public Map<ChannelIdentity, RealtimeSnapshot> parseSnapshots(String xml, Instant receivedAt)
{
Document document = parse(xml, "inquire_resp");
- NodeList nodes = document.getElementsByTagName("inquire");
+ NodeList nodes = firstElement(document, "list").getElementsByTagName("inquire");
Map<ChannelIdentity, RealtimeSnapshot> snapshots = new LinkedHashMap<>();
for (int i = 0; i < nodes.getLength(); i++)
{
@@ -148,7 +148,7 @@
public List<HistoryAvailability> parseHistoryAvailability(String xml)
{
Document document = parse(xml, "inquiredf_resp");
- NodeList nodes = document.getElementsByTagName("chl");
+ NodeList nodes = firstElement(document, "list").getElementsByTagName("chl");
List<HistoryAvailability> availability = new ArrayList<>(nodes.getLength());
for (int i = 0; i < nodes.getLength(); i++)
{
@@ -174,7 +174,7 @@
|| !btsTestId.equals(requestedTestId))
throw new BtsProtocolException(BtsProtocolException.Reason.IDENTITY_MISMATCH,
"BTS history response identity does not match the request");
- NodeList nodes = document.getElementsByTagName("data");
+ NodeList nodes = firstElement(document, "list").getElementsByTagName("data");
List<HistorySample> samples = new ArrayList<>(nodes.getLength());
long next = requestedStart;
for (int i = 0; i < nodes.getLength(); i++)
diff --git a/ruoyi-device/src/test/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java
similarity index 88%
rename from ruoyi-device/src/test/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java
rename to ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java
index 5a61ea5..f4fb784 100644
--- a/ruoyi-device/src/test/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java
@@ -16,7 +16,10 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
-final class ScriptedBtsTcpSimulator implements AutoCloseable
+/**
+ * 提供可复用的 BTS TCP 脚本设备边界,用于闭环与恢复测试。
+ */
+public final class ScriptedBtsTcpSimulator implements AutoCloseable
{
private static final byte[] FRAME_END = "#\r\n".getBytes(StandardCharsets.UTF_8);
@@ -26,7 +29,7 @@
private final BlockingQueue<ScriptedAction> actions = new LinkedBlockingQueue<>();
private final List<String> requests = new CopyOnWriteArrayList<>();
- ScriptedBtsTcpSimulator() throws IOException
+ public ScriptedBtsTcpSimulator() throws IOException
{
this(true);
}
@@ -41,38 +44,38 @@
serverSocket.close();
}
- static ScriptedBtsTcpSimulator refusingConnections() throws IOException
+ public static ScriptedBtsTcpSimulator refusingConnections() throws IOException
{
return new ScriptedBtsTcpSimulator(false);
}
- int port()
+ public int port()
{
return port;
}
- void enqueue(String response)
+ public void enqueue(String response)
{
actions.add(new ScriptedAction(Duration.ZERO, response.getBytes(StandardCharsets.UTF_8), false));
}
- void enqueueDelayed(Duration delay, String response)
+ public void enqueueDelayed(Duration delay, String response)
{
if (delay.isNegative()) throw new IllegalArgumentException("delay must not be negative");
actions.add(new ScriptedAction(delay, response.getBytes(StandardCharsets.UTF_8), false));
}
- void enqueueRaw(byte[] response)
+ public void enqueueRaw(byte[] response)
{
actions.add(new ScriptedAction(Duration.ZERO, response.clone(), false));
}
- void enqueueDisconnect()
+ public void enqueueDisconnect()
{
actions.add(new ScriptedAction(Duration.ZERO, null, true));
}
- List<String> requests()
+ public List<String> requests()
{
return List.copyOf(requests);
}
diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/model/DeviceWorkStatus.java b/ruoyi-device/src/main/java/com/ruoyi/device/model/DeviceWorkStatus.java
index d7684eb..c302225 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/model/DeviceWorkStatus.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/model/DeviceWorkStatus.java
@@ -5,6 +5,7 @@
public enum DeviceWorkStatus
{
WORKING,
+ FINISHED,
STOPPED,
PROTECTED,
PAUSED,
@@ -21,6 +22,7 @@
return switch (value.trim().toLowerCase(Locale.ROOT))
{
case "working" -> WORKING;
+ case "finish" -> FINISHED;
case "stop" -> STOPPED;
case "protect" -> PROTECTED;
case "pause" -> PAUSED;
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 2699acd..d6d2862 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
@@ -77,6 +77,17 @@
}
@Test
+ void mapsFinishAsNormalCompletion()
+ {
+ String xml = "<bts><cmd>inquire_resp</cmd><list count=\"1\">"
+ + "<inquire dev=\"22-1-1-1-0\" workstatus=\"finish\"/></list></bts>";
+
+ RealtimeSnapshot snapshot = codec.parseSnapshots(xml, Instant.EPOCH).values().iterator().next();
+
+ assertEquals(DeviceWorkStatus.FINISHED, snapshot.workStatus());
+ }
+
+ @Test
void parsesHistoryPageAndHongKongDeviceTime()
{
ChannelIdentity channel = new ChannelIdentity("lab-bts", 24, 1, 1, 1);
@@ -164,6 +175,27 @@
}
@Test
+ void rejectsResponsesMissingRequiredContainers()
+ {
+ BtsProtocolException discovery = assertThrows(BtsProtocolException.class,
+ () -> codec.parseDeviceInfo("<bts><cmd>getdevinfo_resp</cmd></bts>"));
+ BtsProtocolException realtime = assertThrows(BtsProtocolException.class,
+ () -> codec.parseSnapshots("<bts><cmd>inquire_resp</cmd></bts>", Instant.EPOCH));
+ BtsProtocolException availability = assertThrows(BtsProtocolException.class,
+ () -> codec.parseHistoryAvailability("<bts><cmd>inquiredf_resp</cmd></bts>"));
+ ChannelIdentity channel = new ChannelIdentity("lab-bts", 24, 1, 1, 1);
+ BtsProtocolException history = assertThrows(BtsProtocolException.class,
+ () -> codec.parseHistory("<bts><cmd>download_resp</cmd>"
+ + "<download devtype=\"24\" devid=\"1\" subdevid=\"1\" chlid=\"1\" testid=\"42\"/>"
+ + "</bts>", channel, "42", 1));
+
+ assertEquals(BtsProtocolException.Reason.MISSING_FIELD, discovery.reason());
+ assertEquals(BtsProtocolException.Reason.MISSING_FIELD, realtime.reason());
+ assertEquals(BtsProtocolException.Reason.MISSING_FIELD, availability.reason());
+ assertEquals(BtsProtocolException.Reason.MISSING_FIELD, history.reason());
+ }
+
+ @Test
void rejectsDoctypeAndExternalEntities()
{
String xml = "<!DOCTYPE bts [<!ENTITY xxe SYSTEM \"file:///etc/passwd\">]><bts><cmd>inquire_resp</cmd>&xxe;</bts>";
diff --git a/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java b/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java
new file mode 100644
index 0000000..383fd6d
--- /dev/null
+++ b/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java
@@ -0,0 +1,25 @@
+package com.ruoyi.device.contract;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.time.Duration;
+import org.junit.jupiter.api.Test;
+import com.ruoyi.device.bts.BtsTcpTransport;
+import com.ruoyi.device.bts.ScriptedBtsTcpSimulator;
+
+class SimulatorPublicContractTest
+{
+ @Test
+ void simulatorIsReusableOutsideTheBtsPackage() throws Exception
+ {
+ try (ScriptedBtsTcpSimulator simulator = new ScriptedBtsTcpSimulator();
+ BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(),
+ Duration.ofSeconds(1), Duration.ofSeconds(1)))
+ {
+ simulator.enqueue("<bts><cmd>getdevinfo_resp</cmd><middle count=\"0\"/></bts>");
+
+ assertEquals("<bts><cmd>getdevinfo_resp</cmd><middle count=\"0\"/></bts>",
+ transport.exchange("<bts><cmd>getdevinfo</cmd></bts>"));
+ assertEquals(1, simulator.requests().size());
+ }
+ }
+}
--
Gitblit v1.7.1