194 lines
5.5 KiB
Go
194 lines
5.5 KiB
Go
package web
|
|
|
|
import (
|
|
"music-server/internal/backend"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestCleanTerm tests that search terms are cleaned correctly
|
|
func TestCleanTerm(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
// CleanSearchTermForMatch removes spaces, replaces specific chars, and lowercases
|
|
{"MGS4", "mgs4"},
|
|
{"RE2", "re2"},
|
|
{"FFVII", "ffvii"},
|
|
{"Ys", "ys"},
|
|
{" test ", "test"},
|
|
{"Test 123", "test123"},
|
|
{"A+B", "aplusb"},
|
|
{"é", "e"},
|
|
// Note: CleanSearchTermForMatch does NOT remove apostrophes, it only replaces "'n" with "and"
|
|
{"It's", "it's"},
|
|
// But it does replace "'n" with "and"
|
|
{"I'n", "iand"},
|
|
{"", ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := backend.CleanSearchTermForMatch(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("CleanSearchTermForMatch(%q) = %q, want %q", tt.input, result, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// searchWithDataHelper is a helper to create backend.SoundtrackData from original names
|
|
func searchWithDataHelper(originals []string) []backend.SoundtrackData {
|
|
data := make([]backend.SoundtrackData, len(originals))
|
|
for i, orig := range originals {
|
|
data[i] = backend.SoundtrackData{
|
|
Original: orig,
|
|
Lowercase: strings.ToLower(orig),
|
|
Cleaned: backend.CleanSearchTermForMatch(orig),
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
// TestNormalSearch tests the normal search function
|
|
func TestNormalSearch(t *testing.T) {
|
|
// Set up comprehensive test data with all user-provided soundtracks
|
|
testData := searchWithDataHelper([]string{
|
|
"A Bird Story",
|
|
"Actraiser",
|
|
"Actraiser 2",
|
|
"Bangai-O Spirits",
|
|
"Ciconia When they Cry",
|
|
"Civilization IV",
|
|
"Civilization VI",
|
|
"Clair Obscur - Expedition 33",
|
|
"Club House Games (42 All-Time Classics)",
|
|
"Final Fantasy Dissidia 012 Duodecim (Sista Fantasin Meningsskiljaktigheter 012 Tolv)",
|
|
"Final Fantasy VII Crisis Core",
|
|
"Final Fantasy VII Rebirth",
|
|
"Final Fantasy VII Remake",
|
|
"Final Fantasy VII",
|
|
"Joe & Mac",
|
|
"Judgment",
|
|
"Kingdom Hearts",
|
|
"Kingdom Hearts Birth By Sleep",
|
|
"Kingdom Hearts II",
|
|
"Kingdom Hearts III",
|
|
"Lemmings (SNES + Mega Drive)",
|
|
"Lemmings 3D",
|
|
"Mega Man X4",
|
|
"Metal Gear Rising - Revengeance",
|
|
"Metal Gear Solid (+Twin Snakes)",
|
|
"Metal Gear Solid 2 - Sons of Liberty",
|
|
"Metal Gear Solid 3 - Snake Eater",
|
|
"Metal Gear Solid 4 - Guns of the Patriots",
|
|
"Metroid Prime",
|
|
"Octopath Traveler",
|
|
"Octopath Traveler 2",
|
|
"Ori and the Blind Forest",
|
|
"Ori and the Will of the Wisps",
|
|
"OutRun 2006 - Coast 2 Coast",
|
|
"Paper Mario - The Thousand Year Door",
|
|
"Pokémon HeartGold & SoulSilver",
|
|
"Resident Evil 2 (Remake)",
|
|
"Shenmue",
|
|
"Super Mario Galaxy",
|
|
"Trails in the Sky FC (Sora no Kiseki FC)",
|
|
"Trails in the Sky SC (Sora no Kiseki SC)",
|
|
"Turrican II - The Final Fight",
|
|
"Umineko no Naku Koro ni",
|
|
"Umineko no Naku Koro ni Chiru",
|
|
"VVVVVV",
|
|
"YS VIII - Lacrimosa of DANA",
|
|
"Yakuza 0",
|
|
"Ys (Chronicles)",
|
|
"Ys - Memories of Celceta",
|
|
"Ys - The Ark of Napishtim",
|
|
"Ys II (Chronicles)",
|
|
"Ys III - Wanderers from Ys (+The Oath in Felghana)",
|
|
"Ys IV - The Dawn of Ys",
|
|
"Ys Origin",
|
|
"Ys Seven",
|
|
"Ys X Nordics",
|
|
"Call of Duty: Modern Warfare",
|
|
"Half-Life 3",
|
|
})
|
|
|
|
// Test cases covering all match types
|
|
tests := []struct {
|
|
query string
|
|
wantFound string
|
|
}{
|
|
// Exact substring matches
|
|
{"Metal", "Metal Gear Solid 4 - Guns of the Patriots"},
|
|
{"Resident", "Resident Evil 2 (Remake)"},
|
|
{"Final", "Final Fantasy VII"},
|
|
{"Ys", "Ys III - Wanderers from Ys (+The Oath in Felghana)"},
|
|
|
|
// Contains matches (on cleaned terms)
|
|
{"gotp", "Metal Gear Solid 4 - Guns of the Patriots"},
|
|
{"mgs", "Metal Gear Solid 4 - Guns of the Patriots"},
|
|
{"kh", "Kingdom Hearts"},
|
|
{"mg", "Metal Gear Solid 4 - Guns of the Patriots"},
|
|
|
|
// Regex matches
|
|
{"mgs4go", "Metal Gear Solid 4 - Guns of the Patriots"},
|
|
{"re2r", "Resident Evil 2 (Remake)"},
|
|
{"ffv", "Final Fantasy VII"},
|
|
{"yiwfyt", "Ys III - Wanderers from Ys (+The Oath in Felghana)"},
|
|
{"codmw", "Call of Duty: Modern Warfare"},
|
|
{"mgs4gop", "Metal Gear Solid 4 - Guns of the Patriots"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.query, func(t *testing.T) {
|
|
results := searchWithData(tt.query, testData)
|
|
|
|
// Check that the expected item is in the results
|
|
found := false
|
|
for _, result := range results {
|
|
if result == tt.wantFound {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("searchWithData(%q, ...) did not find expected item %q, got %v", tt.query, tt.wantFound, results)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSearchOrdering tests that Pass 1 matches come before Pass 2 matches
|
|
func TestSearchOrdering(t *testing.T) {
|
|
// "MG" matches Pass 1 (exact substring on original)
|
|
// "Metal Gear" matches Pass 2 (contains on cleaned) but NOT Pass 1 (no "mg" substring in "metal gear")
|
|
testData := searchWithDataHelper([]string{
|
|
"Metal Gear",
|
|
"MG",
|
|
})
|
|
|
|
results := searchWithData("mg", testData)
|
|
|
|
// Find indices
|
|
indexPass1 := -1
|
|
indexPass2 := -1
|
|
for i, result := range results {
|
|
if result == "MG" {
|
|
indexPass1 = i
|
|
}
|
|
if result == "Metal Gear" {
|
|
indexPass2 = i
|
|
}
|
|
}
|
|
|
|
// Pass 1 match (MG) should come before Pass 2 match (Metal Gear)
|
|
if indexPass1 >= 0 && indexPass2 >= 0 && indexPass1 >= indexPass2 {
|
|
t.Errorf("Expected Pass 1 match 'MG' to come before Pass 2 match 'Metal Gear', but got order: %v", results)
|
|
}
|
|
}
|
|
|
|
// Note: The tests above verify:
|
|
// 1. clean_term - the term cleaning logic
|
|
// 2. search - the complete normal search with exact, contains, and regex matching
|
|
// 3. ordering - exact matches come before contains matches
|