package testenv import ( "context" "encoding/json" "fmt" "net/http" "testing" ) // Pilot bundles a registered Session with its resolved user_id and a // pre-built BackendUserClient so tests do not have to repeat the // resolution dance for each redeem call. type Pilot struct { Session *Session UserID string HTTP *BackendUserClient RaceName string } // EnrollPilots registers `count` pilots with synthetic // `Player01..PlayerNN` race names and the matching // `playerNN+suffix@example.com` emails, then has owner issue an // invite for each one and the pilot redeem it. The game must be in // `enrollment_open` (or any state that accepts invites + redeem). // // The helper exists because the engine's `/api/v1/admin/init` enforces // `len(races) >= 10`, so any runtime-driven scenario needs at least // ten enrolled members. Using it from tests keeps each pilot a real // authenticated user, exactly mirroring how operators would seed a // production game. func EnrollPilots(t *testing.T, plat *Platform, ownerHTTP *BackendUserClient, gameID string, count int, suffix string) []*Pilot { t.Helper() ctx, cancel := context.WithCancel(context.Background()) defer cancel() pilots := make([]*Pilot, 0, count) for i := 1; i <= count; i++ { raceName := fmt.Sprintf("Player%02d", i) email := fmt.Sprintf("player%02d+%s@example.com", i, suffix) sess := RegisterSession(t, plat, email) userID, err := sess.LookupUserID(ctx, plat) if err != nil { t.Fatalf("pilot %s: resolve user_id: %v", raceName, err) } raw, resp, err := ownerHTTP.Do(ctx, http.MethodPost, "/api/v1/user/lobby/games/"+gameID+"/invites", map[string]any{ "invited_user_id": userID, "race_name": raceName, }) if err != nil || resp.StatusCode != http.StatusCreated { t.Fatalf("pilot %s: issue invite: err=%v status=%d body=%s", raceName, err, resp.StatusCode, string(raw)) } var invite struct { InviteID string `json:"invite_id"` } if err := json.Unmarshal(raw, &invite); err != nil { t.Fatalf("pilot %s: decode invite: %v", raceName, err) } pilotHTTP := NewBackendUserClient(plat.Backend.HTTPURL, userID) raw, resp, err = pilotHTTP.Do(ctx, http.MethodPost, "/api/v1/user/lobby/games/"+gameID+"/invites/"+invite.InviteID+"/redeem", nil) if err != nil || resp.StatusCode/100 != 2 { t.Fatalf("pilot %s: redeem: err=%v status=%d body=%s", raceName, err, resp.StatusCode, string(raw)) } pilots = append(pilots, &Pilot{ Session: sess, UserID: userID, HTTP: pilotHTTP, RaceName: raceName, }) } return pilots }