using static GitConverter.Lib.Converters.JsonFormatDetector;
namespace GitConverter.TestsApp.Converters
{
///
/// Unit tests for .
///
///
///
/// Purpose
/// - Verify the heuristic JSON GIS format detection implemented by .
/// - Ensure the detector correctly classifies common JSON GIS payloads: GeoJSON, EsriJSON,
/// TopoJSON and GeoJSON sequences (NDJSON or array-of-objects).
///
///
/// Test strategy
/// - For each supported format we create minimal on-disk examples and assert that
/// returns the expected value.
/// - We validate order-independence (properties appearing in different orders) and
/// edge cases such as empty files and missing files.
/// - NDJSON detection is exercised with and without a BOM and with leading whitespace.
///
///
/// Implementation notes
/// - Tests write ephemeral files to a per-test temporary folder under the system temp path
/// and remove them in to avoid leaving artifacts on the developer
/// machine or CI agent.
/// - These are pure unit tests that exercise only the detection heuristics; they do not
/// require Aspose or other heavy dependencies.
///
///
public class JsonFormatDetectorTests : IDisposable
{
private readonly string _root;
///
/// Create an isolated temp folder for this test instance.
///
public JsonFormatDetectorTests()
{
_root = Path.Combine(Path.GetTempPath(), "GitConverter.JsonFormatTests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_root);
}
///
/// Clean up ephemeral test files and folders created during the tests.
///
public void Dispose()
{
try { if (Directory.Exists(_root)) Directory.Delete(_root, true); } catch { }
}
///
/// Helper to write a temporary file inside the test folder and return its path.
///
/// Relative file name under the test folder.
/// File content to write.
/// Full path to the written file.
private string Write(string name, string content)
{
var path = Path.Combine(_root, name);
File.WriteAllText(path, content);
return path;
}
// ---------- GeoJSON tests ----------
///
/// Minimal GeoJSON FeatureCollection should be detected as GeoJson.
///
[Fact(DisplayName = "Detect_GeoJson_Simple")]
public void Detect_GeoJson_Simple()
{
var json = @"{ ""type"": ""FeatureCollection"", ""features"": [] }";
var path = Write("geo_simple.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.GeoJson, result);
}
///
/// A slightly more complex GeoJSON payload with a feature and geometry should be detected as GeoJson.
///
[Fact(DisplayName = "Detect_GeoJson_Variant")]
public void Detect_GeoJson_Variant()
{
var json = @"{
""type"": ""FeatureCollection"",
""features"": [
{ ""type"": ""Feature"", ""geometry"": { ""type"": ""Point"", ""coordinates"": [0,0] }, ""properties"": { ""id"": 1 } }
]
}";
var path = Write("geo_variant.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.GeoJson, result);
}
///
/// The detector should recognize GeoJSON even if the 'type' property is not the first property.
///
[Fact(DisplayName = "Detect_GeoJson_TypeNotFirst")]
public void Detect_GeoJson_TypeNotFirst()
{
// 'features' property before 'type' — detector should find FeatureCollection regardless of order
var json = @"{ ""features"": [], ""type"": ""FeatureCollection"" }";
var path = Write("geo_type_not_first.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.GeoJson, result);
}
// ---------- EsriJSON tests ----------
///
/// Esri JSON containing spatialReference and features should be detected as EsriJson.
///
[Fact(DisplayName = "Detect_EsriJson_Simple")]
public void Detect_EsriJson_Simple()
{
var json = @"{ ""spatialReference"": { ""wkid"": 4326 }, ""features"": [] }";
var path = Write("esri_simple.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.EsriJson, result);
}
///
/// Variant Esri JSON exercise geometryType and spatialReference detection.
///
[Fact(DisplayName = "Detect_EsriJson_Variant")]
public void Detect_EsriJson_Variant()
{
var json = @"{ ""features"": [], ""geometryType"": ""esriGeometryPoint"", ""spatialReference"": { ""wkt"": ""GEOGCS[...]"" } }";
var path = Write("esri_variant.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.EsriJson, result);
}
///
/// Detector handles order-independence between 'features' and 'spatialReference' properties.
///
[Fact(DisplayName = "Detect_EsriJson_OrderIndependence")]
public void Detect_EsriJson_OrderIndependence()
{
// 'features' appears before 'spatialReference' or vice versa - detector should handle either order
var json = @"{ ""features"": [], ""otherProp"": 1, ""spatialReference"": { ""wkid"": 3857 } }";
var path = Write("esri_order.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.EsriJson, result);
}
// ---------- GeoJSON Sequence / NDJSON tests ----------
///
/// A top-level array of objects should be classified as GeoJsonSeq.
///
[Fact(DisplayName = "Detect_GeoJsonSeq_Array")]
public void Detect_GeoJsonSeq_Array()
{
var json = @"[ { ""type"": ""Feature"" }, { ""type"": ""Feature"" } ]";
var path = Write("seq_array.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.GeoJsonSeq, result);
}
///
/// Newline-delimited JSON (two lines) should be detected as GeoJsonSeq by probing first line when full-parse fails.
///
[Fact(DisplayName = "Detect_GeoJsonSeq_NDJSON")]
public void Detect_GeoJsonSeq_NDJSON()
{
// NDJSON: newline-delimited JSON objects (we put two lines)
var json = "{ \"type\": \"Feature\" }\n{ \"type\": \"Feature\" }\n";
var path = Write("seq_ndjson.json", json);
// DetectFromFile attempts to parse and falls back to first-line parsing for NDJSON
var result = DetectFromFile(path);
Assert.Equal(Format.GeoJsonSeq, result);
}
///
/// NDJSON with a BOM and leading blank lines should still be detected as GeoJsonSeq.
///
[Fact(DisplayName = "Detect_GeoJsonSeq_NDJSON_WithBOM_AndWhitespace")]
public void Detect_GeoJsonSeq_NDJSON_WithBOM_AndWhitespace()
{
// Leading BOM and blank lines before NDJSON content should still be detected as GeoJsonSeq
var json = "\uFEFF\n\n{ \"type\": \"Feature\" }\n{ \"type\": \"Feature\" }\n";
var path = Write("seq_ndjson_bom.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.GeoJsonSeq, result);
}
// ---------- TopoJSON tests ----------
///
/// TopoJSON documents with "type":"Topology" should be identified as TopoJson.
///
[Fact(DisplayName = "Detect_TopoJson_Simple")]
public void Detect_TopoJson_Simple()
{
var json = @"{ ""type"": ""Topology"", ""objects"": {} }";
var path = Write("topo_simple.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.TopoJson, result);
}
///
/// Variant TopoJSON with nested objects should also be recognized.
///
[Fact(DisplayName = "Detect_TopoJson_Variant")]
public void Detect_TopoJson_Variant()
{
var json = @"{
""type"": ""Topology"",
""objects"": {
""example"": { ""type"": ""GeometryCollection"", ""geometries"": [] }
}
}";
var path = Write("topo_variant.json", json);
var result = DetectFromFile(path);
Assert.Equal(Format.TopoJson, result);
}
// ---------- Edge cases ----------
///
/// Empty files should be classified as Unknown.
///
[Fact(DisplayName = "Detect_Unknown_Empty")]
public void Detect_Unknown_Empty()
{
var path = Write("empty.json", string.Empty);
var result = DetectFromFile(path);
Assert.Equal(Format.Unknown, result);
}
///
/// TryDetectFromFile should return false for missing files and set the out format to Unknown.
///
[Fact(DisplayName = "TryDetectFromFile_ReturnsFalse_OnMissingFile")]
public void TryDetectFromFile_ReturnsFalse_OnMissingFile()
{
var missing = Path.Combine(_root, "does-not-exist.json");
Assert.False(File.Exists(missing));
var ok = TryDetectFromFile(missing, out var fmt);
Assert.False(ok);
Assert.Equal(Format.Unknown, fmt);
}
}
}