3306a016a0
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 12s
CI / integration (pull_request) Successful in 28s
CI / ui (pull_request) Successful in 1m11s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m46s
Carry the caller's per-tier active-game caps and each game's kind on the wire (Profile.game_limits + GameView.kind, additive FBS + gateway transcode + client codec, committed regen). The New Game screen counts the player's active games per kind from the lobby and locks a capped start: an outline button with a lock that opens a funnel modal instead of a game -- a sign-in prompt for a guest, a "finish a current game first" notice for a signed-in account (native Telegram popup, in-app modal elsewhere). The lock lifts via the existing profile refetch after a guest->durable upgrade. Remove the lobby's old at_game_limit New-Game tab disable + notice: the flag (now the random-kind cap) conflicted with the per-kind lock -- it hid the screen where the lock lives and wrongly blocked an unfulfilled kind. The New Game tab is always enabled; the per-kind start lock is the only gate. The at_game_limit wire field stays (unused by the client) for a later cleanup. Tests: client lock logic + codec kind/game_limits roundtrip + gateway transcode encode + native popup builders (unit); a mock e2e for the lock badge and the modal.
62 lines
2.2 KiB
Go
62 lines
2.2 KiB
Go
package transcode_test
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"scrabble/gateway/internal/transcode"
|
|
fb "scrabble/pkg/fbs/scrabblefb"
|
|
)
|
|
|
|
// TestProfileGetEncodesGameLimits verifies the gateway forwards the backend's per-kind active-game
|
|
// caps into the Profile payload — the caps the client's New-Game lock reads. The encode is not
|
|
// exercised by the mock e2e (it bypasses the codec), so a dropped field would only surface here.
|
|
func TestProfileGetEncodesGameLimits(t *testing.T) {
|
|
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet || r.URL.Path != "/api/v1/user/profile" {
|
|
t.Errorf("unexpected %s %q", r.Method, r.URL.Path)
|
|
}
|
|
_, _ = w.Write([]byte(`{"user_id":"u-1","display_name":"Kaya","preferred_language":"en",` +
|
|
`"game_limits":{"vs_ai":1,"random":1,"friends":0}}`))
|
|
})
|
|
defer cleanup()
|
|
|
|
reg := transcode.NewRegistry(backend, nil)
|
|
op, ok := reg.Lookup(transcode.MsgProfileGet)
|
|
if !ok {
|
|
t.Fatal("profile.get not registered")
|
|
}
|
|
payload, err := op.Handler(context.Background(), transcode.Request{UserID: "u-1"})
|
|
if err != nil {
|
|
t.Fatalf("handler: %v", err)
|
|
}
|
|
|
|
gl := fb.GetRootAsProfile(payload, 0).GameLimits(nil)
|
|
if gl == nil {
|
|
t.Fatal("profile carries no game_limits block")
|
|
}
|
|
if gl.VsAi() != 1 || gl.Random() != 1 || gl.Friends() != 0 {
|
|
t.Errorf("game limits = %d/%d/%d, want 1/1/0", gl.VsAi(), gl.Random(), gl.Friends())
|
|
}
|
|
}
|
|
|
|
// TestProfileGetNoGameLimits verifies a profile without a game_limits block encodes none (the
|
|
// backend omits it only when the limits config is unwired; normally the block is present).
|
|
func TestProfileGetNoGameLimits(t *testing.T) {
|
|
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"user_id":"u-1","display_name":"Kaya","preferred_language":"en"}`))
|
|
})
|
|
defer cleanup()
|
|
|
|
reg := transcode.NewRegistry(backend, nil)
|
|
op, _ := reg.Lookup(transcode.MsgProfileGet)
|
|
payload, err := op.Handler(context.Background(), transcode.Request{UserID: "u-1"})
|
|
if err != nil {
|
|
t.Fatalf("handler: %v", err)
|
|
}
|
|
if p := fb.GetRootAsProfile(payload, 0); p.GameLimits(nil) != nil {
|
|
t.Error("profile without a game_limits block unexpectedly carries one")
|
|
}
|
|
}
|