fix(lidarr): address review findings on HTTP client
- Add ArtistMBID/AlbumMBID to LookupResult so handlers can build the full lidarr_requests row from album/track lookups (parent MBID is needed for the AddAlbum API call and for the schema's NOT NULL lidarr_artist_mbid) - Trim trailing slash on BaseURL before joining paths to avoid double-slash URLs when operators enter "http://lidarr.lan/" - Check json.Marshal errors in AddArtist/AddAlbum - Extract lidarrImage as a named unexported type instead of repeating the anonymous struct three times - Add NewClient(baseURL, apiKey) constructor with 30s default timeout - Add ErrLookupFailed test coverage for the 4xx-non-auth branch on both get and post helpers - Rename TestListRootFolders_BadJSON -> _NullBody (it tests null, not malformed); add a real malformed-JSON test - Defensive separator guard on LookupAlbum.Secondary so an empty ArtistName doesn't produce a leading " · " Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -154,6 +154,12 @@ func TestLookupAlbum_HappyPath(t *testing.T) {
|
||||
if got[0].MBID != "a1b2c3d4-e5f6-7890-abcd-ef1234567890" {
|
||||
t.Errorf("MBID = %q", got[0].MBID)
|
||||
}
|
||||
if got[0].ArtistMBID != "069b64b6-7884-4f6a-94cc-e4c1d6c87a01" {
|
||||
t.Errorf("ArtistMBID = %q, want 069b64b6-7884-4f6a-94cc-e4c1d6c87a01", got[0].ArtistMBID)
|
||||
}
|
||||
if got[0].AlbumMBID != "" {
|
||||
t.Errorf("AlbumMBID = %q, want empty on album result", got[0].AlbumMBID)
|
||||
}
|
||||
if got[0].Name != "Music Has the Right to Children" {
|
||||
t.Errorf("Name = %q", got[0].Name)
|
||||
}
|
||||
@@ -213,6 +219,12 @@ func TestLookupTrack_HappyPath(t *testing.T) {
|
||||
if got[0].Secondary != want {
|
||||
t.Errorf("Secondary = %q, want %q", got[0].Secondary, want)
|
||||
}
|
||||
if got[0].ArtistMBID != "069b64b6-7884-4f6a-94cc-e4c1d6c87a01" {
|
||||
t.Errorf("ArtistMBID = %q, want 069b64b6-7884-4f6a-94cc-e4c1d6c87a01", got[0].ArtistMBID)
|
||||
}
|
||||
if got[0].AlbumMBID != "a1b2c3d4-e5f6-7890-abcd-ef1234567890" {
|
||||
t.Errorf("AlbumMBID = %q, want a1b2c3d4-e5f6-7890-abcd-ef1234567890", got[0].AlbumMBID)
|
||||
}
|
||||
if got[0].ImageURL != "" {
|
||||
t.Errorf("tracks carry no images; ImageURL = %q", got[0].ImageURL)
|
||||
}
|
||||
@@ -406,6 +418,51 @@ func TestListQualityProfiles_BadJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- LookupArtist (additional) ---
|
||||
|
||||
func TestLookupArtist_LookupFailed(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer srv.Close()
|
||||
c := &Client{BaseURL: srv.URL, APIKey: "x", HTTP: srv.Client()}
|
||||
_, err := c.LookupArtist(context.Background(), "boards")
|
||||
if !errors.Is(err, ErrLookupFailed) {
|
||||
t.Fatalf("err = %v, want ErrLookupFailed", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet_BaseURLWithTrailingSlash(t *testing.T) {
|
||||
body, _ := os.ReadFile("testdata/lookup_artist.json")
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify path is /api/v1/artist/lookup, NOT //api/v1/artist/lookup
|
||||
if r.URL.Path != "/api/v1/artist/lookup" {
|
||||
t.Errorf("got path %q; trailing-slash BaseURL produced double-slash", r.URL.Path)
|
||||
}
|
||||
_, _ = w.Write(body)
|
||||
}))
|
||||
defer srv.Close()
|
||||
c := &Client{BaseURL: srv.URL + "/", APIKey: "k", HTTP: srv.Client()}
|
||||
_, err := c.LookupArtist(context.Background(), "boards")
|
||||
if err != nil {
|
||||
t.Fatalf("LookupArtist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- AddArtist (additional) ---
|
||||
|
||||
func TestAddArtist_PostFailed(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
}))
|
||||
defer srv.Close()
|
||||
c := &Client{BaseURL: srv.URL, APIKey: "x", HTTP: srv.Client()}
|
||||
err := c.AddArtist(context.Background(), AddArtistParams{ForeignArtistID: "x", QualityProfileID: 1, RootFolderPath: "/m"})
|
||||
if !errors.Is(err, ErrLookupFailed) {
|
||||
t.Fatalf("err = %v, want ErrLookupFailed", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- ListRootFolders ---
|
||||
|
||||
func TestListRootFolders_HappyPath(t *testing.T) {
|
||||
@@ -439,7 +496,7 @@ func TestListRootFolders_HappyPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRootFolders_BadJSON(t *testing.T) {
|
||||
func TestListRootFolders_NullBody(t *testing.T) {
|
||||
c, srv := newTestClient(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte(`null`))
|
||||
})
|
||||
@@ -454,6 +511,18 @@ func TestListRootFolders_BadJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRootFolders_BadJSON(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("{not valid json"))
|
||||
}))
|
||||
defer srv.Close()
|
||||
c := &Client{BaseURL: srv.URL, APIKey: "x", HTTP: srv.Client()}
|
||||
_, err := c.ListRootFolders(context.Background())
|
||||
if !errors.Is(err, ErrInvalidPayload) {
|
||||
t.Fatalf("err = %v, want ErrInvalidPayload", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Ping ---
|
||||
|
||||
func TestPing_ReturnsVersion(t *testing.T) {
|
||||
@@ -507,11 +576,7 @@ func TestPing_BadJSON(t *testing.T) {
|
||||
// --- pickPosterImage ---
|
||||
|
||||
func TestPickPosterImage_PreferRemoteURL(t *testing.T) {
|
||||
imgs := []struct {
|
||||
CoverType string `json:"coverType"`
|
||||
RemoteURL string `json:"remoteUrl"`
|
||||
URL string `json:"url"`
|
||||
}{
|
||||
imgs := []lidarrImage{
|
||||
{CoverType: "poster", RemoteURL: "https://remote.example/img.jpg", URL: "https://local.example/img.jpg"},
|
||||
}
|
||||
got := pickPosterImage(imgs)
|
||||
@@ -521,11 +586,7 @@ func TestPickPosterImage_PreferRemoteURL(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPickPosterImage_FallsBackToLocalURL(t *testing.T) {
|
||||
imgs := []struct {
|
||||
CoverType string `json:"coverType"`
|
||||
RemoteURL string `json:"remoteUrl"`
|
||||
URL string `json:"url"`
|
||||
}{
|
||||
imgs := []lidarrImage{
|
||||
{CoverType: "poster", RemoteURL: "", URL: "https://local.example/img.jpg"},
|
||||
}
|
||||
got := pickPosterImage(imgs)
|
||||
@@ -535,11 +596,7 @@ func TestPickPosterImage_FallsBackToLocalURL(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPickPosterImage_SkipsNonPoster(t *testing.T) {
|
||||
imgs := []struct {
|
||||
CoverType string `json:"coverType"`
|
||||
RemoteURL string `json:"remoteUrl"`
|
||||
URL string `json:"url"`
|
||||
}{
|
||||
imgs := []lidarrImage{
|
||||
{CoverType: "banner", RemoteURL: "https://banner.example/img.jpg"},
|
||||
{CoverType: "cover", RemoteURL: "https://cover.example/img.jpg"},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user