using System.Net; using AcDream.App.Net; using AcDream.Core.Net; using AcDream.UI.Abstractions; namespace AcDream.App.Tests.Net; public sealed class LiveSessionLifecycleHostTests { [Fact] public void HostRoutesLifecycleAndReleasesOnlyTheExactBoundSession() { var calls = new List(); using var sessionA = CreateSession(9000); using var sessionB = CreateSession(9001); var host = CreateHost(calls); LiveSessionBinding binding = host.BindSession(sessionA); host.ResetSessionState(); host.ReportConnecting("host", 9000, "user"); host.ReportConnected(); var selection = new LiveSessionCharacterSelection(2, 3u, "toon", "account"); host.ApplySelectedCharacter(selection); binding.ActivateCommands(); host.ApplyEnteredWorld(selection); Assert.Throws(() => host.DetachSession(sessionB)); binding.Dispose(); host.DetachSession(sessionA); LiveSessionBinding replacement = host.BindSession(sessionB); Assert.Equal( [ "bind", "reset", "connecting:host:9000:user", "connected", "selected:toon", "activate", "entered:toon", "deactivate", "detach-events", "bind", ], calls); replacement.Dispose(); host.DetachSession(sessionB); } [Fact] public void FailedBindingFactoryDoesNotClaimTheHost() { var calls = new List(); using var session = CreateSession(9000); bool fail = true; LiveSessionLifecycleHost host = CreateHost(calls, _ => { if (fail) { fail = false; throw new InvalidOperationException("bind failure"); } return CreateBinding(session, calls); }); Assert.Throws(() => host.BindSession(session)); LiveSessionBinding retry = host.BindSession(session); retry.Dispose(); host.DetachSession(session); } private static LiveSessionLifecycleHost CreateHost( List calls, Func? bind = null) => new(new LiveSessionLifecycleBindings( Bind: bind ?? (session => CreateBinding(session, calls)), Reset: () => calls.Add("reset"), Connecting: (host, port, user) => calls.Add($"connecting:{host}:{port}:{user}"), Connected: () => calls.Add("connected"), Selected: selection => calls.Add($"selected:{selection.CharacterName}"), Entered: selection => calls.Add($"entered:{selection.CharacterName}"))); private static LiveSessionBinding CreateBinding( WorldSession session, List calls) { calls.Add("bind"); return new LiveSessionBinding( session, NullCommandBus.Instance, activateCommands: () => calls.Add("activate"), deactivateCommands: () => calls.Add("deactivate"), detachEvents: () => calls.Add("detach-events")); } private static WorldSession CreateSession(int port) => new( new IPEndPoint(IPAddress.Loopback, port), new TestTransport()); private sealed class TestTransport : IWorldSessionTransport { public void Send(ReadOnlySpan datagram) { } public void Send(IPEndPoint remote, ReadOnlySpan datagram) { } public byte[]? Receive(TimeSpan timeout, out IPEndPoint? from) { from = null; return null; } public void Dispose() { } } }