using GitConverter.Lib.Converters; using GitConverter.Lib.Factories; using GitConverter.Lib.Models; using System.IO.Compression; namespace GitConverter.TestsApp.Converters { /// /// Unit tests for ConversionService.Run covering: /// - JSON detection (file and archive voting) /// - Archive detection (KMZ guard, KML fallback, FileGDB) /// - Extension mapping /// public class ConversionServiceTests : IDisposable { private readonly string _tempDir; public ConversionServiceTests() { _tempDir = Path.Combine(Path.GetTempPath(), "GitConverter.ConversionServiceTests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_tempDir); } public void Dispose() { try { if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, true); } catch { } } // Simple fake converter that records invocation and returns success class FakeConverter : IConverter { public bool Invoked { get; private set; } public string LastOption { get; private set; } public ConversionResult Convert(string gisInputFilePath, string gisTargetFormatOption, string outputFolderPath, string tempFolderPath) { Invoked = true; LastOption = gisTargetFormatOption; try { if (!string.IsNullOrWhiteSpace(outputFolderPath)) { Directory.CreateDirectory(outputFolderPath); var fileName = $"{gisTargetFormatOption}_output_{DateTime.UtcNow:yyyyMMddHHmmssfff}.txt"; File.WriteAllText(Path.Combine(outputFolderPath, fileName), "fake-output"); } } catch { } return ConversionResult.Success("ok"); } } // Fake factory that maps one key to the provided fake converter class FakeFactory : IConverterFactory { private readonly string _key; private readonly IConverter _conv; public FakeFactory(string key, IConverter conv) { _key = key; _conv = conv; } public IConverter Create(string formatOption) => throw new NotImplementedException(); public bool TryCreate(string formatOption, out IConverter converter) { if (string.Equals(formatOption, _key, StringComparison.OrdinalIgnoreCase)) { converter = _conv; return true; } converter = null; return false; } public IReadOnlyCollection GetSupportedOptions() => new[] { _key }; } [Fact(DisplayName = "Archive_Kmz_with_doc_kml_outer_kmz_invokes_Kmz")] public void Archive_Kmz_with_doc_kml_outer_kmz_invokes_Kmz() { var src = Path.Combine(_tempDir, "kmz_src"); Directory.CreateDirectory(src); File.WriteAllText(Path.Combine(src, "doc.kml"), ""); var archive = Path.Combine(_tempDir, "sample.kmz"); if (File.Exists(archive)) File.Delete(archive); ZipFile.CreateFromDirectory(src, archive, CompressionLevel.Fastest, includeBaseDirectory: false); var outFolder = Path.Combine(_tempDir, "kmz_out"); var tempFolder = Path.Combine(_tempDir, "kmz_tmp"); Directory.CreateDirectory(outFolder); Directory.CreateDirectory(tempFolder); var fake = new FakeConverter(); var factory = new FakeFactory("Kmz", fake); var result = ConversionService.Run(archive, outFolder, tempFolder, factory); Assert.NotNull(result); Assert.True(result.IsSuccess); Assert.True(fake.Invoked); Assert.Equal("Kmz", fake.LastOption); } [Fact(DisplayName = "Archive_GenericZip_with_nested_kml_invokes_Kml_not_Kmz")] public void Archive_GenericZip_with_nested_kml_invokes_Kml_not_Kmz() { var parent = Path.Combine(_tempDir, "zip_parent"); var nested = Path.Combine(parent, "data"); var images = Path.Combine(parent, "images"); Directory.CreateDirectory(nested); Directory.CreateDirectory(images); File.WriteAllText(Path.Combine(nested, "roads.kml"), ""); File.WriteAllText(Path.Combine(images, "img.png"), "PNG"); var archive = Path.Combine(_tempDir, "generic.zip"); if (File.Exists(archive)) File.Delete(archive); ZipFile.CreateFromDirectory(parent, archive, CompressionLevel.Fastest, includeBaseDirectory: false); var outFolder = Path.Combine(_tempDir, "zip_out"); var tempFolder = Path.Combine(_tempDir, "zip_tmp"); Directory.CreateDirectory(outFolder); Directory.CreateDirectory(tempFolder); var fake = new FakeConverter(); var factory = new FakeFactory("Kml", fake); var result = ConversionService.Run(archive, outFolder, tempFolder, factory); Assert.NotNull(result); Assert.True(result.IsSuccess); Assert.True(fake.Invoked); Assert.Equal("Kml", fake.LastOption); } [Fact(DisplayName = "Archive_GdbZip_with_gdb_folder_invokes_Gdb")] public void Archive_GdbZip_with_gdb_folder_invokes_Gdb() { var parent = Path.Combine(_tempDir, "gdb_parent"); var gdb = Path.Combine(parent, "mydata.gdb"); Directory.CreateDirectory(gdb); File.WriteAllText(Path.Combine(gdb, "table.gdbtable"), "x"); var archive = Path.Combine(_tempDir, "gdb.zip"); if (File.Exists(archive)) File.Delete(archive); ZipFile.CreateFromDirectory(parent, archive, CompressionLevel.Fastest, includeBaseDirectory: false); var outFolder = Path.Combine(_tempDir, "gdb_out"); var tempFolder = Path.Combine(_tempDir, "gdb_tmp"); Directory.CreateDirectory(outFolder); Directory.CreateDirectory(tempFolder); var fake = new FakeConverter(); var factory = new FakeFactory("Gdb", fake); var result = ConversionService.Run(archive, outFolder, tempFolder, factory); Assert.NotNull(result); Assert.True(result.IsSuccess); Assert.True(fake.Invoked); Assert.Equal("Gdb", fake.LastOption); } [Fact(DisplayName = "Single_geojson_feature_is_classified_as_GeoJson_not_GeoJsonSeq")] public void Single_geojson_feature_is_classified_as_GeoJson_not_GeoJsonSeq() { var json = @"{ ""type"": ""Feature"", ""geometry"": { ""type"": ""Point"", ""coordinates"": [0,0] }, ""properties"": {} }"; var input = Path.Combine(_tempDir, "single.geojson"); File.WriteAllText(input, json); var outFolder = Path.Combine(_tempDir, "out_single"); var tempFolder = Path.Combine(_tempDir, "tmp_single"); Directory.CreateDirectory(outFolder); Directory.CreateDirectory(tempFolder); var fake = new FakeConverter(); var factory = new FakeFactory("GeoJson", fake); var result = ConversionService.Run(input, outFolder, tempFolder, factory); Assert.NotNull(result); Assert.True(result.IsSuccess); Assert.True(fake.Invoked); Assert.Equal("GeoJson", fake.LastOption); } [Fact(DisplayName = "Seq_json_with_three_ndjson_lines_is_classified_as_GeoJsonSeq")] public void Seq_json_with_three_ndjson_lines_is_classified_as_GeoJsonSeq() { var ndjson = "{ \"type\": \"Feature\", \"properties\": {} }\n{ \"type\": \"Feature\", \"properties\": {} }\n{ \"type\": \"Feature\", \"properties\": {} }\n"; var input = Path.Combine(_tempDir, "seq.json"); File.WriteAllText(input, ndjson); var outFolder = Path.Combine(_tempDir, "out_seq"); var tempFolder = Path.Combine(_tempDir, "tmp_seq"); Directory.CreateDirectory(outFolder); Directory.CreateDirectory(tempFolder); var fake = new FakeConverter(); var factory = new FakeFactory("GeoJsonSeq", fake); var result = ConversionService.Run(input, outFolder, tempFolder, factory); Assert.NotNull(result); Assert.True(result.IsSuccess); Assert.True(fake.Invoked); Assert.Equal("GeoJsonSeq", fake.LastOption); } // ---------- New tests for JSON voting inside archives ---------- [Fact(DisplayName = "Archive_JsonEntries_Tie_Is_Ambiguous")] public void Archive_JsonEntries_Tie_Is_Ambiguous() { // Create archive with one topo.json and one esri-like layer.json -> tie -> ambiguous -> failure var dir = Path.Combine(_tempDir, "vote_tie"); Directory.CreateDirectory(dir); File.WriteAllText(Path.Combine(dir, "topo.json"), "{ \"type\": \"Topology\", \"topology\": {} }"); File.WriteAllText(Path.Combine(dir, "layer.json"), "{ \"spatialReference\": { \"wkid\": 4326 }, \"features\": [] }"); var archive = Path.Combine(_tempDir, "vote_tie.zip"); if (File.Exists(archive)) File.Delete(archive); ZipFile.CreateFromDirectory(dir, archive, CompressionLevel.Fastest, includeBaseDirectory: false); var outFolder = Path.Combine(_tempDir, "vote_tie_out"); var tempFolder = Path.Combine(_tempDir, "vote_tie_tmp"); Directory.CreateDirectory(outFolder); Directory.CreateDirectory(tempFolder); var result = ConversionService.Run(archive, outFolder, tempFolder, factory: null); Assert.NotNull(result); Assert.False(result.IsSuccess, "Tie between TopoJson and EsriJson should produce an ambiguous failure."); } [Fact(DisplayName = "Archive_JsonEntries_TopoMajority_Selects_TopoJson")] public void Archive_JsonEntries_TopoMajority_Selects_TopoJson() { // Create archive with two topo.json and one esri-like layer.json -> TopoJson majority var dir = Path.Combine(_tempDir, "vote_major"); Directory.CreateDirectory(dir); File.WriteAllText(Path.Combine(dir, "topo1.json"), "{ \"type\": \"Topology\", \"topology\": {} }"); File.WriteAllText(Path.Combine(dir, "topo2.json"), "{ \"type\": \"Topology\", \"topology\": {} }"); File.WriteAllText(Path.Combine(dir, "layer.json"), "{ \"spatialReference\": { \"wkid\": 4326 }, \"features\": [] }"); var archive = Path.Combine(_tempDir, "vote_major.zip"); if (File.Exists(archive)) File.Delete(archive); ZipFile.CreateFromDirectory(dir, archive, CompressionLevel.Fastest, includeBaseDirectory: false); var outFolder = Path.Combine(_tempDir, "vote_major_out"); var tempFolder = Path.Combine(_tempDir, "vote_major_tmp"); Directory.CreateDirectory(outFolder); Directory.CreateDirectory(tempFolder); var fake = new FakeConverter(); var factory = new FakeFactory("TopoJson", fake); var result = ConversionService.Run(archive, outFolder, tempFolder, factory); Assert.NotNull(result); Assert.True(result.IsSuccess, "Majority TopoJson votes should select TopoJson converter."); Assert.True(fake.Invoked); Assert.Equal("TopoJson", fake.LastOption); } } }