ed53e25e57
CI / changes (pull_request) Successful in 5s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 20s
CI / ui (pull_request) Has been skipped
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m41s
Cap a player's simultaneous unfinished games per kind (vs_ai, random, friends) with independent guest and durable-account tiers, held in a new single-row backend.config table (-1 = unlimited) behind an in-memory cache and editable live in the admin console (/_gm/limits). Each game is tagged with games.game_kind on creation. This replaces the earlier flat MaxActiveQuickGames=10 combined cap: the per-tier/kind config is the single mechanism, enforced at the same handler gate (ensureUnderGameLimit by kind on lobby/enqueue) plus the durable friends cap in CreateInvitation. game.Service.AtGameLimit only resolves the tier and counts; the limit policy stays at the request edge. Guests are now refused friend requests, friend-code redemption, befriend-in-game and invitation creation outright (403 guest_forbidden) -- previously only the UI hid these. Admin: a kind column in both game lists and the config editor. Defaults: guest 1 vs_ai / 1 random / 0 friends; durable 10 / 10 / 10.
45 lines
1.4 KiB
Go
45 lines
1.4 KiB
Go
package gamelimits
|
|
|
|
import "testing"
|
|
|
|
// TestLimitsCap checks Cap maps each kind to its field and leaves the unknown kind uncapped, so a
|
|
// untagged game (game_kind 0) is never gated.
|
|
func TestLimitsCap(t *testing.T) {
|
|
l := Limits{VsAI: 1, Random: 2, Friends: 3}
|
|
for _, tc := range []struct {
|
|
kind Kind
|
|
want int
|
|
}{
|
|
{KindVsAI, 1},
|
|
{KindRandom, 2},
|
|
{KindFriends, 3},
|
|
{KindUnknown, Unlimited},
|
|
{Kind(99), Unlimited},
|
|
} {
|
|
if got := l.Cap(tc.kind); got != tc.want {
|
|
t.Errorf("Cap(%d) = %d, want %d", tc.kind, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestServiceLimitsForTier checks LimitsFor selects the guest or durable tier from the cached config.
|
|
func TestServiceLimitsForTier(t *testing.T) {
|
|
svc := NewService(nil)
|
|
svc.set(Config{
|
|
Guest: Limits{VsAI: 1, Random: 1, Friends: 0},
|
|
Durable: Limits{VsAI: 10, Random: 10, Friends: 10},
|
|
})
|
|
|
|
if got := svc.LimitsFor(true); got != (Limits{VsAI: 1, Random: 1, Friends: 0}) {
|
|
t.Errorf("guest limits = %+v, want {1 1 0}", got)
|
|
}
|
|
if got := svc.LimitsFor(false); got != (Limits{VsAI: 10, Random: 10, Friends: 10}) {
|
|
t.Errorf("durable limits = %+v, want {10 10 10}", got)
|
|
}
|
|
// The unlimited sentinel resolves through the tier too.
|
|
svc.set(Config{Durable: Limits{VsAI: Unlimited, Random: Unlimited, Friends: Unlimited}})
|
|
if got := svc.LimitsFor(false).Cap(KindVsAI); got != Unlimited {
|
|
t.Errorf("durable vs_ai cap = %d, want unlimited (-1)", got)
|
|
}
|
|
}
|