mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-12 14:15:12 +00:00
wsapi_test.go was 2688 lines and 82 tests spanning every topic in the package. Pure moves: wsapi_test.go keeps the harness (the hand-built dictionary, the server-over-a-real-socket helpers, and the client-side driving methods every topic file below uses); game_test.go, presence_test.go, resume_test.go, lobby_test.go, protocol_test.go, ratelimit_test.go, bot_board_test.go, chat_test.go, deadend_test.go and report_test.go each hold one topic's tests. Three tests about sanitizing and distinguishing nicknames moved into the existing nickname_test.go alongside its fuzz target. hub_test.go was three lines of comment pointing at hub.go; the note now lives there instead, next to roomCount. Verified by counting: the wsapi package's declaration count is unchanged, `go test -list` finds the same tests it did before the split, and go build/vet/test -race and golangci-lint stay clean.
105 lines
2.6 KiB
Go
105 lines
2.6 KiB
Go
package wsapi
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
noituv1 "github.com/tiennm99dev/noitu/server/gen/noitu/v1"
|
|
)
|
|
|
|
// The rate limiters themselves, and the actions that spend them.
|
|
|
|
// TestSubmitRateLimit checks the token bucket refuses a burst well past what a
|
|
// person types, since each submit costs a dictionary lookup.
|
|
func TestSubmitRateLimit(t *testing.T) {
|
|
_, url := newTestServer(t, chainDict(), Config{})
|
|
c := dial(t, url)
|
|
c.hello("Người thử")
|
|
c.send(&noituv1.ClientMessage{Payload: &noituv1.ClientMessage_StartBotGame{
|
|
StartBotGame: &noituv1.StartBotGame{Difficulty: noituv1.Difficulty_DIFFICULTY_EASY},
|
|
}})
|
|
started := c.await("game_started").GetGameStarted()
|
|
|
|
for range submitBurst + 5 {
|
|
c.submit("khong co", started.GetTurnSeq())
|
|
}
|
|
|
|
for range 40 {
|
|
m := c.recv()
|
|
if payloadCase(m) == "error" && m.GetError().GetCode() == "too_fast" {
|
|
return
|
|
}
|
|
}
|
|
t.Error("never rate-limited despite submitting past the burst")
|
|
}
|
|
|
|
func TestBucketRefills(t *testing.T) {
|
|
now := time.Now()
|
|
b := newBucket(5, 2, now)
|
|
|
|
for i := range 2 {
|
|
if !b.allow(now) {
|
|
t.Fatalf("burst should cover the first two, call %d refused", i+1)
|
|
}
|
|
}
|
|
if b.allow(now) {
|
|
t.Fatal("third call should exhaust the bucket")
|
|
}
|
|
|
|
// 5/sec means one token back after 200ms.
|
|
if !b.allow(now.Add(220 * time.Millisecond)) {
|
|
t.Error("bucket did not refill")
|
|
}
|
|
}
|
|
|
|
func TestKeyedLimiterIsPerKey(t *testing.T) {
|
|
now := time.Now()
|
|
l := newKeyedLimiter(1, 1, time.Minute)
|
|
|
|
if !l.allow("a", now) {
|
|
t.Fatal("first call for a key should pass")
|
|
}
|
|
if l.allow("a", now) {
|
|
t.Fatal("second call for the same key should be refused")
|
|
}
|
|
if !l.allow("b", now) {
|
|
t.Error("a different key must have its own bucket")
|
|
}
|
|
}
|
|
|
|
func TestKeyedLimiterSweepsIdleBuckets(t *testing.T) {
|
|
now := time.Now()
|
|
l := newKeyedLimiter(10, 5, time.Minute)
|
|
l.allow("gone", now)
|
|
|
|
l.sweep(now.Add(2 * time.Minute))
|
|
|
|
l.mu.Lock()
|
|
n := len(l.buckets)
|
|
l.mu.Unlock()
|
|
if n != 0 {
|
|
t.Errorf("%d buckets survived the sweep, want 0", n)
|
|
}
|
|
}
|
|
|
|
// TestLobbyActionsAreRateLimited: every accepted action is broadcast to both
|
|
// seats, so an unbounded one lets a player fill the opponent's outbox until
|
|
// the server closes their session for falling behind.
|
|
func TestLobbyActionsAreRateLimited(t *testing.T) {
|
|
_, url := newTestServer(t, chainDict(), Config{})
|
|
_, guest, _ := pvpLobby(t, url)
|
|
|
|
for i := range submitBurst + 5 {
|
|
guest.setReady(i%2 == 0)
|
|
}
|
|
|
|
// The limiter answers before the room does, so a refusal has to appear in
|
|
// the stream rather than an unbroken run of room states.
|
|
for range 30 {
|
|
if payloadCase(guest.recv()) == "error" {
|
|
return
|
|
}
|
|
}
|
|
t.Error("a burst of lobby actions was never refused")
|
|
}
|