using Sdt.Config; using Sdt.Core; using Xunit; namespace DevTool.Tests; public sealed class PrereqInstallerServiceTests { [Fact] public async Task PreferredInstallCommands_AreUsedFirst() { var svc = new PrereqInstallerService(); var cfg = new DevToolConfig { Tooling = new ToolingConfig { Tools = [ new ToolInstallDefinition { Tool = "dotnet", PreferredInstallCommands = [ "echo install dotnet", "dotnet --info" ] } ] } }; var plan = await svc.GetInstallPlanAsync("dotnet", Directory.GetCurrentDirectory(), cfg); Assert.True(plan.Supported); Assert.Equal("dotnet", plan.Tool); Assert.Equal(2, plan.Commands.Count); Assert.Equal("echo", plan.Commands[0].Command); Assert.Equal("dotnet", plan.Commands[1].Command); } [Fact] public async Task PreferredWingetCommand_IsNormalizedForNonInteractiveInstall() { var svc = new PrereqInstallerService(); var cfg = new DevToolConfig { Tooling = new ToolingConfig { Tools = [ new ToolInstallDefinition { Tool = "dotnet", PreferredInstallCommands = [ "winget install Microsoft.DotNet.SDK.10" ] } ] } }; var plan = await svc.GetInstallPlanAsync("dotnet", Directory.GetCurrentDirectory(), cfg); var args = plan.Commands[0].Args; Assert.Contains("--accept-package-agreements", args, StringComparer.OrdinalIgnoreCase); Assert.Contains("--accept-source-agreements", args, StringComparer.OrdinalIgnoreCase); Assert.Contains("--disable-interactivity", args, StringComparer.OrdinalIgnoreCase); Assert.Contains("--source", args, StringComparer.OrdinalIgnoreCase); } [Fact] public async Task PreferredShellAptCommand_IsNormalizedForNonInteractiveInstall() { var svc = new PrereqInstallerService(); var cfg = new DevToolConfig { Tooling = new ToolingConfig { Tools = [ new ToolInstallDefinition { Tool = "git", PreferredInstallCommands = [ "sh -c \"sudo apt-get update && sudo apt-get install git\"" ] } ] } }; var plan = await svc.GetInstallPlanAsync("git", Directory.GetCurrentDirectory(), cfg); Assert.Equal("sh", plan.Commands[0].Command, ignoreCase: true); Assert.True(plan.Commands[0].Args.Count >= 2); var script = plan.Commands[0].Args[1]; Assert.Contains("DEBIAN_FRONTEND=noninteractive", script, StringComparison.OrdinalIgnoreCase); Assert.Contains("apt-get install -y", script, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task DiagInstallPlanFailure_FallsBackToTemplatePlan() { var root = Path.Combine(Path.GetTempPath(), "sdt-prereq-" + Guid.NewGuid().ToString("N")); var scripts = Path.Combine(root, "scripts"); Directory.CreateDirectory(scripts); await File.WriteAllTextAsync(Path.Combine(scripts, "diag.py"), "import sys\nsys.exit(2)\n"); var svc = new PrereqInstallerService(); var plan = await svc.GetInstallPlanAsync("npm", root, new DevToolConfig()); Assert.True(plan.Supported); Assert.NotEmpty(plan.Commands); Assert.Contains("fallback", plan.Summary, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task DiagInstallPlanInvalidJson_FallsBackToTemplatePlan() { var root = Path.Combine(Path.GetTempPath(), "sdt-prereq-" + Guid.NewGuid().ToString("N")); var scripts = Path.Combine(root, "scripts"); Directory.CreateDirectory(scripts); await File.WriteAllTextAsync(Path.Combine(scripts, "diag.py"), "print('not-json')\n"); var svc = new PrereqInstallerService(); var plan = await svc.GetInstallPlanAsync("dotnet", root, new DevToolConfig()); Assert.True(plan.Supported); Assert.NotEmpty(plan.Commands); Assert.Contains("fallback", plan.Summary, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task TauriFallbackPlan_IsMultiStepAndClear() { var root = Path.Combine(Path.GetTempPath(), "sdt-prereq-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); var svc = new PrereqInstallerService(); var plan = await svc.GetInstallPlanAsync("tauri", root, new DevToolConfig()); Assert.True(plan.Supported); Assert.True(plan.Commands.Count >= 3); Assert.Contains("tauri", plan.Summary, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task TauriFallbackPlan_IncludesRustAndCliCommands() { var root = Path.Combine(Path.GetTempPath(), "sdt-prereq-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); var svc = new PrereqInstallerService(); var plan = await svc.GetInstallPlanAsync("tauri", root, new DevToolConfig()); Assert.Contains(plan.Commands, c => c.Args.Any(a => a.Contains("rustup", StringComparison.OrdinalIgnoreCase))); Assert.Contains(plan.Commands, c => c.Args.Any(a => a.Contains("@tauri-apps/cli", StringComparison.OrdinalIgnoreCase))); } }