Files
settled-reach/client/tests/test_e2e_connection.gd
T
jpmschweitzerandClaude Opus 4.6 5e4b8dbcf3 feat(client): add E2E connection test and batch encoding tests
D-030 Layer 3: E2E test spawns the Rust server binary, connects via
LocalBridge, sends a batched MoveNorth input, and verifies the player
moved to (16.5, 15.5). Also adds batch encoding roundtrip tests,
framed batch test, and Player entity fixture decode test. 43/43 pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 21:44:27 +01:00

100 lines
3.2 KiB
GDScript

## D-030 Layer 3: End-to-end connection test
## Spawns the Rust simulation server, connects via LocalBridge,
## sends a MoveNorth input, and verifies the snapshot response.
## Requires: server binary built (cargo build in server/)
class_name TestE2EConnection
extends GdUnitTestSuite
const TEST_PORT: int = 19876
const CONNECT_TIMEOUT: float = 3.0
const RESPONSE_TIMEOUT: float = 5.0
var _server_pid: int = -1
var _bridge: LocalBridge = null
func _server_binary_path() -> String:
# Resolve path relative to the Godot project root
var project_dir := ProjectSettings.globalize_path("res://")
return project_dir.path_join("../server/target/debug/settled-reach-server")
func after_test() -> void:
# Clean up regardless of test outcome
if _bridge != null:
_bridge.disconnect_from_server()
_bridge = null
if _server_pid > 0 and OS.is_process_running(_server_pid):
OS.kill(_server_pid)
_server_pid = -1
# -- E2E: full round-trip through server binary --------------------------------
func test_send_input_receive_snapshot() -> void:
var server_path := _server_binary_path()
if not FileAccess.file_exists(server_path):
push_warning("E2E test skipped: server binary not found at %s" % server_path)
return
# Spawn server
var addr := "127.0.0.1:%d" % TEST_PORT
_server_pid = OS.create_process(server_path, [addr])
assert_that(_server_pid).is_greater(0)
# Connect with retries (server needs time to bind port)
_bridge = LocalBridge.new()
var connected := false
var elapsed := 0.0
while elapsed < CONNECT_TIMEOUT:
if _bridge.get_status() == StreamPeerTCP.STATUS_NONE:
_bridge.connect_to_server("127.0.0.1", TEST_PORT)
_bridge.poll()
if _bridge.get_status() == StreamPeerTCP.STATUS_CONNECTED:
connected = true
break
if _bridge.get_status() == StreamPeerTCP.STATUS_ERROR:
# Reset and retry
_bridge.disconnect_from_server()
_bridge.reset()
await get_tree().create_timer(0.1).timeout
elapsed += 0.1
assert_bool(connected).is_true()
# Send batch input: MoveNorth at tick 0 (matching game_loop.rs test)
var inputs: Array = [{"tick": 0, "action_name": "MoveNorth"}]
var encoded := Protocol.encode_player_inputs(inputs)
assert_that(encoded.size()).is_greater(0)
var send_err := _bridge.send_message(encoded)
assert_that(send_err).is_equal(OK)
# Poll for snapshot response
var snapshot_bytes := PackedByteArray()
elapsed = 0.0
while elapsed < RESPONSE_TIMEOUT:
_bridge.poll()
snapshot_bytes = _bridge.poll_message()
if snapshot_bytes.size() > 0:
break
await get_tree().create_timer(0.05).timeout
elapsed += 0.05
assert_that(snapshot_bytes.size()).is_greater(0)
# Decode snapshot
var snapshot: Variant = Protocol.decode_snapshot(snapshot_bytes)
assert_that(snapshot).is_not_null()
# Server starts at tick 0, snapshot reflects state after processing
assert_that(snapshot.tick).is_equal(0)
assert_that(snapshot.entities.size()).is_equal(1)
# Player started at (16, 16, 0), moved north (y-1) to (16, 15, 0)
# Render coords: tile center offset → (16.5, 15.5, 0)
var player: Dictionary = snapshot.entities[0]
assert_float(player.x).is_equal_approx(16.5, 0.001)
assert_float(player.y).is_equal_approx(15.5, 0.001)
assert_that(player.z).is_equal(0)
assert_that(player.kind.variant).is_equal("Player")