Files
CosmicClash/Game/tests/cases/test_match_net.gd
T
Josh Creek 4533da34e0 feat(multiplayer): Phase 1 transport, connection, and lobby
Lands tasks 1.0-1.8 of multiplayer-todo.md: the pure-function test runner,
net_codec (wire format quantizers/pack-unpack), NetworkManager (ENet
transport, manual polling, min-RTT clock sync), MatchNet (handshake,
protocol/tick-rate gating, roster with team+ready state), lobby.tscn (team
columns, switch team, ready toggle), server_boot.tscn (headless dedicated
server with structured logging and an overrun watchdog), and main_menu.gd's
Host/Join-by-IP UI (connecting overlay, cancel, bounded failure path).

Followed by an adversarial review (Opus subagent) that found and fixed two
real bugs - an unvalidated player_name broadcast that let one client's
oversized name head-of-line-block the reliable channel for everyone, and a
server-side roster leak across a host/re-host cycle - plus three gaps in
the test suite itself where a claim of "verified" wasn't actually backed
by what the test checked. All five two-process smoke tests plus the
pure-function suite are green with the strengthened assertions in place.
2026-08-20 08:18:59 +01:00

40 lines
1.8 KiB
GDScript

extends "res://tests/test_case.gd"
const MatchNet = preload("res://scripts/match_net.gd")
# Adversarial-review regression: _hello's player_name used to be broadcast
# to every peer completely unvalidated — a multi-MB name head-of-line-
# blocked the reliable control channel hard enough that a concurrently-
# joining client's own _welcome never arrived. _sanitize_player_name() is
# the fix; these are pure-function tests for it, independent of the live
# two-process rejection test in tests/match_net_smoke.gd (--role=client-longname).
func test_normal_name_unchanged() -> void:
assert_eq(MatchNet._sanitize_player_name("Alice"), "Alice", "a normal name passes through unchanged")
func test_strips_control_characters() -> void:
var bell := String.chr(7) # a control char with no named GDScript escape
var raw := "Bad\nName\twith\rcontrol" + bell + "chars"
var clean := MatchNet._sanitize_player_name(raw)
assert_true(not clean.contains("\n"), "no newline")
assert_true(not clean.contains("\t"), "no tab")
assert_true(not clean.contains("\r"), "no carriage return")
assert_true(not clean.contains(bell), "no bell/control char")
func test_clamps_to_max_display_length() -> void:
var raw := "X".repeat(1000)
var clean := MatchNet._sanitize_player_name(raw)
assert_eq(clean.length(), MatchNet.MAX_PLAYER_NAME_LENGTH, "clamped to MAX_PLAYER_NAME_LENGTH")
func test_empty_or_whitespace_only_falls_back_to_default() -> void:
assert_eq(MatchNet._sanitize_player_name(""), "Player", "empty string falls back")
assert_eq(MatchNet._sanitize_player_name(" "), "Player", "whitespace-only falls back")
assert_eq(MatchNet._sanitize_player_name("\n\t\r"), "Player", "control-characters-only falls back")
func test_leading_trailing_whitespace_trimmed() -> void:
assert_eq(MatchNet._sanitize_player_name(" Bob "), "Bob", "surrounding whitespace trimmed")