using ItWorx.KnowledgeHub.Api.Operations; using ItWorx.KnowledgeHub.Application.Operations; using Microsoft.Extensions.DependencyInjection; namespace ItWorx.KnowledgeHub.Api.Tests; /// /// is deliberately pure/testable without booting a real /// web host or database — no Environment.Exit call inside the tested unit /// itself (only Program.cs's thin top-level wrapper performs the real process exit), /// so a fake is enough to prove the CLI contract. /// public sealed class RestoreModeRunnerTests { [Fact] public void IsRestoreMode_recognizes_the_flag_alone() { Assert.True(RestoreModeRunner.IsRestoreMode(["--restore"])); } [Fact] public void IsRestoreMode_recognizes_the_flag_alongside_other_arguments() { Assert.True(RestoreModeRunner.IsRestoreMode(["--restore", "--backup-id=x"])); } [Fact] public void IsRestoreMode_is_false_for_ordinary_startup_args() { Assert.False(RestoreModeRunner.IsRestoreMode([])); } [Fact] public async Task Missing_required_arguments_returns_usage_exit_code_without_touching_the_restore_service() { var services = new ServiceCollection() .AddSingleton(new FakeRestoreService(shouldSucceed: true)) .BuildServiceProvider(); using var output = new StringWriter(); var exitCode = await RestoreModeRunner.RunAsync(["--restore"], services, output, CancellationToken.None); Assert.Equal(2, exitCode); } [Fact] public async Task A_successful_restore_returns_exit_code_zero() { var connectionString = "Host=restore-db;Username=restore;Password=public-fixture"; var connectionFile = await CreateConnectionFileAsync(connectionString); string? receivedConnectionString = null; var services = new ServiceCollection() .AddSingleton(new FakeRestoreService(shouldSucceed: true, target => receivedConnectionString = target.ConnectionString)) .BuildServiceProvider(); using var output = new StringWriter(); int exitCode; try { exitCode = await RestoreModeRunner.RunAsync( ["--restore", "--backup-id=abc", $"--target-connection-file={connectionFile}", "--target-attachments=/tmp/x"], services, output, CancellationToken.None); } finally { File.Delete(connectionFile); } Assert.Equal(0, exitCode); Assert.Contains("GESLAAGD", output.ToString(), StringComparison.Ordinal); Assert.Equal(connectionString, receivedConnectionString); Assert.DoesNotContain(connectionString, output.ToString(), StringComparison.Ordinal); } [Fact] public async Task A_failed_restore_returns_a_non_zero_exit_code_and_reports_the_mismatch() { var connectionFile = await CreateConnectionFileAsync("Host=restore-db;Username=restore;Password=public-fixture"); var services = new ServiceCollection() .AddSingleton(new FakeRestoreService(shouldSucceed: false)) .BuildServiceProvider(); using var output = new StringWriter(); int exitCode; try { exitCode = await RestoreModeRunner.RunAsync( ["--restore", "--backup-id=abc", $"--target-connection-file={connectionFile}", "--target-attachments=/tmp/x"], services, output, CancellationToken.None); } finally { File.Delete(connectionFile); } Assert.Equal(1, exitCode); Assert.Contains("MISLUKT", output.ToString(), StringComparison.Ordinal); Assert.Contains("AFWIJKING", output.ToString(), StringComparison.Ordinal); } [Fact] public async Task Plaintext_connection_argument_is_not_accepted_or_echoed() { const string secret = "Password=public-fixture"; var services = new ServiceCollection() .AddSingleton(new FakeRestoreService(shouldSucceed: true)) .BuildServiceProvider(); using var output = new StringWriter(); var exitCode = await RestoreModeRunner.RunAsync( ["--restore", "--backup-id=abc", $"--target-connection={secret}", "--target-attachments=/tmp/x"], services, output, CancellationToken.None); Assert.Equal(2, exitCode); Assert.DoesNotContain(secret, output.ToString(), StringComparison.Ordinal); } [Fact] public async Task Restore_failure_does_not_print_exception_details_that_may_contain_credentials() { const string secret = "Password=public-fixture"; var connectionFile = await CreateConnectionFileAsync("Host=restore-db;" + secret); var services = new ServiceCollection() .AddSingleton(new FakeRestoreService(exception: new InvalidOperationException(secret))) .BuildServiceProvider(); using var output = new StringWriter(); int exitCode; try { exitCode = await RestoreModeRunner.RunAsync( ["--restore", "--backup-id=abc", $"--target-connection-file={connectionFile}", "--target-attachments=/tmp/x"], services, output, CancellationToken.None); } finally { File.Delete(connectionFile); } Assert.Equal(1, exitCode); Assert.DoesNotContain(secret, output.ToString(), StringComparison.Ordinal); } private static async Task CreateConnectionFileAsync(string connectionString) { var path = Path.Combine(Path.GetTempPath(), "ikh-restore-connection-" + Guid.NewGuid().ToString("N")); await File.WriteAllTextAsync(path, connectionString); if (!OperatingSystem.IsWindows()) File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite); return path; } private sealed class FakeRestoreService( bool shouldSucceed = true, Action? onRestore = null, Exception? exception = null) : IRestoreService { public Task RestoreAsync(string backupId, RestoreTarget target, CancellationToken cancellationToken) { if (exception is not null) throw exception; onRestore?.Invoke(target); var tables = shouldSucceed ? new List { new("pages", 3, 3) } : new List { new("pages", 2, 3) }; return Task.FromResult(new RestoreReport(backupId, shouldSucceed, tables, shouldSucceed ? 1 : 0, 1, [])); } } }