diff --git a/internal/api/v1/download_clients.go b/internal/api/v1/download_clients.go index 7b5c759..aaf30a0 100644 --- a/internal/api/v1/download_clients.go +++ b/internal/api/v1/download_clients.go @@ -78,11 +78,6 @@ type dlClientTestInput struct { } } -func toDLClientBody(row interface{ GetID() string }) dlClientBody { - // This is handled inline since the generated type doesn't have methods - return dlClientBody{} -} - // RegisterDownloadClientRoutes registers download client management endpoints. func RegisterDownloadClientRoutes(api huma.API, svc *downloadclient.Service) { // POST /api/v1/download-clients — create diff --git a/internal/api/v1/torznab.go b/internal/api/v1/torznab.go index a34145d..ca613db 100644 --- a/internal/api/v1/torznab.go +++ b/internal/api/v1/torznab.go @@ -114,8 +114,8 @@ func (h *TorznabHandler) handleCaps(w http.ResponseWriter, runner *scraper.Runne w.Header().Set("Content-Type", "application/xml; charset=utf-8") w.WriteHeader(http.StatusOK) - w.Write([]byte(xml.Header)) - xml.NewEncoder(w).Encode(caps) + _, _ = w.Write([]byte(xml.Header)) + _ = xml.NewEncoder(w).Encode(caps) } // handleSearch executes a search and returns Torznab XML results. @@ -211,8 +211,8 @@ func (h *TorznabHandler) writeResults(w http.ResponseWriter, r *http.Request, id w.Header().Set("Content-Type", "application/xml; charset=utf-8") w.WriteHeader(http.StatusOK) - w.Write([]byte(xml.Header)) - xml.NewEncoder(w).Encode(feed) + _, _ = w.Write([]byte(xml.Header)) + _ = xml.NewEncoder(w).Encode(feed) } // xmlError writes a Torznab error response. @@ -304,7 +304,7 @@ func (h *TorznabHandler) HandleDownload(w http.ResponseWriter, r *http.Request) w.Header().Set("Content-Disposition", cd) } w.WriteHeader(proxyResp.StatusCode) - io.Copy(w, proxyResp.Body) + _, _ = io.Copy(w, proxyResp.Body) } // TestSearchResult is the JSON response from the test-search endpoint. @@ -371,7 +371,7 @@ func (h *TorznabHandler) HandleTestSearch(w http.ResponseWriter, r *http.Request func writeJSON(w http.ResponseWriter, v interface{}) { w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(v) + _ = json.NewEncoder(w).Encode(v) } func since(start time.Time) string { diff --git a/internal/core/health/checker.go b/internal/core/health/checker.go index fc47f43..5e66f03 100644 --- a/internal/core/health/checker.go +++ b/internal/core/health/checker.go @@ -82,7 +82,7 @@ func (c *Checker) checkOne(ctx context.Context, svc db.Service) { return } defer func() { - io.Copy(io.Discard, resp.Body) + _, _ = io.Copy(io.Discard, resp.Body) resp.Body.Close() }() diff --git a/internal/core/indexer/prowlarr.go b/internal/core/indexer/prowlarr.go index bcdac6b..65ad096 100644 --- a/internal/core/indexer/prowlarr.go +++ b/internal/core/indexer/prowlarr.go @@ -119,13 +119,6 @@ func (pc *ProwlarrCatalog) Refresh(ctx context.Context) error { pc.logger.Info("prowlarr: found definition files", "count", len(files)) - // Step 2: Fetch and parse each YAML file. - // Use a semaphore to limit concurrent requests. - type result struct { - entry CatalogEntry - ok bool - } - // Fetch all definitions via the zipball to avoid per-file rate limits. pc.logger.Info("prowlarr: downloading zipball...") entries, errCount, err := pc.fetchAllViaZip(ctx, files) diff --git a/internal/core/indexer/tester.go b/internal/core/indexer/tester.go index 2ae6ebd..bbaf632 100644 --- a/internal/core/indexer/tester.go +++ b/internal/core/indexer/tester.go @@ -33,11 +33,7 @@ func TestIndexer(ctx context.Context, kind, url, apiKey string) TestResult { func testTorznab(ctx context.Context, client *http.Client, baseURL, apiKey string, start time.Time) TestResult { // Torznab/Newznab caps endpoint: /api?t=caps&apikey=... - capsURL := baseURL - if capsURL != "" && capsURL[len(capsURL)-1] != '/' { - capsURL += "/" - } - capsURL = baseURL + "?t=caps" + capsURL := baseURL + "?t=caps" if apiKey != "" { capsURL += "&apikey=" + apiKey } @@ -105,7 +101,7 @@ func testGenericHTTP(ctx context.Context, client *http.Client, url, apiKey strin return TestResult{Success: false, Message: fmt.Sprintf("Connection failed: %v", err), Duration: since(start)} } defer func() { - io.Copy(io.Discard, resp.Body) + _, _ = io.Copy(io.Discard, resp.Body) resp.Body.Close() }() diff --git a/internal/events/bus_test.go b/internal/events/bus_test.go index 67c3338..e3dbc37 100644 --- a/internal/events/bus_test.go +++ b/internal/events/bus_test.go @@ -31,17 +31,21 @@ func TestBusPublishSubscribe(t *testing.T) { func TestBusPublishSetsTimestamp(t *testing.T) { bus := New(slog.Default()) - var received Event + received := make(chan Event, 1) bus.Subscribe(func(_ context.Context, e Event) { - received = e + received <- e }) bus.Publish(context.Background(), Event{Type: TypeConfigUpdated}) - time.Sleep(50 * time.Millisecond) - if received.Timestamp.IsZero() { - t.Error("expected non-zero timestamp") + select { + case e := <-received: + if e.Timestamp.IsZero() { + t.Error("expected non-zero timestamp") + } + case <-time.After(1 * time.Second): + t.Fatal("timed out waiting for event") } } diff --git a/internal/torznab/types.go b/internal/torznab/types.go index 4502414..f867f6f 100644 --- a/internal/torznab/types.go +++ b/internal/torznab/types.go @@ -88,7 +88,9 @@ type Enclosure struct { // MarshalXML produces the Torznab-compatible XML for an Item. func (item Item) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Name = xml.Name{Local: "item"} - e.EncodeToken(start) + if err := e.EncodeToken(start); err != nil { + return err + } writeElement(e, "title", item.Title) writeElement(e, "guid", item.GUID) @@ -103,39 +105,49 @@ func (item Item) MarshalXML(e *xml.Encoder, start xml.StartElement) error { if encType == "" { encType = "application/x-bittorrent" } - e.EncodeToken(xml.StartElement{ + if err := e.EncodeToken(xml.StartElement{ Name: xml.Name{Local: "enclosure"}, Attr: []xml.Attr{ {Name: xml.Name{Local: "url"}, Value: item.Enclosure.URL}, {Name: xml.Name{Local: "length"}, Value: strconv.FormatInt(item.Enclosure.Length, 10)}, {Name: xml.Name{Local: "type"}, Value: encType}, }, - }) - e.EncodeToken(xml.EndElement{Name: xml.Name{Local: "enclosure"}}) + }); err != nil { + return err + } + if err := e.EncodeToken(xml.EndElement{Name: xml.Name{Local: "enclosure"}}); err != nil { + return err + } } // Torznab attributes torznabNS := "http://torznab.com/schemas/2015/feed" for name, value := range item.Attrs { - e.EncodeToken(xml.StartElement{ + if err := e.EncodeToken(xml.StartElement{ Name: xml.Name{Space: torznabNS, Local: "attr"}, Attr: []xml.Attr{ {Name: xml.Name{Local: "name"}, Value: name}, {Name: xml.Name{Local: "value"}, Value: value}, }, - }) - e.EncodeToken(xml.EndElement{Name: xml.Name{Space: torznabNS, Local: "attr"}}) + }); err != nil { + return err + } + if err := e.EncodeToken(xml.EndElement{Name: xml.Name{Space: torznabNS, Local: "attr"}}); err != nil { + return err + } } - e.EncodeToken(xml.EndElement{Name: xml.Name{Local: "item"}}) - return nil + return e.EncodeToken(xml.EndElement{Name: xml.Name{Local: "item"}}) } func writeElement(e *xml.Encoder, name, value string) { start := xml.StartElement{Name: xml.Name{Local: name}} - e.EncodeToken(start) - e.EncodeToken(xml.CharData(value)) - e.EncodeToken(xml.EndElement{Name: xml.Name{Local: name}}) + // The outer MarshalXML returns any encoder error via its final + // EncodeToken call, so these helpers ignore errors by design — + // a broken encoder will surface at the next checked call. + _ = e.EncodeToken(start) + _ = e.EncodeToken(xml.CharData(value)) + _ = e.EncodeToken(xml.EndElement{Name: xml.Name{Local: name}}) } // ── Builders ──────────────────────────────────────────────────────────────── diff --git a/pkg/sdk/client_test.go b/pkg/sdk/client_test.go index e68e030..334f26d 100644 --- a/pkg/sdk/client_test.go +++ b/pkg/sdk/client_test.go @@ -45,7 +45,7 @@ func setupTestServer(t *testing.T) *httptest.Server { // Clean all tables for test isolation. for _, table := range []string{"indexer_tags", "service_tags", "tags", "config_subscriptions", "config_entries", "indexer_assignments", "indexers", "service_capabilities", "services", "filter_presets", "download_clients"} { - sqlDB.Exec("DELETE FROM " + table) + _, _ = sqlDB.Exec("DELETE FROM " + table) } logger := slog.Default()