summaryrefslogtreecommitdiff
path: root/player-server/internal/service/progress_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-17 21:44:42 +0300
committerPaul Buetow <paul@buetow.org>2026-05-17 21:44:42 +0300
commit60df3397fbd14a6ad200e1fdd2530f708403ee99 (patch)
tree2d8615adad60a0d37d6d670a4dabebeea144c869 /player-server/internal/service/progress_test.go
parent0462f639bc1e1973d30b9ffcd358ac62fa10fb46 (diff)
Add bulk progress sync endpoint for offline mobile clients
Diffstat (limited to 'player-server/internal/service/progress_test.go')
-rw-r--r--player-server/internal/service/progress_test.go111
1 files changed, 111 insertions, 0 deletions
diff --git a/player-server/internal/service/progress_test.go b/player-server/internal/service/progress_test.go
index a2b36db..6e6a1a0 100644
--- a/player-server/internal/service/progress_test.go
+++ b/player-server/internal/service/progress_test.go
@@ -4,7 +4,9 @@ import (
"context"
"errors"
"testing"
+ "time"
+ "codeberg.org/snonux/player/internal/clock"
"codeberg.org/snonux/player/internal/model"
"codeberg.org/snonux/player/internal/repository"
)
@@ -198,6 +200,115 @@ func TestProgressService_UpdateProgress(t *testing.T) {
}
}
+func TestProgressService_BatchUpdateProgress_OrdersByObservedAt(t *testing.T) {
+ ctx := context.Background()
+ observedBase := time.Date(2026, 5, 17, 10, 0, 0, 0, time.UTC)
+ progressByMedia := make(map[int64]*model.PlaybackProgress)
+ accByMedia := make(map[int64]*model.PlaybackAccumulator)
+ var positions []float64
+
+ store := &repository.MockStore{
+ PlaybackProgressRepo: repository.MockPlaybackProgressRepo{
+ GetProgressFunc: func(ctx context.Context, userID, mediaID int64) (*model.PlaybackProgress, error) {
+ return progressByMedia[mediaID], nil
+ },
+ UpsertProgressFunc: func(ctx context.Context, progress *model.PlaybackProgress) error {
+ cp := *progress
+ progressByMedia[progress.MediaID] = &cp
+ positions = append(positions, progress.PositionSeconds)
+ return nil
+ },
+ },
+ PlaybackAccumulatorRepo: repository.MockPlaybackAccumulatorRepo{
+ GetAccumulatorFunc: func(ctx context.Context, sessionID string, mediaID int64) (*model.PlaybackAccumulator, error) {
+ return accByMedia[mediaID], nil
+ },
+ UpsertAccumulatorFunc: func(ctx context.Context, acc *model.PlaybackAccumulator) error {
+ cp := *acc
+ accByMedia[acc.MediaID] = &cp
+ return nil
+ },
+ },
+ }
+
+ svc := NewProgressService(store, &clock.MockClock{T: observedBase})
+ err := svc.BatchUpdateProgress(ctx, "sess", 1, []ProgressUpdate{
+ {MediaID: 10, PositionSeconds: 30, ObservedAt: observedBase.Add(2 * time.Minute)},
+ {MediaID: 10, PositionSeconds: 10, ObservedAt: observedBase},
+ {MediaID: 11, PositionSeconds: 20, ObservedAt: observedBase.Add(time.Minute)},
+ })
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ wantPositions := []float64{10, 20, 30}
+ if len(positions) != len(wantPositions) {
+ t.Fatalf("expected %d progress writes, got %d", len(wantPositions), len(positions))
+ }
+ for i, want := range wantPositions {
+ if positions[i] != want {
+ t.Fatalf("position call %d: expected %v, got %v", i, want, positions[i])
+ }
+ }
+ if got := progressByMedia[10].PositionSeconds; got != 30 {
+ t.Fatalf("expected latest media 10 position to win, got %v", got)
+ }
+}
+
+func TestProgressService_BatchUpdateProgress_TransactionRollback(t *testing.T) {
+ ctx := context.Background()
+ store, err := repository.Open(":memory:")
+ if err != nil {
+ t.Fatalf("open store: %v", err)
+ }
+ defer store.Close()
+
+ now := time.Date(2026, 5, 17, 10, 0, 0, 0, time.UTC)
+ userID, err := store.CreateUser(ctx, &model.User{Username: "alice", PasswordHash: "hash", CreatedAt: now})
+ if err != nil {
+ t.Fatalf("create user: %v", err)
+ }
+ setID, err := store.CreateSet(ctx, &model.Set{Name: "set", RootPath: "/media/set", CreatedAt: now})
+ if err != nil {
+ t.Fatalf("create set: %v", err)
+ }
+ mediaID, err := store.CreateMedia(ctx, &model.Media{
+ SetID: setID,
+ RelPath: "one.mp4",
+ FileName: "one.mp4",
+ AbsPath: "/media/set/one.mp4",
+ Type: model.MediaTypeVideo,
+ CreatedAt: now,
+ })
+ if err != nil {
+ t.Fatalf("create media: %v", err)
+ }
+ if err := store.CreateSession(ctx, &model.Session{
+ ID: "sess",
+ UserID: userID,
+ ExpiresAt: now.Add(time.Hour),
+ CreatedAt: now,
+ }); err != nil {
+ t.Fatalf("create session: %v", err)
+ }
+
+ svc := NewProgressService(store, &clock.MockClock{T: now})
+ err = svc.BatchUpdateProgress(ctx, "sess", userID, []ProgressUpdate{
+ {MediaID: mediaID, PositionSeconds: 10, ObservedAt: now},
+ {MediaID: 9999, PositionSeconds: 20, ObservedAt: now.Add(time.Second)},
+ })
+ if err == nil {
+ t.Fatal("expected batch update error")
+ }
+ progress, err := store.GetProgress(ctx, userID, mediaID)
+ if err != nil {
+ t.Fatalf("get progress: %v", err)
+ }
+ if progress != nil {
+ t.Fatalf("expected transaction rollback to remove first progress update, got %+v", progress)
+ }
+}
+
func TestProgressService_MarkFinished(t *testing.T) {
ctx := context.Background()
var saved *model.PlaybackProgress