168 lines
5.0 KiB
Python
168 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
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))
|
|
|
|
from routers import fleet, unified
|
|
|
|
|
|
def _devices(*ids: str):
|
|
return [
|
|
{"device_id": device_id, "project_id": "2130", "capabilities": ["hook"]}
|
|
for device_id in ids
|
|
]
|
|
|
|
|
|
def test_fleet_write_requires_confirm_even_for_one_device(monkeypatch):
|
|
async def fake_select(**kwargs):
|
|
return _devices("device-1")
|
|
|
|
monkeypatch.setattr(fleet, "_select_devices", fake_select)
|
|
response = asyncio.run(
|
|
fleet.fleet_execute(
|
|
fleet.FleetExecuteRequest(
|
|
device_ids=["device-1"],
|
|
platform="wechat",
|
|
action="send_message",
|
|
params={"to_id": "filehelper", "content": "contract"},
|
|
)
|
|
)
|
|
)
|
|
|
|
assert response["code"] == 200
|
|
assert response["success"] is False
|
|
assert response["trace_id"]
|
|
assert response["raw_rpc_receipt"]["confirm_required"] is True
|
|
assert response["data"]["success"] is False
|
|
assert response["data"]["confirm_required"] is True
|
|
|
|
|
|
def test_fleet_dry_run_does_not_execute(monkeypatch):
|
|
async def fake_select(**kwargs):
|
|
return _devices("device-1")
|
|
|
|
monkeypatch.setattr(fleet, "_select_devices", fake_select)
|
|
|
|
async def should_not_execute(*args, **kwargs):
|
|
pytest.fail("dry_run 不应下发设备命令")
|
|
|
|
monkeypatch.setattr(unified, "_execute_skill", should_not_execute)
|
|
response = asyncio.run(
|
|
fleet.fleet_execute(
|
|
fleet.FleetExecuteRequest(
|
|
device_ids=["device-1"],
|
|
platform="wechat",
|
|
action="send_message",
|
|
dry_run=True,
|
|
)
|
|
)
|
|
)
|
|
|
|
assert response["code"] == 200
|
|
assert response["channel_used"] == "fleet/dry_run"
|
|
assert response["raw_rpc_receipt"]["executed"] is False
|
|
assert response["readback"] is None
|
|
assert response["data"]["dry_run"] is True
|
|
assert response["data"]["success"] is True
|
|
|
|
|
|
def test_fleet_uses_explicit_business_success_not_code(monkeypatch):
|
|
async def fake_select(**kwargs):
|
|
return _devices("device-1")
|
|
|
|
monkeypatch.setattr(fleet, "_select_devices", fake_select)
|
|
|
|
async def fake_execute(*args, **kwargs):
|
|
return {
|
|
"code": 200,
|
|
"success": False,
|
|
"error_code": "no_receiver_registered",
|
|
"error_message": "设备端没有接收器",
|
|
"channel_used": "server/frida",
|
|
}
|
|
|
|
monkeypatch.setattr(unified, "_execute_skill", fake_execute)
|
|
response = asyncio.run(
|
|
fleet.fleet_execute(
|
|
fleet.FleetExecuteRequest(
|
|
device_ids=["device-1"],
|
|
platform="wechat",
|
|
action="get_hook_status",
|
|
confirm=True,
|
|
)
|
|
)
|
|
)
|
|
|
|
item = response["data"]["results"][0]
|
|
assert response["data"]["success"] is False
|
|
assert response["data"]["success_count"] == 0
|
|
assert item["success"] is False
|
|
assert item["result"]["error_code"] == "no_receiver_registered"
|
|
assert response["trace_id"]
|
|
assert response["raw_rpc_receipt"]["operation"] == "fleet_execute"
|
|
|
|
|
|
def test_fleet_timeout_returns_retryable_structured_error(monkeypatch):
|
|
async def fake_select(**kwargs):
|
|
return _devices("device-1")
|
|
|
|
monkeypatch.setattr(fleet, "_select_devices", fake_select)
|
|
|
|
async def fake_execute(*args, **kwargs):
|
|
raise asyncio.TimeoutError
|
|
|
|
monkeypatch.setattr(unified, "_execute_skill", fake_execute)
|
|
response = asyncio.run(
|
|
fleet.fleet_execute(
|
|
fleet.FleetExecuteRequest(
|
|
device_ids=["device-1"],
|
|
platform="wechat",
|
|
action="get_hook_status",
|
|
confirm=True,
|
|
)
|
|
)
|
|
)
|
|
|
|
assert response.status_code == 503
|
|
body = json.loads(response.body)
|
|
result = body["data"]["results"][0]["result"]
|
|
assert body["data"]["success"] is False
|
|
assert result["code"] == 504
|
|
assert result["error_code"] == "timeout"
|
|
assert result["retryable"] is True
|
|
assert result["trace_id"]
|
|
assert result["raw_rpc_receipt"]["channel"] == "websocket/timeout"
|
|
|
|
|
|
def test_fleet_wechat_forces_hook_only(monkeypatch):
|
|
async def fake_select(**kwargs):
|
|
return _devices("device-1")
|
|
|
|
monkeypatch.setattr(fleet, "_select_devices", fake_select)
|
|
received = {}
|
|
|
|
async def fake_execute(*args, **kwargs):
|
|
received.update(kwargs)
|
|
return {"code": 200, "data": {"success": True}, "trace_id": "rpc-trace"}
|
|
|
|
monkeypatch.setattr(unified, "_execute_skill", fake_execute)
|
|
response = asyncio.run(
|
|
fleet.fleet_execute(
|
|
fleet.FleetExecuteRequest(
|
|
device_ids=["device-1"], platform="wechat", action="get_hook_status", confirm=True
|
|
)
|
|
)
|
|
)
|
|
|
|
assert received["hook_only"] is True
|
|
assert response["channel_used"] == "frida_rpc"
|
|
assert response["data"]["results"][0]["trace_id"] == "rpc-trace"
|