diff --git a/pkg/lidarr/client.go b/pkg/lidarr/client.go index fa42742..9eb40c7 100644 --- a/pkg/lidarr/client.go +++ b/pkg/lidarr/client.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/json" "errors" + "fmt" + "io" "net/http" "net/url" "path" @@ -11,7 +13,7 @@ import ( "github.com/rs/zerolog/log" ) -func (c *client) get(endpoint string) (*http.Response, error) { +func (c *client) get(endpoint string) (int, []byte, error) { u, err := url.Parse(c.config.Hostname) u.Path = path.Join(u.Path, "/api/v1/", endpoint) reqUrl := u.String() @@ -19,27 +21,29 @@ func (c *client) get(endpoint string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) if err != nil { log.Error().Err(err).Msgf("lidarr client request error : %v", reqUrl) - return nil, err + return 0, nil, err } if c.config.BasicAuth { req.SetBasicAuth(c.config.Username, c.config.Password) } - req.Header.Add("X-Api-Key", c.config.APIKey) - req.Header.Set("User-Agent", "autobrr") + c.setHeaders(req) - res, err := c.http.Do(req) + resp, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("lidarr client request error : %v", reqUrl) - return nil, err + log.Error().Err(err).Msgf("lidarr client.get request error: %v", reqUrl) + return 0, nil, fmt.Errorf("lidarr.http.Do(req): %w", err) } - if res.StatusCode == http.StatusUnauthorized { - return nil, errors.New("unauthorized: bad credentials") + defer resp.Body.Close() + + var buf bytes.Buffer + if _, err = io.Copy(&buf, resp.Body); err != nil { + return resp.StatusCode, nil, fmt.Errorf("lidarr.io.Copy: %w", err) } - return res, nil + return resp.StatusCode, buf.Bytes(), nil } func (c *client) post(endpoint string, data interface{}) (*http.Response, error) { @@ -85,3 +89,56 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) // return raw response and let the caller handle json unmarshal of body return res, nil } + +func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error) { + u, err := url.Parse(c.config.Hostname) + u.Path = path.Join(u.Path, "/api/v1/", endpoint) + reqUrl := u.String() + + jsonData, err := json.Marshal(data) + if err != nil { + log.Error().Err(err).Msgf("lidarr client could not marshal data: %v", reqUrl) + return 0, nil, err + } + + req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) + if err != nil { + log.Error().Err(err).Msgf("lidarr client request error: %v", reqUrl) + return 0, nil, err + } + + if c.config.BasicAuth { + req.SetBasicAuth(c.config.Username, c.config.Password) + } + + c.setHeaders(req) + + resp, err := c.http.Do(req) + if err != nil { + log.Error().Err(err).Msgf("lidarr client request error: %v", reqUrl) + return 0, nil, fmt.Errorf("lidarr.http.Do(req): %w", err) + } + + defer resp.Body.Close() + + var buf bytes.Buffer + if _, err = io.Copy(&buf, resp.Body); err != nil { + return resp.StatusCode, nil, fmt.Errorf("lidarr.io.Copy: %w", err) + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return resp.StatusCode, buf.Bytes(), fmt.Errorf("lidarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) + } + + return resp.StatusCode, buf.Bytes(), nil +} + +func (c *client) setHeaders(req *http.Request) { + if req.Body != nil { + req.Header.Set("Content-Type", "application/json") + } + + req.Header.Set("User-Agent", "autobrr") + + req.Header.Set("X-Api-Key", c.config.APIKey) +} diff --git a/pkg/lidarr/lidarr.go b/pkg/lidarr/lidarr.go index ccd18c1..2fe4385 100644 --- a/pkg/lidarr/lidarr.go +++ b/pkg/lidarr/lidarr.go @@ -2,7 +2,7 @@ package lidarr import ( "encoding/json" - "io" + "errors" "net/http" "strings" "time" @@ -67,60 +67,44 @@ type SystemStatusResponse struct { } func (c *client) Test() (*SystemStatusResponse, error) { - res, err := c.get("system/status") + status, res, err := c.get("system/status") if err != nil { log.Error().Stack().Err(err).Msg("lidarr client get error") return nil, err } - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - log.Error().Stack().Err(err).Msg("lidarr client error reading body") - return nil, err + if status == http.StatusUnauthorized { + return nil, errors.New("unauthorized: bad credentials") } + log.Trace().Msgf("lidarr system/status response status: %v body: %v", status, string(res)) + response := SystemStatusResponse{} - err = json.Unmarshal(body, &response) + err = json.Unmarshal(res, &response) if err != nil { log.Error().Stack().Err(err).Msg("lidarr client error json unmarshal") return nil, err } - log.Trace().Msgf("lidarr system/status response: %+v", response) - return &response, nil } func (c *client) Push(release Release) ([]string, error) { - res, err := c.post("release/push", release) + status, res, err := c.postBody("release/push", release) if err != nil { log.Error().Stack().Err(err).Msg("lidarr client post error") return nil, err } - if res == nil { - return nil, err - } - - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - log.Error().Stack().Err(err).Msg("lidarr client error reading body") - return nil, err - } + log.Trace().Msgf("lidarr release/push response status: %v body: %v", status, string(res)) pushResponse := PushResponse{} - err = json.Unmarshal(body, &pushResponse) + err = json.Unmarshal(res, &pushResponse) if err != nil { log.Error().Stack().Err(err).Msg("lidarr client error json unmarshal") return nil, err } - log.Trace().Msgf("lidarr release/push response body: %+v", string(body)) - // log and return if rejected if pushResponse.Rejected { rejections := strings.Join(pushResponse.Rejections, ", ") diff --git a/pkg/radarr/client.go b/pkg/radarr/client.go index f5b8f2c..6726514 100644 --- a/pkg/radarr/client.go +++ b/pkg/radarr/client.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/json" "errors" + "fmt" + "io" "net/http" "net/url" "path" @@ -11,7 +13,7 @@ import ( "github.com/rs/zerolog/log" ) -func (c *client) get(endpoint string) (*http.Response, error) { +func (c *client) get(endpoint string) (int, []byte, error) { u, err := url.Parse(c.config.Hostname) u.Path = path.Join(u.Path, "/api/v3/", endpoint) reqUrl := u.String() @@ -19,27 +21,29 @@ func (c *client) get(endpoint string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) if err != nil { log.Error().Err(err).Msgf("radarr client request error : %v", reqUrl) - return nil, err + return 0, nil, err } if c.config.BasicAuth { req.SetBasicAuth(c.config.Username, c.config.Password) } - req.Header.Add("X-Api-Key", c.config.APIKey) - req.Header.Set("User-Agent", "autobrr") + c.setHeaders(req) - res, err := c.http.Do(req) + resp, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("radarr client request error : %v", reqUrl) - return nil, err + log.Error().Err(err).Msgf("radarr client.get request error: %v", reqUrl) + return 0, nil, fmt.Errorf("radarr.http.Do(req): %w", err) } - if res.StatusCode == http.StatusUnauthorized { - return nil, errors.New("unauthorized: bad credentials") + defer resp.Body.Close() + + var buf bytes.Buffer + if _, err = io.Copy(&buf, resp.Body); err != nil { + return resp.StatusCode, nil, fmt.Errorf("radarr.io.Copy: %w", err) } - return res, nil + return resp.StatusCode, buf.Bytes(), nil } func (c *client) post(endpoint string, data interface{}) (*http.Response, error) { @@ -77,6 +81,9 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) if res.StatusCode == http.StatusUnauthorized { log.Error().Err(err).Msgf("radarr client bad request: %v", reqUrl) return nil, errors.New("unauthorized: bad credentials") + } else if res.StatusCode == http.StatusBadRequest { + log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) + return nil, errors.New("radarr: bad request") } else if res.StatusCode != http.StatusOK { log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) return nil, errors.New("radarr: bad request") @@ -85,3 +92,56 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) // return raw response and let the caller handle json unmarshal of body return res, nil } + +func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error) { + u, err := url.Parse(c.config.Hostname) + u.Path = path.Join(u.Path, "/api/v3/", endpoint) + reqUrl := u.String() + + jsonData, err := json.Marshal(data) + if err != nil { + log.Error().Err(err).Msgf("radarr client could not marshal data: %v", reqUrl) + return 0, nil, err + } + + req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) + if err != nil { + log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) + return 0, nil, err + } + + if c.config.BasicAuth { + req.SetBasicAuth(c.config.Username, c.config.Password) + } + + c.setHeaders(req) + + resp, err := c.http.Do(req) + if err != nil { + log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) + return 0, nil, fmt.Errorf("radarr.http.Do(req): %w", err) + } + + defer resp.Body.Close() + + var buf bytes.Buffer + if _, err = io.Copy(&buf, resp.Body); err != nil { + return resp.StatusCode, nil, fmt.Errorf("radarr.io.Copy: %w", err) + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return resp.StatusCode, buf.Bytes(), fmt.Errorf("radarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) + } + + return resp.StatusCode, buf.Bytes(), nil +} + +func (c *client) setHeaders(req *http.Request) { + if req.Body != nil { + req.Header.Set("Content-Type", "application/json") + } + + req.Header.Set("User-Agent", "autobrr") + + req.Header.Set("X-Api-Key", c.config.APIKey) +} diff --git a/pkg/radarr/radarr.go b/pkg/radarr/radarr.go index ea81b05..0c578e6 100644 --- a/pkg/radarr/radarr.go +++ b/pkg/radarr/radarr.go @@ -2,7 +2,7 @@ package radarr import ( "encoding/json" - "io" + "errors" "net/http" "strings" "time" @@ -66,22 +66,18 @@ type SystemStatusResponse struct { } func (c *client) Test() (*SystemStatusResponse, error) { - res, err := c.get("system/status") + status, res, err := c.get("system/status") if err != nil { log.Error().Stack().Err(err).Msg("radarr client get error") return nil, err } - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - log.Error().Stack().Err(err).Msg("radarr client error reading body") - return nil, err + if status == http.StatusUnauthorized { + return nil, errors.New("unauthorized: bad credentials") } response := SystemStatusResponse{} - err = json.Unmarshal(body, &response) + err = json.Unmarshal(res, &response) if err != nil { log.Error().Stack().Err(err).Msg("radarr client error json unmarshal") return nil, err @@ -93,32 +89,20 @@ func (c *client) Test() (*SystemStatusResponse, error) { } func (c *client) Push(release Release) ([]string, error) { - res, err := c.post("release/push", release) + status, res, err := c.postBody("release/push", release) if err != nil { - log.Error().Stack().Err(err).Msg("radarr client post error") - return nil, err - } - - if res == nil { - return nil, nil - } - - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - log.Error().Stack().Err(err).Msg("radarr client error reading body") + log.Error().Stack().Err(err).Msgf("radarr client post error. status: %d", status) return nil, err } pushResponse := make([]PushResponse, 0) - err = json.Unmarshal(body, &pushResponse) + err = json.Unmarshal(res, &pushResponse) if err != nil { log.Error().Stack().Err(err).Msg("radarr client error json unmarshal") return nil, err } - log.Trace().Msgf("radarr release/push response body: %+v", string(body)) + log.Trace().Msgf("radarr release/push response status: %v body: %+v", status, string(res)) // log and return if rejected if pushResponse[0].Rejected { diff --git a/pkg/radarr/radarr_test.go b/pkg/radarr/radarr_test.go index fee76d9..075dcae 100644 --- a/pkg/radarr/radarr_test.go +++ b/pkg/radarr/radarr_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -33,6 +34,20 @@ func Test_client_Push(t *testing.T) { } } + defer r.Body.Close() + data, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("expected error to be nil got %v", err) + } + + if strings.Contains(string(data), "Minx 1 epi 9 2160p") { + jsonPayload, _ := ioutil.ReadFile("testdata/release_push_parse_error.json") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + w.Write(jsonPayload) + return + } + // read json response jsonPayload, _ := ioutil.ReadFile("testdata/release_push_response.json") w.Header().Set("Content-Type", "application/json") @@ -102,6 +117,29 @@ func Test_client_Push(t *testing.T) { //err: errors.New("radarr push rejected Could not find Some Old Movie"), //wantErr: true, }, + { + name: "push_parse_error", + fields: fields{ + config: Config{ + Hostname: ts.URL, + APIKey: key, + BasicAuth: false, + Username: "", + Password: "", + }, + }, + args: args{release: Release{ + Title: "Minx 1 epi 9 2160p", + DownloadUrl: "https://www.test.org/rss/download/0000001/00000000000000000000/Minx.1.epi.9.2160p.torrent", + Size: 0, + Indexer: "test", + DownloadProtocol: "torrent", + Protocol: "torrent", + PublishDate: "2021-08-21T15:36:00Z", + }}, + err: errors.New("radarr: bad request: (status: 400 Bad Request): [\n {\n \"propertyName\": \"Title\",\n \"errorMessage\": \"Unable to parse\",\n \"attemptedValue\": \"Minx 1 epi 9 2160p\",\n \"severity\": \"error\"\n }\n]\n"), + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/radarr/testdata/release_push_parse_error.json b/pkg/radarr/testdata/release_push_parse_error.json new file mode 100644 index 0000000..5c41c81 --- /dev/null +++ b/pkg/radarr/testdata/release_push_parse_error.json @@ -0,0 +1,8 @@ +[ + { + "propertyName": "Title", + "errorMessage": "Unable to parse", + "attemptedValue": "Minx 1 epi 9 2160p", + "severity": "error" + } +] diff --git a/pkg/sonarr/client.go b/pkg/sonarr/client.go index 4d8fbe1..f6663d4 100644 --- a/pkg/sonarr/client.go +++ b/pkg/sonarr/client.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/json" "errors" + "fmt" + "io" "net/http" "net/url" "path" @@ -11,7 +13,7 @@ import ( "github.com/rs/zerolog/log" ) -func (c *client) get(endpoint string) (*http.Response, error) { +func (c *client) get(endpoint string) (int, []byte, error) { u, err := url.Parse(c.config.Hostname) u.Path = path.Join(u.Path, "/api/v3/", endpoint) reqUrl := u.String() @@ -19,27 +21,29 @@ func (c *client) get(endpoint string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) if err != nil { log.Error().Err(err).Msgf("sonarr client request error : %v", reqUrl) - return nil, err + return 0, nil, err } if c.config.BasicAuth { req.SetBasicAuth(c.config.Username, c.config.Password) } - req.Header.Add("X-Api-Key", c.config.APIKey) - req.Header.Set("User-Agent", "autobrr") + c.setHeaders(req) - res, err := c.http.Do(req) + resp, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("sonarr client request error : %v", reqUrl) - return nil, err + log.Error().Err(err).Msgf("sonarr client.get request error: %v", reqUrl) + return 0, nil, fmt.Errorf("sonarr.http.Do(req): %w", err) } - if res.StatusCode == http.StatusUnauthorized { - return nil, errors.New("unauthorized: bad credentials") + defer resp.Body.Close() + + var buf bytes.Buffer + if _, err = io.Copy(&buf, resp.Body); err != nil { + return resp.StatusCode, nil, fmt.Errorf("sonarr.io.Copy: %w", err) } - return res, nil + return resp.StatusCode, buf.Bytes(), nil } func (c *client) post(endpoint string, data interface{}) (*http.Response, error) { @@ -85,3 +89,56 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) // return raw response and let the caller handle json unmarshal of body return res, nil } + +func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error) { + u, err := url.Parse(c.config.Hostname) + u.Path = path.Join(u.Path, "/api/v3/", endpoint) + reqUrl := u.String() + + jsonData, err := json.Marshal(data) + if err != nil { + log.Error().Err(err).Msgf("sonarr client could not marshal data: %v", reqUrl) + return 0, nil, err + } + + req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) + if err != nil { + log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) + return 0, nil, err + } + + if c.config.BasicAuth { + req.SetBasicAuth(c.config.Username, c.config.Password) + } + + c.setHeaders(req) + + resp, err := c.http.Do(req) + if err != nil { + log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) + return 0, nil, fmt.Errorf("sonarr.http.Do(req): %w", err) + } + + defer resp.Body.Close() + + var buf bytes.Buffer + if _, err = io.Copy(&buf, resp.Body); err != nil { + return resp.StatusCode, nil, fmt.Errorf("sonarr.io.Copy: %w", err) + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return resp.StatusCode, buf.Bytes(), fmt.Errorf("sonarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) + } + + return resp.StatusCode, buf.Bytes(), nil +} + +func (c *client) setHeaders(req *http.Request) { + if req.Body != nil { + req.Header.Set("Content-Type", "application/json") + } + + req.Header.Set("User-Agent", "autobrr") + + req.Header.Set("X-Api-Key", c.config.APIKey) +} diff --git a/pkg/sonarr/sonarr.go b/pkg/sonarr/sonarr.go index 09cc1dc..3a15e1c 100644 --- a/pkg/sonarr/sonarr.go +++ b/pkg/sonarr/sonarr.go @@ -2,7 +2,7 @@ package sonarr import ( "encoding/json" - "io" + "errors" "net/http" "strings" "time" @@ -67,60 +67,44 @@ type SystemStatusResponse struct { } func (c *client) Test() (*SystemStatusResponse, error) { - res, err := c.get("system/status") + status, res, err := c.get("system/status") if err != nil { log.Error().Stack().Err(err).Msg("sonarr client get error") return nil, err } - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - log.Error().Stack().Err(err).Msg("sonarr client error reading body") - return nil, err + if status == http.StatusUnauthorized { + return nil, errors.New("unauthorized: bad credentials") } + log.Trace().Msgf("sonarr system/status response: %v", string(res)) + response := SystemStatusResponse{} - err = json.Unmarshal(body, &response) + err = json.Unmarshal(res, &response) if err != nil { log.Error().Stack().Err(err).Msg("sonarr client error json unmarshal") return nil, err } - log.Trace().Msgf("sonarr system/status response: %+v", response) - return &response, nil } func (c *client) Push(release Release) ([]string, error) { - res, err := c.post("release/push", release) + status, res, err := c.postBody("release/push", release) if err != nil { log.Error().Stack().Err(err).Msg("sonarr client post error") return nil, err } - if res == nil { - return nil, nil - } - - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - log.Error().Stack().Err(err).Msg("sonarr client error reading body") - return nil, err - } + log.Trace().Msgf("sonarr release/push response status: (%v) body: %v", status, string(res)) pushResponse := make([]PushResponse, 0) - err = json.Unmarshal(body, &pushResponse) + err = json.Unmarshal(res, &pushResponse) if err != nil { log.Error().Stack().Err(err).Msg("sonarr client error json unmarshal") return nil, err } - log.Trace().Msgf("sonarr release/push response body: %+v", string(body)) - // log and return if rejected if pushResponse[0].Rejected { rejections := strings.Join(pushResponse[0].Rejections, ", ")