//go:build integration package inttest import ( "context" "testing" "time" "github.com/google/uuid" "scrabble/backend/internal/engine" "scrabble/backend/internal/game" ) // TestMultiplayerTimeoutContinues drives a three-player game through the domain: // the first timeout drops a seat but the game plays on, and the second leaves a // sole survivor who wins. Empty away windows make the timeouts deterministic. func TestMultiplayerTimeoutContinues(t *testing.T) { ctx := context.Background() svc := newGameService() seats := []uuid.UUID{provisionAccount(t), provisionAccount(t), provisionAccount(t)} for _, s := range seats { setAway(t, s, "UTC", "00:00", "00:00") // empty window → no away grace } g, err := svc.Create(ctx, game.CreateParams{ Variant: engine.VariantEnglish, Seats: seats, TurnTimeout: time.Hour, Seed: 42, }) if err != nil { t.Fatalf("create: %v", err) } if g.Players != 3 { t.Fatalf("players = %d, want 3", g.Players) } // Seat 0 (to move) goes overdue. It times out, but two seats remain, so the // game continues and the turn advances off the dropped seat. backdate(t, g.ID, time.Now().UTC().Add(-2*time.Hour)) if _, err := svc.SweepTimeouts(ctx, time.Now().UTC()); err != nil { t.Fatalf("first sweep: %v", err) } h, err := svc.History(ctx, g.ID) if err != nil { t.Fatalf("history: %v", err) } if h.Game.Status != game.StatusActive { t.Fatalf("a three-player game must continue after one timeout, status %q", h.Game.Status) } if h.Game.ToMove == 0 { t.Errorf("to-move should advance off the timed-out seat 0, got %d", h.Game.ToMove) } // The next seat to move also times out, leaving a single active seat: the game // finishes and the sole survivor wins. backdate(t, g.ID, time.Now().UTC().Add(-2*time.Hour)) if _, err := svc.SweepTimeouts(ctx, time.Now().UTC()); err != nil { t.Fatalf("second sweep: %v", err) } h2, err := svc.History(ctx, g.ID) if err != nil { t.Fatalf("history 2: %v", err) } if h2.Game.Status != game.StatusFinished || h2.Game.EndReason != "timeout" { t.Fatalf("game should finish on the second timeout: status %q reason %q", h2.Game.Status, h2.Game.EndReason) } winners := 0 for _, s := range h2.Game.Seats { if s.IsWinner { winners++ } } if winners != 1 { t.Errorf("want exactly one surviving winner, got %d", winners) } }