using System.Reflection;
namespace GitConverter.TestsApp.ConsoleApp
{
///
/// Basic CLI tests for Program.Run: help, unknown-option behavior and tolerant unquoted-input repair.
///
///
/// Responsibilities
/// - Validate basic CLI behavior of the ConsoleApp entry point without invoking real conversions.
/// - Run Program.Run(...) via reflection so test process is not terminated by Environment.Exit.
///
/// Test strategy
/// - Capture Console.Out and Console.Error to assert on user-facing messages.
/// - Use reflection-based InvokeRun to locate Program.Run in the built ConsoleApp assembly. The helper
/// tries multiple resolution strategies so tests work both inside the IDE and in build/CI outputs.
/// - Tests are intentionally lightweight: they assert presence of usage/help text, unknown-option messages,
/// and that the tolerant unquoted-input repair logic does not treat path fragments as the target option.
///
/// Notes
/// - These tests do not require sample data on disk. They focus on CLI parsing, messaging and argument repair logic.
/// - When adding additional CLI features extend tests to verify new messages and exit codes.
///
[Collection("Logging")]
public class ProgramTests : IDisposable
{
private readonly TextWriter _originalOut;
private readonly TextWriter _originalErr;
private readonly StringWriter _outWriter;
private readonly StringWriter _errWriter;
public ProgramTests()
{
_originalOut = Console.Out;
_originalErr = Console.Error;
_outWriter = new StringWriter();
_errWriter = new StringWriter();
Console.SetOut(_outWriter);
Console.SetError(_errWriter);
}
public void Dispose()
{
Console.SetOut(_originalOut);
Console.SetError(_originalErr);
_outWriter.Dispose();
_errWriter.Dispose();
}
///
/// Invoke Program.Run(...) via reflection and return the int exit code.
/// Robust lookup: Type.GetType -> Assembly.Load -> probe test folder for GitConverter.ConsoleApp*.dll/.exe
/// and search parent project build outputs when necessary.
///
private int InvokeRun(params string[] args)
{
const string typeFullName = "GitConverter.ConsoleApp.Program, GitConverter.ConsoleApp";
var tried = new List();
var t = Type.GetType(typeFullName);
if (t == null)
{
try
{
var asm = Assembly.Load("GitConverter.ConsoleApp");
t = asm?.GetType("GitConverter.ConsoleApp.Program");
}
catch { /* ignore */ }
}
if (t == null)
{
var baseDir = AppContext.BaseDirectory;
tried.Add(baseDir);
try
{
foreach (var path in Directory.GetFiles(baseDir, "GitConverter.ConsoleApp*.dll").Concat(Directory.GetFiles(baseDir, "GitConverter.ConsoleApp*.exe")))
{
tried.Add(path);
try
{
var a = Assembly.LoadFrom(path);
t = a.GetType("GitConverter.ConsoleApp.Program");
if (t != null) break;
}
catch { /* ignore */ }
}
}
catch { /* ignore */ }
}
if (t == null)
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
for (int depth = 0; depth < 6 && current != null; depth++)
{
try
{
var candidateProjectDir = Path.Combine(current.FullName, "GitConverter.ConsoleApp", "bin");
tried.Add(candidateProjectDir);
if (Directory.Exists(candidateProjectDir))
{
foreach (var cfgDir in Directory.EnumerateDirectories(candidateProjectDir))
{
try
{
var files = Directory.GetFiles(cfgDir, "GitConverter.ConsoleApp*.dll", SearchOption.AllDirectories)
.Concat(Directory.GetFiles(cfgDir, "GitConverter.ConsoleApp*.exe", SearchOption.AllDirectories));
foreach (var f in files)
{
tried.Add(f);
try
{
var a = Assembly.LoadFrom(f);
t = a.GetType("GitConverter.ConsoleApp.Program");
if (t != null) break;
}
catch { /* ignore */ }
}
if (t != null) break;
}
catch { /* ignore */ }
}
}
}
catch { /* ignore */ }
current = current.Parent;
}
}
if (t == null)
{
var msg = $"Program type not found - ensure the ConsoleApp project is referenced by the test project. Probed: {string.Join(", ", tried)}";
throw new InvalidOperationException(msg);
}
var mi = t.GetMethod("Run", BindingFlags.Static | BindingFlags.Public);
if (mi == null) throw new InvalidOperationException("Run method not found on Program type. Ensure Program exposes a public static Run(string[]).");
var result = mi.Invoke(null, new object[] { args });
return Convert.ToInt32(result);
}
[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 { }
}
}
}