using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using CanSimulatorServer;
using Xunit;
namespace Aging.MvpTests
{
///
/// 模拟器服务端核心行为测试:HELLO 状态机、NODE_BUSY 仲裁、
/// 跨节点路由推送、自动响应回推、命令串执行(AUTO_RESPONSE/STATUS)。
/// 测试进程内托管 TcpSimulatorHost(随机端口),走真实 TCP 回环。
///
public class SimulatorServerCoreTests : IDisposable
{
private readonly TcpSimulatorHost _host;
private readonly int _port;
public SimulatorServerCoreTests()
{
_host = new TcpSimulatorHost();
_host.StartAsync().GetAwaiter().GetResult();
_port = _host.Port;
}
public void Dispose() => _host.Dispose();
// ==================== 客户端辅助 ====================
private sealed class FakeClient : IDisposable
{
public TcpClient Socket = null!;
public Stream Stream = null!;
public async Task ConnectAsync(int port)
{
Socket = new TcpClient();
await Socket.ConnectAsync("127.0.0.1", port);
Stream = Socket.GetStream();
}
public Task SendAsync(string msg)
{
byte[] f = CanSimulator.SimulatorFraming.Encode(msg);
return Stream.WriteAsync(f, 0, f.Length);
}
public Task RecvAsync() => ReadFrameBlocking();
public async Task HelloAsync(string bus, int node, string tag = "t")
{
await SendAsync($"HELLO:{bus}:{node}:{tag}");
return await ReadFrameBlocking();
}
private async Task ReadFrameBlocking()
{
var s = await CanSimulator.SimulatorFraming.ReadFrameAsync(Stream, CancellationToken.None);
return s ?? "";
}
public void Dispose() { try { Stream?.Dispose(); Socket?.Close(); } catch { } }
}
// ==================== HELLO 状态机 ====================
[Fact]
public void Hello_FirstClient_AcksOk()
{
using var c = new FakeClient();
c.ConnectAsync(_port).GetAwaiter().GetResult();
var ack = c.HelloAsync("CAN_BUS_1", 0).GetAwaiter().GetResult();
Assert.StartsWith("OK:HELLO", ack);
}
[Fact]
public void Query_BeforeHello_ReturnsNotHandshaked()
{
using var c = new FakeClient();
c.ConnectAsync(_port).GetAwaiter().GetResult();
c.SendAsync("QUERY:STATUS").GetAwaiter().GetResult();
var resp = c.RecvAsync().GetAwaiter().GetResult();
Assert.Equal("ERR:NOT_HANDSHAKED", resp);
}
[Fact]
public void UnknownCommand_ReturnsUnknownCmd_WithoutDroppingConnection()
{
using var c = new FakeClient();
c.ConnectAsync(_port).GetAwaiter().GetResult();
c.HelloAsync("CAN_BUS_1", 0).GetAwaiter().GetResult();
c.SendAsync("FOO:bar").GetAwaiter().GetResult();
Assert.Equal("ERR:UNKNOWN_CMD:FOO:bar", c.RecvAsync().GetAwaiter().GetResult());
// 连接保持可用:后续合法命令仍有响应
c.SendAsync("QUERY:STATUS").GetAwaiter().GetResult();
Assert.StartsWith("OK:", c.RecvAsync().GetAwaiter().GetResult());
}
// ==================== NODE_BUSY 仲裁 ====================
[Fact]
public void Hello_SameBusSameNode_SecondRejectedNodeBusy()
{
using var a = new FakeClient();
using var b = new FakeClient();
a.ConnectAsync(_port).GetAwaiter().GetResult();
b.ConnectAsync(_port).GetAwaiter().GetResult();
Assert.StartsWith("OK:HELLO", a.HelloAsync("CAN_BUS_1", 3).GetAwaiter().GetResult());
Assert.Equal("ERR:NODE_BUSY", b.HelloAsync("CAN_BUS_1", 3).GetAwaiter().GetResult());
// 不同总线同节点编号不冲突
Assert.StartsWith("OK:HELLO", b.HelloAsync("CAN_BUS_2", 3).GetAwaiter().GetResult());
}
[Fact]
public void Hello_AfterDisconnect_SameNodeCanReattach()
{
var a = new FakeClient();
a.ConnectAsync(_port).GetAwaiter().GetResult();
a.HelloAsync("CAN_BUS_1", 7).GetAwaiter().GetResult();
a.Dispose(); // 模拟连接断开
using var b = new FakeClient();
b.ConnectAsync(_port).GetAwaiter().GetResult();
// 服务端感知 EOF 需少许时间,轮询重试
string ack = "";
for (int i = 0; i < 50 && !ack.StartsWith("OK"); i++)
{
Thread.Sleep(20);
b.SendAsync($"HELLO:CAN_BUS_1:7:retry{i}").GetAwaiter().GetResult();
ack = b.RecvAsync().GetAwaiter().GetResult();
}
Assert.StartsWith("OK:HELLO", ack);
}
// ==================== 路由与推送 ====================
[Fact]
public void Send_FromOneNode_OtherNodeReceivesEvPush()
{
using var a = new FakeClient();
using var b = new FakeClient();
a.ConnectAsync(_port).GetAwaiter().GetResult();
b.ConnectAsync(_port).GetAwaiter().GetResult();
a.HelloAsync("CAN_BUS_1", 0).GetAwaiter().GetResult();
b.HelloAsync("CAN_BUS_1", 1).GetAwaiter().GetResult();
a.SendAsync("SEND:0x123:AA BB CC").GetAwaiter().GetResult();
var ack = a.RecvAsync().GetAwaiter().GetResult();
Assert.StartsWith("OK:TX", ack);
// 对端节点收到服务端主动推送
var push = b.RecvAsync().GetAwaiter().GetResult();
Assert.StartsWith("EV:RECV:0x123:AA BB CC", push);
}
[Fact]
public void Send_AutoResponseEnabled_RespondsToPeerAndSelf()
{
using var a = new FakeClient();
using var b = new FakeClient();
a.ConnectAsync(_port).GetAwaiter().GetResult();
b.ConnectAsync(_port).GetAwaiter().GetResult();
a.HelloAsync("CAN_BUS_1", 10).GetAwaiter().GetResult();
b.HelloAsync("CAN_BUS_1", 11).GetAwaiter().GetResult();
// b 开启自动响应(QUERY 写命令,响应一行)
b.SendAsync("QUERY:AUTO_RESPONSE:ON").GetAwaiter().GetResult();
Assert.StartsWith("OK:", b.RecvAsync().GetAwaiter().GetResult());
// a 发帧 → b 路由收帧(推送) + 自动响应帧回 a
a.SendAsync("SEND:0x500:11 22").GetAwaiter().GetResult();
Assert.StartsWith("OK:TX", a.RecvAsync().GetAwaiter().GetResult());
// b 自己收到源路由推送
Assert.StartsWith("EV:RECV:0x500:11 22", b.RecvAsync().GetAwaiter().GetResult());
// a 收到自动响应帧(来源=b 节点 11)
var respPush = a.RecvAsync().GetAwaiter().GetResult();
Assert.StartsWith("EV:RECV:0x500:11 22", respPush);
Assert.EndsWith(":11", respPush); // srcNodeIndex=11
}
// ==================== 命令执行 ====================
[Fact]
public void Query_Status_AfterHello_ReturnsOkStatus()
{
using var a = new FakeClient();
a.ConnectAsync(_port).GetAwaiter().GetResult();
a.HelloAsync("CAN_BUS_1", 20).GetAwaiter().GetResult();
a.SendAsync("QUERY:STATUS").GetAwaiter().GetResult();
var resp = a.RecvAsync().GetAwaiter().GetResult();
Assert.StartsWith("OK:STATUS:", resp);
Assert.Contains("Node=20", resp); // 节点以编号寻址
}
[Fact]
public void GarbageFrame_ClosesSession_ButHostKeepsListening()
{
// 非法长度前缀(协议失步):服务端断开该会话,但后续新连接仍能 HELLO
int p = _port;
using (var raw = new TcpClient("127.0.0.1", p))
{
byte[] bad = { 0x00, 0x00, 0x00, 0x00 }; // 长度 0
raw.GetStream().Write(bad, 0, 4);
}
Thread.Sleep(100);
using var c = new FakeClient();
c.ConnectAsync(_port).GetAwaiter().GetResult();
Assert.StartsWith("OK:HELLO", c.HelloAsync("CAN_BUS_9", 0).GetAwaiter().GetResult());
}
// ==================== 总线级应答器 ====================
[Fact]
public void BusEcho_Enabled_SenderReceivesOwnFrameAsResponder()
{
using var a = new FakeClient();
a.ConnectAsync(_port).GetAwaiter().GetResult();
a.HelloAsync("CAN_ECHO_ON", 0).GetAwaiter().GetResult();
a.SendAsync("QUERY:BUS_ECHO:ON").GetAwaiter().GetResult();
Assert.StartsWith("OK:", a.RecvAsync().GetAwaiter().GetResult());
// 单节点发帧(标准 ID):应答器应把同帧回显给发送者(无需第二节点)
a.SendAsync("SEND:0x111:AA BB").GetAwaiter().GetResult();
Assert.StartsWith("OK:TX", a.RecvAsync().GetAwaiter().GetResult());
var echo = a.RecvAsync().GetAwaiter().GetResult();
Assert.StartsWith("EV:RECV:0x111:AA BB", echo);
Assert.EndsWith(":RESPONDER", echo);
}
[Fact]
public void BusEcho_Disabled_NoEchoDelivered()
{
using var a = new FakeClient();
a.ConnectAsync(_port).GetAwaiter().GetResult();
a.HelloAsync("CAN_ECHO_OFF", 0).GetAwaiter().GetResult(); // 默认关
a.SendAsync("SEND:0x912:11").GetAwaiter().GetResult();
Assert.StartsWith("OK:TX", a.RecvAsync().GetAwaiter().GetResult());
// 紧接着的响应应为查询本身,中间不应插入任何 EV:RECV 回显
a.SendAsync("QUERY:BUS_ECHO").GetAwaiter().GetResult();
Assert.Equal("OK:BUS_ECHO:OFF", a.RecvAsync().GetAwaiter().GetResult());
}
}
}