using NUnit.Framework;
using MCPForUnity.Editor.Services.Server;
namespace MCPForUnityTests.Editor.Services.Server
{
///
/// Unit tests for ProcessDetector component.
/// These tests execute subprocess commands (ps, lsof, tasklist, wmic) which can be slow.
/// Marked as [Explicit] to exclude from normal test runs.
///
[TestFixture]
[Explicit]
public class ProcessDetectorTests
{
private ProcessDetector _detector;
[SetUp]
public void SetUp()
{
_detector = new ProcessDetector();
}
#region NormalizeForMatch Tests
[Test]
public void NormalizeForMatch_RemovesWhitespace()
{
// Arrange
string input = "Hello World";
// Act
string result = _detector.NormalizeForMatch(input);
// Assert
Assert.AreEqual("helloworld", result);
}
[Test]
public void NormalizeForMatch_LowercasesInput()
{
// Arrange
string input = "UPPERCASE";
// Act
string result = _detector.NormalizeForMatch(input);
// Assert
Assert.AreEqual("uppercase", result);
}
[Test]
public void NormalizeForMatch_HandlesNull()
{
// Act
string result = _detector.NormalizeForMatch(null);
// Assert
Assert.AreEqual(string.Empty, result);
}
[Test]
public void NormalizeForMatch_HandlesEmptyString()
{
// Act
string result = _detector.NormalizeForMatch(string.Empty);
// Assert
Assert.AreEqual(string.Empty, result);
}
[Test]
public void NormalizeForMatch_RemovesTabs()
{
// Arrange
string input = "hello\tworld";
// Act
string result = _detector.NormalizeForMatch(input);
// Assert
Assert.AreEqual("helloworld", result);
}
[Test]
public void NormalizeForMatch_RemovesNewlines()
{
// Arrange
string input = "hello\nworld\r\ntest";
// Act
string result = _detector.NormalizeForMatch(input);
// Assert
Assert.AreEqual("helloworldtest", result);
}
[Test]
public void NormalizeForMatch_PreservesNonWhitespace()
{
// Arrange
string input = "mcp-for-unity_test123";
// Act
string result = _detector.NormalizeForMatch(input);
// Assert
Assert.AreEqual("mcp-for-unity_test123", result);
}
#endregion
#region GetCurrentProcessId Tests
[Test]
public void GetCurrentProcessId_ReturnsPositiveInt()
{
// Act
int pid = _detector.GetCurrentProcessId();
// Assert
Assert.Greater(pid, 0, "Process ID should be positive");
}
[Test]
public void GetCurrentProcessId_ReturnsConsistentValue()
{
// Act
int pid1 = _detector.GetCurrentProcessId();
int pid2 = _detector.GetCurrentProcessId();
// Assert
Assert.AreEqual(pid1, pid2, "Process ID should be consistent across calls");
}
#endregion
#region ProcessExists Tests
[Test]
public void ProcessExists_CurrentProcess_ReturnsTrue()
{
// Arrange
int currentPid = _detector.GetCurrentProcessId();
// Act
bool exists = _detector.ProcessExists(currentPid);
// Assert
Assert.IsTrue(exists, "Current process should exist");
}
[Test]
public void ProcessExists_InvalidPid_ReturnsFalseOrHandlesGracefully()
{
// Act - Use a very high PID unlikely to exist
bool exists = _detector.ProcessExists(9999999);
// Assert - Should not throw, may return false or true (assumes exists if cannot verify)
Assert.Pass($"ProcessExists returned {exists} for invalid PID (handles gracefully)");
}
[Test]
public void ProcessExists_ZeroPid_HandlesGracefully()
{
// Act & Assert - Should not throw
Assert.DoesNotThrow(() =>
{
_detector.ProcessExists(0);
});
}
[Test]
public void ProcessExists_NegativePid_HandlesGracefully()
{
// Act & Assert - Should not throw
Assert.DoesNotThrow(() =>
{
_detector.ProcessExists(-1);
});
}
#endregion
#region GetListeningProcessIdsForPort Tests
[Test]
public void GetListeningProcessIdsForPort_InvalidPort_ReturnsEmpty()
{
// Act
var pids = _detector.GetListeningProcessIdsForPort(-1);
// Assert
Assert.IsNotNull(pids);
Assert.IsEmpty(pids, "Invalid port should return empty list");
}
[Test]
public void GetListeningProcessIdsForPort_UnusedPort_ReturnsEmpty()
{
// Act - Use a port that's unlikely to be in use
var pids = _detector.GetListeningProcessIdsForPort(59999);
// Assert
Assert.IsNotNull(pids);
Assert.IsEmpty(pids, "Unused port should return empty list");
}
[Test]
public void GetListeningProcessIdsForPort_ReturnsDistinctPids()
{
// Act
var pids = _detector.GetListeningProcessIdsForPort(80);
// Assert
Assert.IsNotNull(pids);
CollectionAssert.AllItemsAreUnique(pids, "PIDs should be distinct");
}
[Test]
public void GetListeningProcessIdsForPort_DoesNotThrow()
{
// Act & Assert - Should handle any port gracefully
Assert.DoesNotThrow(() =>
{
_detector.GetListeningProcessIdsForPort(8080);
});
}
#endregion
#region TryGetProcessCommandLine Tests
[Test]
public void TryGetProcessCommandLine_CurrentProcess_ReturnsResult()
{
// Arrange
int currentPid = _detector.GetCurrentProcessId();
// Act
bool result = _detector.TryGetProcessCommandLine(currentPid, out string argsLower);
// Assert - Platform dependent, but should not throw
Assert.Pass($"TryGetProcessCommandLine: success={result}, argsLower length={argsLower?.Length ?? 0}");
}
[Test]
public void TryGetProcessCommandLine_InvalidPid_ReturnsFalse()
{
// Act
bool result = _detector.TryGetProcessCommandLine(9999999, out string argsLower);
// Assert
Assert.IsFalse(result, "Invalid PID should return false");
Assert.IsEmpty(argsLower, "Args should be empty for invalid PID");
}
[Test]
public void TryGetProcessCommandLine_ReturnsNormalizedOutput()
{
// Arrange
int currentPid = _detector.GetCurrentProcessId();
// Act
bool result = _detector.TryGetProcessCommandLine(currentPid, out string argsLower);
// Assert
if (result && !string.IsNullOrEmpty(argsLower))
{
// Verify output is normalized (no whitespace, lowercase)
Assert.IsFalse(argsLower.Contains(" "), "Output should have no spaces");
Assert.AreEqual(argsLower, argsLower.ToLowerInvariant(), "Output should be lowercase");
}
Assert.Pass("Command line is properly normalized");
}
#endregion
#region LooksLikeMcpServerProcess Tests
[Test]
public void LooksLikeMcpServerProcess_CurrentProcess_ReturnsFalse()
{
// Arrange - Unity Editor process should not be an MCP server
int currentPid = _detector.GetCurrentProcessId();
// Act
bool result = _detector.LooksLikeMcpServerProcess(currentPid);
// Assert
Assert.IsFalse(result, "Unity Editor should not be identified as MCP server");
}
[Test]
public void LooksLikeMcpServerProcess_InvalidPid_ReturnsFalse()
{
// Act
bool result = _detector.LooksLikeMcpServerProcess(9999999);
// Assert
Assert.IsFalse(result, "Invalid PID should return false");
}
[Test]
public void LooksLikeMcpServerProcess_ZeroPid_ReturnsFalse()
{
// Act
bool result = _detector.LooksLikeMcpServerProcess(0);
// Assert
Assert.IsFalse(result, "Zero PID should return false");
}
[Test]
public void LooksLikeMcpServerProcess_NegativePid_ReturnsFalse()
{
// Act
bool result = _detector.LooksLikeMcpServerProcess(-1);
// Assert
Assert.IsFalse(result, "Negative PID should return false");
}
[Test]
public void LooksLikeMcpServerProcess_DoesNotThrow()
{
// Act & Assert - Should handle any PID gracefully
Assert.DoesNotThrow(() =>
{
_detector.LooksLikeMcpServerProcess(12345);
});
}
#endregion
#region Interface Implementation Tests
[Test]
public void ProcessDetector_ImplementsIProcessDetector()
{
// Assert
Assert.IsInstanceOf(_detector);
}
[Test]
public void ProcessDetector_CanBeUsedViaInterface()
{
// Arrange
IProcessDetector detector = new ProcessDetector();
// Act & Assert - All interface methods should work
Assert.DoesNotThrow(() =>
{
detector.NormalizeForMatch("test");
detector.GetCurrentProcessId();
detector.ProcessExists(1);
detector.GetListeningProcessIdsForPort(8080);
detector.TryGetProcessCommandLine(1, out _);
detector.LooksLikeMcpServerProcess(1);
});
}
#endregion
}
}