Files
mcm-26-b/referee/tests/test_http_server.py
T
2026-09-13 18:43:25 +08:00

134 lines
5.2 KiB
Python

"""真实 HTTP 承载测试:接口仅回环、仅窗口期开放、协议边界与官方一致。"""
# pylint: disable=redefined-outer-name # pytest 夹具名必须与测试函数形参同名,非遮蔽缺陷
import http.client
import json
import time
import pytest
from referee import Referee
from referee.server import ActionInterfaceServer
def wait_until(condition, timeout=5.0, interval=0.02):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if condition():
return True
time.sleep(interval)
return False
@pytest.fixture()
def local_referee(tmp_path):
ref = Referee(action_port=0, data_dir=tmp_path / "data")
ActionInterfaceServer(ref)
return ref
def open_port(server):
assert server._httpd is not None
return server._httpd.server_address[1]
def post(port, path, obj=None, *, headers=None, method="POST", raw=None):
conn = http.client.HTTPConnection("127.0.0.1", port, timeout=5)
body = raw if raw is not None else json.dumps(obj or {}).encode("utf-8")
conn.request(method, path, body=body,
headers=headers or {"Content-Type": "application/json"})
response = conn.getresponse()
data = response.read()
conn.close()
parsed = json.loads(data) if data else None
return response.status, parsed
def payload(request_id="e", **extra):
return dict(arena_id="default", robot_id="TEAM-0001", request_id=request_id, **extra)
def start_open_session(local_referee, **opts):
opts.setdefault("countdown_s", 0.05)
session = local_referee.start("q3_practice", "TEAM-0001", **opts)
assert wait_until(lambda: session.phase == "open")
return session
def test_binding_is_loopback_only(local_referee):
_session = start_open_session(local_referee)
server = local_referee._open_interface.__self__ # ActionInterfaceServer
port = open_port(server)
assert server._httpd.server_address[0] == "127.0.0.1"
status, body = post(port, "/enter", payload("enter"))
assert status == 200 and body["accepted"] is True
assert body["remaining_real_duration_s"] == 1200
def test_interface_refuses_connections_outside_window(local_referee):
session = local_referee.start("q3_practice", "TEAM-0001", countdown_s=5)
server = local_referee._open_interface.__self__
assert server.is_open is False # 倒计时期间未监听
assert wait_until(lambda: session.phase == "open", timeout=6)
assert server.is_open is True # 窗口开启后才绑定端口
def test_full_protocol_round_trip_over_http(local_referee):
session = start_open_session(local_referee)
server = local_referee._open_interface.__self__
port = open_port(server)
status, body = post(port, "/enter", payload("enter-1"))
assert status == 200 and body["accepted"] is True
assert body["virtual_time_s"] == 0
# 远点检测:目标域内源 r_eff<=1500,必然 no_signal
status, body = post(port, "/measure", payload(
"m-1", position=dict(x=5000, y=0), channel=1))
assert status == 200 and body["measure_result"] == "no_signal"
assert body["virtual_time_s"] == 1005 # 移动 1000s + 检测 5s
status, body = post(port, "/clear", payload(
"c-1", position=dict(x=5000, y=0), channel=2))
assert body["clear_result"] == "no_target_in_range"
assert body["virtual_time_s"] == 1008 # 未发现 3s
status, body = post(port, "/exit", payload("exit-1"))
assert body["exit_reason"] == "user_exit"
assert wait_until(lambda: session.phase == "finished")
assert session.end_reason == "user_exit"
def test_protocol_error_statuses_over_http(local_referee):
start_open_session(local_referee)
server = local_referee._open_interface.__self__
port = open_port(server)
assert post(port, "/unknown", payload())[0] == 404
assert post(port, "/enter/", payload())[0] == 404
assert post(port, "/enter", payload(), method="GET")[0] == 405
assert post(port, "/enter", payload(),
headers={"Content-Type": "text/plain"})[0] == 415
assert post(port, "/enter", raw=b'{"request_id":"a","request_id":"b"}')[0] == 400
assert post(port, "/enter", raw=b"{}" + b" " * 65535)[0] == 413
status, body = post(port, "/enter", payload("real"))
assert status == 200 and body["accepted"] is True
def test_interface_closes_after_window_timeout(local_referee):
session = start_open_session(local_referee, window_s=0.4)
server = local_referee._open_interface.__self__
port = open_port(server)
assert wait_until(lambda: session.phase == "finished", timeout=5)
assert session.end_reason == "window_timeout"
assert wait_until(lambda: server._httpd is None)
with pytest.raises(OSError):
post(port, "/enter", payload("late"))
def test_recorded_feed_contains_every_request(local_referee):
session = start_open_session(local_referee)
server = local_referee._open_interface.__self__
port = open_port(server)
post(port, "/enter", payload("enter-1"))
post(port, "/unknown", payload("bad"))
assert wait_until(lambda: session.request_count >= 2)
paths = [(entry["path"], entry["status"]) for entry in session.feed]
assert ("/enter", 200) in paths and ("/unknown", 404) in paths