Files
scrabble-game/backend/internal/robot/names_test.go
T
Ilia Denisov 26aa154547
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 37s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
R1: schema & naming reset — squash migrations, rename variants
Squash the 12 goose migrations into one 00001_baseline.sql (there is no prod
data; verified schema-identical to the chain via a pg_dump diff + the green
integration suite) and rename the game-variant labels
english/russian_scrabble/erudit -> scrabble_en/scrabble_ru/erudit_ru across the
backend, the FlatBuffers wire values and the UI.

dawg filenames and the Go enum identifiers are unchanged; the i18n display keys
are kept. Adds PRERELEASE.md (the R1-R7 pre-release tracker), linked from
CLAUDE.md. Contour DB wipe and the scrabble-dictionary tidy are follow-ups.
2026-06-09 12:09:50 +02:00

120 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package robot
import (
"errors"
"testing"
"github.com/google/uuid"
"scrabble/backend/internal/engine"
)
// TestComposeName covers the three rendering forms, including a Cyrillic initial.
func TestComposeName(t *testing.T) {
cases := []struct {
first, surname string
form int
want string
}{
{"Anna", "Carter", nameFormFirstOnly, "Anna"},
{"Anna", "Carter", nameFormInitial, "Anna C."},
{"Anna", "Carter", nameFormFull, "Anna Carter"},
{"Маша", "Суханова", nameFormInitial, "Маша С."},
{"Маша", "Суханова", nameFormFull, "Маша Суханова"},
}
for _, c := range cases {
if got := composeName(c.first, c.surname, c.form); got != c.want {
t.Errorf("composeName(%q,%q,%d) = %q, want %q", c.first, c.surname, c.form, got, c.want)
}
}
}
// TestNamePoolsPaired checks the full and colloquial first-name pools line up by
// index (so a slot's gender and person are consistent) and the surname forms differ.
func TestNamePoolsPaired(t *testing.T) {
if len(firstNamesFullEN) != robotPoolSize || len(firstNamesShortEN) != robotPoolSize {
t.Fatalf("EN first-name pools must each hold %d names", robotPoolSize)
}
if len(firstNamesFullRU) != robotPoolSize || len(firstNamesShortRU) != robotPoolSize {
t.Fatalf("RU first-name pools must each hold %d names", robotPoolSize)
}
for i := range firstNamesFullRU {
if firstNamesFullRU[i].female != firstNamesShortRU[i].female {
t.Errorf("RU pair %d disagrees on gender: %q vs %q", i, firstNamesFullRU[i].name, firstNamesShortRU[i].name)
}
}
for _, sp := range surnamesRU {
if sp.m == sp.f {
t.Errorf("RU surname forms should differ: %q", sp.m)
}
}
}
// TestRobotDisplayNames checks the generated pools are the right size, non-empty and
// deterministic — durable robot accounts must keep a stable name across restarts.
func TestRobotDisplayNames(t *testing.T) {
en1, en2 := robotDisplayNamesEN(), robotDisplayNamesEN()
ru1, ru2 := robotDisplayNamesRU(), robotDisplayNamesRU()
if len(en1) != robotPoolSize || len(ru1) != robotPoolSize {
t.Fatalf("pool sizes en=%d ru=%d, want %d", len(en1), len(ru1), robotPoolSize)
}
for i := range en1 {
if en1[i] != en2[i] || ru1[i] != ru2[i] {
t.Fatalf("pool not deterministic at %d: en %q/%q ru %q/%q", i, en1[i], en2[i], ru1[i], ru2[i])
}
if en1[i] == "" || ru1[i] == "" {
t.Fatalf("empty composed name at index %d", i)
}
}
}
// TestPickVariantRouting checks English games draw the Latin pool and Russian games
// draw mostly Russian names with a Latin minority.
func TestPickVariantRouting(t *testing.T) {
enID, ruID := uuid.New(), uuid.New()
s := &Service{poolEN: []uuid.UUID{enID}, poolRU: []uuid.UUID{ruID}}
for i := 0; i < 200; i++ {
if got, err := s.Pick(engine.VariantEnglish); err != nil || got != enID {
t.Fatalf("scrabble_en Pick = (%v, %v), want (%v, nil)", got, err, enID)
}
}
var en, ru int
for i := 0; i < 4000; i++ {
got, err := s.Pick(engine.VariantRussianScrabble)
if err != nil {
t.Fatalf("scrabble_ru Pick: %v", err)
}
switch got {
case enID:
en++
case ruID:
ru++
}
}
if ru <= en {
t.Errorf("scrabble_ru names should dominate a Russian game: ru=%d en=%d", ru, en)
}
if en == 0 {
t.Errorf("some Latin names should appear in Russian games (got 0 of 4000)")
}
// Эрудит routes like Russian Scrabble.
if _, err := s.Pick(engine.VariantErudit); err != nil {
t.Errorf("erudit_ru Pick: %v", err)
}
}
// TestPickFallback checks an empty side falls back to the other pool and an empty pool
// errors.
func TestPickFallback(t *testing.T) {
id := uuid.New()
if got, err := (&Service{poolEN: []uuid.UUID{id}}).Pick(engine.VariantRussianScrabble); err != nil || got != id {
t.Errorf("scrabble_ru fallback to EN = (%v, %v), want (%v, nil)", got, err, id)
}
if got, err := (&Service{poolRU: []uuid.UUID{id}}).Pick(engine.VariantEnglish); err != nil || got != id {
t.Errorf("scrabble_en fallback to RU = (%v, %v), want (%v, nil)", got, err, id)
}
if _, err := (&Service{}).Pick(engine.VariantEnglish); !errors.Is(err, ErrNoRobotAvailable) {
t.Errorf("empty pool err = %v, want ErrNoRobotAvailable", err)
}
}