package lobby import ( "strings" "testing" "time" ) func TestAddDriver(t *testing.T) { s := NewService() d, err := s.AddDriver("d1", "Alice", "RU") if err != nil { t.Fatalf("AddDriver: %v", err) } if d.Status != DriverStatusIdle { t.Errorf("expected idle, got %s", d.Status) } if d.Name != "Alice" { t.Errorf("name: got %s", d.Name) } } func TestCreateRaceValidates(t *testing.T) { s := NewService() _, _ = s.AddDriver("d1", "Alice", "") tests := []struct { name string opts CreateRaceOptions want string // substring of error message or "" for OK }{ {"empty name", CreateRaceOptions{Name: "", MaxCars: 2, Laps: 1}, "name required"}, {"max cars too low", CreateRaceOptions{Name: "r", MaxCars: 0, Laps: 1}, ""}, // defaults {"max cars too high", CreateRaceOptions{Name: "r", MaxCars: 9, Laps: 1}, "max_cars must be"}, {"no laps and no time", CreateRaceOptions{Name: "r", MaxCars: 2}, "laps or time_limit_s"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := s.CreateRace(tt.opts) if tt.want == "" && err != nil { t.Fatalf("expected ok, got %v", err) } if tt.want != "" && err == nil { t.Fatalf("expected error containing %q, got nil", tt.want) } if tt.want != "" && err != nil && !strings.Contains(err.Error(), tt.want) { t.Fatalf("error %q does not contain %q", err.Error(), tt.want) } }) } } func TestCreateRaceEmpty(t *testing.T) { s := NewService() r, err := s.CreateRace(CreateRaceOptions{ Name: "open", TrackID: "default", MaxCars: 4, Laps: 5, }) if err != nil { t.Fatalf("CreateRace: %v", err) } if r.Status != RaceStatusLobby { t.Errorf("status: got %s", r.Status) } if len(r.DriverIDs) != 0 { t.Errorf("expected empty drivers, got %d", len(r.DriverIDs)) } if r.TrackID != "default" { t.Errorf("track: got %s", r.TrackID) } } func TestQuickPlayJoinsFirstRace(t *testing.T) { s := NewService() _, _ = s.AddDriver("d1", "Alice", "") _, _ = s.AddDriver("d2", "Bob", "") car1, car2 := "car-1", "car-2" _ = s.SelectCar("d1", &car1) _ = s.SelectCar("d2", &car2) r1, _ := s.QuickPlay("d1", 4) r2, err := s.QuickPlay("d2", 4) if err != nil { t.Fatalf("QuickPlay d2: %v", err) } if r2.ID != r1.ID { t.Errorf("expected to join %s, got %s", r1.ID, r2.ID) } if len(r2.DriverIDs) != 2 { t.Errorf("expected 2 drivers, got %d", len(r2.DriverIDs)) } } func TestSubscribeReceivesSnapshot(t *testing.T) { s := NewService() ch, cancel := s.Subscribe() defer cancel() // Subscribe pushes initial snapshot. select { case ev := <-ch: if ev.kind != "snapshot" { t.Fatalf("first event: kind=%s", ev.kind) } case <-time.After(time.Second): t.Fatal("no initial snapshot") } } func TestSubscribeReceivesRaceCreated(t *testing.T) { s := NewService() ch, cancel := s.Subscribe() defer cancel() <-ch // drain initial snapshot _, _ = s.AddDriver("d1", "Alice", "") select { case ev := <-ch: if ev.kind != "driver_joined" { t.Fatalf("expected driver_joined, got %s", ev.kind) } case <-time.After(time.Second): t.Fatal("no event after AddDriver") } }