using System.Text.Json; namespace GisConverter.TestsApp.ConsoleApp { /// /// Tests that validate output file correctness, not just existence. /// Ensures converted files are valid and contain expected data. /// /// /// - Integration-style tests that invoke the ConsoleApp entry point via Program.Run(string[]). /// - These tests exercise conversion pipeline end-to-end using small, synthetic inputs created in a unique /// temporary root for each test class instance to avoid interference with other tests. /// - Console output streams (Console.Out and Console.Error) are captured so assertions can inspect /// user-facing diagnostics without launching a new process. /// - Tests assume Program.Run returns an integer exit code and does not call Environment.Exit. /// - Tests are defensive: /// - They fail fast when the conversion returns a non-zero exit code and include captured stdout/stderr to aid diagnosis. /// - When asserting content, prefer tolerant checks (presence of key tokens / well-formed JSON) rather than exact text. /// - Clean-up: temporary folders are removed in using best-effort deletion to avoid masking test failures. /// - Notes for contributors: /// - Keep sample inputs small to keep tests fast. /// - If converters require external resources or licenses (e.g., Aspose), gate such tests or skip when resources are not available. /// [Collection("Logging")] public class OutputValidationTests : 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 temporary root. /// /// /// - Redirects console streams to in-memory buffers so tests can assert on CLI output. /// - Creates a unique temporary directory per test class instance under the system temp folder. /// - The temporary root is used to store synthetic input files, output folders and temp folders for conversions. /// public OutputValidationTests() { _originalOut = Console.Out; _originalErr = Console.Error; _outWriter = new StringWriter(); _errWriter = new StringWriter(); Console.SetOut(_outWriter); Console.SetError(_errWriter); _tempRoot = Path.Combine(Path.GetTempPath(), "GisConverter.ValidationTests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_tempRoot); } /// /// Tear-down — restore console streams and remove temporary artifacts. /// /// /// - Always restore original console streams to avoid affecting other tests. /// - Dispose the in-memory writers and attempt to delete the temporary root (best-effort). /// - Avoid throwing from cleanup to prevent masking the original test failure. /// public void Dispose() { Console.SetOut(_originalOut); Console.SetError(_originalErr); _outWriter.Dispose(); _errWriter.Dispose(); try { if (Directory.Exists(_tempRoot)) Directory.Delete(_tempRoot, true); } catch { } } /// /// Verify CSV -> GeoJSON conversion produces valid GeoJSON files. /// /// /// - Arranges: writes a small CSV with longitude/latitude and names. /// - Act: invokes Program.Run requesting GeoJson target. /// - Assert: /// - Conversion exit code must be 0; otherwise the test fails and prints captured output for diagnosis. /// - At least one .json output file is produced. /// - Each produced JSON file is parsed with to verify it is syntactically valid JSON /// and contains a top-level type property whose value is either FeatureCollection or Feature. /// - Failure modes: /// - Invalid JSON will cause the catch block to fail the test with the filename and parse error. /// [Fact(DisplayName = "CSV to GeoJSON produces valid JSON")] public void Run_CsvToGeoJson_ProducesValidJson() { var csvInput = Path.Combine(_tempRoot, "points.csv"); File.WriteAllText(csvInput, "lon,lat,name\n-122.4,37.8,\"San Francisco\"\n-118.2,34.0,\"Los Angeles\""); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", csvInput, "GeoJson", outDir, tempDir, "unused" }; var exit = GisConverter.ConsoleApp.Program.Run(args); if (exit != 0) { var output = _outWriter.ToString() + _errWriter.ToString(); Assert.True(false, $"Conversion failed: {output}"); } var outputFiles = Directory.GetFiles(outDir, "*.json", SearchOption.AllDirectories); Assert.NotEmpty(outputFiles); foreach (var file in outputFiles) { var json = File.ReadAllText(file); try { var doc = JsonDocument.Parse(json); Assert.True(doc.RootElement.TryGetProperty("type", out var typeElement)); var type = typeElement.GetString(); Assert.True(type == "FeatureCollection" || type == "Feature", $"Expected GeoJSON type, got {type}"); } catch (JsonException ex) { Assert.True(false, $"Output file {file} is not valid JSON: {ex.Message}"); } } } /// /// Ensures that conversion produces non-empty output files. /// /// /// - Arranges a small CSV file, runs a conversion that should produce at least one output file. /// - Asserts that the output directory contains files and that each output file's length is greater than zero bytes. /// - If conversion fails (non-zero exit), the test will not attempt file checks. /// - This test verifies that converters do not create empty placeholders on success. /// [Fact(DisplayName = "Output files are not empty")] public void Run_Conversion_OutputFilesNotEmpty() { var csvInput = Path.Combine(_tempRoot, "data.csv"); File.WriteAllText(csvInput, "a,b,c\n1,2,3\n4,5,6"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", csvInput, "Csv", outDir, tempDir, "unused" }; var exit = GisConverter.ConsoleApp.Program.Run(args); if (exit == 0) { var files = Directory.GetFiles(outDir, "*", SearchOption.AllDirectories); Assert.NotEmpty(files); foreach (var file in files) { var info = new FileInfo(file); Assert.True(info.Length > 0, $"Output file {file} is empty"); } } } /// /// Verifies that unicode filenames are accepted and do not produce encoding or invalid path errors. /// /// /// - Creates an input file with a Unicode filename and runs a conversion. /// - Asserts that console output does not contain common error tokens such as "encoding" or "invalid". /// - This test focuses on CLI/OS-level handling of Unicode paths rather than content correctness. /// - Note: some filesystems or CI runners may behave differently with certain Unicode characters; skip or adapt tests if the environment cannot support the characters used. /// [Fact(DisplayName = "Unicode filenames are handled correctly")] public void Run_UnicodeFilename_HandledCorrectly() { var unicodeFilename = "שכבות_מידע.csv"; var csvInput = Path.Combine(_tempRoot, unicodeFilename); File.WriteAllText(csvInput, "x,y\n1,2\n3,4"); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", csvInput, "Csv", outDir, tempDir, "unused" }; _outWriter.GetStringBuilder().Clear(); _errWriter.GetStringBuilder().Clear(); var exit = GisConverter.ConsoleApp.Program.Run(args); var output = _outWriter.ToString() + _errWriter.ToString(); Assert.DoesNotContain("encoding", output.ToLowerInvariant()); Assert.DoesNotContain("invalid", output.ToLowerInvariant()); } /// /// Ensures that converted CSV output preserves column headers and basic data tokens. /// /// /// - Writes a small CSV input with known headers and values, runs conversion to CSV (round-trip or transformation scenario). /// - Asserts that output CSV files contain the expected header tokens so that key columns are preserved. /// - If multiple CSV outputs exist, the test checks each for the presence of header tokens. /// - This test does not assert exact row ordering or full equality — only that essential headers and tokens exist. /// [Fact(DisplayName = "Conversion preserves data integrity")] public void Run_CsvConversion_PreservesData() { var csvInput = Path.Combine(_tempRoot, "preserve.csv"); var expectedData = "id,value\n1,test\n2,data"; File.WriteAllText(csvInput, expectedData); var outDir = Path.Combine(_tempRoot, "out"); var tempDir = Path.Combine(_tempRoot, "temp"); var args = new[] { "gis_convert", csvInput, "Csv", outDir, tempDir, "unused" }; var exit = GisConverter.ConsoleApp.Program.Run(args); if (exit == 0) { var outputFiles = Directory.GetFiles(outDir, "*.csv", SearchOption.AllDirectories); Assert.NotEmpty(outputFiles); foreach (var file in outputFiles) { var content = File.ReadAllText(file); Assert.Contains("id", content); Assert.Contains("value", content); } } } } }