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 { }
}
}
}