diff --git a/src/Fallout.Cli/Program.Update.cs b/src/Fallout.Cli/Commands/UpdateCommand.cs similarity index 59% rename from src/Fallout.Cli/Program.Update.cs rename to src/Fallout.Cli/Commands/UpdateCommand.cs index 145bf805c..6bf7ce2da 100644 --- a/src/Fallout.Cli/Program.Update.cs +++ b/src/Fallout.Cli/Commands/UpdateCommand.cs @@ -1,7 +1,7 @@ -using System; using System.IO; using System.Linq; using System.Text.Json.Nodes; +using Fallout.Cli.Prompts; using Fallout.Common; using Fallout.Common.Execution; using Fallout.Common.IO; @@ -10,37 +10,48 @@ using Fallout.Common.Utilities; using static Fallout.Common.Constants; -namespace Fallout.Cli; +namespace Fallout.Cli.Commands; -partial class Program +/// +/// fallout :update: updates the build scripts, build project, configuration file and global.json. +/// +public sealed class UpdateCommand : IFalloutCommand { - public static int Update(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) + private readonly IConsolePrompts _prompts; + + public UpdateCommand(IConsolePrompts prompts) => _prompts = prompts; + + public string Name => "update"; + + // GetConfiguration / WriteBuildScripts / WriteConfigurationFile / BUILD_PROJECT_FILE remain on + // Program as internal residual until the #392 collapse PR extracts them into services. + public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) { - PrintInfo(); + Program.PrintInfo(); Logging.Configure(); Assert.NotNull(rootDirectory); if (buildScript != null) { - ConfirmExecution("Update build scripts", () => UpdateBuildScripts(rootDirectory, buildScript)); - ConfirmExecution("Update build project", () => UpdateBuildProject(buildScript)); + _prompts.ConfirmExecution("Update build scripts", () => UpdateBuildScripts(rootDirectory, buildScript)); + _prompts.ConfirmExecution("Update build project", () => UpdateBuildProject(buildScript)); } - ConfirmExecution("Update configuration file", () => UpdateConfigurationFile(rootDirectory)); - ConfirmExecution("Update global.json", () => UpdateGlobalJsonFile(rootDirectory)); + _prompts.ConfirmExecution("Update configuration file", () => UpdateConfigurationFile(rootDirectory)); + _prompts.ConfirmExecution("Update global.json", () => UpdateGlobalJsonFile(rootDirectory)); - ShowCompletion("Updates"); + _prompts.ShowCompletion("Updates"); return 0; } private static void UpdateBuildScripts(AbsolutePath rootDirectory, AbsolutePath buildScript) { - var configuration = GetConfiguration(buildScript, evaluate: true); - var buildProjectFile = (AbsolutePath) configuration[BUILD_PROJECT_FILE]; + var configuration = Program.GetConfiguration(buildScript, evaluate: true); + var buildProjectFile = (AbsolutePath) configuration[Program.BUILD_PROJECT_FILE]; - WriteBuildScripts( + Program.WriteBuildScripts( scriptDirectory: buildScript.Parent, rootDirectory, buildDirectory: buildProjectFile.NotNull().Parent, @@ -49,8 +60,8 @@ private static void UpdateBuildScripts(AbsolutePath rootDirectory, AbsolutePath private static void UpdateBuildProject(AbsolutePath buildScript) { - var configuration = GetConfiguration(buildScript, evaluate: true); - var projectFile = configuration[BUILD_PROJECT_FILE]; + var configuration = Program.GetConfiguration(buildScript, evaluate: true); + var projectFile = configuration[Program.BUILD_PROJECT_FILE]; ProjectModelTasks.Initialize(); ProjectUpdater.Update(projectFile); } @@ -64,7 +75,7 @@ private static void UpdateConfigurationFile(AbsolutePath rootDirectory) var solutionFile = rootDirectory / configurationFile.ReadAllLines().FirstOrDefault(x => !x.IsNullOrEmpty()); configurationFile.DeleteFile(); - WriteConfigurationFile(rootDirectory, solutionFile); + Program.WriteConfigurationFile(rootDirectory, solutionFile); Host.Warning($"The previous {FalloutFileName} file was transformed to a {FalloutDirectoryName} directory."); Host.Warning($"The .tmp directory can be cleared, as it is moved to {FalloutDirectoryName}/temp as well."); if (solutionFile != null) diff --git a/src/Fallout.Cli/Program.cs b/src/Fallout.Cli/Program.cs index 64b03492c..155e4603e 100644 --- a/src/Fallout.Cli/Program.cs +++ b/src/Fallout.Cli/Program.cs @@ -61,7 +61,10 @@ private static void RegisterCommands(IServiceCollection services) // Legacy handlers still living on Program, adapted until they are extracted into command // types. Each conversion deletes one line here plus its Program.X.cs partial. - services.AddSingleton(new DelegateCommand("update", Update)); + services.AddSingleton(); + + // Legacy handlers still living on Program, adapted until they are extracted into command + // types. Each conversion deletes one line here plus its Program.X.cs partial. services.AddSingleton(new DelegateCommand("cake-convert", CakeConvert)); services.AddSingleton(new DelegateCommand("cake-clean", CakeClean)); services.AddSingleton(new DelegateCommand("secrets", Secrets)); diff --git a/tests/Fallout.Cli.Tests/Commands/FakeConsolePrompts.cs b/tests/Fallout.Cli.Tests/Commands/FakeConsolePrompts.cs new file mode 100644 index 000000000..b0f63edcf --- /dev/null +++ b/tests/Fallout.Cli.Tests/Commands/FakeConsolePrompts.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Fallout.Cli.Prompts; + +namespace Fallout.Cli.Tests.Commands; + +/// +/// Configurable test double for exercising command interaction logic +/// without a real console. +/// +internal sealed class FakeConsolePrompts : IConsolePrompts +{ + /// Answer returned by . + public bool ConfirmationResult { get; init; } + + /// When false, does not run its action (simulates a decline). + public bool InvokeConfirmedActions { get; init; } + + /// Titles passed to , in order. + public List Completions { get; } = new(); + + public bool PromptForConfirmation(string question) => ConfirmationResult; + public void ShowInput(string emoji, string title, string value) { } + public void ShowCompletion(string title) => Completions.Add(title); + public void ClearPreviousLine() { } + public string PromptForInput(string question, string defaultValue = null) => defaultValue; + public string PromptForSecret(string title, int? minLength = null) => string.Empty; + public T PromptForChoice(string question, params (T Value, string Description)[] choices) => default; + + public void ConfirmExecution(string title, Action action) + { + if (InvokeConfirmedActions) + action(); + } +} diff --git a/tests/Fallout.Cli.Tests/Commands/UpdateCommandTests.cs b/tests/Fallout.Cli.Tests/Commands/UpdateCommandTests.cs new file mode 100644 index 000000000..d3c554f66 --- /dev/null +++ b/tests/Fallout.Cli.Tests/Commands/UpdateCommandTests.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using Fallout.Cli.Commands; +using Fallout.Common.IO; +using FluentAssertions; +using Xunit; + +namespace Fallout.Cli.Tests.Commands; + +public class UpdateCommandTests +{ + [Fact] + public void Name_IsUpdate() + => new UpdateCommand(new FakeConsolePrompts()).Name.Should().Be("update"); + + [Fact] + public void Execute_NoBuildScript_DeclineAll_ReturnsZeroAndReportsCompletion() + { + var dir = (AbsolutePath)Path.Combine(Path.GetTempPath(), "fallout-update-" + Guid.NewGuid().ToString("N")); + dir.CreateDirectory(); + var prompts = new FakeConsolePrompts { InvokeConfirmedActions = false }; + try + { + // No build script and every confirmation declined → no update steps run, but the command + // still completes cleanly. + new UpdateCommand(prompts).Execute([], dir, buildScript: null).Should().Be(0); + + prompts.Completions.Should().Contain("Updates"); + } + finally + { + Directory.Delete(dir, recursive: true); + } + } +}