fix: context leak in goroutine handlers + harden smoke test

Bugs fixed:
  - handler/media.go: createLibraryHandler and deleteLibraryHandler
    spawned goroutines that called svc.Watcher.Refresh(c.Request.Context()).
    Since the HTTP request returns immediately, the context is cancelled
    before the watcher finishes. Use context.Background() so the refresh
    always completes.
  - handler/streaming.go: scrapeLibraryHandler had the same issue with
    c.Copy().Request.Context(). Fixed to context.Background().

Smoke test improvements:
  - Always create test media files (dummy or ffmpeg), always run scan and
    search — only ffprobe-specific assertions (width=320) and HLS are
    gated behind HAVE_FFMPEG.
  - ID is now always set (from library media list), so history/favourites
    /playlists tests never hit an unbound variable.
  - NFO + recycle-bin assertions no longer gated behind HAVE_FFMPEG since
    they work on any media row (even dummy files).

Verified: go build + go vet + go test all pass; 55/55 endpoint assertions
pass with 0 server 5xx; smoke test 26/26 PASS with and without ffmpeg.
This commit is contained in:
Kiro
2026-05-15 10:40:42 +00:00
parent 77e3fdd1f6
commit da1a264d98
3 changed files with 54 additions and 45 deletions
+3 -2
View File
@@ -2,6 +2,7 @@
package handler
import (
"context"
"errors"
"net/http"
"strconv"
@@ -43,7 +44,7 @@ func createLibraryHandler(svc *service.Container) gin.HandlerFunc {
uid, _ := c.Get("ctx_user_id")
svc.Audit.Record(c.Request.Context(), toString(uid), "library.create", l.ID, c.ClientIP(), l.Path)
// Refresh fsnotify watcher to pick up the new library root.
go func() { _ = svc.Watcher.Refresh(c.Request.Context()) }()
go func() { _ = svc.Watcher.Refresh(context.Background()) }()
c.JSON(http.StatusOK, l)
}
}
@@ -57,7 +58,7 @@ func deleteLibraryHandler(svc *service.Container) gin.HandlerFunc {
}
uid, _ := c.Get("ctx_user_id")
svc.Audit.Record(c.Request.Context(), toString(uid), "library.delete", id, c.ClientIP(), "")
go func() { _ = svc.Watcher.Refresh(c.Request.Context()) }()
go func() { _ = svc.Watcher.Refresh(context.Background()) }()
c.Status(http.StatusNoContent)
}
}
+2 -1
View File
@@ -2,6 +2,7 @@
package handler
import (
"context"
"errors"
"net/http"
@@ -82,7 +83,7 @@ func scrapeLibraryHandler(svc *service.Container) gin.HandlerFunc {
// Run in the background so HTTP returns instantly; the WS hub
// pushes per-item progress on the "scrape" topic.
go func(libID string) {
_, _ = svc.Scraper.EnrichLibrary(c.Copy().Request.Context(), libID)
_, _ = svc.Scraper.EnrichLibrary(context.Background(), libID)
}(c.Param("id"))
c.JSON(http.StatusAccepted, gin.H{"status": "scraping"})
}