using System.CommandLine; using JdeScoping.ConfigManager.Cli.Commands; using JdeScoping.ConfigManager.Core.Services; using Microsoft.Extensions.DependencyInjection; namespace JdeScoping.ConfigManager.Cli.Tests.Commands; [Collection("Console Tests")] public class BackupCommandsTests { private readonly IServiceProvider _serviceProvider; private readonly IBackupService _backupService; private readonly IAutoDiscoveryService _autoDiscoveryService; private readonly Option _configPathOption; private readonly Option _verboseOption; private readonly Option _quietOption; public BackupCommandsTests() { _backupService = Substitute.For(); _autoDiscoveryService = Substitute.For(); var services = new ServiceCollection(); services.AddSingleton(_backupService); services.AddSingleton(_autoDiscoveryService); services.AddTestLogging(); _serviceProvider = services.BuildServiceProvider(); _configPathOption = new Option(["--config-path", "-c"]); _verboseOption = new Option(["--verbose", "-v"]); _quietOption = new Option(["--quiet", "-q"]); } [Fact] public void CreateCreateCommand_ReturnsCommand() { // Act var command = BackupCommands.CreateCreateCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); // Assert command.ShouldNotBeNull(); command.Name.ShouldBe("create"); } [Fact] public void CreateListCommand_ReturnsCommand() { // Act var command = BackupCommands.CreateListCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); // Assert command.ShouldNotBeNull(); command.Name.ShouldBe("list"); } [Fact] public void CreateRestoreCommand_ReturnsCommandWithTimestampArgument() { // Act var command = BackupCommands.CreateRestoreCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); // Assert command.ShouldNotBeNull(); command.Name.ShouldBe("restore"); command.Arguments.ShouldContain(a => a.Name == "timestamp"); } [Fact] public void CreateCleanupCommand_ReturnsCommandWithKeepOption() { // Act var command = BackupCommands.CreateCleanupCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); // Assert command.ShouldNotBeNull(); command.Name.ShouldBe("cleanup"); command.Options.ShouldContain(o => o.Name == "keep"); } [Fact] public async Task CreateCommand_WithNoConfigFolder_ReturnsError() { // Arrange _autoDiscoveryService.FindConfigFolderAsync(Arg.Any()) .Returns(Task.FromResult(null)); var command = BackupCommands.CreateCreateCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); var rootCommand = new RootCommand { command }; rootCommand.AddGlobalOption(_configPathOption); rootCommand.AddGlobalOption(_verboseOption); rootCommand.AddGlobalOption(_quietOption); // Act var exitCode = await rootCommand.InvokeAsync(["create"]); // Assert - command completes without throwing command.ShouldNotBeNull(); } [Fact] public async Task CreateCommand_WithValidConfig_CreatesBackup() { // Arrange var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); var appSettingsPath = Path.Combine(tempDir, "appsettings.json"); File.WriteAllText(appSettingsPath, "{}"); var backupPath = Path.Combine(tempDir, "appsettings.2026-01-15_120000.bak"); try { _autoDiscoveryService.FindConfigFolderAsync(Arg.Any()) .Returns(Task.FromResult(tempDir)); _backupService.CreateBackupAsync(appSettingsPath, Arg.Any()) .Returns(Task.FromResult(backupPath)); var command = BackupCommands.CreateCreateCommand( _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(["create"]); // Assert var output = writer.ToString(); output.ShouldContain("Backup created successfully"); await _backupService.Received(1).CreateBackupAsync(appSettingsPath, Arg.Any()); } finally { Console.SetOut(originalOut); } } finally { Directory.Delete(tempDir, true); } } [Fact] public void ListCommand_HasCorrectDescription() { // Arrange & Act var command = BackupCommands.CreateListCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); // Assert command.Description.ShouldBe("List available backups"); } [Fact] public async Task ListCommand_WithNoBackups_ShowsEmptyMessage() { // 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)); _backupService.GetBackupsAsync(appSettingsPath, Arg.Any()) .Returns(Task.FromResult>(new List())); var command = BackupCommands.CreateListCommand( _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(["list"]); // Assert var output = writer.ToString(); output.ShouldContain("No backups found"); } finally { Console.SetOut(originalOut); } } finally { Directory.Delete(tempDir, true); } } [Fact] public async Task CleanupCommand_CallsCleanupService() { // 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)); _backupService.GetBackupsAsync(appSettingsPath, Arg.Any()) .Returns(Task.FromResult>(new List())); var command = BackupCommands.CreateCleanupCommand( _serviceProvider, _configPathOption, _verboseOption, _quietOption); var rootCommand = new RootCommand { command }; rootCommand.AddGlobalOption(_configPathOption); rootCommand.AddGlobalOption(_verboseOption); rootCommand.AddGlobalOption(_quietOption); // Act await rootCommand.InvokeAsync(["cleanup", "--keep", "5"]); // Assert await _backupService.Received(1).CleanupOldBackupsAsync(appSettingsPath, 5, Arg.Any()); } finally { Directory.Delete(tempDir, true); } } [Fact] public async Task RestoreCommand_WithNonExistentBackup_ReturnsError() { // 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)); _backupService.GetBackupsAsync(appSettingsPath, Arg.Any()) .Returns(Task.FromResult>(new List())); var command = BackupCommands.CreateRestoreCommand( _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(["restore", "2026-01-99_999999"]); // Assert var output = writer.ToString(); output.ShouldContain("not found"); } finally { Console.SetOut(originalOut); } } finally { Directory.Delete(tempDir, true); } } }