using System.Reflection; namespace GitConverter.TestsApp.ConsoleApp { /// /// Basic CLI tests for Program.Run validating help, unknown-option behavior and argument repair. /// /// /// Responsibilities /// - Validate the ConsoleApp entry point's behavior without invoking real conversions. /// - Execute Program.Run(...) as a normal method (not via Environment.Exit) so the test process remains alive. /// /// Test strategy /// - Capture Console.Out and Console.Error to assert on user-facing messages produced by the CLI. /// - Use the helper to call Program.Run directly. This keeps tests fast and deterministic. /// - Tests focus on parsing, messaging and the tolerant unquoted-input repair logic rather than converter internals. /// /// Behavior & expectations /// - No-argument invocation prints help and returns ExitCode.AppError. /// - Unknown conversion options produce either a detector message or usage output and return an application error. /// - The unquoted-input repair logic should join adjacent tokens into a single input path (when it exists) and not treat path fragments as the target option. /// /// Notes /// - Tests are intentionally lightweight and do not require sample data on disk. /// - If CLI behavior changes (messages, exit codes) update these tests to match the new contract. /// [Collection("Logging")] public class ProgramTests : IDisposable { private readonly TextWriter _originalOut; private readonly TextWriter _originalErr; private readonly StringWriter _outWriter; private readonly StringWriter _errWriter; /// /// Test constructor — captures console output streams for assertion. /// /// /// - Redirects Console.Out/Console.Error to in-memory buffers. /// - Restores original streams in to avoid side-effects between tests. /// public ProgramTests() { _originalOut = Console.Out; _originalErr = Console.Error; _outWriter = new StringWriter(); _errWriter = new StringWriter(); Console.SetOut(_outWriter); Console.SetError(_errWriter); } /// /// Tear-down — restores console streams and disposes buffers. /// /// /// - Always restore original streams even when assertions fail to keep test runner stable. /// - Dispose in-memory writers to release resources. /// public void Dispose() { Console.SetOut(_originalOut); Console.SetError(_originalErr); _outWriter.Dispose(); _errWriter.Dispose(); } /// /// Invoke the ConsoleApp entry point and return the numeric exit code. /// /// Arguments forwarded to Program.Run; the first token is the command name. /// Integer exit code returned by the CLI implementation. /// /// - Tests use this helper to call Program.Run directly. This avoids spawning a separate process /// and keeps assertions on console output straightforward. /// - If the CLI implementation changes to call Environment.Exit unconditionally, this helper will /// no longer work and tests must be updated to run the CLI in a separate process. /// - Captured stdout and stderr are available from the in-memory buffers after this call returns. /// private int InvokeRun(params string[] args) { return GitConverter.ConsoleApp.Program.Run(args); } [Fact(DisplayName = "No args prints help")] public void Run_NoArgs_PrintsHelp() { var exit = InvokeRun(Array.Empty()); var outText = _outWriter.ToString(); Assert.Contains("GIS Converter CLI Tool", outText, StringComparison.OrdinalIgnoreCase); Assert.Contains("Usage", outText, StringComparison.OrdinalIgnoreCase); // Program.Run returns AppError (1) when showing help / no args Assert.Equal(1, exit); } [Fact(DisplayName = "Unknown conversion option prints unknown message")] public void Run_GisConvert_UnknownOption_PrintsUnknownOption() { var args = new[] { "gis_convert", "dummy.input", "zzzz_nonexistent", Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")), Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")), "unused" }; var exit = InvokeRun(args); var outText = _outWriter.ToString(); var containsUnknown = outText.Contains("Unable to auto-detect converter from input", StringComparison.OrdinalIgnoreCase) || outText.Contains("Detector reason", StringComparison.OrdinalIgnoreCase) || outText.Contains("Please use 'help'", StringComparison.OrdinalIgnoreCase); var containsUsage = outText.Contains("Usage", StringComparison.OrdinalIgnoreCase); Assert.True(containsUnknown || containsUsage, $"Expected either 'Unknown target format option', 'Unknown format option' or usage output. Actual output:\n{outText}"); Assert.Equal(1, exit); } [Fact(DisplayName = "Unquoted input path is repaired and not treated as target option")] public void Run_GisConvert_UnquotedInput_Repaired() { // Simulate an input path tokenized by the shell (user forgot to quote). // Place a known supported option token ("Shapefile") after the path fragments. var outDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); Directory.CreateDirectory(outDir); Directory.CreateDirectory(tempDir); var args = new[] { "gis_convert", // split input path across tokens (no quotes) @"D:\GisConverter\Tests\Shapefile\Input\שכבות", "מידע", "(Arc", "View).7z", "Shapefile", // recognized target outDir, tempDir, "unused" }; _outWriter.GetStringBuilder().Clear(); _errWriter.GetStringBuilder().Clear(); var exit = InvokeRun(args); var outText = _outWriter.ToString() + Environment.NewLine + _errWriter.ToString(); // The program should not complain about unknown target option (repair happened). Assert.DoesNotContain("Unknown target format option", outText, StringComparison.OrdinalIgnoreCase); // Clean up try { if (Directory.Exists(outDir)) Directory.Delete(outDir, true); } catch { } try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { } } } }