using Sdt.Core; using Sdt.Config; using Xunit; namespace DevTool.Tests; public sealed class CommandResolverTests { [Fact] public void Resolve_Npm_OnWindows_UsesCmdShim() { var resolved = CommandResolver.Resolve("npm"); if (OperatingSystem.IsWindows()) Assert.True( resolved.EndsWith("npm.cmd", StringComparison.OrdinalIgnoreCase) || resolved.EndsWith("\\npm", StringComparison.OrdinalIgnoreCase) || string.Equals(resolved, "npm.cmd", StringComparison.OrdinalIgnoreCase), $"Resolved npm path was '{resolved}'"); else Assert.Equal("npm", resolved); } [Fact] public void Resolve_PathOrExtension_Unchanged() { Assert.Equal("C:\\tools\\npm.cmd", CommandResolver.Resolve("C:\\tools\\npm.cmd")); var resolved = CommandResolver.Resolve("dotnet.exe"); if (OperatingSystem.IsWindows() && Path.IsPathRooted(resolved)) Assert.EndsWith("dotnet.exe", resolved, StringComparison.OrdinalIgnoreCase); else Assert.Equal("dotnet.exe", resolved); } [Fact] public void Resolve_Npm_PrefersCmdShim_WhenBothBareAndCmdExist() { if (!OperatingSystem.IsWindows()) return; var temp = Path.Combine(Path.GetTempPath(), "sdt-cmdresolve-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(temp); File.WriteAllText(Path.Combine(temp, "npm"), ""); File.WriteAllText(Path.Combine(temp, "npm.cmd"), "@echo off"); var originalPath = Environment.GetEnvironmentVariable("PATH"); try { Environment.SetEnvironmentVariable("PATH", temp); var resolved = CommandResolver.Resolve("npm"); Assert.EndsWith("npm.cmd", resolved, StringComparison.OrdinalIgnoreCase); } finally { Environment.SetEnvironmentVariable("PATH", originalPath); try { Directory.Delete(temp, recursive: true); } catch { } } } [Fact] public void ResolveWithTrace_ConfiguredOverride_IsUsed() { if (!OperatingSystem.IsWindows()) return; var temp = Path.Combine(Path.GetTempPath(), "sdt-cmdresolve-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(temp); var overridePath = Path.Combine(temp, "npm.cmd"); File.WriteAllText(overridePath, "@echo off"); var cfg = new DevToolConfig { Tooling = new ToolingConfig { Tools = [ new ToolInstallDefinition { Tool = "npm", Executables = [overridePath] } ] } }; var resolved = CommandResolver.ResolveWithTrace("npm", cfg, "npm"); Assert.Equal(CommandResolutionSource.ConfiguredOverride, resolved.Source); Assert.EndsWith("npm.cmd", resolved.Resolved, StringComparison.OrdinalIgnoreCase); } [Fact] public void ResolveWithTrace_ExpandsWindowsPathTokens() { if (!OperatingSystem.IsWindows()) return; var nvmHome = Path.Combine(Path.GetTempPath(), "sdt-nvmhome-" + Guid.NewGuid().ToString("N")); var nvmLink = Path.Combine(Path.GetTempPath(), "sdt-nvmlink-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(nvmHome); Directory.CreateDirectory(nvmLink); File.WriteAllText(Path.Combine(nvmLink, "npm.cmd"), "@echo off"); var originalPath = Environment.GetEnvironmentVariable("PATH"); var originalHome = Environment.GetEnvironmentVariable("NVM_HOME"); var originalLink = Environment.GetEnvironmentVariable("NVM_SYMLINK"); try { Environment.SetEnvironmentVariable("NVM_HOME", nvmHome); Environment.SetEnvironmentVariable("NVM_SYMLINK", nvmLink); Environment.SetEnvironmentVariable("PATH", "%NVM_HOME%;%NVM_SYMLINK%"); var result = CommandResolver.ResolveWithTrace("npm"); Assert.True(result.Source is CommandResolutionSource.Shim or CommandResolutionSource.Path); Assert.EndsWith("npm.cmd", result.Resolved, StringComparison.OrdinalIgnoreCase); } finally { Environment.SetEnvironmentVariable("PATH", originalPath); Environment.SetEnvironmentVariable("NVM_HOME", originalHome); Environment.SetEnvironmentVariable("NVM_SYMLINK", originalLink); try { Directory.Delete(nvmHome, recursive: true); } catch { } try { Directory.Delete(nvmLink, recursive: true); } catch { } } } }