From c293e3ab4f35ef6cac354e70cf5e6e1983a9c121 Mon Sep 17 00:00:00 2001 From: darthsharp <48331467+darthsharp@users.noreply.github.com> Date: Tue, 23 Dec 2025 17:53:21 +0100 Subject: [PATCH 1/4] Add options validation support in `DefaultCliHost`: - Introduce `UseValidation` in `ICliHostBuilder` and `CliHostSettings` to enable command options validation. - Add `IOptionsValidation` interface and `OptionsValidationResult` for asynchronous options validation. - Enhance `DefaultCliHost` with validation logic and custom `CliCommandOptionsInvalidException`. - Extend `CliExitCodes` with `CommandOptionsInvalid`. - Add and update unit tests to cover validation scenarios. --- .../CreativeCoders.Cli.Core/CommandResult.cs | 10 + .../IOptionsValidation.cs | 13 + .../OptionsValidationResult.cs | 19 ++ .../CliExitCodes.cs | 2 + .../DefaultCliHost.cs | 54 +++- .../DefaultCliHostBuilder.cs | 19 ++ .../CliCommandOptionsInvalidException.cs | 10 + .../CreativeCoders.Cli.Hosting/ICliHost.cs | 7 + .../ICliHostBuilder.cs | 7 + .../Hosting/DefaultCliHostBuilderTests.cs | 64 +++++ .../Hosting/DefaultCliHostTests.cs | 235 +++++++++++++++++- 11 files changed, 430 insertions(+), 10 deletions(-) create mode 100644 source/Cli/CreativeCoders.Cli.Core/IOptionsValidation.cs create mode 100644 source/Cli/CreativeCoders.Cli.Core/OptionsValidationResult.cs create mode 100644 source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs diff --git a/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs b/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs index 5418023a..3d4d2595 100644 --- a/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs +++ b/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs @@ -1,6 +1,16 @@ +using JetBrains.Annotations; + namespace CreativeCoders.Cli.Core; +[PublicAPI] public class CommandResult { + public CommandResult() { } + + public CommandResult(int exitCode) : this() + { + ExitCode = exitCode; + } + public int ExitCode { get; init; } } diff --git a/source/Cli/CreativeCoders.Cli.Core/IOptionsValidation.cs b/source/Cli/CreativeCoders.Cli.Core/IOptionsValidation.cs new file mode 100644 index 00000000..df23710e --- /dev/null +++ b/source/Cli/CreativeCoders.Cli.Core/IOptionsValidation.cs @@ -0,0 +1,13 @@ +namespace CreativeCoders.Cli.Core; + +/// +/// Interface for command options validation. +/// +public interface IOptionsValidation +{ + /// + /// Validates the command options asynchronously. + /// + /// A indicating the result of the validation. + Task ValidateAsync(); +} diff --git a/source/Cli/CreativeCoders.Cli.Core/OptionsValidationResult.cs b/source/Cli/CreativeCoders.Cli.Core/OptionsValidationResult.cs new file mode 100644 index 00000000..523d2f72 --- /dev/null +++ b/source/Cli/CreativeCoders.Cli.Core/OptionsValidationResult.cs @@ -0,0 +1,19 @@ +namespace CreativeCoders.Cli.Core; + +/// +/// Represents the result of a command options validation. +/// +/// A boolean value indicating whether the options are valid. +/// An optional collection of validation messages. +public class OptionsValidationResult(bool isValid, IEnumerable? messages = null) +{ + /// + /// Gets a value indicating whether the validation was successful. + /// + public bool IsValid { get; } = isValid; + + /// + /// Gets the validation messages. + /// + public IEnumerable Messages { get; } = messages ?? []; +} diff --git a/source/Cli/CreativeCoders.Cli.Hosting/CliExitCodes.cs b/source/Cli/CreativeCoders.Cli.Hosting/CliExitCodes.cs index d7500d06..1a5a1832 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/CliExitCodes.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/CliExitCodes.cs @@ -9,4 +9,6 @@ public static class CliExitCodes public const int CommandCreationFailed = int.MinValue + 1; public const int CommandResultUnknown = int.MinValue + 2; + + public const int CommandOptionsInvalid = int.MinValue + 3; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHost.cs b/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHost.cs index bf718202..189fb331 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHost.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHost.cs @@ -5,6 +5,7 @@ using CreativeCoders.Cli.Hosting.Exceptions; using CreativeCoders.Cli.Hosting.Help; using CreativeCoders.Core; +using CreativeCoders.Core.Collections; using CreativeCoders.Core.Reflection; using CreativeCoders.SysConsole.Cli.Parsing; using Microsoft.Extensions.DependencyInjection; @@ -27,6 +28,9 @@ public class DefaultCliHost( private readonly ICliCommandHelpHandler _commandHelpHandler = Ensure.NotNull(commandHelpHandler); + private readonly CliHostSettings _settings = + serviceProvider.GetService() ?? new CliHostSettings(); + private (object Command, string[] Args, CliCommandInfo CommandInfo) CreateCliCommand(string[] args) { var findCommandNodeResult = _commandStore.FindCommandNode(args); @@ -43,7 +47,7 @@ public class DefaultCliHost( var command = commandInfo.CommandType.CreateInstance(_serviceProvider); - return command == null + return command is null ? throw new CliCommandConstructionFailedException("Command creation failed", args) : (command, findCommandNodeResult.RemainingArgs, commandInfo); } @@ -53,10 +57,10 @@ public class DefaultCliHost( } } - private static async Task ExecuteAsync(CliCommandInfo commandInfo, object command, + private async Task ExecuteAsync(CliCommandInfo commandInfo, object command, string[] optionsArgs) { - if (commandInfo.OptionsType == null) + if (commandInfo.OptionsType is null) { var commandResult = await ((ICliCommand)command).ExecuteAsync().ConfigureAwait(false); @@ -66,6 +70,7 @@ private static async Task ExecuteAsync(CliCommandInfo commandInfo, ob var optionsParser = new OptionParser(commandInfo.OptionsType); var options = optionsParser.Parse(optionsArgs); + await ValidateCommandOptionsAsync(options).ConfigureAwait(false); var commandWithOptionsResult = command.GetType().InvokeMember(nameof(ICliCommand<>.ExecuteAsync), BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, command, @@ -79,6 +84,26 @@ private static async Task ExecuteAsync(CliCommandInfo commandInfo, ob return new CliResult(CliExitCodes.CommandResultUnknown); } + private async Task ValidateCommandOptionsAsync(object options) + { + if (!_settings.UseValidation) + { + return; + } + + if (options is not IOptionsValidation validator) + { + return; + } + + var validationResult = await validator.ValidateAsync().ConfigureAwait(false); + + if (!validationResult.IsValid) + { + throw new CliCommandOptionsInvalidException(validationResult); + } + } + public async Task RunAsync(string[] args) { try @@ -109,10 +134,26 @@ public async Task RunAsync(string[] args) { PrintNearestMatch(args); + return new CliResult(e.ExitCode); + } + catch (CliCommandOptionsInvalidException e) + { + _ansiConsole.MarkupLine("[red]Error validating command options[/]"); + + e.ValidationResult.Messages.ForEach(message => _ansiConsole.MarkupLine($"- {message}")); + return new CliResult(e.ExitCode); } } + /// + public async Task RunMainAsync(string[] args) + { + var result = await RunAsync(args).ConfigureAwait(false); + + return result.ExitCode; + } + private void PrintNearestMatch(string[] args) { _ansiConsole.Markup($"[red]No command found for given arguments: {string.Join(" ", args)}[/]"); @@ -121,7 +162,7 @@ private void PrintNearestMatch(string[] args) var findCommandGroupNodeResult = _commandStore.FindCommandGroupNode(args); - if (findCommandGroupNodeResult?.Node == null) + if (findCommandGroupNodeResult?.Node is null) { _ansiConsole.WriteLine("No matches found"); @@ -131,3 +172,8 @@ private void PrintNearestMatch(string[] args) _commandHelpHandler.PrintHelpFor(findCommandGroupNodeResult.Node.ChildNodes); } } + +public class CliHostSettings +{ + public bool UseValidation { get; init; } +} diff --git a/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHostBuilder.cs b/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHostBuilder.cs index 0735251f..70bdfa4b 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHostBuilder.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHostBuilder.cs @@ -24,6 +24,8 @@ public class DefaultCliHostBuilder : ICliHostBuilder private bool _contextConfigured; + private bool _useValidation; + public ICliHostBuilder UseContext(Action? configure = null) where TContext : class, ICliCommandContext { @@ -70,6 +72,13 @@ public ICliHostBuilder EnableHelp(HelpCommandKind commandKind) return this; } + public ICliHostBuilder UseValidation(bool useValidation = true) + { + _useValidation = useValidation; + + return this; + } + public ICliHostBuilder SkipScanEntryAssembly(bool skipScanEntryAssembly = true) { _skipScanEntryAssembly = skipScanEntryAssembly; @@ -94,6 +103,16 @@ private IServiceProvider BuildServiceProvider() services.TryAddSingleton(); } + if (_useValidation) + { + var settings = new CliHostSettings + { + UseValidation = true + }; + + services.TryAddSingleton(settings); + } + _configureServicesActions.ForEach(x => x(services)); services.AddCliHosting(); diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs new file mode 100644 index 00000000..f428a3cd --- /dev/null +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs @@ -0,0 +1,10 @@ +using CreativeCoders.Cli.Core; +using CreativeCoders.Core; + +namespace CreativeCoders.Cli.Hosting.Exceptions; + +public class CliCommandOptionsInvalidException(OptionsValidationResult validationResult) + : CliExitException("Options for command invalid", CliExitCodes.CommandOptionsInvalid) +{ + public OptionsValidationResult ValidationResult { get; } = Ensure.NotNull(validationResult); +} diff --git a/source/Cli/CreativeCoders.Cli.Hosting/ICliHost.cs b/source/Cli/CreativeCoders.Cli.Hosting/ICliHost.cs index 609a1e8f..1e235cd0 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/ICliHost.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/ICliHost.cs @@ -3,4 +3,11 @@ namespace CreativeCoders.Cli.Hosting; public interface ICliHost { Task RunAsync(string[] args); + + /// + /// Runs the CLI host and returns the exit code as an integer. + /// + /// The command line arguments. + /// The exit code of the executed command. + Task RunMainAsync(string[] args); } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/ICliHostBuilder.cs b/source/Cli/CreativeCoders.Cli.Hosting/ICliHostBuilder.cs index 1412502f..53b1438c 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/ICliHostBuilder.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/ICliHostBuilder.cs @@ -18,6 +18,13 @@ ICliHostBuilder UseContext(Action? configu ICliHostBuilder EnableHelp(HelpCommandKind commandKind); + /// + /// Enables or disables command options validation. + /// + /// A boolean value indicating whether validation should be enabled. Default is true. + /// The same instance. + ICliHostBuilder UseValidation(bool useValidation = true); + ICliHostBuilder SkipScanEntryAssembly(bool skipScanEntryAssembly = true); ICliHost Build(); diff --git a/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostBuilderTests.cs b/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostBuilderTests.cs index df8fa353..53d4c75b 100644 --- a/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostBuilderTests.cs +++ b/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostBuilderTests.cs @@ -267,6 +267,70 @@ public void UseContext_AddsContextToServices() .Be(42); } + [Fact] + public void UseValidation_AddsSettingsToServices() + { + // Arrange + var builder = CreateBuilder(); + + builder.SkipScanEntryAssembly(); + + builder.UseValidation(); + + var commandScanner = A.Fake(); + var commandStore = A.Fake(); + var validator = A.Fake(); + var cliHost = A.Fake(); + + A.CallTo(() => commandScanner.ScanForCommands(A.Ignored)) + .Returns(CreateScanResult()); + + SubstituteServices(builder, commandScanner, commandStore, validator, cliHost); + + var services = GetBuiltServiceProvider(builder); + + // Act + builder.Build(); + + // Assert + var settings = services.GetRequiredService(); + + settings.UseValidation + .Should().Be(true); + } + + [Fact] + public void DontUseValidation_SettingsNotAddedToServicesOrSettingsUseValidationIsFalse() + { + // Arrange + var builder = CreateBuilder(); + + builder.SkipScanEntryAssembly(); + + builder.UseValidation(false); + + var commandScanner = A.Fake(); + var commandStore = A.Fake(); + var validator = A.Fake(); + var cliHost = A.Fake(); + + A.CallTo(() => commandScanner.ScanForCommands(A.Ignored)) + .Returns(CreateScanResult()); + + SubstituteServices(builder, commandScanner, commandStore, validator, cliHost); + + var services = GetBuiltServiceProvider(builder); + + // Act + builder.Build(); + + // Assert + var settings = services.GetService(); + + settings?.UseValidation + .Should().NotBe(true); + } + private static DefaultCliHostBuilder CreateBuilder() { return new DefaultCliHostBuilder(); diff --git a/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs b/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs index 7e4426b3..fc838cd9 100644 --- a/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs +++ b/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs @@ -25,6 +25,8 @@ public async Task RunAsync_WhenHelpIsRequested_PrintsHelpAndReturnsSuccess() var serviceProvider = A.Fake(); var helpHandler = A.Fake(); + SetupServiceProvider(serviceProvider, null); + A.CallTo(() => helpHandler.ShouldPrintHelp(args)) .Returns(true); @@ -53,6 +55,8 @@ public async Task RunAsync_WhenCommandNotFound_PrintsSuggestionsAndReturnsNotFou var serviceProvider = A.Fake(); var helpHandler = A.Fake(); + SetupServiceProvider(serviceProvider, null); + A.CallTo(() => helpHandler.ShouldPrintHelp(args)) .Returns(false); @@ -89,8 +93,7 @@ public async Task RunAsync_WhenCommandWithoutOptions_ExecutesAndReturnsResult() var serviceProvider = A.Fake(); var helpHandler = A.Fake(); - A.CallTo(() => serviceProvider.GetService(typeof(ICliCommandContext))) - .Returns(new CliCommandContext()); + SetupServiceProvider(serviceProvider, new CliCommandContext()); A.CallTo(() => helpHandler.ShouldPrintHelp(args)) .Returns(false); @@ -120,6 +123,47 @@ public async Task RunAsync_WhenCommandWithoutOptions_ExecutesAndReturnsResult() .Be(5); } + [Fact] + public async Task RunMainAsync_WhenCommandWithoutOptions_ExecutesAndReturnsIntResult() + { + // Arrange + var args = new[] { "run" }; + + var ansiConsole = A.Fake(); + var commandStore = A.Fake(); + var serviceProvider = A.Fake(); + var helpHandler = A.Fake(); + + SetupServiceProvider(serviceProvider, new CliCommandContext()); + + A.CallTo(() => helpHandler.ShouldPrintHelp(args)) + .Returns(false); + + var commandInfo = new CliCommandInfo + { + CommandAttribute = new CliCommandAttribute(["run"]), + CommandType = typeof(DummyCommandWithResult) + }; + + var commandNode = new CliCommandNode(commandInfo, "run", null); + + A.CallTo(() => commandStore.FindCommandNode(args)) + .Returns(new FindCommandNodeResult(commandNode, [])); + + A.CallTo(() => serviceProvider.GetService(typeof(int))) + .Returns(5); + + var host = new DefaultCliHost(ansiConsole, commandStore, serviceProvider, helpHandler); + + // Act + var result = await host.RunMainAsync(args); + + // Assert + result + .Should() + .Be(5); + } + [Fact] public async Task RunAsync_OnlyArgsForCommand_CommandContextHasCorrectArgs() { @@ -132,8 +176,7 @@ public async Task RunAsync_OnlyArgsForCommand_CommandContextHasCorrectArgs() var helpHandler = A.Fake(); var commandContext = new CliCommandContext(); - A.CallTo(() => serviceProvider.GetService(typeof(ICliCommandContext))) - .Returns(commandContext); + SetupServiceProvider(serviceProvider, commandContext); A.CallTo(() => helpHandler.ShouldPrintHelp(args)) .Returns(false); @@ -183,8 +226,7 @@ public async Task RunAsync_ArgsForCommandAndOptions_CommandContextHasCorrectArgs var helpHandler = A.Fake(); var commandContext = new CliCommandContext(); - A.CallTo(() => serviceProvider.GetService(typeof(ICliCommandContext))) - .Returns(commandContext); + SetupServiceProvider(serviceProvider, commandContext); A.CallTo(() => helpHandler.ShouldPrintHelp(args)) .Returns(false); @@ -233,6 +275,8 @@ public async Task RunAsync_WhenCommandCreationFails_PrintsErrorAndReturnsExitCod var serviceProvider = A.Fake(); var helpHandler = A.Fake(); + SetupServiceProvider(serviceProvider, null); + A.CallTo(() => helpHandler.ShouldPrintHelp(args)) .Returns(false); @@ -258,6 +302,185 @@ public async Task RunAsync_WhenCommandCreationFails_PrintsErrorAndReturnsExitCod .Be(CliExitCodes.CommandCreationFailed); } + [Fact] + public async Task RunAsync_WithOptionsValidation_ExecutesAndReturnsResult() + { + // Arrange + var args = new[] { "run" }; + + var ansiConsole = A.Fake(); + var commandStore = A.Fake(); + var serviceProvider = A.Fake(); + var helpHandler = A.Fake(); + + SetupServiceProvider(serviceProvider, new CliCommandContext(), + new CliHostSettings { UseValidation = true }); + + A.CallTo(() => helpHandler.ShouldPrintHelp(args)) + .Returns(false); + + var commandInfo = new CliCommandInfo + { + CommandAttribute = new CliCommandAttribute(["run"]), + CommandType = typeof(DummyCommandWithOptions), + OptionsType = typeof(DummyOptions) + }; + + var commandNode = new CliCommandNode(commandInfo, "run", null); + + A.CallTo(() => commandStore.FindCommandNode(args)) + .Returns(new FindCommandNodeResult(commandNode, [])); + + var host = new DefaultCliHost(ansiConsole, commandStore, serviceProvider, helpHandler); + + // Act + var result = await host.RunAsync(args); + + // Assert + result.ExitCode + .Should() + .Be(1357); + } + + [Fact] + public async Task RunAsync_WithOptionsValidationActivatedButWithoutVsalidation_ExecutesAndReturnsResult() + { + // Arrange + var args = new[] { "run" }; + + var ansiConsole = A.Fake(); + var commandStore = A.Fake(); + var serviceProvider = A.Fake(); + var helpHandler = A.Fake(); + + SetupServiceProvider(serviceProvider, new CliCommandContext(), + new CliHostSettings { UseValidation = true }); + + A.CallTo(() => helpHandler.ShouldPrintHelp(args)) + .Returns(false); + + var commandInfo = new CliCommandInfo + { + CommandAttribute = new CliCommandAttribute(["run"]), + CommandType = typeof(DummyCommandWithoutOptionsValidation), + OptionsType = typeof(DummyOptionsWithoutValidation) + }; + + var commandNode = new CliCommandNode(commandInfo, "run", null); + + A.CallTo(() => commandStore.FindCommandNode(args)) + .Returns(new FindCommandNodeResult(commandNode, [])); + + var host = new DefaultCliHost(ansiConsole, commandStore, serviceProvider, helpHandler); + + // Act + var result = await host.RunAsync(args); + + // Assert + result.ExitCode + .Should() + .Be(2468); + } + + [Fact] + public async Task RunAsync_WithOptionsValidationIsInvalid_ExecutesAndReturnsInvalidOptionsExitCode() + { + // Arrange + var args = new[] { "run" }; + + var ansiConsole = A.Fake(); + var commandStore = A.Fake(); + var serviceProvider = A.Fake(); + var helpHandler = A.Fake(); + + SetupServiceProvider(serviceProvider, new CliCommandContext(), + new CliHostSettings { UseValidation = true }); + + A.CallTo(() => helpHandler.ShouldPrintHelp(args)) + .Returns(false); + + var commandInfo = new CliCommandInfo + { + CommandAttribute = new CliCommandAttribute(["run"]), + CommandType = typeof(DummyCommandWithInvalidOptions), + OptionsType = typeof(InvalidDummyOptions) + }; + + var commandNode = new CliCommandNode(commandInfo, "run", null); + + A.CallTo(() => commandStore.FindCommandNode(args)) + .Returns(new FindCommandNodeResult(commandNode, [])); + + var host = new DefaultCliHost(ansiConsole, commandStore, serviceProvider, helpHandler); + + // Act + var result = await host.RunAsync(args); + + // Assert + result.ExitCode + .Should() + .Be(CliExitCodes.CommandOptionsInvalid); + } + + private static void SetupServiceProvider(IServiceProvider serviceProvider, + ICliCommandContext? commandContext, + CliHostSettings? settings = null) + { + if (commandContext != null) + { + A.CallTo(() => serviceProvider.GetService(typeof(ICliCommandContext))) + .Returns(commandContext); + } + + A.CallTo(() => serviceProvider.GetService(typeof(CliHostSettings))) + .Returns(settings); + } + + private sealed class DummyCommandWithOptions : ICliCommand + { + public Task ExecuteAsync(DummyOptions options) + { + return Task.FromResult(new CommandResult { ExitCode = 1357 }); + } + } + + [UsedImplicitly] + private class DummyOptions : IOptionsValidation + { + public Task ValidateAsync() + { + return Task.FromResult(new OptionsValidationResult(true)); + } + } + + private sealed class DummyCommandWithInvalidOptions : ICliCommand + { + public Task ExecuteAsync(InvalidDummyOptions options) + { + return Task.FromResult(new CommandResult { ExitCode = 1357 }); + } + } + + [UsedImplicitly] + private class InvalidDummyOptions : IOptionsValidation + { + public Task ValidateAsync() + { + return Task.FromResult(new OptionsValidationResult(false)); + } + } + + private sealed class DummyCommandWithoutOptionsValidation : ICliCommand + { + public Task ExecuteAsync(DummyOptionsWithoutValidation options) + { + return Task.FromResult(new CommandResult { ExitCode = 2468 }); + } + } + + [UsedImplicitly] + private class DummyOptionsWithoutValidation { } + [UsedImplicitly] private sealed class DummyCommand : ICliCommand { From 9360cc30a9c47252b748b841cbdedbdee0793c29 Mon Sep 17 00:00:00 2001 From: darthsharp <48331467+darthsharp@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:01:51 +0100 Subject: [PATCH 2/4] Update tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs b/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs index fc838cd9..4ca2042d 100644 --- a/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs +++ b/tests/CreativeCoders.Cli.Tests/Hosting/DefaultCliHostTests.cs @@ -343,7 +343,7 @@ public async Task RunAsync_WithOptionsValidation_ExecutesAndReturnsResult() } [Fact] - public async Task RunAsync_WithOptionsValidationActivatedButWithoutVsalidation_ExecutesAndReturnsResult() + public async Task RunAsync_WithOptionsValidationActivatedButWithoutValidation_ExecutesAndReturnsResult() { // Arrange var args = new[] { "run" }; From 0f2e476b3deced147a48dbc7e1bfe36aa4342885 Mon Sep 17 00:00:00 2001 From: darthsharp <48331467+darthsharp@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:02:37 +0100 Subject: [PATCH 3/4] Update source/Cli/CreativeCoders.Cli.Core/CommandResult.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- source/Cli/CreativeCoders.Cli.Core/CommandResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs b/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs index 3d4d2595..4b4b3c32 100644 --- a/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs +++ b/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs @@ -7,7 +7,7 @@ public class CommandResult { public CommandResult() { } - public CommandResult(int exitCode) : this() + public CommandResult(int exitCode) { ExitCode = exitCode; } From 831e9c35a47badc06f3d2cd9c937b2ccb6f004bd Mon Sep 17 00:00:00 2001 From: darthsharp <48331467+darthsharp@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:03:04 +0100 Subject: [PATCH 4/4] Update source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Exceptions/CliCommandOptionsInvalidException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs index f428a3cd..6deae8a3 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs @@ -4,7 +4,7 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; public class CliCommandOptionsInvalidException(OptionsValidationResult validationResult) - : CliExitException("Options for command invalid", CliExitCodes.CommandOptionsInvalid) + : CliExitException("Command options are invalid", CliExitCodes.CommandOptionsInvalid) { public OptionsValidationResult ValidationResult { get; } = Ensure.NotNull(validationResult); }