namespace GisConverter.TestsApp.ConsoleApp { /// /// Comprehensive exit code validation tests. /// Verifies all four exit code paths defined in Program.ExitCode enum. /// /// /// - These tests exercise the ConsoleApp entry point via the public Program.Run(string[]) method and /// assert on returned integer exit codes and captured console output. /// - The test class is marked with [Collection("Logging")] to serialize execution with other logging-related /// tests and avoid races over global logger state or shared log files. /// - Each test captures Console.Out and Console.Error to in-memory buffers so assertions can inspect /// user-facing diagnostics without spawning a separate process. /// - Tests create isolated temporary folders for inputs/outputs to avoid interference and perform best-effort cleanup /// in . /// - Assumptions: /// - Program.Run returns an exit code and does not call Environment.Exit. /// - Messaging (usage text, warnings) may change slightly; prefer using substring, case-insensitive assertions. /// [Collection("Logging")] public class ExitCodeTests : IDisposable { private readonly TextWriter _originalOut; private readonly TextWriter _originalErr; private readonly StringWriter _outWriter; private readonly StringWriter _errWriter; private readonly string _tempRoot; /// /// Test constructor — capture console streams and create an isolated temp root for files used by tests. /// /// /// - Redirects Console.Out and Console.Error to StringWriters so tests can assert on output. /// - Creates a unique temp folder per test class instance; tests should remove it in . /// public ExitCodeTests() { _originalOut = Console.Out; _originalErr = Console.Error; _outWriter = new StringWriter(); _errWriter = new StringWriter(); Console.SetOut(_outWriter); Console.SetError(_errWriter); _tempRoot = Path.Combine(Path.GetTempPath(), "GisConverter.ExitCodeTests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_tempRoot); } /// /// Tear-down — restore console streams and remove temporary artifacts. /// /// /// - Always restore the original console streams to avoid affecting other tests. /// - Performs best-effort deletion of the temporary root and disposes in-memory writers. /// public void Dispose() { Console.SetOut(_originalOut); Console.SetError(_originalErr); _outWriter.Dispose(); _errWriter.Dispose(); try { if (Directory.Exists(_tempRoot)) Directory.Delete(_tempRoot, true); } catch { } } /// /// Verifies a successful conversion returns exit code 0. /// /// /// - Arranges a minimal valid CSV input and expects the converter stub to succeed. /// - If the CLI does not return 0 the test fails and prints captured output for diagnosis. /// [Fact(DisplayName = "Successful conversion returns exit code 0")] public void Run_SuccessfulConversion_ReturnsZero() { var dummyInput = Path.Combine(_tempRoot, "success.csv"); File.WriteAllText(dummyInput, "x,y\n1,2\n3,4"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", dummyInput, "Csv", outDir, tempDir, "unused" }; var exit = GisConverter.ConsoleApp.Program.Run(args); if (exit == 0) { Assert.Equal(0, exit); } else { var output = _outWriter.ToString() + _errWriter.ToString(); Assert.True(false, $"Expected exit 0 for valid CSV conversion but got {exit}. Output: {output}"); } } /// /// Verifies missing input file produces a non-zero exit code. /// /// /// - The CLI may return 1 (app error) or 2 (conversion failed) depending on the code path; the test accepts either. /// - Use this test to ensure the application does not silently succeed when inputs are missing. /// [Fact(DisplayName = "Missing input file returns non-zero exit code")] public void Run_MissingInputFile_ReturnsError() { var nonexistentInput = Path.Combine(_tempRoot, "doesnotexist.csv"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", nonexistentInput, "Csv", outDir, tempDir, "unused" }; var exit = GisConverter.ConsoleApp.Program.Run(args); Assert.True(exit == 1 || exit == 2, $"Expected exit 1 or 2 for missing file, got {exit}"); } /// /// Verifies that an invalid target format results in an application error exit code (1). /// /// /// - The test uses a dummy CSV input and supplies an unrecognized format option. /// - The expected behavior is that the CLI reports the invalid option and returns exit code 1. /// [Fact(DisplayName = "Invalid target format returns app error (1)")] public void Run_InvalidTargetFormat_ReturnsAppError() { var dummyInput = Path.Combine(_tempRoot, "dummy.csv"); File.WriteAllText(dummyInput, "x,y\n1,2"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", dummyInput, "InvalidFormatThatDoesNotExist", outDir, tempDir, "unused" }; var exit = GisConverter.ConsoleApp.Program.Run(args); Assert.Equal(1, exit); } /// /// Verifies help flags return the application error exit code (1). /// /// /// - Validates that top-level help options and the formats help option produce consistent exit codes and show help text. /// [Fact(DisplayName = "Help commands return app error (1)")] public void Run_HelpCommands_ReturnAppError() { var exitHelp = GisConverter.ConsoleApp.Program.Run(new[] { "help" }); var exitDashHelp = GisConverter.ConsoleApp.Program.Run(new[] { "--help" }); var exitFormats = GisConverter.ConsoleApp.Program.Run(new[] { "--help-formats" }); Assert.Equal(1, exitHelp); Assert.Equal(1, exitDashHelp); Assert.Equal(1, exitFormats); } /// /// Verifies insufficient arguments returns app error (1). /// /// /// - Ensures the CLI validates minimum argument count and returns the appropriate application error code. /// [Fact(DisplayName = "Insufficient arguments returns app error (1)")] public void Run_InsufficientArgs_ReturnsAppError() { var args = new[] { "gis_convert" }; var exit = GisConverter.ConsoleApp.Program.Run(args); Assert.Equal(1, exit); } } }