mirror of
https://github.com/navidrome/navidrome.git
synced 2026-07-09 17:18:45 +00:00
Some checks are pending
Pipeline: Test, Lint, Build / Get version info (push) Waiting to run
Pipeline: Test, Lint, Build / Lint Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (Windows) (push) Waiting to run
Pipeline: Test, Lint, Build / Test JS code (push) Waiting to run
Pipeline: Test, Lint, Build / Lint i18n files (push) Waiting to run
Pipeline: Test, Lint, Build / Check Docker configuration (push) Waiting to run
Pipeline: Test, Lint, Build / Build (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-1 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-2 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-3 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-4 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-5 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-6 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-7 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-8 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-9 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-10 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to GHCR (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build Windows installers (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Package/Release (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Blocked by required conditions
Plugin scrobblers read the username from the request context via getUsernameFromContext, but buffered scrobbles are persisted to the DB scrobble buffer (which stores only the userId) and later drained by a background worker running on context.Background(). That context carries no authenticated user, so ScrobbleRequest.Username was always empty for WASM scrobbler plugins. NowPlaying is unaffected because it is dispatched synchronously on context.WithoutCancel(requestCtx), which retains the user. Restore the user in the drain path: processUserQueue now looks up the user by the buffered userId and injects it into the context via request.WithUser before dispatching, mirroring the pattern already used in play_tracker. Builtin scrobblers are unaffected as they resolve the account from the userId argument rather than the context.
102 lines
3 KiB
Go
102 lines
3 KiB
Go
package scrobbler
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/tests"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("BufferedScrobbler", func() {
|
|
var ds model.DataStore
|
|
var scr *fakeScrobbler
|
|
var bs *bufferedScrobbler
|
|
var ctx context.Context
|
|
var buffer *tests.MockedScrobbleBufferRepo
|
|
|
|
BeforeEach(func() {
|
|
ctx = context.Background()
|
|
buffer = tests.CreateMockedScrobbleBufferRepo()
|
|
userRepo := tests.CreateMockUserRepo()
|
|
Expect(userRepo.Put(&model.User{ID: "user1", UserName: "alice"})).To(Succeed())
|
|
ds = &tests.MockDataStore{
|
|
MockedScrobbleBuffer: buffer,
|
|
MockedUser: userRepo,
|
|
}
|
|
scr = &fakeScrobbler{Authorized: true}
|
|
bs = newBufferedScrobbler(ds, scr, "test")
|
|
})
|
|
|
|
It("forwards IsAuthorized calls", func() {
|
|
scr.Authorized = true
|
|
Expect(bs.IsAuthorized(ctx, "user1")).To(BeTrue())
|
|
|
|
scr.Authorized = false
|
|
Expect(bs.IsAuthorized(ctx, "user1")).To(BeFalse())
|
|
})
|
|
|
|
It("forwards NowPlaying calls", func() {
|
|
track := &model.MediaFile{ID: "123", Title: "Test Track"}
|
|
Expect(bs.NowPlaying(ctx, "user1", track, 0)).To(Succeed())
|
|
Expect(scr.GetNowPlayingCalled()).To(BeTrue())
|
|
Expect(scr.GetUserID()).To(Equal("user1"))
|
|
Expect(scr.GetTrack()).To(Equal(track))
|
|
})
|
|
|
|
It("enqueues scrobbles to buffer", func() {
|
|
track := model.MediaFile{ID: "123", Title: "Test Track"}
|
|
now := time.Now()
|
|
scrobble := Scrobble{MediaFile: track, TimeStamp: now}
|
|
Expect(buffer.Length()).To(Equal(int64(0)))
|
|
Expect(scr.ScrobbleCalled.Load()).To(BeFalse())
|
|
|
|
Expect(bs.Scrobble(ctx, "user1", scrobble)).To(Succeed())
|
|
|
|
// Wait for the background goroutine to process the scrobble.
|
|
// We don't check buffer.Length() here because the background goroutine
|
|
// may dequeue the entry before we can observe it.
|
|
Eventually(scr.ScrobbleCalled.Load).Should(BeTrue())
|
|
|
|
lastScrobble := scr.LastScrobble.Load()
|
|
Expect(lastScrobble.MediaFile.ID).To(Equal("123"))
|
|
Expect(lastScrobble.TimeStamp).To(BeTemporally("==", now))
|
|
})
|
|
|
|
It("restores the user in the context when draining buffered scrobbles", func() {
|
|
track := model.MediaFile{ID: "123", Title: "Test Track", Artist: "Test Artist"}
|
|
scrobble := Scrobble{MediaFile: track, TimeStamp: time.Now()}
|
|
|
|
Expect(bs.Scrobble(ctx, "user1", scrobble)).To(Succeed())
|
|
|
|
Eventually(scr.ScrobbleCalled.Load).Should(BeTrue())
|
|
Expect(scr.GetUsername()).To(Equal("alice"))
|
|
})
|
|
|
|
It("stops the background goroutine when Stop is called", func() {
|
|
// Replace the real run method with one that signals when it exits
|
|
done := make(chan struct{})
|
|
|
|
// Start our instrumented run function that will signal when it exits
|
|
go func() {
|
|
defer close(done)
|
|
bs.run(bs.ctx)
|
|
}()
|
|
|
|
// Wait a bit to ensure the goroutine is running
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Call the real Stop method
|
|
bs.Stop()
|
|
|
|
// Wait for the goroutine to exit or timeout
|
|
select {
|
|
case <-done:
|
|
// Success, goroutine exited
|
|
case <-time.After(100 * time.Millisecond):
|
|
Fail("Goroutine did not exit in time after Stop was called")
|
|
}
|
|
})
|
|
})
|