feat(server/m7-cover-sources): Provider interface + registry
Defines the abstraction every cover-art source will implement: - Provider — base interface (ID, DisplayName, RequiresAPIKey, DefaultEnabled, Configure). - AlbumCoverProvider — opt-in capability for fetching album covers by MBID. - ArtistArtProvider — opt-in capability for fetching artist thumb + fanart atomically by MBID. - TestableProvider — opt-in capability for the admin Test-Connection button. Sentinel ErrNotTestable / ErrProviderNotFound. ErrNotFound and ErrTransient stay in fetcher.go for now; they move into provider.go in the next commit when fetcher.go is deleted as part of the MBCAA refactor. Compile-time registry: providers register from init() in their own file. Duplicate IDs panic at startup (compiled-in registration is a build-time bug, not a runtime concern). Tests cover: Register adds, duplicate panics, ProviderByID success + ErrProviderNotFound, capability-interface filtering via Go type assertion.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package coverart
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// fakeProvider is a minimal Provider for registry tests. Does not
|
||||
// implement any capability interface.
|
||||
type fakeProvider struct {
|
||||
id string
|
||||
display string
|
||||
requiresKey bool
|
||||
defaultOn bool
|
||||
}
|
||||
|
||||
func (f *fakeProvider) ID() string { return f.id }
|
||||
func (f *fakeProvider) DisplayName() string { return f.display }
|
||||
func (f *fakeProvider) RequiresAPIKey() bool { return f.requiresKey }
|
||||
func (f *fakeProvider) DefaultEnabled() bool { return f.defaultOn }
|
||||
func (f *fakeProvider) Configure(s ProviderSettings) error { return nil }
|
||||
|
||||
// fakeAlbumProvider opts into the AlbumCoverProvider capability for
|
||||
// the capability-filter test.
|
||||
type fakeAlbumProvider struct {
|
||||
fakeProvider
|
||||
}
|
||||
|
||||
func (f *fakeAlbumProvider) FetchAlbumCover(ctx context.Context, mbid string) ([]byte, error) {
|
||||
return []byte("img"), nil
|
||||
}
|
||||
|
||||
func TestRegister_AddsProvider(t *testing.T) {
|
||||
resetRegistryForTests()
|
||||
defer resetRegistryForTests()
|
||||
|
||||
p := &fakeProvider{id: "fake1", display: "Fake One"}
|
||||
Register(p)
|
||||
|
||||
all := AllProviders()
|
||||
if len(all) != 1 {
|
||||
t.Fatalf("AllProviders len = %d, want 1", len(all))
|
||||
}
|
||||
if all[0].ID() != "fake1" {
|
||||
t.Errorf("ID = %q, want %q", all[0].ID(), "fake1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister_PanicsOnDuplicateID(t *testing.T) {
|
||||
resetRegistryForTests()
|
||||
defer resetRegistryForTests()
|
||||
|
||||
Register(&fakeProvider{id: "dup"})
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fatal("expected panic on duplicate ID")
|
||||
}
|
||||
}()
|
||||
Register(&fakeProvider{id: "dup"}) // should panic
|
||||
}
|
||||
|
||||
func TestProviderByID_Found(t *testing.T) {
|
||||
resetRegistryForTests()
|
||||
defer resetRegistryForTests()
|
||||
|
||||
Register(&fakeProvider{id: "x", display: "X"})
|
||||
|
||||
p, err := ProviderByID("x")
|
||||
if err != nil {
|
||||
t.Fatalf("err = %v, want nil", err)
|
||||
}
|
||||
if p.DisplayName() != "X" {
|
||||
t.Errorf("display = %q, want %q", p.DisplayName(), "X")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderByID_NotFound(t *testing.T) {
|
||||
resetRegistryForTests()
|
||||
defer resetRegistryForTests()
|
||||
|
||||
_, err := ProviderByID("missing")
|
||||
if !errors.Is(err, ErrProviderNotFound) {
|
||||
t.Errorf("err = %v, want ErrProviderNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapabilityFiltering(t *testing.T) {
|
||||
resetRegistryForTests()
|
||||
defer resetRegistryForTests()
|
||||
|
||||
plain := &fakeProvider{id: "plain"}
|
||||
withAlbum := &fakeAlbumProvider{fakeProvider: fakeProvider{id: "alb"}}
|
||||
Register(plain)
|
||||
Register(withAlbum)
|
||||
|
||||
if got := len(AllProviders()); got != 2 {
|
||||
t.Fatalf("AllProviders len = %d, want 2", got)
|
||||
}
|
||||
|
||||
// Type-assert to count AlbumCoverProvider implementers — mimics
|
||||
// what SettingsService.EnabledAlbumProviders does internally.
|
||||
var albumProviders []AlbumCoverProvider
|
||||
for _, p := range AllProviders() {
|
||||
if ap, ok := p.(AlbumCoverProvider); ok {
|
||||
albumProviders = append(albumProviders, ap)
|
||||
}
|
||||
}
|
||||
if len(albumProviders) != 1 {
|
||||
t.Fatalf("AlbumCoverProvider count = %d, want 1", len(albumProviders))
|
||||
}
|
||||
if albumProviders[0].ID() != "alb" {
|
||||
t.Errorf("AlbumCoverProvider[0].ID = %q, want %q", albumProviders[0].ID(), "alb")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user