362 lines
11 KiB
Go
362 lines
11 KiB
Go
package musicbrainz
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"golang.org/x/time/rate"
|
|
"naviwatcher/internal/config"
|
|
)
|
|
|
|
// ---------- FilterReleaseGroups tests ----------
|
|
|
|
// Release groups carry no status in ws/2, so status values are irrelevant to
|
|
// filtering. These groups differ only by the (ignored) status attribute; all
|
|
// are Album/Single and should be retained.
|
|
func TestFilterReleaseGroups_StatusIsNotFiltered(t *testing.T) {
|
|
groups := []ReleaseGroup{
|
|
{ID: "rg-1", Title: "Official Album", Type: "Album"},
|
|
{ID: "rg-2", Title: "Bootleg Live", Type: "Album"},
|
|
{ID: "rg-3", Title: "Promo CD", Type: "Single"},
|
|
{ID: "rg-4", Title: "Pseudo Release", Type: "Album"},
|
|
}
|
|
|
|
result := FilterReleaseGroups(groups, FilterOptions{})
|
|
|
|
if len(result) != 4 {
|
|
t.Fatalf("FilterReleaseGroups() returned %d groups, want 4", len(result))
|
|
}
|
|
}
|
|
|
|
func TestFilterReleaseGroups_IncludesOnlyAllowedTypes(t *testing.T) {
|
|
groups := []ReleaseGroup{
|
|
{ID: "rg-1", Title: "Album", Type: "Album"},
|
|
{ID: "rg-2", Title: "Single", Type: "Single"},
|
|
{ID: "rg-3", Title: "EP", Type: "EP"},
|
|
// A compilation whose primary type is Album (the common case) is
|
|
// classified via its secondary type and must be included.
|
|
{ID: "rg-4", Title: "Greatest Hits", Type: "Album", SecondaryTypes: []string{"Compilation"}},
|
|
{ID: "rg-5", Title: "Soundtrack", Type: "Soundtrack"},
|
|
{ID: "rg-6", Title: "Live", Type: "Live"},
|
|
{ID: "rg-7", Title: "Remix", Type: "Remix"},
|
|
}
|
|
|
|
result := FilterReleaseGroups(groups, FilterOptions{})
|
|
|
|
if len(result) != 4 {
|
|
t.Fatalf("FilterReleaseGroups() returned %d groups, want 4", len(result))
|
|
}
|
|
|
|
allowedIDs := map[string]bool{"rg-1": true, "rg-2": true, "rg-3": true, "rg-4": true}
|
|
for _, rg := range result {
|
|
if !allowedIDs[rg.ID] {
|
|
t.Errorf("unexpected group %q in filtered results", rg.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilterReleaseGroups_IgnoreSingles(t *testing.T) {
|
|
groups := []ReleaseGroup{
|
|
{ID: "rg-1", Title: "Album", Type: "Album"},
|
|
{ID: "rg-2", Title: "Single", Type: "Single"},
|
|
{ID: "rg-3", Title: "EP", Type: "EP"},
|
|
// Single expressed via secondary type (primary is Album).
|
|
{ID: "rg-4", Title: "Single from Album", Type: "Album", SecondaryTypes: []string{"Single"}},
|
|
}
|
|
|
|
result := FilterReleaseGroups(groups, FilterOptions{IgnoreSingles: true})
|
|
|
|
if len(result) != 1 {
|
|
t.Fatalf("FilterReleaseGroups() returned %d groups, want 1", len(result))
|
|
}
|
|
for _, rg := range result {
|
|
if rg.Type == "Single" || rg.Type == "EP" || contains(rg.SecondaryTypes, "Single") || contains(rg.SecondaryTypes, "EP") {
|
|
t.Errorf("single/ep %q should have been filtered out", rg.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilterReleaseGroups_IgnoreCompilations(t *testing.T) {
|
|
groups := []ReleaseGroup{
|
|
{ID: "rg-1", Title: "Album", Type: "Album"},
|
|
{ID: "rg-2", Title: "Greatest Hits", Type: "Album", SecondaryTypes: []string{"Compilation"}},
|
|
{ID: "rg-3", Title: "EP", Type: "EP"},
|
|
}
|
|
|
|
result := FilterReleaseGroups(groups, FilterOptions{IgnoreCompilations: true})
|
|
|
|
if len(result) != 2 {
|
|
t.Fatalf("FilterReleaseGroups() returned %d groups, want 2", len(result))
|
|
}
|
|
for _, rg := range result {
|
|
if rg.Type == "Compilation" || contains(rg.SecondaryTypes, "Compilation") {
|
|
t.Errorf("compilation %q should have been filtered out", rg.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------- ReleaseGroup.ToExternalRelease tests ----------
|
|
|
|
func TestReleaseGroup_ToExternalRelease(t *testing.T) {
|
|
rg := ReleaseGroup{
|
|
ID: "rg-uuid-1",
|
|
Title: "Dark Side of the Moon",
|
|
Type: "Album",
|
|
ArtistID: "mbid-artist-uuid-1",
|
|
ArtistName: "Pink Floyd",
|
|
ReleaseDate: "1973-03-01",
|
|
}
|
|
|
|
// ToExternalRelease stores the canonical artist key (Navidrome ID), not the
|
|
// MusicBrainz ArtistID, so external_releases.artist_id matches artist_settings.
|
|
const navidromeArtistID = "navidrome-artist-uuid-1"
|
|
er := rg.ToExternalRelease(navidromeArtistID)
|
|
|
|
if er.RGID != "rg-uuid-1" {
|
|
t.Errorf("RGID = %q, want %q", er.RGID, "rg-uuid-1")
|
|
}
|
|
if er.ArtistID != navidromeArtistID {
|
|
t.Errorf("ArtistID = %q, want %q", er.ArtistID, navidromeArtistID)
|
|
}
|
|
if er.Title != "Dark Side of the Moon" {
|
|
t.Errorf("Title = %q, want %q", er.Title, "Dark Side of the Moon")
|
|
}
|
|
if er.Type != "Album" {
|
|
t.Errorf("Type = %q, want %q", er.Type, "Album")
|
|
}
|
|
if er.ReleaseDate != "1973-03-01" {
|
|
t.Errorf("ReleaseDate = %q, want %q", er.ReleaseDate, "1973-03-01")
|
|
}
|
|
}
|
|
|
|
// ---------- GetArtistReleaseGroups tests ----------
|
|
|
|
func TestGetArtistReleaseGroups_Success(t *testing.T) {
|
|
artistMBID := "artist-uuid-test"
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
|
<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">
|
|
<release-group-list count="2">
|
|
<release-group id="rg-uuid-1" type="Album">
|
|
<title>Dark Side of the Moon</title>
|
|
<first-release-date>1973-03-01</first-release-date>
|
|
<artist-credit>
|
|
<name-credit>
|
|
<artist id="` + artistMBID + `">
|
|
<name>Pink Floyd</name>
|
|
</artist>
|
|
</name-credit>
|
|
</artist-credit>
|
|
</release-group>
|
|
<release-group id="rg-uuid-2" type="Single">
|
|
<title>Another Brick in the Wall</title>
|
|
<first-release-date>1979-11-30</first-release-date>
|
|
<artist-credit>
|
|
<name-credit>
|
|
<artist id="` + artistMBID + `">
|
|
<name>Pink Floyd</name>
|
|
</artist>
|
|
</name-credit>
|
|
</artist-credit>
|
|
</release-group>
|
|
</release-group-list>
|
|
</metadata>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
cfg := config.MusicBrainzConfig{
|
|
UserAgent: "naviwatcher/0.1.0 (test@example.com)",
|
|
}
|
|
|
|
rl := rate.NewLimiter(rate.Limit(1000), 1000)
|
|
client := &MusicBrainzClient{
|
|
httpClient: server.Client(),
|
|
userAgent: cfg.UserAgent,
|
|
baseURL: server.URL,
|
|
rateLimiter: rl,
|
|
}
|
|
|
|
groups, err := client.GetArtistReleaseGroups(context.Background(), artistMBID)
|
|
if err != nil {
|
|
t.Fatalf("GetArtistReleaseGroups() error = %v", err)
|
|
}
|
|
|
|
if len(groups) != 2 {
|
|
t.Fatalf("GetArtistReleaseGroups() returned %d groups, want 2", len(groups))
|
|
}
|
|
|
|
if groups[0].Title != "Dark Side of the Moon" {
|
|
t.Errorf("groups[0].Title = %q, want %q", groups[0].Title, "Dark Side of the Moon")
|
|
}
|
|
if groups[1].Title != "Another Brick in the Wall" {
|
|
t.Errorf("groups[1].Title = %q, want %q", groups[1].Title, "Another Brick in the Wall")
|
|
}
|
|
}
|
|
|
|
func TestGetArtistReleaseGroups_Pagination(t *testing.T) {
|
|
artistMBID := "artist-page-test"
|
|
requestCount := 0
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
requestCount++
|
|
offset := r.URL.Query().Get("offset")
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
if offset == "0" || offset == "" {
|
|
// First page: return 100 results (full page, matching limit) to trigger pagination
|
|
xml := `<?xml version="1.0" encoding="UTF-8"?>
|
|
<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">
|
|
<release-group-list count="101">`
|
|
for i := 0; i < 100; i++ {
|
|
xml += `
|
|
<release-group id="rg-page1-` + string(rune('0'+i%10)) + `" type="Album">
|
|
<title>Page 1 Album</title>
|
|
<first-release-date>2020-01-01</first-release-date>
|
|
<artist-credit>
|
|
<name-credit>
|
|
<artist id="` + artistMBID + `"><name>Artist</name></artist>
|
|
</name-credit>
|
|
</artist-credit>
|
|
</release-group>`
|
|
}
|
|
xml += `
|
|
</release-group-list>
|
|
</metadata>`
|
|
w.Write([]byte(xml))
|
|
} else {
|
|
// Second page: return only 1 result (< limit, signaling last page)
|
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
|
<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">
|
|
<release-group-list count="101">
|
|
<release-group id="rg-page2" type="Album">
|
|
<title>Page 2 Album</title>
|
|
<first-release-date>2021-01-01</first-release-date>
|
|
<artist-credit>
|
|
<name-credit>
|
|
<artist id="` + artistMBID + `"><name>Artist</name></artist>
|
|
</name-credit>
|
|
</artist-credit>
|
|
</release-group>
|
|
</release-group-list>
|
|
</metadata>`))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
cfg := config.MusicBrainzConfig{
|
|
UserAgent: "naviwatcher/0.1.0 (test@example.com)",
|
|
}
|
|
|
|
rl := rate.NewLimiter(rate.Limit(1000), 1000)
|
|
client := &MusicBrainzClient{
|
|
httpClient: server.Client(),
|
|
userAgent: cfg.UserAgent,
|
|
baseURL: server.URL,
|
|
rateLimiter: rl,
|
|
}
|
|
|
|
groups, err := client.GetArtistReleaseGroups(context.Background(), artistMBID)
|
|
if err != nil {
|
|
t.Fatalf("GetArtistReleaseGroups() error = %v", err)
|
|
}
|
|
|
|
// Should have fetched 2 pages: 100 from first + 1 from second = 101 total
|
|
if len(groups) != 101 {
|
|
t.Fatalf("GetArtistReleaseGroups() returned %d groups, want 101", len(groups))
|
|
}
|
|
|
|
if requestCount != 2 {
|
|
t.Errorf("expected 2 paginated requests, got %d", requestCount)
|
|
}
|
|
}
|
|
|
|
func TestGetArtistReleaseGroups_EmptyResult(t *testing.T) {
|
|
artistMBID := "artist-empty"
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
|
<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">
|
|
<release-group-list count="0">
|
|
</release-group-list>
|
|
</metadata>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
cfg := config.MusicBrainzConfig{
|
|
UserAgent: "naviwatcher/0.1.0 (test@example.com)",
|
|
}
|
|
|
|
rl := rate.NewLimiter(rate.Limit(1000), 1000)
|
|
client := &MusicBrainzClient{
|
|
httpClient: server.Client(),
|
|
userAgent: cfg.UserAgent,
|
|
baseURL: server.URL,
|
|
rateLimiter: rl,
|
|
}
|
|
|
|
groups, err := client.GetArtistReleaseGroups(context.Background(), artistMBID)
|
|
if err != nil {
|
|
t.Fatalf("GetArtistReleaseGroups() error = %v", err)
|
|
}
|
|
|
|
if len(groups) != 0 {
|
|
t.Errorf("GetArtistReleaseGroups() returned %d groups, want 0", len(groups))
|
|
}
|
|
}
|
|
|
|
func TestGetArtistReleaseGroups_ServerError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
w.Write([]byte("Rate limit exceeded"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
cfg := config.MusicBrainzConfig{
|
|
UserAgent: "naviwatcher/0.1.0 (test@example.com)",
|
|
}
|
|
|
|
rl := rate.NewLimiter(rate.Limit(1000), 1000)
|
|
client := &MusicBrainzClient{
|
|
httpClient: server.Client(),
|
|
userAgent: cfg.UserAgent,
|
|
baseURL: server.URL,
|
|
rateLimiter: rl,
|
|
}
|
|
|
|
_, err := client.GetArtistReleaseGroups(context.Background(), "artist-1")
|
|
if err == nil {
|
|
t.Fatal("GetArtistReleaseGroups() expected error for server error, got nil")
|
|
}
|
|
}
|
|
|
|
func TestGetArtistReleaseGroups_InvalidXML(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.Write([]byte(`this is not valid xml`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
cfg := config.MusicBrainzConfig{
|
|
UserAgent: "naviwatcher/0.1.0 (test@example.com)",
|
|
}
|
|
|
|
rl := rate.NewLimiter(rate.Limit(1000), 1000)
|
|
client := &MusicBrainzClient{
|
|
httpClient: server.Client(),
|
|
userAgent: cfg.UserAgent,
|
|
baseURL: server.URL,
|
|
rateLimiter: rl,
|
|
}
|
|
|
|
_, err := client.GetArtistReleaseGroups(context.Background(), "artist-1")
|
|
if err == nil {
|
|
t.Fatal("GetArtistReleaseGroups() expected error for invalid XML, got nil")
|
|
}
|
|
}
|