summaryrefslogtreecommitdiff
path: root/player-server/internal/service/scan_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'player-server/internal/service/scan_test.go')
-rw-r--r--player-server/internal/service/scan_test.go177
1 files changed, 177 insertions, 0 deletions
diff --git a/player-server/internal/service/scan_test.go b/player-server/internal/service/scan_test.go
new file mode 100644
index 0000000..5837688
--- /dev/null
+++ b/player-server/internal/service/scan_test.go
@@ -0,0 +1,177 @@
+package service
+
+import (
+ "context"
+ "errors"
+ "testing"
+ "time"
+
+ "codeberg.org/snonux/player/internal/clock"
+ "codeberg.org/snonux/player/internal/model"
+)
+
+func TestScanService_ScanLibrary(t *testing.T) {
+ ctx := context.Background()
+
+ tests := []struct {
+ name string
+ scanErr error
+ wantErr bool
+ }{
+ {
+ name: "ok",
+ },
+ {
+ name: "scan error",
+ scanErr: errors.New("boom"),
+ wantErr: false, // TriggerRescan returns nil immediately; background goroutine logs error
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ done := make(chan struct{})
+ sc := &fakeScanner{
+ scanFunc: func(_ context.Context, root string, progress *model.ScanProgress) error {
+ defer close(done)
+ return tt.scanErr
+ },
+ }
+ svc := NewScanService(ctx, sc, "/media", clock.RealClock{}, nil)
+ err := svc.TriggerRescan(ctx)
+ if tt.wantErr {
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ return
+ }
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ // Wait for background goroutine to finish.
+ select {
+ case <-done:
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout waiting for scan goroutine")
+ }
+ })
+ }
+}
+
+func TestScanService_TriggerRescanMarksProgressRunningImmediately(t *testing.T) {
+ ctx := context.Background()
+ release := make(chan struct{})
+ started := make(chan struct{})
+ done := make(chan struct{}, 1)
+ sc := &fakeScanner{
+ scanFunc: func(_ context.Context, _ string, _ *model.ScanProgress) error {
+ close(started)
+ <-release
+ return nil
+ },
+ }
+ svc := NewScanService(ctx, sc, "/media", clock.RealClock{}, nil)
+
+ if err := svc.TriggerRescan(ctx); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ p := svc.ScanProgress(ctx)
+ if !p.Running {
+ t.Fatal("expected progress to be running immediately after trigger")
+ }
+ if p.SetsTotal != 0 || p.FilesTotal != 0 {
+ t.Fatalf("expected unknown initial totals, got sets=%d files=%d", p.SetsTotal, p.FilesTotal)
+ }
+
+ select {
+ case <-started:
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout waiting for scan goroutine to start")
+ }
+
+ svc.doneCh = done
+ close(release)
+ select {
+ case <-done:
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout waiting for scan goroutine to finish")
+ }
+}
+
+func TestScanService_CancelledByAppContext(t *testing.T) {
+ appCtx, appCancel := context.WithCancel(context.Background())
+ defer appCancel()
+
+ done := make(chan struct{}, 1)
+ started := make(chan struct{})
+ sc := &fakeScanner{
+ scanFunc: func(scanCtx context.Context, _ string, progress *model.ScanProgress) error {
+ close(started)
+ <-scanCtx.Done()
+ return scanCtx.Err()
+ },
+ }
+
+ svc := NewScanService(appCtx, sc, "/media", clock.RealClock{}, nil)
+ svc.doneCh = done
+ if err := svc.TriggerRescan(context.Background()); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ <-started
+ appCancel()
+
+ select {
+ case <-done:
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout waiting for scan goroutine to exit after app context cancellation")
+ }
+
+ p := svc.ScanProgress(context.Background())
+ if p.Running {
+ t.Fatal("expected scan to be stopped after app context cancellation")
+ }
+ if p.LastError == "" {
+ t.Fatal("expected a last error after cancellation")
+ }
+}
+
+func TestScanService_CancelledContextRecordedWhenScannerReturnsNil(t *testing.T) {
+ appCtx, appCancel := context.WithCancel(context.Background())
+ defer appCancel()
+
+ done := make(chan struct{}, 1)
+ started := make(chan struct{})
+ sc := &fakeScanner{
+ scanFunc: func(scanCtx context.Context, _ string, progress *model.ScanProgress) error {
+ progress.Start(1)
+ close(started)
+ <-scanCtx.Done()
+ return nil
+ },
+ }
+
+ svc := NewScanService(appCtx, sc, "/media", clock.RealClock{}, nil)
+ svc.doneCh = done
+ if err := svc.TriggerRescan(context.Background()); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ <-started
+ appCancel()
+
+ select {
+ case <-done:
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout waiting for scan goroutine to exit after app context cancellation")
+ }
+
+ p := svc.ScanProgress(context.Background())
+ if p.Running {
+ t.Fatal("expected scan to be stopped after app context cancellation")
+ }
+ if p.LastError != context.Canceled.Error() {
+ t.Fatalf("expected last error %q, got %q", context.Canceled.Error(), p.LastError)
+ }
+}