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:
2026-05-26 14:10:22 +03:00
parent 34ea84fc77
commit a5911c257c
13 changed files with 199 additions and 864 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"time"
"golang.org/x/time/rate"
@@ -35,23 +36,6 @@ func NewClient(cfg config.MusicBrainzConfig) *MusicBrainzClient {
}
}
// NewClientWithLimiter creates a MusicBrainzClient with a custom rate limiter.
// This is primarily used for testing to inject a mock rate limiter.
func NewClientWithLimiter(cfg config.MusicBrainzConfig, rl *rate.Limiter) *MusicBrainzClient {
return &MusicBrainzClient{
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
userAgent: cfg.UserAgent,
baseURL: "https://musicbrainz.org/ws/2",
rateLimiter: rl,
}
}
// Close is a no-op for the x/time/rate-based client (the limiter does not
// spawn goroutines), but retained for API compatibility.
func (c *MusicBrainzClient) Close() {}
// doGet performs a rate-limited HTTP GET request to the MusicBrainz API.
// It blocks until the rate limiter allows the request, then sets the proper
// User-Agent header and returns the response body.
@@ -126,18 +110,6 @@ type mbReleaseGroupList struct {
ReleaseGroupList mbReleaseGroupListXML `xml:"release-group-list"`
}
// mbArtistData represents the artist element inside metadata.
type mbArtistData struct {
ID string `xml:"id,attr"`
Name string `xml:"name"`
}
// mbArtist represents the XML structure of a MusicBrainz artist response.
type mbArtist struct {
XMLName xml.Name `xml:"metadata"`
Artist mbArtistData `xml:"artist"`
}
// ParseReleaseGroups parses a MusicBrainz release-group list XML response
// into a ParsedReleaseGroups struct.
func ParseReleaseGroups(data []byte) (*ParsedReleaseGroups, error) {
@@ -163,14 +135,7 @@ func ParseReleaseGroups(data []byte) (*ParsedReleaseGroups, error) {
return result, nil
}
// ParseArtist parses a MusicBrainz artist XML response into a ParsedArtist struct.
func ParseArtist(data []byte) (*ParsedArtist, error) {
var artist mbArtist
if err := xml.Unmarshal(data, &artist); err != nil {
return nil, fmt.Errorf("parse artist XML: %w", err)
}
return &ParsedArtist{
ID: artist.Artist.ID,
Name: artist.Artist.Name,
}, nil
// buildPath constructs a properly URL-encoded query path for the MusicBrainz API.
func buildPath(endpoint string, params url.Values) string {
return endpoint + "?" + params.Encode()
}