using System.Net; using AcDream.Core.Net; using AcDream.Runtime.Session; namespace AcDream.Runtime.Tests.Session; 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(RuntimeGenerationToken.Initial); 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, 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 int Receive( Span destination, TimeSpan timeout, out IPEndPoint? from) { from = null; return -1; } public ValueTask ReceiveAsync( Memory destination, CancellationToken cancellationToken) => throw new OperationCanceledException(cancellationToken); public void Dispose() { } } }