using GitConverter.Lib.Factories;
namespace GitConverter.TestsApp.Factories
{
///
/// Unit tests for normalization and suggestion utilities.
///
///
///
/// Purpose
/// - Verify behavior of , ,
/// and (internal, exposed to tests via InternalsVisibleTo).
///
///
/// Test strategy
/// - Use small, deterministic inputs that exercise normalization, exact matches, near-miss suggestions,
/// tie-breaking, threshold behavior and edge-cases such as null/empty inputs and non-ASCII characters.
/// - Keep tests fast and isolated (no file I/O or external dependencies).
///
///
/// Remarks on algorithmic behavior
/// - Normalization lowers case, trims surrounding quotes/whitespace and removes characters outside [a-z0-9].
/// - Suggestions are based on Levenshtein distance with a conservative acceptance threshold to avoid
/// spurious suggestions for distant strings.
/// - LevenshteinDistance implementation is space-optimized and expected to be symmetric: distance(a,b) == distance(b,a).
///
///
/// Testing guidance
/// - When adapting normalization to support Unicode or transliteration update tests accordingly.
/// - SuggestClosest deterministic selection on ties depends on iteration order of candidates; tests assume
/// the first matching minimal-distance candidate is returned.
///
///
public class FactoryHelpersTests
{
[Theory]
[InlineData("Esri-Json", "esrijson")]
[InlineData(" ESRI_JSON ", "esrijson")]
[InlineData("GeoJsonSeq", "geojsonseq")]
[InlineData("Geo Json", "geojson")]
[InlineData("\"Kml\"", "kml")]
[InlineData(null, "")]
[InlineData("", "")]
public void NormalizeKey_ReturnsExpectedNormalizedKey(string input, string expected)
{
var actual = FactoryHelpers.NormalizeKey(input);
Assert.Equal(expected, actual);
}
[Fact]
public void NormalizeKey_NonAsciiCharacters_AreStripped()
{
// Current normalization strips non-ascii characters; ensure it does not throw and returns a reduced key
var actual = FactoryHelpers.NormalizeKey("Åland-ÄÖ");
Assert.IsType(actual);
// Non-ascii diacritics are stripped leaving the ascii letters 'land'
Assert.Equal("land", actual);
}
[Fact]
public void SuggestClosest_NullTarget_ReturnsNull()
{
var candidates = new[] { "EsriJson", "GeoJson" };
var result = FactoryHelpers.SuggestClosest(null, candidates);
Assert.Null(result);
}
[Fact]
public void SuggestClosest_NullCandidates_ReturnsNull_NoException()
{
var ex = Record.Exception(() => {
var r = FactoryHelpers.SuggestClosest("esrijsn", null);
Assert.Null(r);
});
Assert.Null(ex);
}
[Fact]
public void SuggestClosest_ExactNormalizedMatch_ReturnsCandidate()
{
var candidates = new[] { "EsriJson", "GeoJson" };
var result = FactoryHelpers.SuggestClosest("EsriJson", candidates);
Assert.Equal("EsriJson", result);
}
[Fact]
public void SuggestClosest_Typo_ReturnsSuggestion()
{
var candidates = new[] { "EsriJson", "GeoJson" };
var result = FactoryHelpers.SuggestClosest("esrijsn", candidates);
Assert.Equal("EsriJson", result);
}
[Fact]
public void SuggestClosest_NoCloseMatch_ReturnsNull()
{
var candidates = new[] { "EsriJson", "GeoJson", "Shapefile" };
var result = FactoryHelpers.SuggestClosest("zzzz", candidates);
Assert.Null(result);
}
[Fact]
public void LevenshteinDistance_BasicCases()
{
Assert.Equal(3, FactoryHelpers.LevenshteinDistance("kitten", "sitting"));
Assert.Equal(3, FactoryHelpers.LevenshteinDistance("", "abc"));
Assert.Equal(0, FactoryHelpers.LevenshteinDistance("same", "same"));
Assert.Equal(1, FactoryHelpers.LevenshteinDistance("abc", "ab"));
}
[Fact]
public void LevenshteinDistance_IsSymmetric()
{
var a = "distance";
var b = "distancey";
Assert.Equal(FactoryHelpers.LevenshteinDistance(a, b), FactoryHelpers.LevenshteinDistance(b, a));
}
[Fact]
public void SuggestClosest_SkipsEmptyCandidates()
{
var candidates = new[] { " ", null, "GeoJson" };
var result = FactoryHelpers.SuggestClosest("geojson", candidates);
Assert.Equal("GeoJson", result);
}
[Fact]
public void SuggestClosest_TieBreaks_ReturnsFirstCandidate()
{
// Construct two candidates equidistant from target; first should be selected
var candidates = new[] { "abcd", "abce" };
var result = FactoryHelpers.SuggestClosest("abcf", candidates);
// both candidates have distance 2; implementation should pick the first encountered
Assert.Equal("abcd", result);
}
[Fact]
public void SuggestClosest_Threshold_PreventsFarMatches()
{
var candidates = new[] { "EsriJson", "GeoJson" };
// Make a target that is somewhat long so threshold is higher; use a distant string
var target = "this-is-very-different";
var res = FactoryHelpers.SuggestClosest(target, candidates);
Assert.Null(res);
}
}
}