|
|
|
|
@@ -4,6 +4,7 @@ import (
|
|
|
|
|
"context"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"strconv"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
@@ -31,27 +32,12 @@ func mbReleaseGroupXML(id, title, rgType, status, artistID, artistName, releaseD
|
|
|
|
|
func mbReleaseGroupListResponse(groups string, count int) string {
|
|
|
|
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">
|
|
|
|
|
<release-group-list count="` + itoa(count) + `">` +
|
|
|
|
|
<release-group-list count="` + strconv.Itoa(count) + `">` +
|
|
|
|
|
groups +
|
|
|
|
|
`</release-group-list>
|
|
|
|
|
</metadata>`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// itoa converts an int to a string without importing strconv.
|
|
|
|
|
func itoa(n int) string {
|
|
|
|
|
if n == 0 {
|
|
|
|
|
return "0"
|
|
|
|
|
}
|
|
|
|
|
var buf [20]byte
|
|
|
|
|
i := len(buf)
|
|
|
|
|
for n > 0 {
|
|
|
|
|
i--
|
|
|
|
|
buf[i] = byte('0' + n%10)
|
|
|
|
|
n /= 10
|
|
|
|
|
}
|
|
|
|
|
return string(buf[i:])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// newTestMBServer creates a mock MusicBrainz HTTP server.
|
|
|
|
|
func newTestMBServer(handler http.HandlerFunc) *httptest.Server {
|
|
|
|
|
return httptest.NewServer(handler)
|
|
|
|
|
@@ -99,6 +85,7 @@ func seedArtist(t *testing.T, db *database.DB, id, name string) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_CacheMiss_FetchesAndUpserts(t *testing.T) {
|
|
|
|
|
artistMBID := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
|
|
|
|
artistID := "nav-aaaaaaaa"
|
|
|
|
|
artistName := "Test Artist"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
@@ -115,13 +102,13 @@ func TestSyncArtistDiscography_CacheMiss_FetchesAndUpserts(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, artistName)
|
|
|
|
|
seedArtist(t, db, artistID, artistName)
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -130,18 +117,18 @@ func TestSyncArtistDiscography_CacheMiss_FetchesAndUpserts(t *testing.T) {
|
|
|
|
|
t.Fatalf("expected 3 releases, got %d", len(releases))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify each release has CachedAt set.
|
|
|
|
|
// Verify each release has CachedAt set and is keyed by the Navidrome artist ID.
|
|
|
|
|
for _, r := range releases {
|
|
|
|
|
if r.CachedAt.IsZero() {
|
|
|
|
|
t.Errorf("release %s: CachedAt should be set, got zero", r.RGID)
|
|
|
|
|
}
|
|
|
|
|
if r.ArtistID != artistMBID {
|
|
|
|
|
t.Errorf("release %s: expected ArtistID %q, got %q", r.RGID, artistMBID, r.ArtistID)
|
|
|
|
|
if r.ArtistID != artistID {
|
|
|
|
|
t.Errorf("release %s: expected ArtistID %q, got %q", r.RGID, artistID, r.ArtistID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify data was persisted in the database.
|
|
|
|
|
stored, err := database.GetExternalReleasesByArtist(db, artistMBID)
|
|
|
|
|
// Verify data was persisted in the database under the Navidrome artist ID.
|
|
|
|
|
stored, err := database.GetExternalReleasesByArtist(db, artistID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetExternalReleasesByArtist() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -156,16 +143,17 @@ func TestSyncArtistDiscography_CacheMiss_FetchesAndUpserts(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_CacheHit_ReturnsCached(t *testing.T) {
|
|
|
|
|
artistMBID := "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"
|
|
|
|
|
artistID := "nav-bbbbbbbb"
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Cached Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Cache Artist")
|
|
|
|
|
|
|
|
|
|
// Pre-populate the cache with one release.
|
|
|
|
|
now := time.Now()
|
|
|
|
|
if err := database.SaveExternalRelease(db, &database.ExternalRelease{
|
|
|
|
|
RGID: "rg-cached",
|
|
|
|
|
ArtistID: artistMBID,
|
|
|
|
|
ArtistID: artistID,
|
|
|
|
|
Title: "Cached Album",
|
|
|
|
|
Type: "Album",
|
|
|
|
|
ReleaseDate: "2019-05-01",
|
|
|
|
|
@@ -187,7 +175,7 @@ func TestSyncArtistDiscography_CacheHit_ReturnsCached(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -214,6 +202,7 @@ func TestSyncArtistDiscography_CacheHit_ReturnsCached(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_FiltersExcludedStatuses(t *testing.T) {
|
|
|
|
|
artistMBID := "cccccccc-dddd-eeee-ffff-000000000000"
|
|
|
|
|
artistID := "nav-cccccccc"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
|
@@ -231,13 +220,13 @@ func TestSyncArtistDiscography_FiltersExcludedStatuses(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Filter Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Filter Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -257,6 +246,7 @@ func TestSyncArtistDiscography_FiltersExcludedStatuses(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_FiltersExcludedTypes(t *testing.T) {
|
|
|
|
|
artistMBID := "dddddddd-eeee-ffff-0000-111111111111"
|
|
|
|
|
artistID := "nav-dddddddd"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
|
@@ -274,13 +264,13 @@ func TestSyncArtistDiscography_FiltersExcludedTypes(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Type Filter Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Type Filter Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -305,6 +295,7 @@ func TestSyncArtistDiscography_FiltersExcludedTypes(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_ContextCancellation(t *testing.T) {
|
|
|
|
|
artistMBID := "eeeeeeee-ffff-0000-1111-222222222222"
|
|
|
|
|
artistID := "nav-eeeeeeee"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
|
@@ -318,7 +309,7 @@ func TestSyncArtistDiscography_ContextCancellation(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Cancel Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Cancel Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
@@ -327,7 +318,7 @@ func TestSyncArtistDiscography_ContextCancellation(t *testing.T) {
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
|
|
_, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
_, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("SyncArtistDiscography() expected error for cancelled context, got nil")
|
|
|
|
|
}
|
|
|
|
|
@@ -339,6 +330,7 @@ func TestSyncArtistDiscography_ContextCancellation(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_IdempotentResync(t *testing.T) {
|
|
|
|
|
artistMBID := "ffffffff-0000-1111-2222-333333333333"
|
|
|
|
|
artistID := "nav-ffffffff"
|
|
|
|
|
|
|
|
|
|
callCount := 0
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
@@ -355,13 +347,13 @@ func TestSyncArtistDiscography_IdempotentResync(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Idempotent Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Idempotent Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// First sync.
|
|
|
|
|
releases1, err := SyncArtistDiscography(ctx, client, db, artistMBID, 24*time.Hour)
|
|
|
|
|
releases1, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, 24*time.Hour)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("first SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -374,13 +366,13 @@ func TestSyncArtistDiscography_IdempotentResync(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
// Force cache expiry by setting cached_at to the past.
|
|
|
|
|
_, err = db.Conn().Exec("UPDATE external_releases SET cached_at = ? WHERE artist_id = ?",
|
|
|
|
|
time.Now().Add(-48*time.Hour).Format("2006-01-02 15:04:05"), artistMBID)
|
|
|
|
|
time.Now().Add(-48*time.Hour).Format("2006-01-02 15:04:05"), artistID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("expire cache: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Second sync should re-fetch from API (cache expired).
|
|
|
|
|
releases2, err := SyncArtistDiscography(ctx, client, db, artistMBID, 24*time.Hour)
|
|
|
|
|
releases2, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, 24*time.Hour)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("second SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -392,7 +384,7 @@ func TestSyncArtistDiscography_IdempotentResync(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify no duplicates in the database (transactional delete + insert).
|
|
|
|
|
stored, err := database.GetExternalReleasesByArtist(db, artistMBID)
|
|
|
|
|
stored, err := database.GetExternalReleasesByArtist(db, artistID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetExternalReleasesByArtist() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -407,6 +399,7 @@ func TestSyncArtistDiscography_IdempotentResync(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_EmptyResponse(t *testing.T) {
|
|
|
|
|
artistMBID := "33333333-4444-5555-6666-777777777777"
|
|
|
|
|
artistID := "nav-33333333"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
|
@@ -416,13 +409,13 @@ func TestSyncArtistDiscography_EmptyResponse(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Empty Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Empty Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -432,7 +425,7 @@ func TestSyncArtistDiscography_EmptyResponse(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify nothing in DB.
|
|
|
|
|
stored, err := database.GetExternalReleasesByArtist(db, artistMBID)
|
|
|
|
|
stored, err := database.GetExternalReleasesByArtist(db, artistID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetExternalReleasesByArtist() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -447,6 +440,7 @@ func TestSyncArtistDiscography_EmptyResponse(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_APIError(t *testing.T) {
|
|
|
|
|
artistMBID := "44444444-5555-6666-7777-888888888888"
|
|
|
|
|
artistID := "nav-44444444"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
|
@@ -456,13 +450,13 @@ func TestSyncArtistDiscography_APIError(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Error Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Error Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
_, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
_, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("SyncArtistDiscography() expected error for API failure, got nil")
|
|
|
|
|
}
|
|
|
|
|
@@ -474,6 +468,7 @@ func TestSyncArtistDiscography_APIError(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_FullXMLPipeline(t *testing.T) {
|
|
|
|
|
artistMBID := "66666666-7777-8888-9999-000000000000"
|
|
|
|
|
artistID := "nav-66666666"
|
|
|
|
|
|
|
|
|
|
xmlBody := `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<metadata>
|
|
|
|
|
@@ -511,13 +506,13 @@ func TestSyncArtistDiscography_FullXMLPipeline(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Real Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Real Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -560,6 +555,7 @@ func TestSyncArtistDiscography_FullXMLPipeline(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_ConsistentCachedAtTimestamp(t *testing.T) {
|
|
|
|
|
artistMBID := "77777777-8888-9999-0000-111111111111"
|
|
|
|
|
artistID := "nav-77777777"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
|
@@ -575,13 +571,13 @@ func TestSyncArtistDiscography_ConsistentCachedAtTimestamp(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Timestamp Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Timestamp Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -605,6 +601,7 @@ func TestSyncArtistDiscography_ConsistentCachedAtTimestamp(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_XMLNoTypeAttribute(t *testing.T) {
|
|
|
|
|
artistMBID := "88888888-9999-0000-1111-222222222222"
|
|
|
|
|
artistID := "nav-88888888"
|
|
|
|
|
|
|
|
|
|
xmlBody := `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<metadata>
|
|
|
|
|
@@ -642,13 +639,13 @@ func TestSyncArtistDiscography_XMLNoTypeAttribute(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "No Type Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "No Type Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
ttl := 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, ttl)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, ttl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -668,6 +665,7 @@ func TestSyncArtistDiscography_XMLNoTypeAttribute(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
func TestSyncArtistDiscography_CleansStaleReleases(t *testing.T) {
|
|
|
|
|
artistMBID := "99999999-0000-1111-2222-333333333333"
|
|
|
|
|
artistID := "nav-99999999"
|
|
|
|
|
|
|
|
|
|
callCount := 0
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
@@ -696,13 +694,13 @@ func TestSyncArtistDiscography_CleansStaleReleases(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, "Stale Artist")
|
|
|
|
|
seedArtist(t, db, artistID, "Stale Artist")
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// First sync: 3 releases.
|
|
|
|
|
releases1, err := SyncArtistDiscography(ctx, client, db, artistMBID, 24*time.Hour)
|
|
|
|
|
releases1, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, 24*time.Hour)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("first SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -712,13 +710,13 @@ func TestSyncArtistDiscography_CleansStaleReleases(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
// Force cache expiry by setting cached_at to the past.
|
|
|
|
|
_, err = db.Conn().Exec("UPDATE external_releases SET cached_at = ? WHERE artist_id = ?",
|
|
|
|
|
time.Now().Add(-48*time.Hour).Format("2006-01-02 15:04:05"), artistMBID)
|
|
|
|
|
time.Now().Add(-48*time.Hour).Format("2006-01-02 15:04:05"), artistID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("expire cache: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Second sync should re-fetch from API (cache expired).
|
|
|
|
|
releases2, err := SyncArtistDiscography(ctx, client, db, artistMBID, 24*time.Hour)
|
|
|
|
|
releases2, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, 24*time.Hour)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("second SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -727,17 +725,12 @@ func TestSyncArtistDiscography_CleansStaleReleases(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify stale release was cleaned from DB.
|
|
|
|
|
stored, err := database.GetExternalReleasesByArtist(db, artistMBID)
|
|
|
|
|
stored, err := database.GetExternalReleasesByArtist(db, artistID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetExternalReleasesByArtist() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(stored) != 2 {
|
|
|
|
|
t.Errorf("expected 2 stored releases (stale cleaned), got %d", len(stored))
|
|
|
|
|
}
|
|
|
|
|
for _, r := range stored {
|
|
|
|
|
if r.RGID == "rg-old-3" {
|
|
|
|
|
t.Error("stale release rg-old-3 should have been removed")
|
|
|
|
|
}
|
|
|
|
|
t.Errorf("expected 2 stored releases after cleanup, got %d", len(stored))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -746,6 +739,7 @@ func TestSyncArtistDiscography_CleansStaleReleases(t *testing.T) {
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
func TestSyncArtistDiscography_IgnoreSingles(t *testing.T) {
|
|
|
|
|
artistMBID := "artist-singles-test"
|
|
|
|
|
artistID := "nav-singles-test"
|
|
|
|
|
artistName := "Singles Artist"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
@@ -760,10 +754,10 @@ func TestSyncArtistDiscography_IgnoreSingles(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
// Seed artist with ignore_singles = true.
|
|
|
|
|
// Seed artist (keyed by Navidrome ID) with ignore_singles = true.
|
|
|
|
|
if _, err := db.Conn().Exec(
|
|
|
|
|
"INSERT INTO artist_settings (id, name, ignore_singles, monitored) VALUES (?, ?, 1, 1)",
|
|
|
|
|
artistMBID, artistName,
|
|
|
|
|
artistID, artistName,
|
|
|
|
|
); err != nil {
|
|
|
|
|
t.Fatalf("seed artist: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -771,7 +765,7 @@ func TestSyncArtistDiscography_IgnoreSingles(t *testing.T) {
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, 0)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, 0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -788,6 +782,7 @@ func TestSyncArtistDiscography_IgnoreSingles(t *testing.T) {
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
func TestSyncArtistDiscography_IgnoreCompilations(t *testing.T) {
|
|
|
|
|
artistMBID := "artist-comp-test"
|
|
|
|
|
artistID := "nav-comp-test"
|
|
|
|
|
artistName := "Comp Artist"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
@@ -802,10 +797,10 @@ func TestSyncArtistDiscography_IgnoreCompilations(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
// Seed artist with ignore_compilations = true.
|
|
|
|
|
// Seed artist (keyed by Navidrome ID) with ignore_compilations = true.
|
|
|
|
|
if _, err := db.Conn().Exec(
|
|
|
|
|
"INSERT INTO artist_settings (id, name, ignore_compilations, monitored) VALUES (?, ?, 1, 1)",
|
|
|
|
|
artistMBID, artistName,
|
|
|
|
|
artistID, artistName,
|
|
|
|
|
); err != nil {
|
|
|
|
|
t.Fatalf("seed artist: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -813,7 +808,7 @@ func TestSyncArtistDiscography_IgnoreCompilations(t *testing.T) {
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, 0)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, 0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -830,6 +825,7 @@ func TestSyncArtistDiscography_IgnoreCompilations(t *testing.T) {
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
func TestSyncArtistDiscography_ResyncWithNotifications(t *testing.T) {
|
|
|
|
|
artistMBID := "artist-fk-test"
|
|
|
|
|
artistID := "nav-fk-test"
|
|
|
|
|
artistName := "FK Artist"
|
|
|
|
|
|
|
|
|
|
server := newTestMBServer(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
@@ -843,13 +839,13 @@ func TestSyncArtistDiscography_ResyncWithNotifications(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
db := newTestDB(t)
|
|
|
|
|
defer db.Close()
|
|
|
|
|
seedArtist(t, db, artistMBID, artistName)
|
|
|
|
|
seedArtist(t, db, artistID, artistName)
|
|
|
|
|
|
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// First sync.
|
|
|
|
|
_, err := SyncArtistDiscography(ctx, client, db, artistMBID, 24*time.Hour)
|
|
|
|
|
_, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, 24*time.Hour)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("first SyncArtistDiscography() error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -864,13 +860,13 @@ func TestSyncArtistDiscography_ResyncWithNotifications(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
// Force cache expiry.
|
|
|
|
|
_, err = db.Conn().Exec("UPDATE external_releases SET cached_at = ? WHERE artist_id = ?",
|
|
|
|
|
time.Now().Add(-48*time.Hour).Format("2006-01-02 15:04:05"), artistMBID)
|
|
|
|
|
time.Now().Add(-48*time.Hour).Format("2006-01-02 15:04:05"), artistID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("expire cache: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Second sync should succeed without FK violation.
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistMBID, 24*time.Hour)
|
|
|
|
|
releases, err := SyncArtistDiscography(ctx, client, db, artistID, artistMBID, 24*time.Hour)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("second SyncArtistDiscography() error (FK violation?): %v", err)
|
|
|
|
|
}
|
|
|
|
|
|