195 lines
8.3 KiB
C#
195 lines
8.3 KiB
C#
using NUnit.Framework;
|
|
using MCPForUnity.Editor.Constants;
|
|
using MCPForUnity.Editor.Services;
|
|
using MCPForUnity.Editor.Services.Transport;
|
|
using UnityEditor;
|
|
|
|
namespace MCPForUnityTests.Editor.Services
|
|
{
|
|
/// <summary>
|
|
/// Tests for the auto-start tick decisions (#1229): the session latch must only be
|
|
/// written when the deferred work actually dispatches, so a domain reload that wipes
|
|
/// the pending tick can no longer consume the once-per-session auto-start.
|
|
/// TickCore is a pure decision — it never writes the latch and never spawns servers.
|
|
/// The TryBeginReconnect tests only exercise its deliberate-drop paths, which never
|
|
/// dispatch the async connect.
|
|
/// </summary>
|
|
public class HttpAutoStartHandlerTests
|
|
{
|
|
private FakeTransportClient _fakeClient;
|
|
private TransportManager _savedManager;
|
|
private bool _savedLatch;
|
|
private bool _savedConnectPending;
|
|
private bool _savedResumeFlag;
|
|
private bool _savedAutoStart;
|
|
private bool _savedUseHttpTransport;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
_savedLatch = SessionState.GetBool(HttpAutoStartHandler.SessionInitKey, false);
|
|
_savedConnectPending = SessionState.GetBool(HttpAutoStartHandler.ConnectPendingKey, false);
|
|
_savedResumeFlag = SessionState.GetBool(HttpBridgeReloadHandler.ResumeSessionKey, false);
|
|
_savedAutoStart = EditorPrefs.GetBool(EditorPrefKeys.AutoStartOnLoad, false);
|
|
_savedUseHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
|
|
_savedManager = MCPServiceLocator.TransportManager;
|
|
|
|
SessionState.EraseBool(HttpAutoStartHandler.SessionInitKey);
|
|
SessionState.EraseBool(HttpAutoStartHandler.ConnectPendingKey);
|
|
SessionState.EraseBool(HttpBridgeReloadHandler.ResumeSessionKey);
|
|
EditorPrefs.SetBool(EditorPrefKeys.AutoStartOnLoad, false);
|
|
EditorPrefs.SetBool(EditorPrefKeys.UseHttpTransport, true);
|
|
EditorConfigurationCache.Instance.Refresh();
|
|
|
|
_fakeClient = new FakeTransportClient();
|
|
var manager = new TransportManager();
|
|
manager.Configure(() => _fakeClient, () => _fakeClient);
|
|
MCPServiceLocator.Register(manager);
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
// Stored-false and absent are indistinguishable: every read uses GetBool(key, false).
|
|
SessionState.SetBool(HttpAutoStartHandler.SessionInitKey, _savedLatch);
|
|
SessionState.SetBool(HttpAutoStartHandler.ConnectPendingKey, _savedConnectPending);
|
|
SessionState.SetBool(HttpBridgeReloadHandler.ResumeSessionKey, _savedResumeFlag);
|
|
EditorPrefs.SetBool(EditorPrefKeys.AutoStartOnLoad, _savedAutoStart);
|
|
EditorPrefs.SetBool(EditorPrefKeys.UseHttpTransport, _savedUseHttpTransport);
|
|
EditorConfigurationCache.Instance.Refresh();
|
|
MCPServiceLocator.Register(_savedManager);
|
|
}
|
|
|
|
private static bool LatchSet =>
|
|
SessionState.GetBool(HttpAutoStartHandler.SessionInitKey, false);
|
|
|
|
private static bool ConnectPendingSet =>
|
|
SessionState.GetBool(HttpAutoStartHandler.ConnectPendingKey, false);
|
|
|
|
[Test]
|
|
public void TickCore_EditorBusy_Defers()
|
|
{
|
|
EditorPrefs.SetBool(EditorPrefKeys.AutoStartOnLoad, true);
|
|
|
|
Assert.AreEqual(
|
|
HttpAutoStartHandler.TickDecision.DeferBusy,
|
|
HttpAutoStartHandler.TickCore(editorBusy: true));
|
|
Assert.IsFalse(LatchSet);
|
|
}
|
|
|
|
[Test]
|
|
public void TickCore_ResumePending_YieldsToReloadHandler()
|
|
{
|
|
EditorPrefs.SetBool(EditorPrefKeys.AutoStartOnLoad, true);
|
|
SessionState.SetBool(HttpBridgeReloadHandler.ResumeSessionKey, true);
|
|
|
|
Assert.AreEqual(
|
|
HttpAutoStartHandler.TickDecision.DeferToResume,
|
|
HttpAutoStartHandler.TickCore(editorBusy: false));
|
|
Assert.IsFalse(LatchSet);
|
|
}
|
|
|
|
[Test]
|
|
public void TickCore_ResumePendingButNothingToDo_SkipsWithoutWaiting()
|
|
{
|
|
// Auto-start disabled and not latched: a pending resume must not keep the
|
|
// tick alive when the only possible outcome is Skip.
|
|
SessionState.SetBool(HttpBridgeReloadHandler.ResumeSessionKey, true);
|
|
|
|
Assert.AreEqual(
|
|
HttpAutoStartHandler.TickDecision.Skip,
|
|
HttpAutoStartHandler.TickCore(editorBusy: false));
|
|
}
|
|
|
|
[Test]
|
|
public void TickCore_AutoStartDisabled_SkipsWithoutLatch()
|
|
{
|
|
Assert.AreEqual(
|
|
HttpAutoStartHandler.TickDecision.Skip,
|
|
HttpAutoStartHandler.TickCore(editorBusy: false));
|
|
Assert.IsFalse(LatchSet, "no latch when disabled — the pref is re-read on the next domain load");
|
|
}
|
|
|
|
[Test]
|
|
public void TickCore_AutoStartEnabled_ShouldStartWithoutWritingLatch()
|
|
{
|
|
EditorPrefs.SetBool(EditorPrefKeys.AutoStartOnLoad, true);
|
|
|
|
Assert.AreEqual(
|
|
HttpAutoStartHandler.TickDecision.ShouldStart,
|
|
HttpAutoStartHandler.TickCore(editorBusy: false));
|
|
Assert.IsFalse(LatchSet, "the caller latches only after the start work actually dispatches");
|
|
}
|
|
|
|
[Test]
|
|
public void TickCore_Latched_Skips()
|
|
{
|
|
EditorPrefs.SetBool(EditorPrefKeys.AutoStartOnLoad, true);
|
|
SessionState.SetBool(HttpAutoStartHandler.SessionInitKey, true);
|
|
|
|
Assert.AreEqual(
|
|
HttpAutoStartHandler.TickDecision.Skip,
|
|
HttpAutoStartHandler.TickCore(editorBusy: false));
|
|
}
|
|
|
|
[Test]
|
|
public void TickCore_LatchedWithConnectPending_Reconnects()
|
|
{
|
|
SessionState.SetBool(HttpAutoStartHandler.SessionInitKey, true);
|
|
SessionState.SetBool(HttpAutoStartHandler.ConnectPendingKey, true);
|
|
|
|
Assert.AreEqual(
|
|
HttpAutoStartHandler.TickDecision.ShouldReconnect,
|
|
HttpAutoStartHandler.TickCore(editorBusy: false),
|
|
"a reload that killed the in-flight connect should finish connect-only, never re-spawn");
|
|
}
|
|
|
|
[Test]
|
|
public void TickCore_ResumePendingBeatsReconnect()
|
|
{
|
|
SessionState.SetBool(HttpAutoStartHandler.SessionInitKey, true);
|
|
SessionState.SetBool(HttpAutoStartHandler.ConnectPendingKey, true);
|
|
SessionState.SetBool(HttpBridgeReloadHandler.ResumeSessionKey, true);
|
|
|
|
Assert.AreEqual(
|
|
HttpAutoStartHandler.TickDecision.DeferToResume,
|
|
HttpAutoStartHandler.TickCore(editorBusy: false),
|
|
"the reload handler owns bridge revival while a resume is pending");
|
|
}
|
|
|
|
[Test]
|
|
public void TryBeginReconnect_AutoStartDisabled_DropsPendingReconnect()
|
|
{
|
|
SessionState.SetBool(HttpAutoStartHandler.ConnectPendingKey, true);
|
|
|
|
Assert.IsTrue(HttpAutoStartHandler.TryBeginReconnect());
|
|
Assert.IsFalse(ConnectPendingSet, "a deliberate drop must consume the pending marker");
|
|
}
|
|
|
|
[Test]
|
|
public void TryBeginReconnect_StdioSelected_DropsPendingReconnect()
|
|
{
|
|
EditorPrefs.SetBool(EditorPrefKeys.AutoStartOnLoad, true);
|
|
EditorPrefs.SetBool(EditorPrefKeys.UseHttpTransport, false);
|
|
EditorConfigurationCache.Instance.Refresh();
|
|
SessionState.SetBool(HttpAutoStartHandler.ConnectPendingKey, true);
|
|
|
|
Assert.IsTrue(HttpAutoStartHandler.TryBeginReconnect());
|
|
Assert.IsFalse(ConnectPendingSet);
|
|
}
|
|
|
|
[Test]
|
|
public void TryBeginReconnect_BridgeAlreadyRunning_DropsPendingReconnect()
|
|
{
|
|
EditorPrefs.SetBool(EditorPrefKeys.AutoStartOnLoad, true);
|
|
var start = MCPServiceLocator.TransportManager.StartAsync(TransportMode.Http);
|
|
Assert.IsTrue(start.IsCompleted && start.Result, "fake bridge should start synchronously");
|
|
SessionState.SetBool(HttpAutoStartHandler.ConnectPendingKey, true);
|
|
|
|
Assert.IsTrue(HttpAutoStartHandler.TryBeginReconnect());
|
|
Assert.IsFalse(ConnectPendingSet);
|
|
Assert.AreEqual(1, _fakeClient.StartCalls, "an already-running bridge must not be restarted");
|
|
}
|
|
}
|
|
}
|