namespace GisConverter.TestsApp.ConsoleApp
{
///
/// Tests for command-line argument parsing edge cases.
/// Ensures the CLI handles malformed or unusual inputs gracefully.
///
///
/// - Integration-style tests that invoke the ConsoleApp entry point via Program.Run(string[]).
/// - Tests capture Console.Out and Console.Error to assert on user-facing messages; streams are
/// redirected in the test constructor and restored in .
/// - The test collection attribute [Collection("Logging")] serializes execution with other logging tests to
/// avoid races on global logger state and shared log files.
/// - Tests should not rely on exact wording of messages; prefer case-insensitive substring assertions to remain
/// resilient to minor wording changes.
/// - Some tests use synthetic or temporary input paths. The ConsoleApp performs detection before filesystem
/// validation in some code paths; tests that exercise parsing/detection should accept either detection-related
/// diagnostics or an input-not-found diagnostic as valid outcomes.
/// - These tests assume Program.Run returns an exit code and does not call Environment.Exit.
///
[Collection("Logging")]
public class ArgumentParsingTests : IDisposable
{
private readonly TextWriter _originalOut;
private readonly TextWriter _originalErr;
private readonly StringWriter _outWriter;
private readonly StringWriter _errWriter;
///
/// Test constructor — capture console output streams for assertions.
///
///
/// - Redirects Console.Out and Console.Error to in-memory buffers so tests can examine CLI output.
/// - Restores original streams in to avoid side-effects between tests.
///
public ArgumentParsingTests()
{
_originalOut = Console.Out;
_originalErr = Console.Error;
_outWriter = new StringWriter();
_errWriter = new StringWriter();
Console.SetOut(_outWriter);
Console.SetError(_errWriter);
}
///
/// Tear-down — restore console streams and dispose 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();
}
[Fact(DisplayName = "Too few arguments returns app error and shows usage")]
public void Run_TooFewArgs_ReturnsAppError()
{
var args = new[] { "gis_convert", "somefile.csv" };
var exit = GisConverter.ConsoleApp.Program.Run(args);
var output = _outWriter.ToString();
Assert.Contains("usage:", output.ToLowerInvariant());
Assert.Equal(1, exit);
}
[Fact(DisplayName = "Empty string arguments are handled without crashing")]
public void Run_EmptyStringArgs_HandledGracefully()
{
var args = new[] { "gis_convert", "", "Csv", "", "", "Log", "" };
var exit = GisConverter.ConsoleApp.Program.Run(args);
Assert.NotEqual(3, exit);
}
[Fact(DisplayName = "Help flags return expected exit code")]
public void Run_HelpFlag_ReturnsExpectedCode()
{
var exitHelp = GisConverter.ConsoleApp.Program.Run(new[] { "help" });
var exitDashHelp = GisConverter.ConsoleApp.Program.Run(new[] { "--help" });
var exitH = GisConverter.ConsoleApp.Program.Run(new[] { "-h" });
Assert.Equal(1, exitHelp);
Assert.Equal(1, exitDashHelp);
Assert.Equal(1, exitH);
}
[Fact(DisplayName = "Unknown command returns app error")]
public void Run_UnknownCommand_ReturnsAppError()
{
var args = new[] { "unknown_command", "arg1", "arg2" };
var exit = GisConverter.ConsoleApp.Program.Run(args);
var output = _outWriter.ToString();
Assert.Contains("unknown command", output.ToLowerInvariant());
Assert.Equal(1, exit);
}
[Theory(DisplayName = "Special characters in paths are preserved")]
[InlineData("file with spaces.csv")]
[InlineData("file_with_underscore.csv")]
[InlineData("file-with-dash.csv")]
[InlineData("file.multiple.dots.csv")]
public void Run_SpecialCharsInPath_PreservedCorrectly(string filename)
{
var tempRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempRoot);
var inputPath = Path.Combine(tempRoot, filename);
File.WriteAllText(inputPath, "x,y\n1,2");
var outDir = Path.Combine(tempRoot, "out");
var tempDir = Path.Combine(tempRoot, "temp");
var args = new[]
{
"gis_convert",
inputPath,
"Csv",
outDir,
tempDir,
"unused"
};
_outWriter.GetStringBuilder().Clear();
_errWriter.GetStringBuilder().Clear();
var exit = GisConverter.ConsoleApp.Program.Run(args);
var combined = _outWriter.ToString() + _errWriter.ToString();
Assert.DoesNotContain("not found", combined.ToLowerInvariant());
try { if (Directory.Exists(tempRoot)) Directory.Delete(tempRoot, true); } catch { }
}
[Fact(DisplayName = "Format options are case-insensitive")]
public void Run_FormatOption_CaseInsensitive()
{
var tempRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempRoot);
var inputPath = Path.Combine(tempRoot, "test.csv");
File.WriteAllText(inputPath, "x,y\n1,2");
var outDir = Path.Combine(tempRoot, "out");
var tempDir = Path.Combine(tempRoot, "temp");
var argsLower = new[] { "gis_convert", inputPath, "csv", outDir, tempDir, "unused" };
var argsUpper = new[] { "gis_convert", inputPath, "CSV", outDir, tempDir, "unused" };
var argsMixed = new[] { "gis_convert", inputPath, "CsV", outDir, tempDir, "unused" };
_outWriter.GetStringBuilder().Clear();
var exit1 = GisConverter.ConsoleApp.Program.Run(argsLower);
var output1 = _outWriter.ToString();
_outWriter.GetStringBuilder().Clear();
var exit2 = GisConverter.ConsoleApp.Program.Run(argsUpper);
var output2 = _outWriter.ToString();
_outWriter.GetStringBuilder().Clear();
var exit3 = GisConverter.ConsoleApp.Program.Run(argsMixed);
var output3 = _outWriter.ToString();
Assert.DoesNotContain("unknown", output1.ToLowerInvariant());
Assert.DoesNotContain("unknown", output2.ToLowerInvariant());
Assert.DoesNotContain("unknown", output3.ToLowerInvariant());
try { if (Directory.Exists(tempRoot)) Directory.Delete(tempRoot, true); } catch { }
}
}
}