219 lines
6.4 KiB
Go
219 lines
6.4 KiB
Go
package musicbrainz
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"naviwatcher/internal/database"
|
|
)
|
|
|
|
// stubResolver is a configurable MBIDResolver for tests.
|
|
type stubResolver struct {
|
|
byName map[string]string // name -> mbid
|
|
calls []string // names requested, in order
|
|
err error // optional error to return for any resolve
|
|
}
|
|
|
|
func (s *stubResolver) ResolveArtistMBID(ctx context.Context, name string) (string, error) {
|
|
s.calls = append(s.calls, name)
|
|
if s.err != nil {
|
|
return "", s.err
|
|
}
|
|
if mbid, ok := s.byName[name]; ok {
|
|
return mbid, nil
|
|
}
|
|
return "", errors.New("no match")
|
|
}
|
|
|
|
// stubDiscography records per-artist discography syncs.
|
|
type stubDiscography struct {
|
|
synced []string // artistIDs
|
|
err error
|
|
}
|
|
|
|
func (s *stubDiscography) SyncArtistDiscography(
|
|
ctx context.Context,
|
|
db *database.DB,
|
|
artistID, artistMBID string,
|
|
ttl time.Duration,
|
|
) ([]database.ExternalRelease, error) {
|
|
if s.err != nil {
|
|
return nil, s.err
|
|
}
|
|
s.synced = append(s.synced, artistID)
|
|
return nil, nil
|
|
}
|
|
|
|
// stubAlbums records album-sync invocations.
|
|
type stubAlbums struct {
|
|
called int
|
|
err error
|
|
}
|
|
|
|
func (s *stubAlbums) SyncAlbums(ctx context.Context, db *database.DB) error {
|
|
s.called++
|
|
return s.err
|
|
}
|
|
|
|
func seedArtistRow(t *testing.T, db *database.DB, id, name, mbid string, monitored bool) {
|
|
t.Helper()
|
|
if err := database.SaveArtistSettings(db, &database.ArtistSettings{
|
|
ID: id,
|
|
Name: name,
|
|
MBID: mbid,
|
|
Monitored: monitored,
|
|
}); err != nil {
|
|
t.Fatalf("seed artist: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSyncAll_NewArtistGetsMBID(t *testing.T) {
|
|
db := newTestDB(t)
|
|
seedArtistRow(t, db, "ar1", "Radiohead", "", true)
|
|
|
|
resolver := &stubResolver{byName: map[string]string{"Radiohead": "mbid-radiohead"}}
|
|
disco := &stubDiscography{}
|
|
albs := &stubAlbums{}
|
|
|
|
err := SyncAll(context.Background(), db, resolver, disco, albs, 24*time.Hour)
|
|
if err != nil {
|
|
t.Fatalf("SyncAll() error = %v", err)
|
|
}
|
|
|
|
// Resolver must have been called for the new artist.
|
|
if len(resolver.calls) != 1 || resolver.calls[0] != "Radiohead" {
|
|
t.Fatalf("resolver calls = %v, want [Radiohead]", resolver.calls)
|
|
}
|
|
// MBID persisted on the row.
|
|
got, gerr := database.GetArtistSettings(db, "ar1")
|
|
if gerr != nil {
|
|
t.Fatalf("GetArtistSettings() error = %v", gerr)
|
|
}
|
|
if got.MBID != "mbid-radiohead" {
|
|
t.Errorf("persisted MBID = %q, want %q", got.MBID, "mbid-radiohead")
|
|
}
|
|
// Discography and albums synced.
|
|
if len(disco.synced) != 1 || disco.synced[0] != "ar1" {
|
|
t.Errorf("discography synced = %v, want [ar1]", disco.synced)
|
|
}
|
|
if albc := albs.called; albc != 1 {
|
|
t.Errorf("albums sync called = %d, want 1", albc)
|
|
}
|
|
}
|
|
|
|
func TestSyncAll_ExistingMBIDReused(t *testing.T) {
|
|
db := newTestDB(t)
|
|
seedArtistRow(t, db, "ar1", "Radiohead", "preset-mbid", true)
|
|
|
|
resolver := &stubResolver{byName: map[string]string{"Radiohead": "resolved-mbid"}}
|
|
disco := &stubDiscography{}
|
|
albs := &stubAlbums{}
|
|
|
|
if err := SyncAll(context.Background(), db, resolver, disco, albs, 24*time.Hour); err != nil {
|
|
t.Fatalf("SyncAll() error = %v", err)
|
|
}
|
|
|
|
// Resolver must NOT be called when MBID already present.
|
|
if len(resolver.calls) != 0 {
|
|
t.Errorf("resolver calls = %v, want none (MBID reused)", resolver.calls)
|
|
}
|
|
got, _ := database.GetArtistSettings(db, "ar1")
|
|
if got.MBID != "preset-mbid" {
|
|
t.Errorf("MBID = %q, want preserved preset-mbid", got.MBID)
|
|
}
|
|
if len(disco.synced) != 1 {
|
|
t.Errorf("discography synced = %v, want [ar1]", disco.synced)
|
|
}
|
|
}
|
|
|
|
func TestSyncAll_UnmonitoredSkipped(t *testing.T) {
|
|
db := newTestDB(t)
|
|
seedArtistRow(t, db, "ar1", "Radiohead", "", false) // unmonitored
|
|
|
|
resolver := &stubResolver{byName: map[string]string{"Radiohead": "mbid-x"}}
|
|
disco := &stubDiscography{}
|
|
albs := &stubAlbums{}
|
|
|
|
if err := SyncAll(context.Background(), db, resolver, disco, albs, 24*time.Hour); err != nil {
|
|
t.Fatalf("SyncAll() error = %v", err)
|
|
}
|
|
|
|
if len(resolver.calls) != 0 {
|
|
t.Errorf("resolver calls = %v, want none (unmonitored skipped)", resolver.calls)
|
|
}
|
|
if len(disco.synced) != 0 {
|
|
t.Errorf("discography synced = %v, want none", disco.synced)
|
|
}
|
|
// Album sync still runs (it internally skips unmonitored too), but no
|
|
// discography work should have happened for the skipped artist.
|
|
}
|
|
|
|
func TestSyncAll_ResolutionErrorSkipsArtist(t *testing.T) {
|
|
db := newTestDB(t)
|
|
seedArtistRow(t, db, "ar1", "Unknown", "", true)
|
|
|
|
resolver := &stubResolver{err: errors.New("mb down")}
|
|
disco := &stubDiscography{}
|
|
albs := &stubAlbums{}
|
|
|
|
err := SyncAll(context.Background(), db, resolver, disco, albs, 24*time.Hour)
|
|
if err == nil {
|
|
t.Fatal("SyncAll() expected error when resolution fails")
|
|
}
|
|
if len(disco.synced) != 0 {
|
|
t.Errorf("discography synced = %v, want none (resolution failed)", disco.synced)
|
|
}
|
|
// MBID must remain empty since persistence was skipped.
|
|
got, _ := database.GetArtistSettings(db, "ar1")
|
|
if got.MBID != "" {
|
|
t.Errorf("MBID = %q, want empty after failed resolution", got.MBID)
|
|
}
|
|
}
|
|
|
|
func TestSyncAll_ContextCancel(t *testing.T) {
|
|
db := newTestDB(t)
|
|
seedArtistRow(t, db, "ar1", "Radiohead", "", true)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
if err := SyncAll(ctx, db, &stubResolver{}, &stubDiscography{}, &stubAlbums{}, 24*time.Hour); err == nil {
|
|
t.Fatal("SyncAll() expected context error, got nil")
|
|
}
|
|
}
|
|
|
|
// TestDiscographySyncer_AdapterForwards verifies the adapter produced by
|
|
// NewDiscographySyncer forwards to the real SyncArtistDiscography so that the
|
|
// App's wiring uses the actual MusicBrainz client.
|
|
func TestDiscographySyncer_AdapterForwards(t *testing.T) {
|
|
db := newTestDB(t)
|
|
artistID := "nav-adapter"
|
|
artistMBID := "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"
|
|
seedArtistRow(t, db, artistID, "Adapter Artist", "", true)
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
resp := mbReleaseGroupListResponse(
|
|
mbReleaseGroupXML("rg1", "Adapter Album", "Album", "", artistMBID, "Adapter Artist", "2020-01-01"),
|
|
1,
|
|
)
|
|
w.Write([]byte(resp))
|
|
})
|
|
defer server.Close()
|
|
|
|
syncer := NewDiscographySyncer(newTestClient(server.URL))
|
|
releases, err := syncer.SyncArtistDiscography(context.Background(), db, artistID, artistMBID, 24*time.Hour)
|
|
if err != nil {
|
|
t.Fatalf("adapter SyncArtistDiscography() error = %v", err)
|
|
}
|
|
if len(releases) != 1 {
|
|
t.Fatalf("adapter expected 1 release, got %d", len(releases))
|
|
}
|
|
if releases[0].RGID != "rg1" {
|
|
t.Errorf("adapter release RGID = %q, want rg1", releases[0].RGID)
|
|
}
|
|
}
|