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

325 lines
11 KiB
Python

from __future__ import annotations
import asyncio
import sys
from pathlib import Path
from fastapi import Response
SDK = Path(__file__).resolve().parents[1]
APP = SDK / "app"
if str(SDK) not in sys.path:
sys.path.insert(0, str(SDK))
if str(APP) not in sys.path:
sys.path.insert(1, str(APP))
from routers import unified
from services.device_transport import WECHAT_STRICT_FRIDA_ACTIONS
def test_hl04_params_and_action_enum_are_explicit():
req = unified.WP_HL04ActionRequest(
device_id="DEVICE_ID",
action="wechat_search",
params={"keyword": "hello", "limit": 10},
)
assert req.params.keyword == "hello"
assert len(unified.WP_HL04_ACTIONS) == 38
assert req.dry_run is True
def test_hl04_write_defaults_to_dry_run_without_rpc(monkeypatch):
called = False
async def fake_execute(*args, **kwargs):
nonlocal called
called = True
return {"success": True}
monkeypatch.setattr(unified, "_execute_skill", fake_execute)
req = unified.WP_HL04ActionRequest(
device_id="DEVICE_ID",
action="like_video",
params={"index": 1},
)
response = Response()
body = asyncio.run(unified.execute_wp_hl04(req, response))
assert called is False
assert response.status_code == 200
assert body["data"]["error_code"] == "dry_run_no_write"
assert body["data"]["raw_rpc_receipt"] is None
assert body["data"]["readback"] is None
def test_hl04_failure_sets_http_503_and_preserves_trace_receipt_readback(monkeypatch):
async def fake_execute(*args, **kwargs):
return {
"code": 200,
"success": False,
"trace_id": "trace-hl04-001",
"channel": "frida_rpc",
"error_code": "capability_unavailable",
"error_message": "rpc unavailable",
"raw_rpc_receipt": {"implemented": False},
"readback": None,
}
monkeypatch.setattr(unified, "_execute_skill", fake_execute)
req = unified.WP_HL04ActionRequest(
device_id="DEVICE_ID",
action="like_video",
params={"index": 1},
trace_id="trace-hl04-001",
dry_run=False,
confirm=True,
)
response = Response()
body = asyncio.run(unified.execute_wp_hl04(req, response))
assert response.status_code == 503
assert body["data"]["trace_id"] == "trace-hl04-001"
assert body["data"]["raw_rpc_receipt"] == {"implemented": False}
assert body["data"]["readback"] is None
def test_openapi_declares_headless_and_video_503():
from main import app
schema = app.openapi()
headless = schema["paths"]["/api/v3/wechat/headless/execute"]["post"]
assert {"200", "422", "503"} <= set(headless["responses"])
action = schema["components"]["schemas"]["WP_HL04ActionRequest"]["properties"]["action"]
assert len(action["enum"]) == 38
for name in ("like", "comment", "follow", "share"):
path = f"/api/v3/video-channel/{name}"
assert "503" in schema["paths"][path]["post"]["responses"]
def test_friend_group_request_has_ten_explicit_actions_and_default_dry_run():
req = unified.FriendGroupActionRequest(
device_id="DEVICE_ID",
action="set_group_notice",
params={"group_id": "GROUP_ID", "notice": "notice"},
)
assert len(unified.FRIEND_GROUP_FIRST_ACTIONS) == 10
assert req.platform == "wechat"
assert req.dry_run is True
def test_friend_group_invalid_params_is_http_422_without_rpc(monkeypatch):
called = False
async def fake_execute(*args, **kwargs):
nonlocal called
called = True
return {"success": True}
monkeypatch.setattr(unified, "_execute_skill", fake_execute)
req = unified.FriendGroupActionRequest(
device_id="DEVICE_ID",
action="invite_to_group",
params={"group_id": "GROUP_ID"},
)
response = Response()
body = asyncio.run(unified.execute_friend_group_action(req, response))
assert called is False
assert response.status_code == 422
assert body["data"]["error_code"] == "invalid_params"
assert body["data"]["raw_rpc_receipt"] is None
assert body["data"]["readback"] is None
def test_friend_group_capability_failure_is_http_503_and_preserves_receipts(monkeypatch):
async def fake_execute(*args, **kwargs):
return {
"success": False,
"trace_id": "trace-fg-001",
"channel": "frida_rpc",
"error_code": "capability_unavailable",
"error_message": "friend group rpc unavailable",
"raw_rpc_receipt": {"implemented": False},
"readback": None,
}
monkeypatch.setattr(unified, "_execute_skill", fake_execute)
req = unified.FriendGroupActionRequest(
device_id="DEVICE_ID",
action="set_friend_remark",
params={"user_id": "wxid_target", "remark": "remark"},
trace_id="trace-fg-001",
dry_run=False,
confirm=True,
)
response = Response()
body = asyncio.run(unified.execute_friend_group_action(req, response))
assert response.status_code == 503
assert body["data"]["trace_id"] == "trace-fg-001"
assert body["data"]["raw_rpc_receipt"] == {"implemented": False}
assert body["data"]["readback"] is None
def test_friend_group_openapi_has_422_503_and_ten_action_enum():
from main import app
schema = app.openapi()
operation = schema["paths"]["/api/v3/wechat/friend-group/execute"]["post"]
assert {"200", "422", "503"} <= set(operation["responses"])
action = schema["components"]["schemas"]["FriendGroupActionRequest"]["properties"]["action"]
assert len(action["enum"]) == 10
def test_group_welcome_requires_chatroom_and_returns_http_422_without_rpc(monkeypatch):
called = False
async def fake_execute(*args, **kwargs):
nonlocal called
called = True
return {"success": True}
monkeypatch.setattr(unified, "_execute_skill", fake_execute)
req = unified.SetGroupWelcomeRequest(
device_id="DEVICE_ID",
group_id="invalid_group",
welcome_text="welcome",
)
response = Response()
body = asyncio.run(unified.set_group_welcome(req, response))
assert called is False
assert response.status_code == 422
assert body["data"]["error_code"] == "invalid_params"
assert body["data"]["raw_rpc_receipt"] is None
assert body["data"]["readback"] is None
def test_group_welcome_capability_unavailable_preserves_probe_and_readback(monkeypatch):
seen = {}
async def fake_execute(device_id, platform, action, params, **kwargs):
seen.update(action=action, params=params, kwargs=kwargs)
return {
"success": False,
"trace_id": "trace-group-welcome-001",
"channel": "frida_rpc",
"error_code": "capability_unavailable",
"error_message": "scene unavailable",
"raw_rpc_receipt": {"operation": "set_group_welcome", "probe_only": True},
"readback": {"group_id": "GROUP@chatroom", "group_found": True, "chatroom_notice": ""},
}
monkeypatch.setattr(unified, "_execute_skill", fake_execute)
req = unified.SetGroupWelcomeRequest(
device_id="DEVICE_ID",
group_id="GROUP@chatroom",
welcome_text="welcome",
dry_run=True,
confirm=True,
trace_id="trace-group-welcome-001",
)
response = Response()
body = asyncio.run(unified.set_group_welcome(req, response))
assert response.status_code == 503
assert seen["action"] == "set_group_welcome"
assert seen["kwargs"]["hook_only"] is True
assert seen["params"]["welcome"] == "welcome"
assert body["data"]["raw_rpc_receipt"]["probe_only"] is True
assert body["data"]["readback"]["group_found"] is True
def test_group_welcome_openapi_keeps_422_and_503_gray_contract():
from main import app
operation = app.openapi()["paths"]["/api/v3/group/set-welcome"]["post"]
assert {"200", "422", "503"} <= set(operation["responses"])
schema = app.openapi()["components"]["schemas"]["SetGroupWelcomeRequest"]
assert {"dry_run", "confirm", "trace_id"} <= set(schema["properties"])
def test_wireless_qr_request_has_five_actions_and_media_payload():
req = unified.WirelessQrActionRequest(
device_id="DEVICE_ID",
action="extract_qr_from_image",
media={"media_id": "MEDIA_ID", "mime_type": "image/png"},
)
assert len(unified.WIRELESS_QR_ACTIONS) == 5
assert set(unified.WIRELESS_QR_ACTIONS) <= WECHAT_STRICT_FRIDA_ACTIONS
assert req.media.media_id == "MEDIA_ID"
assert req.dry_run is True
def test_wireless_qr_missing_media_is_http_422_without_rpc(monkeypatch):
called = False
async def fake_execute(*args, **kwargs):
nonlocal called
called = True
return {"success": True}
monkeypatch.setattr(unified, "_execute_wechat_wss_frida", fake_execute)
req = unified.WirelessQrActionRequest(device_id="DEVICE_ID", action="scan_qr_code")
response = Response()
body = asyncio.run(unified.execute_wireless_qr_action(req, response))
assert called is False
assert response.status_code == 422
assert body["data"]["error_code"] == "invalid_params"
assert body["data"]["raw_rpc_receipt"] is None
assert body["data"]["readback"] is None
def test_wireless_qr_media_decoder_unavailable_is_http_503_and_transparent(monkeypatch):
async def fake_execute(*args, **kwargs):
return {
"success": False,
"trace_id": "trace-qr-001",
"channel": "frida_rpc",
"error_code": "wireless_qr_media_decoder_unavailable",
"error_message": "wireless media decoder unavailable",
"raw_rpc_receipt": {"implemented": False},
"readback": None,
}
monkeypatch.setattr(unified, "_execute_wechat_wss_frida", fake_execute)
req = unified.WirelessQrActionRequest(
device_id="DEVICE_ID",
action="extract_qr_from_image",
media={"media_id": "MEDIA_ID"},
trace_id="trace-qr-001",
)
response = Response()
body = asyncio.run(unified.execute_wireless_qr_action(req, response))
assert response.status_code == 503
assert body["data"]["error_code"] == "wireless_qr_media_decoder_unavailable"
assert body["data"]["trace_id"] == "trace-qr-001"
assert body["data"]["raw_rpc_receipt"] == {"implemented": False}
assert body["data"]["readback"] is None
def test_wireless_qr_add_friend_dry_run_does_not_execute(monkeypatch):
called = False
async def fake_execute(*args, **kwargs):
nonlocal called
called = True
return {"success": True}
monkeypatch.setattr(unified, "_execute_wechat_wss_frida", fake_execute)
req = unified.WirelessQrActionRequest(
device_id="DEVICE_ID",
action="add_friend_by_qr",
wechat_id="wxid_target",
)
response = Response()
body = asyncio.run(unified.execute_wireless_qr_action(req, response))
assert called is False
assert response.status_code == 200
assert body["data"]["error_code"] == "dry_run_no_write"
def test_wireless_qr_openapi_has_media_model_and_http_semantics():
from main import app
schema = app.openapi()
operation = schema["paths"]["/api/v3/wechat/qr/execute"]["post"]
assert {"200", "422", "503"} <= set(operation["responses"])
action = schema["components"]["schemas"]["WirelessQrActionRequest"]["properties"]["action"]
assert len(action["enum"]) == 5
assert "WirelessQrMediaPayload" in schema["components"]["schemas"]