refactor: move test-only helpers out of production code and drop stale comments

bot.BoardFor and hub.roomCount were reachable from tests only and now live beside them; session.serve drops an always-true nil check (readLoop never returns nil); invisible characters in test literals become escapes; comments no longer cite plan phases; `.dockerignore` keeps the dump out of the build context. No behaviour change; vet, deadcode, staticcheck, go test -race, svelte-check and vitest green.
This commit is contained in:
tiennm99 committed 2026-09-08 23:10:39 +07:00
1 parent f00d0ef974
commit e29c06d6f0
11 files changed
+64 -50

No files matched your search

+1 -1
View File
@@ -59,7 +59,7 @@ func TestStripWikitext(t *testing.T) {
"Một nghĩa."},
{"a stray closer is not text", "Một }} nghĩa.", "Một nghĩa."},
{"only punctuation is empty", "(...).", ""},
{"control characters and whitespace collapse", " Một từ \t hai ", "Một từ hai"},
{"control characters and whitespace collapse", " Một\a từ \t hai ", "Một từ hai"},
{"unclosed template does not leak", "Một {{label|vi|x từ.", "Một"},
}
for _, c := range cases {
+36
View File
@@ -0,0 +1,36 @@
package bot
import (
"iter"
"github.com/tiennm99dev/noitu/server/internal/game"
)
// engineBoard adapts an Engine to Board for the simulations in this package.
// The server never needs it: the room hands the bot a Board of its own.
type engineBoard struct {
e *game.Engine
dict game.Dictionary
}
// BoardFor wraps an engine so a test can drive a strategy against it.
//
// The dictionary comes from the engine rather than the caller: handing in a
// different one would let the bot search a graph the engine does not validate
// against, and that failure surfaces as the engine rejecting its own bot's
// move at runtime.
func BoardFor(e *game.Engine) Board {
return &engineBoard{e: e, dict: e.Dict()}
}
func (b *engineBoard) LegalMoves() []string { return b.e.LegalMoves() }
func (b *engineBoard) Used(word string) bool { return b.e.Used(word) }
func (b *engineBoard) WordsStartingWith(syllable string) iter.Seq[string] {
return b.dict.WordsStartingWith(syllable)
}
func (b *engineBoard) LastSyllable(word string) (string, bool) {
return b.dict.LastSyllable(word)
}
-30
View File
@@ -14,8 +14,6 @@ import (
"iter"
"math/rand/v2"
"time"
"github.com/tiennm99dev/noitu/server/internal/game"
)
// Difficulty selects a strategy.
@@ -96,34 +94,6 @@ func thinkingDelay(rng *rand.Rand, min, max time.Duration) time.Duration {
return min + time.Duration(rng.Int64N(int64(max-min)))
}
// engineBoard adapts an Engine to Board.
type engineBoard struct {
e *game.Engine
dict game.Dictionary
}
// BoardFor wraps an engine so strategies can inspect it.
//
// The dictionary comes from the engine rather than the caller: handing in a
// different one would let the bot search a graph the engine does not validate
// against, and that failure surfaces as the engine rejecting its own bot's
// move at runtime.
func BoardFor(e *game.Engine) Board {
return &engineBoard{e: e, dict: e.Dict()}
}
func (b *engineBoard) LegalMoves() []string { return b.e.LegalMoves() }
func (b *engineBoard) Used(word string) bool { return b.e.Used(word) }
func (b *engineBoard) WordsStartingWith(syllable string) iter.Seq[string] {
return b.dict.WordsStartingWith(syllable)
}
func (b *engineBoard) LastSyllable(word string) (string, bool) {
return b.dict.LastSyllable(word)
}
// remainingOutDegree counts the continuations still available from a syllable,
// ignoring `excluding` — the move being considered, which will itself be spent
// once played. Pass "" to exclude nothing.
+2 -2
View File
@@ -1,6 +1,6 @@
// Package wsapi is the transport layer: it translates between the game engine
// and the generated protobuf wire types, and (from phase 5 on) owns the
// WebSocket sessions and rooms built on top of them.
// and the generated protobuf wire types, and owns the WebSocket sessions and
// rooms built on top of them.
//
// The engine's reason enums and the wire enums are deliberately distinct
// types. Renumbering an internal constant must never silently change what a
-8
View File
@@ -230,14 +230,6 @@ func (h *hub) shutdown() {
}
}
// roomCount reports live rooms. Tests use it to assert eviction; nothing in
// the server depends on it.
func (h *hub) roomCount() int {
h.mu.Lock()
defer h.mu.Unlock()
return len(h.rooms)
}
// playerIDFor is the seat a session holds. Declared here because the mapping
// between a connection and a seat is registry knowledge, not game knowledge.
func playerIDFor(seatName string) game.PlayerID { return game.PlayerID(seatName) }
+9
View File
@@ -0,0 +1,9 @@
package wsapi
// roomCount reports live rooms, so a test can assert that a room was evicted.
// Nothing in the server depends on it.
func (h *hub) roomCount() int {
h.mu.Lock()
defer h.mu.Unlock()
return len(h.rooms)
}
+1 -1
View File
@@ -28,7 +28,7 @@ type Config struct {
// that also serves the frontend.
AllowedOrigins []string
// WebDir is the built frontend. Empty, or missing on disk, serves the API
// alone — which is the state until phase 6 produces a bundle.
// alone, which is how the server runs before the frontend has been built.
WebDir string
}
+3 -1
View File
@@ -276,8 +276,10 @@ func (s *session) run() {
s.close()
wg.Wait()
// readLoop only ever returns an error; a cancelled context is the one
// that means the server chose to close.
status, reason := websocket.StatusNormalClosure, ""
if err != nil && !errors.Is(err, context.Canceled) {
if !errors.Is(err, context.Canceled) {
status, reason = websocket.StatusPolicyViolation, "protocol error"
}
_ = s.conn.Close(status, reason)
+6 -4
View File
@@ -878,8 +878,8 @@ func TestSanitizeNickname(t *testing.T) {
{"empty falls back", "", defaultNickname},
{"whitespace only falls back", " \t\n ", defaultNickname},
{"control characters stripped", "Mi\x00nh\x07", "Minh"},
{"zero width stripped", "Mi​nh‍", "Minh"},
{"bidi override stripped", "Minh‮", "Minh"},
{"zero width stripped", "Mi\u200bnh\u200d", "Minh"},
{"bidi override stripped", "Minh\u202e", "Minh"},
{"whitespace collapsed", " Minh Nguyen ", "Minh Nguyen"},
{"newlines become spaces", "Minh\nNguyen", "Minh Nguyen"},
{"over length truncated", strings.Repeat("a", 40), strings.Repeat("a", maxNicknameRunes)},
@@ -939,8 +939,10 @@ func TestBucketRefills(t *testing.T) {
now := time.Now()
b := newBucket(5, 2, now)
if !b.allow(now) || !b.allow(now) {
t.Fatal("burst should cover the first two")
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")