From b544d5cccd76735e034c8055918a50927b437474 Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期二, 10 十一月 2026 08:29:04 +0800
Subject: [PATCH] fix: 维护模式重复进入失败与按钮灰死 + Broker 并发验证用例

---
 tests/Aging.MvpTests/CanDeviceDriverTests.cs |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 120 insertions(+), 0 deletions(-)

diff --git a/tests/Aging.MvpTests/CanDeviceDriverTests.cs b/tests/Aging.MvpTests/CanDeviceDriverTests.cs
new file mode 100644
index 0000000..e136499
--- /dev/null
+++ b/tests/Aging.MvpTests/CanDeviceDriverTests.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Aging.Infrastructure.Drivers;
+using CanSimulatorServer;
+using Xunit;
+
+namespace Aging.MvpTests
+{
+    /// <summary>
+    /// 缁熶竴 CAN 椹卞姩锛圕anDeviceDriver, TCP NodeChannel 鍚庣锛夎涓烘祴璇曪細
+    /// 鍦ㄦ祴璇曡繘绋嬪唴鎵樼妯℃嫙鍣ㄦ湇鍔★紝楠岃瘉杩炴帴/鍙戦��/鎺ㄩ�佷簨浠�/鏈湴缂撳啿鏌ヨ/閿欒璇箟
+    /// 涓庤皟璇曢潰鏉跨幇鏈夎В鏋愭牸寮忥紙RECV_ALL:n:鈥︺�丱K:TX鈥︼級淇濇寔鍏煎銆�
+    /// </summary>
+    public class CanDeviceDriverTests : IDisposable
+    {
+        private readonly TcpSimulatorHost _host;
+
+        public CanDeviceDriverTests()
+        {
+            _host = new TcpSimulatorHost();
+            _host.StartAsync().GetAwaiter().GetResult();
+        }
+
+        public void Dispose() => _host.Dispose();
+
+        private CanDeviceDriver NewDriver(string deviceId, string bus, int node) =>
+            new CanDeviceDriver(deviceId, $"{bus}:{node}", hostOverride: "127.0.0.1", portOverride: _host.Port);
+
+        [Fact]
+        public void ConnectAsync_OnRunningServer_Succeeds()
+        {
+            using var drv = NewDriver("CAN-T1", "CAN_BUS_1", 0);
+            drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
+            Assert.True(drv.IsConnected);
+        }
+
+        [Fact]
+        public void ConnectAsync_NoServer_ThrowsWithStartupHint()
+        {
+            // 鎸囧悜蹇呯劧绌洪棽鐨勭鍙o細閿欒鏂囨搴斿紩瀵肩敤鎴峰惎鍔ㄦā鎷熷櫒鏈嶅姟
+            using var drv = new CanDeviceDriver("CAN-T2", "CAN_BUS_1:0", hostOverride: "127.0.0.1", portOverride: 1);
+            var ex = Assert.ThrowsAny<Exception>(() =>
+                drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult());
+            Assert.Contains("妯℃嫙鍣�", ex.Message);
+        }
+
+        [Fact]
+        public void SendCommandAsync_ReturnsOkTx_ReadableByPanel()
+        {
+            using var drv = NewDriver("CAN-T3", "CAN_BUS_1", 1);
+            drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
+
+            string resp = drv.SendCommandAsync("SEND:0x100:AA BB", CancellationToken.None)
+                              .GetAwaiter().GetResult();
+            Assert.StartsWith("OK:TX", resp);
+        }
+
+        [Fact]
+        public void Push_FromPeer_TriggersEvent_AndLocalRecvAll()
+        {
+            using var a = NewDriver("CAN-T4A", "CAN_BUS_1", 2);
+            using var b = NewDriver("CAN-T4B", "CAN_BUS_1", 3);
+            a.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
+            b.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
+
+            CanSimulator.CanFrame received = null;
+            a.DriverFrameReceived += (s, f) => received = f;
+
+            b.SendCommandAsync("SEND:0x222:11 22 33", CancellationToken.None).GetAwaiter().GetResult();
+
+            // 绛夊緟鎺ㄩ�佷簨浠跺埌杈撅紙璇诲惊鐜嚎绋嬭Е鍙戯級
+            SpinWait.SpinUntil(() => received != null, 3000);
+            Assert.NotNull(received);
+            Assert.Equal("0x222", received.FrameIdHex);
+
+            // RECV_ALL 鐢遍┍鍔ㄦ湰鍦扮紦鍐插簲绛旓紙涓嶄緷璧栨湇鍔$閭锛�
+            string all = a.QueryAsync("RECV_ALL", CancellationToken.None).GetAwaiter().GetResult();
+            Assert.StartsWith("RECV_ALL:1:", all);
+            Assert.Contains("0x222:11 22 33", all);
+        }
+
+        [Fact]
+        public void Query_AutoResponse_ProxiedToServer_ReturnsOk()
+        {
+            using var drv = NewDriver("CAN-T5", "CAN_BUS_1", 4);
+            drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
+
+            string resp = drv.QueryAsync("AUTO_RESPONSE:ON", CancellationToken.None).GetAwaiter().GetResult();
+            Assert.StartsWith("OK:", resp);
+            string back = drv.QueryAsync("AUTO_RESPONSE", CancellationToken.None).GetAwaiter().GetResult();
+            Assert.Contains("ON", back);
+        }
+
+        [Fact]
+        public void Query_Status_HasLegacyCompatibleFormat()
+        {
+            using var drv = NewDriver("CAN-T6", "CAN_BUS_1", 5);
+            drv.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
+
+            // 闈㈡澘 GetStatValue 瑙f瀽 "STATUS:Node=鈥�, 鈥�, Rx=0, Tx=0, 鈥�"锛堥�楀彿绛夊彿鍒嗛殧锛�
+            string resp = drv.QueryAsync("STATUS", CancellationToken.None).GetAwaiter().GetResult();
+            Assert.StartsWith("STATUS:Node=", resp);
+            Assert.Contains("Tx=", resp);
+            Assert.Contains("Rx=", resp);
+        }
+
+        [Fact]
+        public void Connect_NodeBusy_ThrowsWithNodeIndexHint()
+        {
+            using var first = NewDriver("CAN-T7A", "CAN_BUS_1", 6);
+            first.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
+
+            using var second = NewDriver("CAN-T7B", "CAN_BUS_1", 6);
+            var ex = Assert.ThrowsAny<Exception>(() =>
+                second.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult());
+            Assert.Contains("鑺傜偣缂栧彿", ex.Message);
+        }
+    }
+}

--
Gitblit v1.9.1