385eb3b163
Rewires EnrichAlbum to iterate EnabledAlbumProviders from the SettingsService instead of a single hardcoded MBCAA fetcher. Sidecar layer is unchanged (always tried first). Per-row eligibility check honors cover_art_sources_version: terminal 'found' values skip; 'none' flips to NULL via ClearAlbumCoverNone and proceeds (the DB- level ListAlbumsMissingCover query guards the version check for batch callers; RetryAlbum clears via ClearAlbumCover first); NULL is eligible. The provider chain uses an inline allWere404 accumulator to settle the row to 'none' only when every provider returned ErrNotFound. Any provider returning ErrTransient leaves the row NULL for next- pass retry — even if other providers returned ErrNotFound — since the transient call's MBID might succeed on a future attempt. Boot wiring (cmd/minstrel/main.go) replaces the NewFetcher+NewEnricher(fetcher, mbcaaOn) shape with NewMBCAAProviderFromConfig (swaps in production User-Agent on the registered provider) + NewSettingsService + NewEnricher(settings). The old cfg.Library.CoverArtFromMBCAA flag is no-op'd; DB-backed settings replace it. Field stays in the config struct for backward compat with deployed config files. Consolidates fetcher.go's HTTP logic into provider_mbcaa.go (no more wrapper indirection): mbcaaProvider now holds cfg/mu/lastCall/client directly. Deletes fetcher.go and fetcher_test.go. ErrNotFound and ErrTransient move into provider.go alongside the other sentinels. Updates admin_covers_test.go and provider_mbcaa_test.go to the new constructor shapes. Adds multi-provider-chain, transient-leaves-null, and stale-none-flips-and-retries test cases in enricher_test.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
169 lines
4.7 KiB
Go
169 lines
4.7 KiB
Go
package coverart
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// newMBCAAProviderForTest registers a fresh mbcaaProvider pointing at
|
|
// a test server.
|
|
func newMBCAAProviderForTest(t *testing.T, mockURL string) *mbcaaProvider {
|
|
t.Helper()
|
|
resetRegistryForTests()
|
|
t.Cleanup(resetRegistryForTests)
|
|
|
|
p := newMBCAAProvider(FetcherConfig{
|
|
BaseURL: mockURL,
|
|
UserAgent: "Minstrel/test (https://example.com)",
|
|
MinPeriod: 0,
|
|
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
|
})
|
|
Register(p)
|
|
p.enabled.Store(true)
|
|
return p
|
|
}
|
|
|
|
func TestMBCAAProvider_ID(t *testing.T) {
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
p := newMBCAAProvider(FetcherConfig{})
|
|
if p.ID() != "mbcaa" {
|
|
t.Errorf("ID = %q, want mbcaa", p.ID())
|
|
}
|
|
if p.DisplayName() != "Cover Art Archive" {
|
|
t.Errorf("DisplayName = %q, want Cover Art Archive", p.DisplayName())
|
|
}
|
|
if p.RequiresAPIKey() {
|
|
t.Error("RequiresAPIKey = true, want false")
|
|
}
|
|
if !p.DefaultEnabled() {
|
|
t.Error("DefaultEnabled = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestMBCAAProvider_FetchAlbumCover_Success(t *testing.T) {
|
|
imageBody := []byte("FAKE_JPEG")
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !strings.Contains(r.URL.Path, "/release/abc-123/front-500") {
|
|
http.Error(w, "wrong path: "+r.URL.Path, http.StatusBadRequest)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "image/jpeg")
|
|
_, _ = w.Write(imageBody)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newMBCAAProviderForTest(t, srv.URL)
|
|
body, err := p.FetchAlbumCover(context.Background(), "abc-123")
|
|
if err != nil {
|
|
t.Fatalf("FetchAlbumCover err = %v, want nil", err)
|
|
}
|
|
if string(body) != string(imageBody) {
|
|
t.Errorf("body = %q, want %q", body, imageBody)
|
|
}
|
|
}
|
|
|
|
func TestMBCAAProvider_FetchAlbumCover_NotFound(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newMBCAAProviderForTest(t, srv.URL)
|
|
_, err := p.FetchAlbumCover(context.Background(), "missing")
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestMBCAAProvider_FetchAlbumCover_DisabledReturnsNotFound(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
t.Error("server should not be hit when provider is disabled")
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newMBCAAProviderForTest(t, srv.URL)
|
|
p.enabled.Store(false)
|
|
|
|
_, err := p.FetchAlbumCover(context.Background(), "any")
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound (disabled)", err)
|
|
}
|
|
}
|
|
|
|
func TestMBCAAProvider_Configure_TogglesEnabled(t *testing.T) {
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
p := newMBCAAProvider(FetcherConfig{})
|
|
|
|
if err := p.Configure(ProviderSettings{Enabled: true}); err != nil {
|
|
t.Fatalf("Configure: %v", err)
|
|
}
|
|
if !p.enabled.Load() {
|
|
t.Error("after Configure(Enabled: true), enabled = false")
|
|
}
|
|
|
|
if err := p.Configure(ProviderSettings{Enabled: false}); err != nil {
|
|
t.Fatalf("Configure: %v", err)
|
|
}
|
|
if p.enabled.Load() {
|
|
t.Error("after Configure(Enabled: false), enabled = true")
|
|
}
|
|
}
|
|
|
|
func TestMBCAAProvider_TestConnection_TreatsNotFoundAsSuccess(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newMBCAAProviderForTest(t, srv.URL)
|
|
if err := p.TestConnection(context.Background()); err != nil {
|
|
t.Errorf("TestConnection err = %v, want nil (404 should be treated as connection ok)", err)
|
|
}
|
|
}
|
|
|
|
func TestMBCAAProvider_TestConnection_PropagatesTransient(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "boom", http.StatusInternalServerError)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newMBCAAProviderForTest(t, srv.URL)
|
|
if err := p.TestConnection(context.Background()); err == nil {
|
|
t.Error("TestConnection err = nil, want non-nil for 500")
|
|
}
|
|
}
|
|
|
|
func TestNewMBCAAProviderFromConfig_ReplacesRegisteredConfig(t *testing.T) {
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
|
|
// Pre-register with default config (mimics init() behaviour).
|
|
Register(newMBCAAProvider(FetcherConfig{}))
|
|
|
|
cfg := FetcherConfig{
|
|
UserAgent: "Minstrel/test-replaced (x)",
|
|
MinPeriod: 100 * time.Millisecond,
|
|
}
|
|
NewMBCAAProviderFromConfig(cfg)
|
|
|
|
p, err := ProviderByID("mbcaa")
|
|
if err != nil {
|
|
t.Fatalf("ProviderByID: %v", err)
|
|
}
|
|
mp := p.(*mbcaaProvider)
|
|
// Verify the cfg was replaced on the registered instance.
|
|
mp.mu.Lock()
|
|
gotUA := mp.cfg.UserAgent
|
|
mp.mu.Unlock()
|
|
if gotUA != "Minstrel/test-replaced (x)" {
|
|
t.Errorf("cfg.UserAgent = %q, want %q", gotUA, "Minstrel/test-replaced (x)")
|
|
}
|
|
}
|