package main import ( "testing" ) // TestNormalizePath validates the GProTab path normalization logic func TestNormalizePath(t *testing.T) { tests := []struct { name string input string expected string }{ { name: "Full https GProTab URL", input: "https://gprotab.net/en/tabs/metallica/one", expected: "/en/tabs/metallica/one", }, { name: "Full http GProTab URL", input: "http://gprotab.net/en/tabs/slayer/raining-blood", expected: "/en/tabs/slayer/raining-blood", }, { name: "Clean relative path", input: "/en/tabs/pantera/walk", expected: "/en/tabs/pantera/walk", }, { name: "Partial slash slug", input: "megadeth/holy-wars", expected: "/en/tabs/megadeth/holy-wars", }, { name: "Simple text input", input: "metallica", expected: "metallica", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := normalizePath(tt.input) if result != tt.expected { t.Errorf("normalizePath(%q) = %q; expected %q", tt.input, result, tt.expected) } }) } } // TestCleanSongsterrName validates slug generation formatting func TestCleanSongsterrName(t *testing.T) { tests := []struct { name string input string expected string }{ { name: "Simple lowercase conversion", input: "Metallica", expected: "metallica", }, { name: "Spaces to hyphens", input: "The Eagles", expected: "the-eagles", }, { name: "Special characters removal", input: "Guns N' Roses & Friends!", expected: "guns-n-roses-friends", }, { name: "Collapsing multiple hyphens", input: "rock---and---roll", expected: "rock-and-roll", }, { name: "Trailing and leading hyphens removal", input: "---led zeppelin---", expected: "led-zeppelin", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := cleanSongsterrName(tt.input) if result != tt.expected { t.Errorf("cleanSongsterrName(%q) = %q; expected %q", tt.input, result, tt.expected) } }) } } // TestSanitizeDirName validates OS-safe folder name formatting func TestSanitizeDirName(t *testing.T) { tests := []struct { name string input string expected string }{ { name: "Preserve capitalization and spaces", input: "As I Lay Dying", expected: "As I Lay Dying", }, { name: "Filter standard Windows slash characters", input: "AC/DC", expected: "ACDC", }, { name: "Remove illegal OS characters but keep capitalization and symbols", input: "Guns N' Roses: Live & Loud *Special?", expected: "Guns N' Roses Live & Loud Special", }, { name: "Collapse adjacent spaces", input: " Slayer Metal ", expected: "Slayer Metal", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := sanitizeDirName(tt.input) if result != tt.expected { t.Errorf("sanitizeDirName(%q) = %q; expected %q", tt.input, result, tt.expected) } }) } } // TestIsTargetArtist validates the robust sub-artist matching logic func TestIsTargetArtist(t *testing.T) { tests := []struct { name string songArtist string targetArtist string expected bool }{ { name: "Direct matching exact", songArtist: "August Burns Red", targetArtist: "August Burns Red", expected: true, }, { name: "Direct matching case insensitive", songArtist: "august burns red", targetArtist: "August Burns Red", expected: true, }, { name: "Squished name matching", songArtist: "augustburnsred", targetArtist: "August Burns Red", expected: true, }, { name: "Trailing punctuation/comma matching", songArtist: "August Burns Red,", targetArtist: "August Burns Red", expected: true, }, { name: "Collaboration split by comma matching", songArtist: "August Burns Red, Jeremy McKinnon", targetArtist: "August Burns Red", expected: true, }, { name: "Collaboration split by ft matching", songArtist: "August Burns Red ft. Jeremy McKinnon", targetArtist: "August Burns Red", expected: true, }, { name: "Collaborator second in split", songArtist: "Jeremy McKinnon & August Burns Red", targetArtist: "August Burns Red", expected: true, }, { name: "Not matching similar name parody", songArtist: "August Burns All The Tabs", targetArtist: "August Burns Red", expected: false, }, { name: "Not matching misspelling", songArtist: "August Burn Red", targetArtist: "August Burns Red", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isTargetArtist(tt.songArtist, tt.targetArtist) if result != tt.expected { t.Errorf("isTargetArtist(%q, %q) = %v; expected %v", tt.songArtist, tt.targetArtist, result, tt.expected) } }) } } // TestParseSelection validates parsing selections func TestParseSelection(t *testing.T) { tests := []struct { name string input string maxVal int expected []int expectError bool }{ { name: "Single number", input: "3", maxVal: 10, expected: []int{2}, expectError: false, }, { name: "Comma separated", input: "1,3,5", maxVal: 10, expected: []int{0, 2, 4}, expectError: false, }, { name: "Range", input: "2-5", maxVal: 10, expected: []int{1, 2, 3, 4}, expectError: false, }, { name: "Mixed comma and range", input: "1,3-5,8", maxVal: 10, expected: []int{0, 2, 3, 4, 7}, expectError: false, }, { name: "De-duplicate and sort", input: "5,1-3,2", maxVal: 10, expected: []int{0, 1, 2, 4}, expectError: false, }, { name: "Out of bounds single", input: "11", maxVal: 10, expected: nil, expectError: true, }, { name: "Out of bounds range", input: "8-12", maxVal: 10, expected: nil, expectError: true, }, { name: "Empty input", input: "", maxVal: 10, expected: nil, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { res, err := parseSelection(tt.input, tt.maxVal) if tt.expectError { if err == nil { t.Errorf("expected error for input %q, but got none", tt.input) } } else { if err != nil { t.Errorf("unexpected error: %v", err) } if len(res) != len(tt.expected) { t.Errorf("expected length %d, got %d", len(tt.expected), len(res)) } else { for i := range res { if res[i] != tt.expected[i] { t.Errorf("at index %d: expected %d, got %d", i, tt.expected[i], res[i]) } } } } }) } }