using NUnit.Framework; using MCPForUnity.Editor.Helpers; using MCPForUnity.External.Tommy; using MCPForUnity.Editor.Services; using System.IO; namespace MCPForUnityTests.Editor.Helpers { public class CodexConfigHelperTests { /// /// Mock platform service for testing /// private class MockPlatformService : IPlatformService { private readonly bool _isWindows; private readonly string _systemRoot; public MockPlatformService(bool isWindows, string systemRoot = "C:\\Windows") { _isWindows = isWindows; _systemRoot = systemRoot; } public bool IsWindows() => _isWindows; public string GetSystemRoot() => _isWindows ? _systemRoot : null; } [TearDown] public void TearDown() { // Reset service locator after each test MCPServiceLocator.Reset(); } [Test] public void TryParseCodexServer_SingleLineArgs_ParsesSuccessfully() { string toml = string.Join("\n", new[] { "[mcp_servers.unityMCP]", "command = \"uv\"", "args = [\"run\", \"--directory\", \"/abs/path\", \"server.py\"]" }); bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args); Assert.IsTrue(result, "Parser should detect server definition"); Assert.AreEqual("uv", command); CollectionAssert.AreEqual(new[] { "run", "--directory", "/abs/path", "server.py" }, args); } [Test] public void TryParseCodexServer_MultiLineArgsWithTrailingComma_ParsesSuccessfully() { string toml = string.Join("\n", new[] { "[mcp_servers.unityMCP]", "command = \"uv\"", "args = [", " \"run\",", " \"--directory\",", " \"/abs/path\",", " \"server.py\",", "]" }); bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args); Assert.IsTrue(result, "Parser should handle multi-line arrays with trailing comma"); Assert.AreEqual("uv", command); CollectionAssert.AreEqual(new[] { "run", "--directory", "/abs/path", "server.py" }, args); } [Test] public void TryParseCodexServer_MultiLineArgsWithComments_IgnoresComments() { string toml = string.Join("\n", new[] { "[mcp_servers.unityMCP]", "command = \"uv\"", "args = [", " \"run\", # launch command", " \"--directory\",", " \"/abs/path\",", " \"server.py\"", "]" }); bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args); Assert.IsTrue(result, "Parser should tolerate comments within the array block"); Assert.AreEqual("uv", command); CollectionAssert.AreEqual(new[] { "run", "--directory", "/abs/path", "server.py" }, args); } [Test] public void TryParseCodexServer_HeaderWithComment_StillDetected() { string toml = string.Join("\n", new[] { "[mcp_servers.unityMCP] # annotated header", "command = \"uv\"", "args = [\"run\", \"--directory\", \"/abs/path\", \"server.py\"]" }); bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args); Assert.IsTrue(result, "Parser should recognize section headers even with inline comments"); Assert.AreEqual("uv", command); CollectionAssert.AreEqual(new[] { "run", "--directory", "/abs/path", "server.py" }, args); } [Test] public void TryParseCodexServer_SingleQuotedArgsWithApostrophes_ParsesSuccessfully() { string toml = string.Join("\n", new[] { "[mcp_servers.unityMCP]", "command = 'uv'", "args = ['run', '--directory', '/Users/O''Connor/codex', 'server.py']" }); bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args); Assert.IsTrue(result, "Parser should accept single-quoted arrays with escaped apostrophes"); Assert.AreEqual("uv", command); CollectionAssert.AreEqual(new[] { "run", "--directory", "/Users/O'Connor/codex", "server.py" }, args); } [Test] public void BuildCodexServerBlock_OnWindows_IncludesSystemRootEnv() { // This test verifies the fix for https://github.com/CoplayDev/unity-mcp/issues/315 // On Windows, Codex requires SystemRoot environment variable to be set // Mock Windows platform MCPServiceLocator.Register(new MockPlatformService(isWindows: true, systemRoot: "C:\\Windows")); string uvPath = "C:\\path\\to\\uv.exe"; string serverSrc = "C:\\path\\to\\server"; string result = CodexConfigHelper.BuildCodexServerBlock(uvPath, serverSrc); Assert.IsNotNull(result, "BuildCodexServerBlock should return a valid TOML string"); // Parse the generated TOML to validate structure TomlTable parsed; using (var reader = new StringReader(result)) { parsed = TOML.Parse(reader); } // Verify basic structure Assert.IsTrue(parsed.TryGetNode("mcp_servers", out var mcpServersNode), "TOML should contain mcp_servers"); Assert.IsInstanceOf(mcpServersNode, "mcp_servers should be a table"); var mcpServers = mcpServersNode as TomlTable; Assert.IsTrue(mcpServers.TryGetNode("unityMCP", out var unityMcpNode), "mcp_servers should contain unityMCP"); Assert.IsInstanceOf(unityMcpNode, "unityMCP should be a table"); var unityMcp = unityMcpNode as TomlTable; Assert.IsTrue(unityMcp.TryGetNode("command", out _), "unityMCP should contain command"); Assert.IsTrue(unityMcp.TryGetNode("args", out _), "unityMCP should contain args"); // Verify env.SystemRoot is present on Windows bool hasEnv = unityMcp.TryGetNode("env", out var envNode); Assert.IsTrue(hasEnv, "Windows config should contain env table"); Assert.IsInstanceOf(envNode, "env should be a table"); var env = envNode as TomlTable; Assert.IsTrue(env.TryGetNode("SystemRoot", out var systemRootNode), "env should contain SystemRoot"); Assert.IsInstanceOf(systemRootNode, "SystemRoot should be a string"); var systemRoot = (systemRootNode as TomlString).Value; Assert.AreEqual("C:\\Windows", systemRoot, "SystemRoot should be C:\\Windows"); } [Test] public void BuildCodexServerBlock_OnNonWindows_ExcludesEnv() { // This test verifies that non-Windows platforms don't include env configuration // Mock non-Windows platform (e.g., macOS/Linux) MCPServiceLocator.Register(new MockPlatformService(isWindows: false)); string uvPath = "/usr/local/bin/uv"; string serverSrc = "/path/to/server"; string result = CodexConfigHelper.BuildCodexServerBlock(uvPath, serverSrc); Assert.IsNotNull(result, "BuildCodexServerBlock should return a valid TOML string"); // Parse the generated TOML to validate structure TomlTable parsed; using (var reader = new StringReader(result)) { parsed = TOML.Parse(reader); } // Verify basic structure Assert.IsTrue(parsed.TryGetNode("mcp_servers", out var mcpServersNode), "TOML should contain mcp_servers"); Assert.IsInstanceOf(mcpServersNode, "mcp_servers should be a table"); var mcpServers = mcpServersNode as TomlTable; Assert.IsTrue(mcpServers.TryGetNode("unityMCP", out var unityMcpNode), "mcp_servers should contain unityMCP"); Assert.IsInstanceOf(unityMcpNode, "unityMCP should be a table"); var unityMcp = unityMcpNode as TomlTable; Assert.IsTrue(unityMcp.TryGetNode("command", out _), "unityMCP should contain command"); Assert.IsTrue(unityMcp.TryGetNode("args", out _), "unityMCP should contain args"); // Verify env is NOT present on non-Windows platforms bool hasEnv = unityMcp.TryGetNode("env", out _); Assert.IsFalse(hasEnv, "Non-Windows config should not contain env table"); } [Test] public void UpsertCodexServerBlock_OnWindows_IncludesSystemRootEnv() { // This test verifies the fix for https://github.com/CoplayDev/unity-mcp/issues/315 // Ensures that upsert operations also include Windows-specific env configuration // Mock Windows platform MCPServiceLocator.Register(new MockPlatformService(isWindows: true, systemRoot: "C:\\Windows")); string existingToml = string.Join("\n", new[] { "[other_section]", "key = \"value\"" }); string uvPath = "C:\\path\\to\\uv.exe"; string serverSrc = "C:\\path\\to\\server"; string result = CodexConfigHelper.UpsertCodexServerBlock(existingToml, uvPath, serverSrc); Assert.IsNotNull(result, "UpsertCodexServerBlock should return a valid TOML string"); // Parse the generated TOML to validate structure TomlTable parsed; using (var reader = new StringReader(result)) { parsed = TOML.Parse(reader); } // Verify existing sections are preserved Assert.IsTrue(parsed.TryGetNode("other_section", out _), "TOML should preserve existing sections"); // Verify mcp_servers structure Assert.IsTrue(parsed.TryGetNode("mcp_servers", out var mcpServersNode), "TOML should contain mcp_servers"); Assert.IsInstanceOf(mcpServersNode, "mcp_servers should be a table"); var mcpServers = mcpServersNode as TomlTable; Assert.IsTrue(mcpServers.TryGetNode("unityMCP", out var unityMcpNode), "mcp_servers should contain unityMCP"); Assert.IsInstanceOf(unityMcpNode, "unityMCP should be a table"); var unityMcp = unityMcpNode as TomlTable; Assert.IsTrue(unityMcp.TryGetNode("command", out _), "unityMCP should contain command"); Assert.IsTrue(unityMcp.TryGetNode("args", out _), "unityMCP should contain args"); // Verify env.SystemRoot is present on Windows bool hasEnv = unityMcp.TryGetNode("env", out var envNode); Assert.IsTrue(hasEnv, "Windows config should contain env table"); Assert.IsInstanceOf(envNode, "env should be a table"); var env = envNode as TomlTable; Assert.IsTrue(env.TryGetNode("SystemRoot", out var systemRootNode), "env should contain SystemRoot"); Assert.IsInstanceOf(systemRootNode, "SystemRoot should be a string"); var systemRoot = (systemRootNode as TomlString).Value; Assert.AreEqual("C:\\Windows", systemRoot, "SystemRoot should be C:\\Windows"); } [Test] public void UpsertCodexServerBlock_OnNonWindows_ExcludesEnv() { // This test verifies that upsert operations on non-Windows platforms don't include env configuration // Mock non-Windows platform (e.g., macOS/Linux) MCPServiceLocator.Register(new MockPlatformService(isWindows: false)); string existingToml = string.Join("\n", new[] { "[other_section]", "key = \"value\"" }); string uvPath = "/usr/local/bin/uv"; string serverSrc = "/path/to/server"; string result = CodexConfigHelper.UpsertCodexServerBlock(existingToml, uvPath, serverSrc); Assert.IsNotNull(result, "UpsertCodexServerBlock should return a valid TOML string"); // Parse the generated TOML to validate structure TomlTable parsed; using (var reader = new StringReader(result)) { parsed = TOML.Parse(reader); } // Verify existing sections are preserved Assert.IsTrue(parsed.TryGetNode("other_section", out _), "TOML should preserve existing sections"); // Verify mcp_servers structure Assert.IsTrue(parsed.TryGetNode("mcp_servers", out var mcpServersNode), "TOML should contain mcp_servers"); Assert.IsInstanceOf(mcpServersNode, "mcp_servers should be a table"); var mcpServers = mcpServersNode as TomlTable; Assert.IsTrue(mcpServers.TryGetNode("unityMCP", out var unityMcpNode), "mcp_servers should contain unityMCP"); Assert.IsInstanceOf(unityMcpNode, "unityMCP should be a table"); var unityMcp = unityMcpNode as TomlTable; Assert.IsTrue(unityMcp.TryGetNode("command", out _), "unityMCP should contain command"); Assert.IsTrue(unityMcp.TryGetNode("args", out _), "unityMCP should contain args"); // Verify env is NOT present on non-Windows platforms bool hasEnv = unityMcp.TryGetNode("env", out _); Assert.IsFalse(hasEnv, "Non-Windows config should not contain env table"); } } }