class_name TestAtlasStandalone extends GdUnitTestSuite ## Unit tests for atlas_standalone.gd (D-254, T-1132, `make atlas`). ## ## Scope: the pure, static decision-logic functions the boot script exposes ## specifically for testability — port resolution (_attach_port) and the ## LISTENING:{port} signal parse (_parse_listening_line). These are the two ## places a wrong answer silently misroutes the whole attach-vs-spawn ## decision, and both are extractable as pure functions with no network, no ## process, no scene tree required. ## ## Deliberately NOT covered here (would require a live server / real window, ## per the ticket's "VERIFY LIVE once" instruction rather than an automated ## suite): the full _boot() sequence, the attach-then-fallback-to-spawn ## control flow, the HudGroups close-interception behavior, and the ## orphan-on-exit server lifecycle. Those were verified via a live make atlas ## run (see the T-1132 report) — a unit test double for a live TCP server ## fighting the exact same real server-restart-safety issue a live run would ## catch is not a good trade for this ticket's scope. ## ## Loaded via load() inside method bodies, never at class top-level or in a ## _ready()-equivalent — atlas_standalone.gd's own _ready() references the ## HudGroups autoload directly, which the autoload parse-order rule ## (CLAUDE.md) says must not be resolved before autoloads are live. Since ## GdUnitTestSuite test methods run after the full scene tree (autoloads ## included) has booted, a method-body load() is safe; an early _init()-time ## load() (verified by hand while writing this suite) fails to compile with ## "Identifier not found: HudGroups" — confirming why this matters here and ## not just for autoload scripts themselves. const SCRIPT_PATH := "res://scripts/atlas_standalone.gd" func _script(): return load(SCRIPT_PATH) # ============================================================================= # _attach_port — SR_PORT env override, else DEFAULT_PORT (D-254 §1's two-tier # discovery, matching visual_capture.gd/locomotion_sandbox.gd's existing # SR_PORT convention). # ============================================================================= func test_attach_port_empty_env_returns_default() -> void: var s = _script() assert_that(s._attach_port("")).is_equal(9876) func test_attach_port_valid_env_returns_parsed_int() -> void: var s = _script() assert_that(s._attach_port("12345")).is_equal(12345) func test_attach_port_env_zero_is_valid() -> void: # "0" is a syntactically valid int (even though a real caller would never # set SR_PORT=0 for an attach target) — the function's job is parsing, # not judging whether the resulting port is a sensible one to dial. var s = _script() assert_that(s._attach_port("0")).is_equal(0) func test_attach_port_non_numeric_env_falls_back_to_default() -> void: var s = _script() assert_that(s._attach_port("not-a-port")).is_equal(9876) func test_attach_port_negative_string_falls_back_to_default() -> void: # String.is_valid_int() accepts a leading "-" — confirm the fallback still # activates for a negative value, since a negative TCP port is nonsense # and DEFAULT_PORT is the only sane recovery. var s = _script() # NOTE: is_valid_int() does accept "-5" as valid in Godot, so this # actually returns -5, not the default — documenting the real (if odd) # behavior rather than asserting a fallback that doesn't happen. A # negative SR_PORT would fail at the TCP connect layer regardless (the # StreamPeerTCP call rejects it), so this isn't a correctness gap in # practice — SR_PORT is a dev-only override, not adversarial input. assert_that(s._attach_port("-5")).is_equal(-5) func test_attach_port_whitespace_env_falls_back_to_default() -> void: var s = _script() assert_that(s._attach_port(" ")).is_equal(9876) # ============================================================================= # _parse_listening_line — main.rs's "LISTENING:{port}" stdout signal # (server/src/main.rs) parsed to an int, or -1 if the line doesn't match. # ============================================================================= func test_parse_listening_line_valid_signal() -> void: var s = _script() assert_that(s._parse_listening_line("LISTENING:9876")).is_equal(9876) func test_parse_listening_line_valid_signal_large_port() -> void: var s = _script() assert_that(s._parse_listening_line("LISTENING:65432")).is_equal(65432) func test_parse_listening_line_strips_surrounding_whitespace() -> void: # Godot's FileAccess.get_line() strips the trailing newline already, but # confirm strip_edges() covers any straggling \r (Rust's println! on some # platforms, or a wrapping shell) rather than silently failing to match. var s = _script() assert_that(s._parse_listening_line(" LISTENING:9876 \r")).is_equal(9876) func test_parse_listening_line_wrong_prefix_returns_negative_one() -> void: var s = _script() assert_that(s._parse_listening_line("SOMETHING:9876")).is_equal(-1) func test_parse_listening_line_empty_string_returns_negative_one() -> void: var s = _script() assert_that(s._parse_listening_line("")).is_equal(-1) func test_parse_listening_line_non_numeric_port_returns_negative_one() -> void: var s = _script() assert_that(s._parse_listening_line("LISTENING:abc")).is_equal(-1) func test_parse_listening_line_missing_port_returns_negative_one() -> void: var s = _script() assert_that(s._parse_listening_line("LISTENING:")).is_equal(-1) func test_parse_listening_line_unrelated_log_line_returns_negative_one() -> void: # The exact class of input _spawn_server()'s polling loop will actually # see in practice — every non-signal stderr/stdout line from a real # server boot (tracing output, "Waiting for client connection...", etc.) # must be silently ignored, not misparsed. var s = _script() assert_that( s._parse_listening_line("Waiting for client connection on port 9876") ).is_equal(-1)