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);
}
}
}