//go:build integration package inttest import ( "context" "testing" "github.com/google/uuid" "scrabble/backend/internal/engine" "scrabble/backend/internal/game" ) // createTwoSeatGame starts a plain two-human game of the given variant. func createTwoSeatGame(t *testing.T, svc *game.Service, variant engine.Variant) game.Game { t.Helper() g, err := svc.Create(context.Background(), game.CreateParams{ Variant: variant, Seats: []uuid.UUID{provisionAccount(t), provisionAccount(t)}, HintsAllowed: true, HintsPerPlayer: 1, MultipleWordsPerTurn: true, }) if err != nil { t.Fatalf("create %s game: %v", variant, err) } return g } // TestNoRepeatWordsPinnedFromTheVariant checks the rule is written into games.no_repeat_words at // creation from the variant — on for Erudit, off for both Scrabble variants — and read back with // the game rather than re-derived. func TestNoRepeatWordsPinnedFromTheVariant(t *testing.T) { svc := newGameService() for _, tc := range []struct { variant engine.Variant want bool }{ {engine.VariantErudit, true}, {engine.VariantRussianScrabble, false}, {engine.VariantEnglish, false}, } { t.Run(tc.variant.String(), func(t *testing.T) { created := createTwoSeatGame(t, svc, tc.variant) if created.NoRepeatWords != tc.want { t.Errorf("created game NoRepeatWords = %v, want %v", created.NoRepeatWords, tc.want) } // Re-read it: the value must come from the row, not from the create call. reloaded, err := svc.GameByID(context.Background(), created.ID) if err != nil { t.Fatalf("get game: %v", err) } if reloaded.NoRepeatWords != tc.want { t.Errorf("reloaded game NoRepeatWords = %v, want %v", reloaded.NoRepeatWords, tc.want) } }) } } // TestNoRepeatWordsHonoursThePinNotTheVariant is the compatibility guard for every Erudit game // that existed before the rule: those rows carry no_repeat_words false (the migration's default), // and such a game must keep playing unrestricted. Flipping the stored flag stands in for one. func TestNoRepeatWordsHonoursThePinNotTheVariant(t *testing.T) { ctx := context.Background() svc := newGameService() g := createTwoSeatGame(t, svc, engine.VariantErudit) if _, err := testDB.ExecContext(ctx, `UPDATE backend.games SET no_repeat_words = false WHERE game_id = $1`, g.ID); err != nil { t.Fatalf("clear the pin: %v", err) } reloaded, err := svc.GameByID(ctx, g.ID) if err != nil { t.Fatalf("get game: %v", err) } if reloaded.NoRepeatWords { t.Fatal("an Erudit game whose row has the rule off must report it off") } // The rule reaches the engine from the row, so the reconstructed game plays unrestricted: // its generated moves are the solver's full list, none filtered away. if _, err := svc.Hint(ctx, reloaded.ID, reloaded.Seats[reloaded.ToMove].AccountID); err != nil { t.Fatalf("hint on an unrestricted Erudit game: %v", err) } }