fix: address code review findings
Remove dead code: duplicate ExternalRelease/Artist/ParsedArtist structs in model.go, ParseArtist/mbArtist/mbArtistData in client.go, ArtistTypeFilter and related filtering functions in api.go, SyncArtistDiscographyWithFilter in sync.go, and CacheStats/IsArtistCacheValid in cache.go. Fix bugs: SaveExternalRelease now stores NULL instead of empty string for zero CachedAt; sync upserts are now transactional with stale release cleanup; getCachedReleases returns int instead of *CacheStats; doGet uses url.Values for proper query encoding of MBID. Fix tests: removed duplicate TestRun_GracefulShutdown, removed dead code (_ = dbPath) from TestNewApp, fixed assertions in httptest handler goroutine to avoid data race, increased rate limiter timing tolerance, removed Client.Close() calls (no-op removed), fixed sync test cache expiry to use UPDATE instead of 0 TTL races. Fix formatting: cancel()}() formatting in main.go, error format string in sync.go.
This commit is contained in:
@@ -50,275 +50,11 @@ func TestFilterReleaseGroups_IncludesOnlyAllowedTypes(t *testing.T) {
|
||||
allowedIDs := map[string]bool{"rg-1": true, "rg-2": true, "rg-3": true, "rg-4": true}
|
||||
for _, rg := range result {
|
||||
if !allowedIDs[rg.ID] {
|
||||
t.Errorf("unexpected release group %q (type %q) passed filter", rg.ID, rg.Type)
|
||||
t.Errorf("unexpected group %q in filtered results", rg.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterReleaseGroups_Empty(t *testing.T) {
|
||||
result := FilterReleaseGroups(nil)
|
||||
if len(result) != 0 {
|
||||
t.Errorf("FilterReleaseGroups(nil) returned %d groups, want 0", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterReleaseGroups_AllExcluded(t *testing.T) {
|
||||
groups := []ReleaseGroup{
|
||||
{ID: "rg-1", Title: "Bootleg", Type: "Album", Status: "Bootleg"},
|
||||
{ID: "rg-2", Title: "Promo", Type: "Single", Status: "Promotion"},
|
||||
{ID: "rg-3", Title: "Soundtrack", Type: "Soundtrack", Status: "Official"},
|
||||
}
|
||||
|
||||
result := FilterReleaseGroups(groups)
|
||||
if len(result) != 0 {
|
||||
t.Errorf("FilterReleaseGroups() returned %d groups, want 0 (all excluded)", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterReleaseGroups_NoStatus(t *testing.T) {
|
||||
// Release groups with empty status should pass (not excluded)
|
||||
groups := []ReleaseGroup{
|
||||
{ID: "rg-1", Title: "Unknown Status", Type: "Album", Status: ""},
|
||||
}
|
||||
|
||||
result := FilterReleaseGroups(groups)
|
||||
if len(result) != 1 {
|
||||
t.Errorf("FilterReleaseGroups() returned %d groups, want 1 (empty status is not excluded)", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- IsStatusExcluded tests ----------
|
||||
|
||||
func TestIsStatusExcluded(t *testing.T) {
|
||||
tests := []struct {
|
||||
status string
|
||||
excluded bool
|
||||
}{
|
||||
{"Bootleg", true},
|
||||
{"Promotion", true},
|
||||
{"Pseudo-Release", true},
|
||||
{"Official", false},
|
||||
{"", false},
|
||||
{"official", false}, // case-sensitive: only exact match
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.status, func(t *testing.T) {
|
||||
got := IsStatusExcluded(tt.status)
|
||||
if got != tt.excluded {
|
||||
t.Errorf("IsStatusExcluded(%q) = %v, want %v", tt.status, got, tt.excluded)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- IsTypeIncluded tests ----------
|
||||
|
||||
func TestIsTypeIncluded(t *testing.T) {
|
||||
tests := []struct {
|
||||
rgType string
|
||||
included bool
|
||||
}{
|
||||
{"Album", true},
|
||||
{"Single", true},
|
||||
{"EP", true},
|
||||
{"Compilation", true},
|
||||
{"Soundtrack", false},
|
||||
{"Live", false},
|
||||
{"Remix", false},
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.rgType, func(t *testing.T) {
|
||||
got := IsTypeIncluded(tt.rgType)
|
||||
if got != tt.included {
|
||||
t.Errorf("IsTypeIncluded(%q) = %v, want %v", tt.rgType, got, tt.included)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Artist type filter tests ----------
|
||||
|
||||
func TestFilterReleaseGroupsWithArtistFilter_AllEnabled(t *testing.T) {
|
||||
groups := []ReleaseGroup{
|
||||
{ID: "rg-1", Title: "Album", Type: "Album", Status: "Official"},
|
||||
{ID: "rg-2", Title: "Single", Type: "Single", Status: "Official"},
|
||||
{ID: "rg-3", Title: "EP", Type: "EP", Status: "Official"},
|
||||
{ID: "rg-4", Title: "Compilation", Type: "Compilation", Status: "Official"},
|
||||
}
|
||||
|
||||
filter := DefaultArtistTypeFilter("artist-1")
|
||||
result := FilterReleaseGroupsWithArtistFilter(groups, filter)
|
||||
|
||||
if len(result) != 4 {
|
||||
t.Fatalf("FilterReleaseGroupsWithArtistFilter() returned %d groups, want 4", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterReleaseGroupsWithArtistFilter_ExcludeSingles(t *testing.T) {
|
||||
groups := []ReleaseGroup{
|
||||
{ID: "rg-1", Title: "Album", Type: "Album", Status: "Official"},
|
||||
{ID: "rg-2", Title: "Single", Type: "Single", Status: "Official"},
|
||||
{ID: "rg-3", Title: "EP", Type: "EP", Status: "Official"},
|
||||
}
|
||||
|
||||
filter := &ArtistTypeFilter{
|
||||
ArtistID: "artist-1",
|
||||
IncludeSingles: false,
|
||||
IncludeCompilations: true,
|
||||
IncludeEP: true,
|
||||
}
|
||||
|
||||
result := FilterReleaseGroupsWithArtistFilter(groups, filter)
|
||||
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("FilterReleaseGroupsWithArtistFilter() returned %d groups, want 2", len(result))
|
||||
}
|
||||
|
||||
for _, rg := range result {
|
||||
if rg.Type == "Single" {
|
||||
t.Errorf("Single %q should have been excluded", rg.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterReleaseGroupsWithArtistFilter_ExcludeCompilations(t *testing.T) {
|
||||
groups := []ReleaseGroup{
|
||||
{ID: "rg-1", Title: "Album", Type: "Album", Status: "Official"},
|
||||
{ID: "rg-2", Title: "Compilation", Type: "Compilation", Status: "Official"},
|
||||
}
|
||||
|
||||
filter := &ArtistTypeFilter{
|
||||
ArtistID: "artist-1",
|
||||
IncludeSingles: true,
|
||||
IncludeCompilations: false,
|
||||
IncludeEP: true,
|
||||
}
|
||||
|
||||
result := FilterReleaseGroupsWithArtistFilter(groups, filter)
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("FilterReleaseGroupsWithArtistFilter() returned %d groups, want 1", len(result))
|
||||
}
|
||||
if result[0].ID != "rg-1" {
|
||||
t.Errorf("expected rg-1, got %s", result[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterReleaseGroupsWithArtistFilter_ExcludeEP(t *testing.T) {
|
||||
groups := []ReleaseGroup{
|
||||
{ID: "rg-1", Title: "Album", Type: "Album", Status: "Official"},
|
||||
{ID: "rg-2", Title: "EP", Type: "EP", Status: "Official"},
|
||||
}
|
||||
|
||||
filter := &ArtistTypeFilter{
|
||||
ArtistID: "artist-1",
|
||||
IncludeSingles: true,
|
||||
IncludeCompilations: true,
|
||||
IncludeEP: false,
|
||||
}
|
||||
|
||||
result := FilterReleaseGroupsWithArtistFilter(groups, filter)
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("FilterReleaseGroupsWithArtistFilter() returned %d groups, want 1", len(result))
|
||||
}
|
||||
if result[0].ID != "rg-1" {
|
||||
t.Errorf("expected rg-1, got %s", result[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterReleaseGroupsWithArtistFilter_NilFilter(t *testing.T) {
|
||||
groups := []ReleaseGroup{
|
||||
{ID: "rg-1", Title: "Album", Type: "Album", Status: "Official"},
|
||||
{ID: "rg-2", Title: "Soundtrack", Type: "Soundtrack", Status: "Official"},
|
||||
}
|
||||
|
||||
result := FilterReleaseGroupsWithArtistFilter(groups, nil)
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("FilterReleaseGroupsWithArtistFilter(nil filter) returned %d groups, want 1", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultArtistTypeFilter(t *testing.T) {
|
||||
filter := DefaultArtistTypeFilter("artist-1")
|
||||
|
||||
if filter.ArtistID != "artist-1" {
|
||||
t.Errorf("ArtistID = %q, want %q", filter.ArtistID, "artist-1")
|
||||
}
|
||||
if !filter.IncludeSingles {
|
||||
t.Error("IncludeSingles should be true by default")
|
||||
}
|
||||
if !filter.IncludeCompilations {
|
||||
t.Error("IncludeCompilations should be true by default")
|
||||
}
|
||||
if !filter.IncludeEP {
|
||||
t.Error("IncludeEP should be true by default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsTypeIncludedForArtist(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
rgType string
|
||||
filter *ArtistTypeFilter
|
||||
included bool
|
||||
}{
|
||||
{
|
||||
name: "Album always included",
|
||||
rgType: "Album",
|
||||
filter: DefaultArtistTypeFilter("artist-1"),
|
||||
included: true,
|
||||
},
|
||||
{
|
||||
name: "Single included with filter",
|
||||
rgType: "Single",
|
||||
filter: DefaultArtistTypeFilter("artist-1"),
|
||||
included: true,
|
||||
},
|
||||
{
|
||||
name: "Single excluded",
|
||||
rgType: "Single",
|
||||
filter: &ArtistTypeFilter{
|
||||
IncludeSingles: false,
|
||||
IncludeCompilations: true,
|
||||
IncludeEP: true,
|
||||
},
|
||||
included: false,
|
||||
},
|
||||
{
|
||||
name: "Soundtrack excluded",
|
||||
rgType: "Soundtrack",
|
||||
filter: DefaultArtistTypeFilter("artist-1"),
|
||||
included: false,
|
||||
},
|
||||
{
|
||||
name: "Nil filter falls back to base",
|
||||
rgType: "Album",
|
||||
filter: nil,
|
||||
included: true,
|
||||
},
|
||||
{
|
||||
name: "Nil filter excludes non-base types",
|
||||
rgType: "Soundtrack",
|
||||
filter: nil,
|
||||
included: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := IsTypeIncludedForArtist(tt.rgType, tt.filter)
|
||||
if got != tt.included {
|
||||
t.Errorf("IsTypeIncludedForArtist(%q) = %v, want %v", tt.rgType, got, tt.included)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- NormalizeString tests ----------
|
||||
|
||||
func TestNormalizeString_Basic(t *testing.T) {
|
||||
@@ -474,7 +210,6 @@ func TestGetArtistReleaseGroups_Success(t *testing.T) {
|
||||
baseURL: server.URL,
|
||||
rateLimiter: rl,
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
groups, err := client.GetArtistReleaseGroups(context.Background(), artistMBID)
|
||||
if err != nil {
|
||||
@@ -504,8 +239,7 @@ func TestGetArtistReleaseGroups_Pagination(t *testing.T) {
|
||||
w.Header().Set("Content-Type", "application/xml")
|
||||
|
||||
if offset == "0" || offset == "" {
|
||||
// First page: return "100" results (full page, matching limit) to trigger pagination
|
||||
// We generate multiple release-group elements in the XML
|
||||
// First page: return 100 results (full page, matching limit) to trigger pagination
|
||||
xml := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">
|
||||
<release-group-list count="150">`
|
||||
@@ -556,7 +290,6 @@ func TestGetArtistReleaseGroups_Pagination(t *testing.T) {
|
||||
baseURL: server.URL,
|
||||
rateLimiter: rl,
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
groups, err := client.GetArtistReleaseGroups(context.Background(), artistMBID)
|
||||
if err != nil {
|
||||
@@ -597,7 +330,6 @@ func TestGetArtistReleaseGroups_EmptyResult(t *testing.T) {
|
||||
baseURL: server.URL,
|
||||
rateLimiter: rl,
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
groups, err := client.GetArtistReleaseGroups(context.Background(), artistMBID)
|
||||
if err != nil {
|
||||
@@ -627,7 +359,6 @@ func TestGetArtistReleaseGroups_ServerError(t *testing.T) {
|
||||
baseURL: server.URL,
|
||||
rateLimiter: rl,
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
_, err := client.GetArtistReleaseGroups(context.Background(), "artist-1")
|
||||
if err == nil {
|
||||
@@ -653,7 +384,6 @@ func TestGetArtistReleaseGroups_InvalidXML(t *testing.T) {
|
||||
baseURL: server.URL,
|
||||
rateLimiter: rl,
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
_, err := client.GetArtistReleaseGroups(context.Background(), "artist-1")
|
||||
if err == nil {
|
||||
|
||||
Reference in New Issue
Block a user