using System.Net.WebSockets; namespace Zcbot.WindowsNode.Tests; public sealed class NodeSessionTests { [Fact] public async Task SessionReconnectsAfterNetworkFailureAndSendsHelloBeforeRecovery() { var config = new NodeConfig( new Uri("https://node.example/"), Guid.NewGuid(), Guid.NewGuid(), "test-node", "secret", 30, []); var socket = new ClosingWebSocket(); var attempts = 0; var events = new List(); using var stop = new CancellationTokenSource(); var session = new NodeSession( config, () => new { available_slots = 0 }, (client, cancellationToken) => { events.Add("recovery"); stop.Cancel(); return Task.CompletedTask; }, (_, _) => Task.CompletedTask, (_, _, _) => Task.CompletedTask, (state, _) => events.Add(state.ToString()), (_, _) => { attempts++; return attempts == 1 ? Task.FromException(new WebSocketException("offline")) : Task.FromResult(new NodeProtocolClient(socket)); }, (_, _) => Task.CompletedTask); await session.RunAsync(stop.Token); Assert.Equal(2, attempts); Assert.Equal(["Connecting", "Offline", "Connecting", "Online", "recovery"], events); Assert.Single(socket.SentTypes); Assert.Equal("hello", socket.SentTypes[0]); } private sealed class ClosingWebSocket : WebSocket { private WebSocketState state = WebSocketState.Open; internal List SentTypes { get; } = []; public override WebSocketCloseStatus? CloseStatus => WebSocketCloseStatus.NormalClosure; public override string? CloseStatusDescription => "done"; public override WebSocketState State => state; public override string? SubProtocol => null; public override void Abort() => state = WebSocketState.Aborted; public override Task CloseAsync( WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken) { state = WebSocketState.Closed; return Task.CompletedTask; } public override Task CloseOutputAsync( WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken) { state = WebSocketState.CloseSent; return Task.CompletedTask; } public override void Dispose() => state = WebSocketState.Closed; public override Task ReceiveAsync( ArraySegment buffer, CancellationToken cancellationToken) => Task.FromResult( new WebSocketReceiveResult( 0, WebSocketMessageType.Close, true, WebSocketCloseStatus.NormalClosure, "done")); public override Task SendAsync( ArraySegment buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) { var message = NodeProtocolCodec.Decode(buffer.ToArray()); SentTypes.Add(message!.Type); return Task.CompletedTask; } } }