"""微信三项验收 + P0 路由离线回归(不依赖真机) 确保 frida 一上线即可跑通:请求模型字段、路由可导入、159 action 对齐。 """ from __future__ import annotations import sys from pathlib import Path import pytest APP = Path(__file__).resolve().parents[1] / "app" if str(APP) not in sys.path: sys.path.insert(0, str(APP)) def test_send_message_request_fields(): from routers.unified import SendMessageRequest f = SendMessageRequest.model_fields for k in ("device_id", "platform", "to_id", "content", "msg_type"): assert k in f, f"SendMessageRequest 缺字段 {k}" def test_get_messages_request_fields(): from routers.unified import GetMessagesRequest f = GetMessagesRequest.model_fields for k in ("device_id", "platform", "conversation_id", "limit"): assert k in f, f"GetMessagesRequest 缺字段 {k}" def test_post_moments_request_fields(): from routers.unified import PostMomentsRequest f = PostMomentsRequest.model_fields for k in ("device_id", "platform", "content", "images"): assert k in f, f"PostMomentsRequest 缺字段 {k}" def test_wechat_159_actions_aligned(): # 仓库根 sdk/skills 会抢占 "skills" 命名空间,这里按 app 下的真实文件稳健加载 import importlib.util skill_path = APP / "skills" / "wechat" / "skill_v2.py" assert skill_path.is_file(), f"skill_v2.py 缺失: {skill_path}" spec = importlib.util.spec_from_file_location("_wechat_skill_v2_offline", skill_path) mod = importlib.util.module_from_spec(spec) assert spec and spec.loader spec.loader.exec_module(mod) WECHAT_ACTIONS = mod.WECHAT_ACTIONS WechatSkillV2 = mod.WechatSkillV2 assert len(WECHAT_ACTIONS) == 159, f"WECHAT_ACTIONS={len(WECHAT_ACTIONS)} != 159" catalog = WechatSkillV2.get_catalog_action_list() assert len(catalog) >= 174, f"catalog={len(catalog)} < 174" def test_friend_add_route_importable(): """TODO-02 friend-add → 存客宝线索链路可导入。""" from services.cunke_bao_service import CunKeBaoService, LeadData assert hasattr(CunKeBaoService, "report_friend_add") assert hasattr(CunKeBaoService, "report_lead") lead = LeadData(wechat_id="wxid_test", wechat_nickname="t", source="测试") params = lead.to_api_params() assert params.get("wechatId") == "wxid_test" def test_ai_chat_orchestrator_importable(): """TODO-03 AI chat → send_message 编排可导入。""" from services.karuo_device_ai import chat_and_execute_on_device, device_status assert callable(chat_and_execute_on_device) assert callable(device_status) def test_unblock_customer_service_exists(): """TODO-01 解封客服 Skill 文件存在。""" p = Path(__file__).resolve().parents[1] / "agent" / "skills" / "wechat" / "unblock_customer_service.py" assert p.is_file(), "解封客服 Skill 缺失" def test_three_acceptance_scripts_exist(): base = Path(__file__).resolve().parents[1] / "scripts" for s in ("run_three_acceptance.sh", "auto_accept_when_ready.sh", "termux_frida_up_onphone.sh"): assert (base / s).is_file(), f"脚本缺失 {s}" def test_wp_wx04_scan_routes_and_agent_skill(): """WP-WX-04 离线路由 + Agent u2 skill 对齐(不替代真机 E2E)。""" from routers import unified assert callable(getattr(unified, "extract_qr_from_image", None)) assert callable(getattr(unified, "add_friend_from_image", None)) skill_path = Path(__file__).resolve().parents[1] / "agent" / "skills" / "wechat" / "skill.py" src = skill_path.read_text(encoding="utf-8") assert "def add_friend_from_image" in src assert "_save_qr_image_to_album" in src agent_py = Path(__file__).resolve().parents[1] / "agent" / "agent.py" assert '"add_friend_from_image"' in agent_py.read_text(encoding="utf-8") if __name__ == "__main__": sys.exit(pytest.main([__file__, "-q"]))