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);
}
}
}