Files
Commonwealth-Online-Server/server/tests/test_networking_v2.py
T

151 lines
4.4 KiB
Python

from __future__ import annotations
import json
import socket
import time
import pytest
from server_core import PROTOCOL_VERSION, FalloutTogetherServer, states_share_interest
def recv_packet(sock: socket.socket, timeout: float = 2.0) -> dict:
sock.settimeout(timeout)
data = b""
while b"\n" not in data:
data += sock.recv(4096)
line, _ = data.split(b"\n", 1)
return json.loads(line.decode("utf-8"))
def send_packet(sock: socket.socket, packet: dict) -> None:
sock.sendall(json.dumps(packet, separators=(",", ":")).encode() + b"\n")
def connect(server: FalloutTogetherServer) -> tuple[socket.socket, dict]:
sock = socket.create_connection(("127.0.0.1", server.port), timeout=2.0)
return sock, recv_packet(sock)
def hello(sock: socket.socket) -> dict:
send_packet(sock, {"type": "hello", "protocolVersion": PROTOCOL_VERSION})
return recv_packet(sock)
def transform(cell: str, x: float, y: float, world: str = "0000003C") -> dict:
return {
"type": "transform",
"x": x,
"y": y,
"z": 0.0,
"angleZ": 0.0,
"cellId": cell,
"worldspaceId": world,
}
def start_server(max_players: int = 16) -> FalloutTogetherServer:
server = FalloutTogetherServer(host="127.0.0.1", port=0, max_players=max_players)
server.start()
deadline = time.time() + 2
while not server.is_running() and time.time() < deadline:
time.sleep(0.01)
return server
def test_interest_exact_cell_and_exterior_radius():
a = transform("00000001", 0, 0)
same = transform("00000001", 50000, 50000)
near = transform("00000002", 1000, 1000)
far = transform("00000002", 20000, 20000)
other_world = transform("00000002", 1000, 1000, world="0000003D")
assert states_share_interest(a, same)
assert states_share_interest(a, near)
assert not states_share_interest(a, far)
assert not states_share_interest(a, other_world)
def test_idle_transport_does_not_take_world_authority():
server = start_server()
idle, _ = connect(server)
active, active_welcome = connect(server)
ready = hello(active)
assert ready["playerId"] == active_welcome["playerId"]
stats = server.get_stats()
assert stats["connectedClients"] == 1
assert stats["pendingConnections"] == 1
assert server._world_state_host_player_id == active_welcome["playerId"]
idle.close()
active.close()
server.stop()
def test_max_players_applies_to_activated_sessions_not_probes():
server = start_server(max_players=1)
first, _ = connect(server)
hello(first)
second, _ = connect(server)
send_packet(second, {"type": "hello", "protocolVersion": PROTOCOL_VERSION})
ended = recv_packet(second)
assert ended["type"] == "sessionEnded"
assert ended["code"] == "server_full"
first.close()
second.close()
server.stop()
def test_protocol_mismatch_is_rejected():
server = start_server()
sock, _ = connect(server)
send_packet(sock, {"type": "hello", "protocolVersion": PROTOCOL_VERSION + 1})
ended = recv_packet(sock)
assert ended["type"] == "sessionEnded"
assert ended["code"] == "protocol_mismatch"
sock.close()
server.stop()
def test_nan_transform_is_rejected_and_not_cached():
server = start_server()
sock, welcome = connect(server)
hello(sock)
sock.sendall(
b'{"type":"transform","x":NaN,"y":0,"z":0,"angleZ":0,"cellId":"00000001"}\n'
)
time.sleep(0.05)
client = server._find_client_by_player_id(welcome["playerId"])
assert client is not None
assert client.last_transform is None
assert server.get_stats()["packetsRejected"] >= 1
sock.close()
server.stop()
def test_transform_interest_filters_distant_peer():
server = start_server()
a, _ = connect(server)
hello(a)
b, _ = connect(server)
hello(b)
send_packet(b, transform("00000020", 25000, 25000))
time.sleep(0.05)
send_packet(a, transform("00000010", 0, 0))
b.settimeout(0.2)
with pytest.raises(socket.timeout):
b.recv(4096)
assert server.get_stats()["transformPacketsInterestFiltered"] >= 1
a.close()
b.close()
server.stop()
def test_oversized_unterminated_packet_closes_session():
server = start_server()
sock, _ = connect(server)
sock.sendall(b"x" * (64 * 1024 + 1))
ended = recv_packet(sock)
assert ended["type"] == "sessionEnded"
assert ended["code"] == "packet_too_large"
sock.close()
server.stop()