mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-11 03:13:45 +00:00
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:
1 parent
f00d0ef974
commit
e29c06d6f0
11 files changed
+64
-50
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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", "Minh", "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")
|
||||
|
||||
Reference in new issue
Block a user