package scanner import "testing" func TestSimilarity(t *testing.T) { tests := []struct { name string a string b string want float64 epsilon float64 }{ { name: "exact match scores 1.0", a: "The Wall", b: "The Wall", want: 1.0, epsilon: 1e-9, }, { name: "case-insensitive exact match scores 1.0", a: "The Wall", b: "the wall", want: 1.0, epsilon: 1e-9, }, { name: "remastered variant stays above threshold", a: "The Wall", b: "The Wall (Remastered)", want: 1.0, // parenthesized content is stripped during normalization epsilon: 1e-9, }, { name: "year-suffixed variant stays above threshold", a: "Abbey Road", b: "Abbey Road (2019 Remix)", // After normalization both collapse to "abbey road" → identical. want: 1.0, epsilon: 1e-9, }, { name: "clearly different titles score below 0.85", a: "The Wall", b: "Completely Different Album", want: 0.0, epsilon: 1e-9, }, { name: "substring-ish title scores moderately", a: "Dark Side of the Moon", b: "Dark Side of the Moon Part II", want: 0.0, // non-empty; value asserted only as below threshold epsilon: 1e-9, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Similarity(tt.a, tt.b) switch { case tt.name == "clearly different titles score below 0.85" || tt.name == "substring-ish title scores moderately": if got >= 0.85 { t.Errorf("Similarity(%q, %q) = %v, want < 0.85", tt.a, tt.b, got) } default: if diff := got - tt.want; diff > tt.epsilon || diff < -tt.epsilon { t.Errorf("Similarity(%q, %q) = %v, want %v (+/- %v)", tt.a, tt.b, got, tt.want, tt.epsilon) } } }) } } func TestSimilarity_EmptyStrings(t *testing.T) { // Both empty → no match (0.0). if got := Similarity("", ""); got != 0.0 { t.Errorf("Similarity(%q, %q) = %v, want 0.0", "", "", got) } // One empty, one non-empty → no similarity. if got := Similarity("The Wall", ""); got != 0.0 { t.Errorf("Similarity(%q, %q) = %v, want 0.0", "The Wall", "", got) } if got := Similarity("", "The Wall"); got != 0.0 { t.Errorf("Similarity(%q, %q) = %v, want 0.0", "", "The Wall", got) } // Whitespace-only inputs normalize to empty → no match. if got := Similarity(" ", "The Wall"); got != 0.0 { t.Errorf("Similarity(%q, %q) = %v, want 0.0", " ", "The Wall", got) } } func TestIsMatch(t *testing.T) { const threshold = 0.85 tests := []struct { name string a string b string expected bool }{ {name: "exact match is a match", a: "The Wall", b: "The Wall", expected: true}, {name: "remastered variant is a match", a: "The Wall", b: "The Wall (Remastered)", expected: true}, {name: "year variant is a match", a: "Abbey Road", b: "Abbey Road (2019)", expected: true}, {name: "clearly different is not a match", a: "The Wall", b: "Random Noise", expected: false}, {name: "empty vs non-empty is not a match", a: "", b: "The Wall", expected: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsMatch(tt.a, tt.b, threshold); got != tt.expected { t.Errorf("IsMatch(%q, %q, %v) = %v, want %v", tt.a, tt.b, threshold, got, tt.expected) } }) } } func TestIsMatch_ThresholdBoundary(t *testing.T) { // A moderately different title should be a match at a low threshold but // not at a high one, confirming the boundary is inclusive (>=). a, b := "The Wall", "The Wall Live" low := IsMatch(a, b, 0.5) high := IsMatch(a, b, 0.99) if !low { t.Errorf("IsMatch(%q, %q, 0.5) = false, want true", a, b) } if high { t.Errorf("IsMatch(%q, %q, 0.99) = true, want false", a, b) } }