425f12db38
Changes the AlbumCoverProvider and ArtistArtProvider interfaces from MBID-only string parameters to ref-struct signatures so name-based providers (Deezer, Last.fm in upcoming tasks) can act on rows without MBIDs. Today, 53 of 213 artists in the dev DB have NULL MBIDs — featured artists, remixers, and compilation contributors created from track-tag splits where the primary-artist MBID is in the file but collaborator IDs aren't. These rows are unreachable by the current MBID-only chain. Refactoring the interface unblocks future name-based providers from acting on them. TheAudioDB and MBCAA keep MBID-only behavior internally: they return ErrNotFound when ref.MBID is empty rather than attempting a name-based fallback. The MBID guard inside EnrichAlbum/EnrichArtist is removed; providers receive the ref unconditionally and decide whether they can act on it. GetAlbumWithFirstTrackPath now JOINs artists to return artist_name alongside the existing fields, supporting AlbumRef construction. No behavioral change today (only TheAudioDB + MBCAA registered, both keep MBID-only behavior). The change unblocks T3 (Deezer) and T4 (Last.fm) which can now register and act on every artist/album regardless of MBID presence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
234 lines
6.8 KiB
Go
234 lines
6.8 KiB
Go
package coverart
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
// fakeArtistProvider opts into ArtistArtProvider for capability tests.
|
|
type fakeArtistProvider struct {
|
|
fakeProvider
|
|
}
|
|
|
|
func (f *fakeArtistProvider) FetchArtistArt(_ context.Context, _ ArtistRef) (thumb, fanart []byte, err error) {
|
|
return []byte("t"), []byte("f"), nil
|
|
}
|
|
|
|
// fakeTestableAlbumProvider implements TestableProvider for the
|
|
// TestProvider test.
|
|
type fakeTestableAlbumProvider struct {
|
|
fakeAlbumProvider
|
|
testErr error
|
|
}
|
|
|
|
func (f *fakeTestableAlbumProvider) TestConnection(_ context.Context) error {
|
|
return f.testErr
|
|
}
|
|
|
|
func TestSettingsService_BootInsertsDefaultRows(t *testing.T) {
|
|
pool := newPool(t)
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
|
|
Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", display: "Fake Album", defaultOn: true}})
|
|
Register(&fakeArtistProvider{fakeProvider: fakeProvider{id: "fake-artist", display: "Fake Artist", defaultOn: false}})
|
|
|
|
s, err := NewSettingsService(context.Background(), pool, discardLogger())
|
|
if err != nil {
|
|
t.Fatalf("NewSettingsService: %v", err)
|
|
}
|
|
|
|
// Both providers should now have rows; only fake-album is enabled.
|
|
enabled := s.EnabledAlbumProviders()
|
|
if len(enabled) != 1 {
|
|
t.Fatalf("EnabledAlbumProviders = %d, want 1", len(enabled))
|
|
}
|
|
if enabled[0].ID() != "fake-album" {
|
|
t.Errorf("enabled[0].ID = %q, want fake-album", enabled[0].ID())
|
|
}
|
|
|
|
// fake-artist defaultOn=false, so it should NOT be in EnabledArtistProviders.
|
|
artists := s.EnabledArtistProviders()
|
|
if len(artists) != 0 {
|
|
t.Errorf("EnabledArtistProviders = %d, want 0", len(artists))
|
|
}
|
|
|
|
if s.CurrentVersion() != 1 {
|
|
t.Errorf("CurrentVersion = %d, want 1", s.CurrentVersion())
|
|
}
|
|
}
|
|
|
|
func TestSettingsService_UpdateProvider_FlippingEnabledBumpsVersion(t *testing.T) {
|
|
pool := newPool(t)
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
|
|
Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", defaultOn: true}})
|
|
|
|
s, err := NewSettingsService(context.Background(), pool, discardLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
startVersion := s.CurrentVersion()
|
|
|
|
disabled := false
|
|
bumped, err := s.UpdateProvider(context.Background(), "fake-album", ProviderUpdatePatch{Enabled: &disabled})
|
|
if err != nil {
|
|
t.Fatalf("UpdateProvider: %v", err)
|
|
}
|
|
if !bumped {
|
|
t.Error("versionBumped = false, want true after disabling")
|
|
}
|
|
if s.CurrentVersion() <= startVersion {
|
|
t.Errorf("CurrentVersion = %d, want > %d", s.CurrentVersion(), startVersion)
|
|
}
|
|
|
|
enabled := s.EnabledAlbumProviders()
|
|
if len(enabled) != 0 {
|
|
t.Errorf("after disable: EnabledAlbumProviders = %d, want 0", len(enabled))
|
|
}
|
|
}
|
|
|
|
func TestSettingsService_UpdateProvider_KeyOnlyDoesNotBumpVersion(t *testing.T) {
|
|
pool := newPool(t)
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
|
|
Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", defaultOn: true}})
|
|
|
|
s, err := NewSettingsService(context.Background(), pool, discardLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
startVersion := s.CurrentVersion()
|
|
|
|
newKey := "abc123"
|
|
bumped, err := s.UpdateProvider(context.Background(), "fake-album", ProviderUpdatePatch{APIKey: &newKey})
|
|
if err != nil {
|
|
t.Fatalf("UpdateProvider: %v", err)
|
|
}
|
|
if bumped {
|
|
t.Error("versionBumped = true, want false for key-only change")
|
|
}
|
|
if s.CurrentVersion() != startVersion {
|
|
t.Errorf("CurrentVersion changed: %d → %d, want unchanged", startVersion, s.CurrentVersion())
|
|
}
|
|
|
|
infos := s.ListProviderInfo()
|
|
var info ProviderInfo
|
|
for _, i := range infos {
|
|
if i.ID == "fake-album" {
|
|
info = i
|
|
break
|
|
}
|
|
}
|
|
if !info.APIKeySet {
|
|
t.Error("APIKeySet = false, want true after setting key")
|
|
}
|
|
}
|
|
|
|
func TestSettingsService_UpdateProvider_ClearKey(t *testing.T) {
|
|
pool := newPool(t)
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
|
|
Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", defaultOn: true}})
|
|
|
|
s, err := NewSettingsService(context.Background(), pool, discardLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set a key, then clear it.
|
|
setKey := "abc"
|
|
if _, err := s.UpdateProvider(context.Background(), "fake-album", ProviderUpdatePatch{APIKey: &setKey}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
emptyKey := ""
|
|
if _, err := s.UpdateProvider(context.Background(), "fake-album", ProviderUpdatePatch{APIKey: &emptyKey}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
infos := s.ListProviderInfo()
|
|
for _, i := range infos {
|
|
if i.ID == "fake-album" && i.APIKeySet {
|
|
t.Error("APIKeySet = true after clear, want false")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSettingsService_TestProvider_Routing(t *testing.T) {
|
|
pool := newPool(t)
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
|
|
plain := &fakeAlbumProvider{fakeProvider: fakeProvider{id: "plain", defaultOn: true}}
|
|
testable := &fakeTestableAlbumProvider{
|
|
fakeAlbumProvider: fakeAlbumProvider{fakeProvider: fakeProvider{id: "testable", defaultOn: true}},
|
|
testErr: nil,
|
|
}
|
|
Register(plain)
|
|
Register(testable)
|
|
|
|
s, err := NewSettingsService(context.Background(), pool, discardLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Testable returns nil → success.
|
|
if err := s.TestProvider(context.Background(), "testable"); err != nil {
|
|
t.Errorf("TestProvider(testable) = %v, want nil", err)
|
|
}
|
|
|
|
// Plain doesn't implement TestableProvider → ErrNotTestable.
|
|
if err := s.TestProvider(context.Background(), "plain"); !errors.Is(err, ErrNotTestable) {
|
|
t.Errorf("TestProvider(plain) = %v, want ErrNotTestable", err)
|
|
}
|
|
|
|
// Missing → ErrProviderNotFound.
|
|
if err := s.TestProvider(context.Background(), "missing"); !errors.Is(err, ErrProviderNotFound) {
|
|
t.Errorf("TestProvider(missing) = %v, want ErrProviderNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestSettingsService_ListProviderInfo_Capabilities(t *testing.T) {
|
|
pool := newPool(t)
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
|
|
plain := &fakeProvider{id: "plain", defaultOn: true}
|
|
album := &fakeAlbumProvider{fakeProvider: fakeProvider{id: "album-only", defaultOn: true}}
|
|
artist := &fakeArtistProvider{fakeProvider: fakeProvider{id: "artist-only", defaultOn: true}}
|
|
Register(plain)
|
|
Register(album)
|
|
Register(artist)
|
|
|
|
s, err := NewSettingsService(context.Background(), pool, discardLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
infos := s.ListProviderInfo()
|
|
if len(infos) != 3 {
|
|
t.Fatalf("ListProviderInfo len = %d, want 3", len(infos))
|
|
}
|
|
|
|
byID := map[string]ProviderInfo{}
|
|
for _, i := range infos {
|
|
byID[i.ID] = i
|
|
}
|
|
|
|
if got := byID["plain"].Supports; len(got) != 0 {
|
|
t.Errorf("plain.Supports = %v, want empty", got)
|
|
}
|
|
if got := byID["album-only"].Supports; len(got) != 1 || got[0] != "album_cover" {
|
|
t.Errorf("album-only.Supports = %v, want [album_cover]", got)
|
|
}
|
|
if got := byID["artist-only"].Supports; len(got) != 2 || got[0] != "artist_thumb" || got[1] != "artist_fanart" {
|
|
t.Errorf("artist-only.Supports = %v, want [artist_thumb, artist_fanart]", got)
|
|
}
|
|
}
|