Files
workphone-sdk/sdk/tests/test_agent_public_fallback.py

143 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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
from argparse import Namespace
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_device_loopback_config_enters_wireless_discovery(monkeypatch, tmp_path):
"""Termux 端历史 127 配置不得继续把连接指向手机自身。"""
mod = _load_agent_module()
config = tmp_path / "config.json"
config.write_text('{"device_id":"dev1","server_url":"ws://127.0.0.1:8899/ws/device"}')
monkeypatch.setenv("WP_AGENT_ON_DEVICE", "1")
monkeypatch.setattr(mod, "_AGENT_DIR", str(tmp_path))
monkeypatch.setitem(sys.modules, "sdk_discovery", type("D", (), {
"discover_sdk_ws_base": staticmethod(lambda timeout: "ws://192.168.110.47:8899/ws/device")
}))
cfg = mod._resolve_config(Namespace(config=str(config), device_id=None, server=None,
heartbeat=None, project=None, discover=False,
public_servers=None))
assert cfg["server_url"] == "ws://192.168.110.47:8899/ws/device/dev1"
def test_probe_unreachable():
mod = _load_agent_module()
Agent = mod.WorkPhoneAgent
# 保留端口 9discard本机通常不监听确定性不可达
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 候选回退+寻服阶段上报离线回归全部通过")