using System; using System.Threading; using System.Threading.Tasks; using Aging.Infrastructure.Drivers; using CanSimulatorServer; using Xunit; namespace Aging.MvpTests { /// /// 统一 CAN 驱动(CanDeviceDriver, TCP NodeChannel 后端)行为测试: /// 在测试进程内托管模拟器服务,验证连接/发送/推送事件/本地缓冲查询/错误语义 /// 与调试面板现有解析格式(RECV_ALL:n:…、OK:TX…)保持兼容。 /// 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() { // 指向必然空闲的端口:错误文案应引导用户启动模拟器服务 using var drv = new CanDeviceDriver("CAN-T2", "CAN_BUS_1:0", hostOverride: "127.0.0.1", portOverride: 1); var ex = Assert.ThrowsAny(() => 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 解析 "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(() => second.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult()); Assert.Contains("节点编号", ex.Message); } } }