using System.Reflection;
using GitConverter.TestsApp.TestSupport;
namespace GitConverter.TestsApp.ConsoleApp
{
///
/// Integration tests that exercise the console Program.Run(...) end-to-end.
///
///
/// - Executes the ConsoleApp entrypoint as a regular method call (via ) so tests are not
/// terminated by Environment.Exit. This avoids spawning a separate process and simplifies CI integration.
/// - Tests are defensive: they skip when required sample artifacts are missing to keep CI runs non-fatal when private
/// samples are not available.
/// - Each test creates isolated temporary output, temp and log folders under a unique test root to avoid collisions.
/// - StdOut and StdErr are captured and restored for each test to allow assertions on console output.
/// - Tests assert either successful output files in the output folder (exit code 0) or presence of diagnostic console
/// output when the command fails (non-zero exit codes).
///
[Collection("Logging")]
public class ProgramIntegrationTests : IDisposable
{
private readonly TextWriter _origOut;
private readonly TextWriter _origErr;
private readonly StringWriter _outWriter;
private readonly StringWriter _errWriter;
private readonly string _root;
///
/// Test constructor — captures console output and creates an isolated temp root.
///
///
/// - Redirects and to in-memory buffers.
/// - Creates a unique temp root under the OS temp folder; subfolders for each test case are created beneath it.
/// - Any long-lived resources or file handles should be released in .
///
public ProgramIntegrationTests()
{
_origOut = Console.Out;
_origErr = Console.Error;
_outWriter = new StringWriter();
_errWriter = new StringWriter();
Console.SetOut(_outWriter);
Console.SetError(_errWriter);
_root = Path.Combine(Path.GetTempPath(), "GitConverter.ProgramIntegration", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_root);
}
///
/// Restores console streams and attempts best-effort cleanup of temporary files.
///
///
/// - Restores original console streams to avoid interfering with other tests.
/// - Disposes in-memory writers and deletes the temporary root directory if possible.
/// - Cleanup is best-effort; exceptions during deletion are swallowed to avoid failing test teardown.
///
public void Dispose()
{
Console.SetOut(_origOut);
Console.SetError(_origErr);
_outWriter.Dispose();
_errWriter.Dispose();
try { if (Directory.Exists(_root)) Directory.Delete(_root, true); } catch { }
}
///
/// Invoke the ConsoleApp program entrypoint and return the exit code.
///
/// Arguments forwarded to the CLI (first token is the command name).
/// Integer exit code returned by Program.Run.
///
/// - Tests call this helper instead of calling Environment.Exit to keep the test runner alive.
/// - The method calls GitConverter.ConsoleApp.Program.Run(...) directly; if the CLI implementation
/// changes to call Environment.Exit unconditionally, tests will need to be adapted to invoke a
/// separate process or to use a different invocation strategy.
/// - Captured stdout/stderr buffers are available to callers after this returns.
///
private int InvokeRun(params string[] args)
{
return GitConverter.ConsoleApp.Program.Run(args);
}
///
/// Integration test: run converters against CSV samples found in test data.
///
///
/// - Skips when the CSV test data folder is missing or contains no samples.
/// - For each sample and target option this test creates isolated out/temp/log folders and asserts on the result.
/// - Tests perform best-effort cleanup of created folders.
///
[Fact(DisplayName = "Program.Run converts CSV samples to all target options (when CSV samples present)")]
public void Program_Run_CsvFiles_ConvertToAllTargets_WhenPresent()
{
var dataFolder = IntegrationTestConstants.TestDataCsvFolder;
if (!Directory.Exists(dataFolder)) return;
var samples = Directory.EnumerateFiles(dataFolder, "*", SearchOption.TopDirectoryOnly)
.Where(f => f.EndsWith(".csv", StringComparison.OrdinalIgnoreCase)
|| f.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)
|| f.EndsWith(".7z", StringComparison.OrdinalIgnoreCase))
.ToList();
if (!samples.Any()) return;
foreach (var samplePath in samples)
{
var sampleName = Path.GetFileName(samplePath);
foreach (var target in IntegrationTestConstants.TargetOptions)
{
var baseName = Path.GetFileNameWithoutExtension(sampleName);
var outFolder = Path.Combine(_root, $"{baseName}_{target}_out");
var tempDir = Path.Combine(_root, $"{baseName}_{target}_tmp");
var logDir = Path.Combine(_root, "logs");
Directory.CreateDirectory(outFolder);
Directory.CreateDirectory(tempDir);
Directory.CreateDirectory(logDir);
var args = new[]
{
"gis_convert",
samplePath,
target,
outFolder,
tempDir,
logDir
};
_outWriter.GetStringBuilder().Clear();
_errWriter.GetStringBuilder().Clear();
var exit = InvokeRun(args);
var stdout = _outWriter.ToString();
var stderr = _errWriter.ToString();
var combined = string.Concat(stdout, Environment.NewLine, stderr).Trim();
if (exit == 0)
{
Assert.True(Directory.Exists(outFolder), $"Expected output folder '{outFolder}' for '{sampleName}' -> '{target}'. Console: {combined}");
var files = Directory.EnumerateFiles(outFolder, "*", SearchOption.AllDirectories).ToList();
Assert.True(files.Count > 0, $"Expected one or more output files in '{outFolder}' for '{sampleName}' -> '{target}'.");
}
else
{
Assert.False(string.IsNullOrWhiteSpace(combined), $"Program.Run exited {exit} for '{sampleName}' -> '{target}' but produced no console output.");
}
try { if (Directory.Exists(outFolder)) Directory.Delete(outFolder, true); } catch { }
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
try { if (Directory.Exists(logDir)) Directory.Delete(logDir, true); } catch { }
}
}
}
///
/// Integration test: run converters against GML samples found in test data.
///
///
/// - Skips when the GML test data folder is missing or contains no samples.
/// - For each sample and target option this test creates isolated out/temp/log folders and asserts on the result.
/// - Tests perform best-effort cleanup of created folders.
///
[Fact(DisplayName = "Program.Run converts GML samples to all target options (when GML samples present)")]
public void Program_Run_GmlFiles_ConvertToAllTargets_WhenPresent()
{
var dataFolder = IntegrationTestConstants.TestDataGmlFolder;
if (!Directory.Exists(dataFolder)) return;
var samples = Directory.EnumerateFiles(dataFolder, "*", SearchOption.TopDirectoryOnly)
.Where(f => f.EndsWith(".gml", StringComparison.OrdinalIgnoreCase)
|| f.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)
|| f.EndsWith(".7z", StringComparison.OrdinalIgnoreCase))
.ToList();
if (!samples.Any()) return;
foreach (var samplePath in samples)
{
var sampleName = Path.GetFileName(samplePath);
foreach (var target in IntegrationTestConstants.TargetOptions)
{
var baseName = Path.GetFileNameWithoutExtension(sampleName);
var outFolder = Path.Combine(_root, $"{baseName}_{target}_out");
var tempDir = Path.Combine(_root, $"{baseName}_{target}_tmp");
var logDir = Path.Combine(_root, "logs");
Directory.CreateDirectory(outFolder);
Directory.CreateDirectory(tempDir);
Directory.CreateDirectory(logDir);
var args = new[]
{
"gis_convert",
samplePath,
target,
outFolder,
tempDir,
logDir
};
_outWriter.GetStringBuilder().Clear();
_errWriter.GetStringBuilder().Clear();
var exit = InvokeRun(args);
var stdout = _outWriter.ToString();
var stderr = _errWriter.ToString();
var combined = string.Concat(stdout, Environment.NewLine, stderr).Trim();
if (exit == 0)
{
Assert.True(Directory.Exists(outFolder), $"Expected output folder '{outFolder}' for '{sampleName}' -> '{target}'. Console: {combined}");
var files = Directory.EnumerateFiles(outFolder, "*", SearchOption.AllDirectories).ToList();
Assert.True(files.Count > 0, $"Expected one or more output files in '{outFolder}' for '{sampleName}' -> '{target}'.");
}
else
{
Assert.False(string.IsNullOrWhiteSpace(combined), $"Program.Run exited {exit} for '{sampleName}' -> '{target}' but produced no console output.");
}
try { if (Directory.Exists(outFolder)) Directory.Delete(outFolder, true); } catch { }
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
try { if (Directory.Exists(logDir)) Directory.Delete(logDir, true); } catch { }
}
}
}
///
/// Integration test: run converters against shapefile archives found in test data.
///
///
/// - Skips when the shapefile test data folder is missing or contains no archives.
/// - For each archive and target option this test creates isolated out/temp/log folders, invokes the CLI
/// and asserts either successful output files (exit code 0) or non-empty diagnostic console output.
/// - Cleans up created folders per iteration to avoid accumulating artifacts on the host.
///
[Fact(DisplayName = "Program.Run converts provided shapefile archives to all target options (when shapefile archives present)")]
public void Program_Run_ShapefileArchives_ConvertToAllTargets_WhenPresent()
{
var dataFolder = IntegrationTestConstants.TestDataShapefileFolder;
if (!Directory.Exists(dataFolder)) return;
var samples = Directory.EnumerateFiles(dataFolder, "*", SearchOption.TopDirectoryOnly)
.Where(f => f.EndsWith(".7z", StringComparison.OrdinalIgnoreCase)
|| f.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
.ToList();
if (!samples.Any()) return;
foreach (var archive in samples)
{
var archiveName = Path.GetFileName(archive);
foreach (var target in IntegrationTestConstants.TargetOptions)
{
var baseName = Path.GetFileNameWithoutExtension(archiveName);
var outFolder = Path.Combine(_root, $"{baseName}_{target}_out");
var tempDir = Path.Combine(_root, $"{baseName}_{target}_tmp");
var logDir = Path.Combine(_root, "logs");
// ensure parent folders exist
Directory.CreateDirectory(outFolder);
Directory.CreateDirectory(tempDir);
Directory.CreateDirectory(logDir);
var args = new[]
{
"gis_convert",
archive,
"EsriJson",
outFolder,
tempDir,
logDir
};
_outWriter.GetStringBuilder().Clear();
_errWriter.GetStringBuilder().Clear();
var exit = InvokeRun(args);
var stdout = _outWriter.ToString();
var stderr = _errWriter.ToString();
var combined = string.Concat(stdout, Environment.NewLine, stderr).Trim();
if (exit == 0)
{
// Require an output folder and at least one file inside it.
Assert.True(Directory.Exists(outFolder), $"Expected output folder '{outFolder}' for '{archiveName}' -> '{target}'. Console: {combined}");
var files = Directory.EnumerateFiles(outFolder, "*", SearchOption.AllDirectories).ToList();
Assert.True(files.Count > 0, $"Expected one or more output files in '{outFolder}' for '{archiveName}' -> '{target}'.");
}
else
{
Assert.False(string.IsNullOrWhiteSpace(combined), $"Program.Run exited {exit} for '{archiveName}' -> '{target}' but produced no console output.");
}
try { if (Directory.Exists(outFolder)) Directory.Delete(outFolder, true); } catch { }
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
try { if (Directory.Exists(logDir)) Directory.Delete(logDir, true); } catch { }
}
}
}
}
}