using System; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using ivf_tl_Operate.Debug; using Xunit; namespace IvfTl.ControlHost.Tests { /// /// operate 端 DebugSessionClient 纯逻辑单测:用 fake HttpMessageHandler 注入响应, /// 验证 acquire 解析(含培养态)/SESSION_EXPIRED 触发失效回调/release 幂等/心跳定时器。 /// public class DebugSessionClientTests { private sealed class FakeHandler : HttpMessageHandler { public Func Responder; public int HeartbeatCount; protected override Task SendAsync(HttpRequestMessage req, CancellationToken ct) { if (req.RequestUri.AbsolutePath.Contains("heartbeat")) Interlocked.Increment(ref HeartbeatCount); var (code, body) = Responder(req); return Task.FromResult(new HttpResponseMessage(code) { Content = new StringContent(body) }); } } [Fact] public async Task Acquire_ParsesSessionIdAndCultivation() { var h = new FakeHandler { Responder = _ => (HttpStatusCode.OK, "{\"ok\":true,\"result\":\"sid123\",\"cultivating\":true,\"embryoCount\":3}") }; var c = new DebugSessionClient("http://127.0.0.1:9/", new HttpClient(h)); var r = await c.AcquireAsync(6); Assert.True(r.Ok); Assert.Equal("sid123", r.SessionId); Assert.True(r.Cultivating); Assert.Equal(3, r.EmbryoCount); await c.ReleaseAsync(); } [Fact] public async Task Command_SessionExpired_FiresOnSessionExpired() { bool fired = false; var h = new FakeHandler { Responder = req => req.RequestUri.AbsolutePath.Contains("command") ? (HttpStatusCode.Gone, "{\"ok\":false,\"code\":\"SESSION_EXPIRED\",\"error\":\"过期\"}") : (HttpStatusCode.OK, "{\"ok\":true,\"result\":\"sid123\"}") }; var c = new DebugSessionClient("http://127.0.0.1:9/", new HttpClient(h)); c.OnSessionExpired = () => fired = true; await c.AcquireAsync(6); var r = await c.CommandAsync("ReadTemp", null); Assert.False(r.Ok); Assert.Equal("SESSION_EXPIRED", r.Code); Assert.True(fired); } [Fact] public async Task Release_IsIdempotent_NoThrowWhenCalledTwice() { var h = new FakeHandler { Responder = _ => (HttpStatusCode.OK, "{\"ok\":true,\"result\":\"sid123\"}") }; var c = new DebugSessionClient("http://127.0.0.1:9/", new HttpClient(h)); await c.AcquireAsync(6); await c.ReleaseAsync(); await c.ReleaseAsync(); // 第二次不抛 } [Fact] public async Task Heartbeat_TimerSendsAfterAcquire() { var h = new FakeHandler { Responder = _ => (HttpStatusCode.OK, "{\"ok\":true,\"result\":\"sid123\"}") }; var c = new DebugSessionClient("http://127.0.0.1:9/", new HttpClient(h)) { HeartbeatIntervalMs = 50 }; await c.AcquireAsync(6); await Task.Delay(180); await c.ReleaseAsync(); Assert.True(h.HeartbeatCount >= 2); // 50ms 间隔 180ms 内至少 2 次 } } }