Files
allstarr/allstarr.Tests/Matching/FuzzyMatcherTests.cs
T

197 lines
6.6 KiB
C#

using Xunit;
using allstarr.Services.Common;
namespace allstarr.Tests;
public class FuzzyMatcherTests
{
[Fact]
public void Artist_aware_search_uses_the_primary_credit()
{
Assert.Equal(
"Crush Selena Gomez & The Scene",
FuzzyMatcher.SearchQuery("Crush", "Selena Gomez & The Scene"));
Assert.Equal(
"Feels Calvin Harris",
FuzzyMatcher.SearchQuery("Feels", "Calvin Harris, Pharrell Williams"));
}
[Theory]
[InlineData("Link Up (Metro Boomin & Don Toliver, Wizkid feat. BEAM & Toian) - Spider-Verse Remix (Spider-Man: Across the Spider-Verse)", "Link Up")]
[InlineData("Feels", "Feels")]
public void SearchQuery_UsesTheBaseTitle(string title, string expected)
{
Assert.Equal(expected, FuzzyMatcher.SearchQuery(title));
}
[Theory]
[InlineData("Mr. Brightside", "Mr. Brightside", 100)]
[InlineData("Mr Brightside", "Mr. Brightside", 100)]
[InlineData("Mr. Brightside", "Mr Brightside", 100)]
[InlineData("The Killers", "Killers", 85)]
[InlineData("Dua Lipa", "Dua-Lipa", 100)]
public void CalculateSimilarity_ExactAndNearMatches_ReturnsHighScore(string str1, string str2, int expectedMin)
{
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.True(score >= expectedMin, $"Expected score >= {expectedMin}, got {score}");
}
[Theory]
[InlineData("Mr. Brightside", "Somebody Told Me", 20)]
[InlineData("The Killers", "The Beatles", 40)]
[InlineData("Hot Fuss", "Sam's Town", 20)]
public void CalculateSimilarity_DifferentStrings_ReturnsLowScore(string str1, string str2, int expectedMax)
{
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.True(score <= expectedMax, $"Expected score <= {expectedMax}, got {score}");
}
[Fact]
public void CalculateSimilarity_IgnoresPunctuation()
{
var str1 = "Don't Stop Believin'";
var str2 = "Dont Stop Believin";
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.True(score >= 95, $"Expected high score for punctuation differences, got {score}");
}
[Fact]
public void CalculateSimilarity_IgnoresCase()
{
var str1 = "Mr. Brightside";
var str2 = "mr. brightside";
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.Equal(100, score);
}
[Fact]
public void CalculateSimilarity_HandlesArticles()
{
var str1 = "The Killers";
var str2 = "Killers";
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.True(score >= 80, $"Expected high score when 'The' is removed, got {score}");
}
[Fact]
public void CalculateSimilarity_HandlesFeaturedArtists()
{
var str1 = "Song Title (feat. Artist)";
var str2 = "Song Title";
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.True(score >= 70, $"Expected decent score for featured artist variations, got {score}");
}
[Fact]
public void CalculateSimilarity_HandlesRemixes()
{
var str1 = "Song Title - Radio Edit";
var str2 = "Song Title";
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.True(score >= 70, $"Expected decent score for remix/edit variations, got {score}");
}
[Theory]
[InlineData("Kesha", "Ke$ha")]
[InlineData("PILLOWTALK", "PiLlOwT4lK")]
[InlineData("PILLOWTALK", "P1ll0wtalk")]
[InlineData("Heebiejeebies - Bonus", "Heebiejeebies")]
[InlineData("Homemade Dynamite (Feat. Khalid, Post Malone & SZA) - REMIX", "Homemade Dynamite (REMIX)")]
[InlineData("Song Title - Bonus Track", "Song Title")]
[InlineData("Song Title - Deluxe Edition", "Song Title")]
[InlineData("Song Title (Deluxe Edition)", "Song Title")]
[InlineData("Song Title [Bonus Track]", "Song Title")]
[InlineData("[Bonus Track] Song Title", "Song Title")]
[InlineData("Song Title (Album Version)", "Song Title")]
public void CalculateSimilarityAggressive_NormalizesSafeDecoratorsAndStylization(string source, string candidate)
{
Assert.Equal(100, FuzzyMatcher.CalculateSimilarityAggressive(source, candidate));
}
[Theory]
[InlineData("U2", "ua")]
[InlineData("24K Magic", "2ak magic")]
[InlineData("1999", "iggg")]
public void NormalizeForMatching_DoesNotFoldShortNamesOrNumericTokens(
string source,
string different)
{
Assert.NotEqual(
FuzzyMatcher.NormalizeForMatching(source),
FuzzyMatcher.NormalizeForMatching(different));
}
[Theory]
[InlineData("", "", 0)]
[InlineData("Test", "", 0)]
[InlineData("", "Test", 0)]
public void CalculateSimilarity_EmptyStrings_ReturnsZero(string str1, string str2, int expected)
{
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.Equal(expected, score);
}
[Fact]
public void CalculateSimilarity_TokenOrder_DoesNotMatter()
{
var str1 = "Bright Side Mr";
var str2 = "Mr Bright Side";
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.True(score >= 90, $"Expected high score regardless of token order, got {score}");
}
[Fact]
public void CalculateSimilarity_PartialTokenMatch_ReturnsModerateScore()
{
var str1 = "Mr. Brightside";
var str2 = "Mr. Brightside (Live)";
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.True(score >= 70 && score < 100, $"Expected moderate score for partial match, got {score}");
}
[Fact]
public void CalculateSimilarity_SpecialCharacters_AreNormalized()
{
var str1 = "Café del Mar";
var str2 = "Cafe del Mar";
var score = FuzzyMatcher.CalculateSimilarity(str1, str2);
Assert.True(score >= 90, $"Expected high score for accented characters, got {score}");
}
[Fact]
public void CalculateSimilarity_StackallocAndHeapBranches_ReturnConsistentResults()
{
// stackalloc branch: targetLength + 1 <= 128
Assert.Equal(42, FuzzyMatcher.CalculateSimilarity("kitten", "sitting"));
// 127-char string -> 128 -> stackalloc (pure Levenshtein max is scaled to 75)
var str127 = new string('a', 126) + "x";
var str127b = new string('a', 126) + "y";
Assert.True(FuzzyMatcher.CalculateSimilarity(str127, str127b) > 70);
// 130-char string -> 131 -> heap fallback
var str130 = new string('a', 129) + "x";
var str130b = new string('a', 129) + "y";
Assert.True(FuzzyMatcher.CalculateSimilarity(str130, str130b) > 70);
}
}