using System.CommandLine; using JdeScoping.ConfigManager.Cli.Commands; using JdeScoping.ConfigManager.Core.Models; using JdeScoping.ConfigManager.Core.Services; using JdeScoping.DataSync.Configuration; using Microsoft.Extensions.DependencyInjection; namespace JdeScoping.ConfigManager.Cli.Tests.Commands; [Collection("Console Tests")] public class ValidateCommandTests { private readonly IServiceProvider _serviceProvider; private readonly IConfigFileService _configFileService; private readonly IAutoDiscoveryService _autoDiscoveryService; private readonly IValidationService _validationService; private readonly IRuntimeConfigValidationService _runtimeValidationService; private readonly Option _configPathOption; private readonly Option _verboseOption; private readonly Option _quietOption; public ValidateCommandTests() { _configFileService = Substitute.For(); _autoDiscoveryService = Substitute.For(); _validationService = Substitute.For(); _runtimeValidationService = Substitute.For(); var services = new ServiceCollection(); services.AddSingleton(_configFileService); services.AddSingleton(_autoDiscoveryService); services.AddSingleton(_validationService); services.AddSingleton(_runtimeValidationService); services.AddTestLogging(); _serviceProvider = services.BuildServiceProvider(); _configPathOption = new Option(["--config-path", "-c"]); _verboseOption = new Option(["--verbose", "-v"]); _quietOption = new Option(["--quiet", "-q"]); } [Fact] public void CreateAppSettingsCommand_ReturnsCommand() { // Act var command = ValidateCommand.CreateAppSettingsCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); // Assert command.ShouldNotBeNull(); command.Name.ShouldBe("appsettings"); command.Description.ShouldBe("Validate appsettings.json"); } [Fact] public void CreatePipelinesCommand_ReturnsCommand() { // Act var command = ValidateCommand.CreatePipelinesCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); // Assert command.ShouldNotBeNull(); command.Name.ShouldBe("pipelines"); command.Description.ShouldBe("Validate pipeline configuration files"); } [Fact] public void CreateAllCommand_ReturnsCommand() { // Act var command = ValidateCommand.CreateAllCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); // Assert command.ShouldNotBeNull(); command.Name.ShouldBe("all"); command.Description.ShouldBe("Validate all configuration files"); } [Fact] public void CreateRuntimeCommand_ReturnsCommand() { // Act var command = ValidateCommand.CreateRuntimeCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); // Assert command.ShouldNotBeNull(); command.Name.ShouldBe("runtime"); command.Description.ShouldBe("Run Infrastructure validators"); } [Fact] public async Task AppSettingsCommand_WithNoConfigFolder_ReturnsError() { // Arrange _autoDiscoveryService.FindConfigFolderAsync(Arg.Any()) .Returns(Task.FromResult(null)); var command = ValidateCommand.CreateAppSettingsCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); var rootCommand = new RootCommand { command }; rootCommand.AddGlobalOption(_configPathOption); rootCommand.AddGlobalOption(_verboseOption); rootCommand.AddGlobalOption(_quietOption); // Act var exitCode = await rootCommand.InvokeAsync(["appsettings"]); // Assert - command completes without throwing command.ShouldNotBeNull(); } [Fact] public async Task AppSettingsCommand_WithValidConfig_ReturnsSuccess() { // Arrange var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); var appSettingsPath = Path.Combine(tempDir, "appsettings.json"); File.WriteAllText(appSettingsPath, "{}"); try { _autoDiscoveryService.FindConfigFolderAsync(Arg.Any()) .Returns(Task.FromResult(tempDir)); var config = new ConfigModel(); _configFileService.LoadAppSettingsAsync(appSettingsPath, Arg.Any()) .Returns(Task.FromResult(config)); _validationService.ValidateAppSettings(config) .Returns(new ValidationResult()); // No errors = IsValid true var command = ValidateCommand.CreateAppSettingsCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); var rootCommand = new RootCommand { command }; rootCommand.AddGlobalOption(_configPathOption); rootCommand.AddGlobalOption(_verboseOption); rootCommand.AddGlobalOption(_quietOption); // Capture console output var originalOut = Console.Out; using var writer = new StringWriter(); Console.SetOut(writer); try { // Act await rootCommand.InvokeAsync(["appsettings"]); // Assert var output = writer.ToString(); output.ShouldContain("Valid"); } finally { Console.SetOut(originalOut); } } finally { Directory.Delete(tempDir, true); } } [Fact] public async Task AppSettingsCommand_WithInvalidConfig_ReturnsErrors() { // Arrange var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); var appSettingsPath = Path.Combine(tempDir, "appsettings.json"); File.WriteAllText(appSettingsPath, "{}"); try { _autoDiscoveryService.FindConfigFolderAsync(Arg.Any()) .Returns(Task.FromResult(tempDir)); var config = new ConfigModel(); _configFileService.LoadAppSettingsAsync(appSettingsPath, Arg.Any()) .Returns(Task.FromResult(config)); var result = new ValidationResult(); result.Errors.Add("Connection string 'LocalCache' is required"); _validationService.ValidateAppSettings(config).Returns(result); var command = ValidateCommand.CreateAppSettingsCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); var rootCommand = new RootCommand { command }; rootCommand.AddGlobalOption(_configPathOption); rootCommand.AddGlobalOption(_verboseOption); rootCommand.AddGlobalOption(_quietOption); // Capture console output (Serilog writes all output to stdout) var originalOut = Console.Out; using var writer = new StringWriter(); Console.SetOut(writer); try { // Act await rootCommand.InvokeAsync(["appsettings"]); // Assert var output = writer.ToString(); output.ShouldContain("Connection string 'LocalCache' is required"); } finally { Console.SetOut(originalOut); } } finally { Directory.Delete(tempDir, true); } } [Fact] public async Task PipelinesCommand_WithValidPipelines_ReturnsSuccess() { // Arrange var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); var pipelinesDir = Path.Combine(tempDir, "Pipelines"); Directory.CreateDirectory(pipelinesDir); try { _autoDiscoveryService.FindConfigFolderAsync(Arg.Any()) .Returns(Task.FromResult(tempDir)); var pipelines = new Dictionary { ["TestPipeline"] = new EtlPipelineConfig { Name = "TestPipeline" } }; _configFileService.LoadAllPipelinesAsync(pipelinesDir, Arg.Any()) .Returns(Task.FromResult(pipelines)); _validationService.ValidatePipelines(pipelines) .Returns(new ValidationResult()); // No errors = IsValid true var command = ValidateCommand.CreatePipelinesCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); var rootCommand = new RootCommand { command }; rootCommand.AddGlobalOption(_configPathOption); rootCommand.AddGlobalOption(_verboseOption); rootCommand.AddGlobalOption(_quietOption); // Capture console output var originalOut = Console.Out; using var writer = new StringWriter(); Console.SetOut(writer); try { // Act await rootCommand.InvokeAsync(["pipelines"]); // Assert var output = writer.ToString(); output.ShouldContain("Valid"); } finally { Console.SetOut(originalOut); } } finally { Directory.Delete(tempDir, true); } } [Fact] public async Task RuntimeCommand_WithNoConfigFolder_ReturnsError() { // Arrange _autoDiscoveryService.FindConfigFolderAsync(Arg.Any()) .Returns(Task.FromResult(null)); var command = ValidateCommand.CreateRuntimeCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); var rootCommand = new RootCommand { command }; rootCommand.AddGlobalOption(_configPathOption); rootCommand.AddGlobalOption(_verboseOption); rootCommand.AddGlobalOption(_quietOption); // Act var exitCode = await rootCommand.InvokeAsync(["runtime"]); // Assert - command completes without throwing command.ShouldNotBeNull(); } }