using System.Threading.Tasks; using NUnit.Framework; using MCPForUnity.Editor.Services.Transport; namespace MCPForUnityTests.Editor.Services { /// /// Pins TransportManager.StartAsync's coalescing contract: concurrent starts for the /// same mode share one in-flight attempt instead of racing — a second StartAsync would /// otherwise tear down the first connection mid-handshake (manual Connect vs the /// reload-resume/auto-start loops). /// public class TransportManagerTests { private sealed class PendingTransportClient : IMcpTransportClient { public readonly TaskCompletionSource Pending = new TaskCompletionSource(); public int StartCalls; public bool IsConnected => false; public string TransportName => "http"; public TransportState State { get; } = TransportState.Disconnected("http"); public Task StartAsync() { StartCalls++; return Pending.Task; } public Task StopAsync() => Task.CompletedTask; public Task VerifyAsync() => Task.FromResult(false); public Task ReregisterToolsAsync() => Task.CompletedTask; } [Test] public void StartAsync_ConcurrentCallsSameMode_CoalesceIntoOneAttempt() { var client = new PendingTransportClient(); var manager = new TransportManager(); manager.Configure(() => client, () => client); Task first = manager.StartAsync(TransportMode.Http); Task second = manager.StartAsync(TransportMode.Http); Assert.AreEqual(1, client.StartCalls, "concurrent starts must share one client attempt"); Assert.AreSame(first, second, "the in-flight task is returned to concurrent callers"); client.Pending.SetResult(true); // let the shared attempt finish } [Test] public void StartAsync_AfterCompletedStart_StartsFresh() { var client = new FakeTransportClient(); var manager = new TransportManager(); manager.Configure(() => client, () => client); Task first = manager.StartAsync(TransportMode.Http); Assert.IsTrue(first.IsCompleted && first.Result, "fake start should complete synchronously"); Task second = manager.StartAsync(TransportMode.Http); Assert.AreEqual(2, client.StartCalls, "a completed start must not block later restarts"); Assert.IsTrue(second.IsCompleted && second.Result); } } }