Hello @Dani_S ,
Sorry for the late response. Your factory test suite has good coverage overall, but there are some improvements could be made.
FactoryHelpers.cs
Add tests for mixed alphanumeric keys, empty candidate collections, and symmetric distance edge cases with empty strings.
[Fact(DisplayName = "NormalizeKey handles mixed alphanumeric and strips unicode consistently")]
public void NormalizeKey_MixedAlphanumeric_StripsNonAscii()
{
Assert.Equal("geojson2024", FactoryHelpers.NormalizeKey("GeoJson2024"));
Assert.Equal("esrijson", FactoryHelpers. NormalizeKey("Esri–Json")); // em-dash stripped
Assert.Equal("land", FactoryHelpers.NormalizeKey("Géo-Land")); // diacritics stripped
}
[Fact(DisplayName = "SuggestClosest with empty candidate collection returns null")]
public void SuggestClosest_EmptyCandidates_ReturnsNull()
{
var result = FactoryHelpers.SuggestClosest("foo", new string[0]);
Assert.Null(result);
}
[Fact(DisplayName = "LevenshteinDistance symmetric with empty strings")]
public void LevenshteinDistance_EmptyStrings_Symmetric()
{
Assert.Equal(FactoryHelpers.LevenshteinDistance("", "abc"), FactoryHelpers.LevenshteinDistance("abc", ""));
Assert.Equal(3, FactoryHelpers.LevenshteinDistance("", "abc"));
}
ConverterFactory.cs
Add test to verify alias expansion, whitespace trimming in aliases, or null factory delegates.
[Fact(DisplayName = "Constructor registers comma-separated aliases and both resolve")]
public void Constructor_AliasRegistration_BothKeysResolve()
{
var registrations = new Dictionary<string, Func<IConverter>>(StringComparer.OrdinalIgnoreCase)
{
{ "Kml,Kmz", () => new DummyConverter("Kml") }
};
var factory = new ConverterFactory(registrations, null);
var okKml = factory.TryCreate("Kml", out var convKml);
var okKmz = factory.TryCreate("Kmz", out var convKmz);
Assert.True(okKml);
Assert.True(okKmz);
Assert.NotNull(convKml);
Assert.NotNull(convKmz);
}
[Fact(DisplayName = "Constructor trims whitespace in alias keys")]
public void Constructor_AliasWithTrim_Registered()
{
var registrations = new Dictionary<string, Func<IConverter>>(StringComparer.OrdinalIgnoreCase)
{
{ "Kml , Kmz ", () => new DummyConverter("Kml") }
};
var factory = new ConverterFactory(registrations, null);
Assert.True(factory.TryCreate("Kml", out _));
Assert.True(factory.TryCreate("Kmz", out _));
}
[Fact(DisplayName = "Constructor skips empty alias segments")]
public void Constructor_EmptyAliasSegments_Skipped()
{
var registrations = new Dictionary<string, Func<IConverter>>(StringComparer.OrdinalIgnoreCase)
{
{ "Kml,,Kmz", () => new DummyConverter("Kml") }
};
var factory = new ConverterFactory(registrations, null);
Assert.True(factory.TryCreate("Kml", out _));
Assert.True(factory.TryCreate("Kmz", out _));
}
[Fact(DisplayName = "Constructor throws when factory delegate is null")]
public void Constructor_NullFactoryDelegate_Throws()
{
var registrations = new Dictionary<string, Func<IConverter>>(StringComparer.OrdinalIgnoreCase)
{
{ "BadFmt", null }
};
Assert.Throws<ArgumentNullException>(() => new ConverterFactory(registrations, null));
}
ConverterFactoryTests.cs
You should test what happens when no suggestion is available and verify that normalized matches don't throw exceptions.
[Fact(DisplayName = "Create with very different input lists supported options without suggestion")]
public void Create_VeryDifferentInput_ListSupportedOptions()
{
var factory = new ConverterFactory();
var ex = Assert.Throws<KeyNotFoundException>(() => factory.Create("zzzzqqqq"));
Assert.DoesNotContain("Did you mean", ex.Message, StringComparison.OrdinalIgnoreCase);
Assert.Contains("Supported:", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact(DisplayName = "Create with normalized match does not throw")]
public void Create_NormalizedMatch_Succeeds()
{
var registrations = new Dictionary<string, Func<IConverter>>(StringComparer.OrdinalIgnoreCase)
{
{ "GeoJson", () => new DummyConverter("Geo") }
};
var factory = new ConverterFactory(registrations, null);
var conv = factory.Create("geo-json"); // should normalize and match
Assert.NotNull(conv);
}
ConverterFactoryInputExtensions.cs
Add some archive edge cases like mixed formats, incomplete file requirements, and small file headers.
[Fact(DisplayName = "Archive with explicit geojson and shapefile prefers geojson without voting")]
public void Archive_ExplicitGeoJsonAndShapefile_PrefersGeoJson()
{
var f = new FakeFactory();
var zip = CreateZipWithEntries(
("data.geojson", "{ \"type\": \"FeatureCollection\" }"),
("data.shp", "shp"),
("data.shx", "shx"),
("data.dbf", "dbf")
);
var ok = f.TryCreateForInput(zip, out var conv, out var detected, out var reason);
Assert.True(ok);
Assert.Equal("GeoJson", f.LastRequestedKey, ignoreCase: true);
}
[Fact(DisplayName = "Archive with gdb folder but missing required files fails")]
public void Archive_GdbFolderMissingRequiredFiles_Fails()
{
var f = new FakeFactory();
var zip = CreateZipWithEntries(
("data.gdb/dummy. txt", "not a gdb table")
);
var ok = f.TryCreateForInput(zip, out var conv, out var detected, out var reason);
Assert.False(ok);
Assert.Contains("no format matched", reason, StringComparison. OrdinalIgnoreCase);
}
[Fact(DisplayName = "Kmz outer extension without doc.kml still detects as kmz")]
public void Kmz_OuterExtensionWithoutDocKml_DetectedAsKmz()
{
var f = new FakeFactory();
var zip = CreateZipWithEntries(("other.kml", "<kml/>"));
var kmzPath = Path. ChangeExtension(zip, ". kmz");
File.Copy(zip, kmzPath);
var ok = f.TryCreateForInput(kmzPath, out var conv, out var detected, out var reason);
Assert.True(ok);
Assert.Equal("Kmz", f.LastRequestedKey, ignoreCase: true);
}
[Fact(DisplayName = "Archive with jsonl file maps to GeoJsonSeq")]
public void Archive_JsonlFile_MapsToGeoJsonSeq()
{
var f = new FakeFactory();
var zip = CreateZipWithEntries(
("data.jsonl", "{ \"type\": \"Feature\" }\n{ \"type\": \"Feature\" }\n")
);
var ok = f.TryCreateForInput(zip, out var conv, out var detected, out var reason);
Assert.True(ok);
Assert.Equal("GeoJsonSeq", f. LastRequestedKey, ignoreCase: true);
}
And the other files look good. You can use them as is.
Happy holidays.