feat: publish workphone SDK deployment and API docs
This commit is contained in:
125
sdk/tests/test_agent_public_fallback.py
Normal file
125
sdk/tests/test_agent_public_fallback.py
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
BIND-03 离线回归:Agent 公网主服有序回退候选构建 + 健康探测。
|
||||
不依赖真机/WS;仅校验 _build_server_candidates 与 _probe_ws_base 纯逻辑。
|
||||
真机寻服 E2E(断 LAN 自动连公网)见 §八 V3,仍须真机留痕。
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import threading
|
||||
import importlib.util
|
||||
|
||||
AGENT_PATH = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"agent", "agent.py",
|
||||
)
|
||||
|
||||
|
||||
def _load_agent_module():
|
||||
# 直接按文件路径加载,避免 import 触发 u2/websockets 之外的副作用
|
||||
spec = importlib.util.spec_from_file_location("wp_agent_under_test", AGENT_PATH)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
def test_build_candidates_dedup_and_fill():
|
||||
mod = _load_agent_module()
|
||||
Agent = mod.WorkPhoneAgent
|
||||
primary = "ws://192.168.110.251:8899/ws/device/dev1"
|
||||
publics = [
|
||||
"ws://sdk.quwanzhi.com:8899", # 缺 /ws/device + device_id
|
||||
"ws://sdk.quwanzhi.com:8899/ws/device", # 缺 device_id
|
||||
"ws://192.168.110.251:8899/ws/device/dev1", # 与主连接重复
|
||||
"ws://backup.quwanzhi.com:8899/ws/device/dev1", # 已完整
|
||||
]
|
||||
cands = Agent._build_server_candidates(primary, publics, "dev1")
|
||||
assert cands[0] == primary
|
||||
assert "ws://sdk.quwanzhi.com:8899/ws/device/dev1" in cands
|
||||
assert "ws://backup.quwanzhi.com:8899/ws/device/dev1" in cands
|
||||
# 去重:主连接与重复公网只出现一次
|
||||
assert cands.count(primary) == 1
|
||||
assert len(cands) == 3, f"应去重为 3 个候选,实际 {cands}"
|
||||
|
||||
|
||||
def test_build_candidates_empty_public():
|
||||
mod = _load_agent_module()
|
||||
Agent = mod.WorkPhoneAgent
|
||||
primary = "ws://127.0.0.1:8899/ws/device/x"
|
||||
assert Agent._build_server_candidates(primary, [], "x") == [primary]
|
||||
assert Agent._build_server_candidates(primary, None, "x") == [primary]
|
||||
|
||||
|
||||
def test_probe_unreachable():
|
||||
mod = _load_agent_module()
|
||||
Agent = mod.WorkPhoneAgent
|
||||
# 保留端口 9(discard)本机通常不监听,确定性不可达
|
||||
assert Agent._probe_ws_base("ws://127.0.0.1:9/ws/device/x", timeout=0.5) is False
|
||||
|
||||
|
||||
def test_probe_reachable_local_socket():
|
||||
mod = _load_agent_module()
|
||||
Agent = mod.WorkPhoneAgent
|
||||
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
srv.bind(("127.0.0.1", 0))
|
||||
srv.listen(1)
|
||||
port = srv.getsockname()[1]
|
||||
stop = threading.Event()
|
||||
|
||||
def _accept():
|
||||
srv.settimeout(2)
|
||||
try:
|
||||
c, _ = srv.accept()
|
||||
c.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
t = threading.Thread(target=_accept, daemon=True)
|
||||
t.start()
|
||||
try:
|
||||
url = f"ws://127.0.0.1:{port}/ws/device/x"
|
||||
assert Agent._probe_ws_base(url, timeout=1.0) is True
|
||||
finally:
|
||||
stop.set()
|
||||
srv.close()
|
||||
|
||||
|
||||
class _FakeAgent:
|
||||
"""最小 duck-typed self,避免重型构造(u2/frida/anti_ban)。"""
|
||||
VERSION = "test"
|
||||
def __init__(self, stage):
|
||||
self.d = None
|
||||
self.connect_stage = stage
|
||||
self.device_id = "dev1"
|
||||
self.project_id = "cunkebao"
|
||||
self.server_url = "ws://x:8899/ws/device/dev1"
|
||||
self.server_candidates = ["ws://x:8899/ws/device/dev1", "ws://y:8899/ws/device/dev1"]
|
||||
|
||||
|
||||
def test_quick_status_includes_connect_stage():
|
||||
mod = _load_agent_module()
|
||||
fake = _FakeAgent("public")
|
||||
st = mod.WorkPhoneAgent._get_quick_status(fake)
|
||||
assert st["online"] is True
|
||||
assert st["connect_stage"] == "public", f"心跳须带 connect_stage,实际 {st}"
|
||||
|
||||
|
||||
def test_device_info_includes_connect_stage():
|
||||
mod = _load_agent_module()
|
||||
fake = _FakeAgent("retry")
|
||||
info = mod.WorkPhoneAgent._get_device_info(fake)
|
||||
assert info["connect_stage"] == "retry"
|
||||
assert info["server_url"] == "ws://x:8899/ws/device/dev1"
|
||||
assert info["server_candidate_count"] == 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_build_candidates_dedup_and_fill()
|
||||
test_build_candidates_empty_public()
|
||||
test_probe_unreachable()
|
||||
test_probe_reachable_local_socket()
|
||||
test_quick_status_includes_connect_stage()
|
||||
test_device_info_includes_connect_stage()
|
||||
print("✅ BIND-03/07 候选回退+寻服阶段上报离线回归全部通过")
|
||||
Reference in New Issue
Block a user