import json from rpa_yisaiqi.callback_client import StateCenterClient from rpa_yisaiqi.runtime.errors import LoginFailed, NodeError from rpa_yisaiqi.runtime.shell import execute_node class FakeHttp: def __init__(self, responses=None): self.requests = [] self.responses = list(responses or [{"ok": True}, {"ok": True, "next_node": "PRODUCT_MATCH"}]) def __call__(self, request, timeout): body = json.loads(request.data.decode("utf-8")) if request.data else None self.requests.append({ "url": request.full_url, "method": request.get_method(), "body": body, }) return FakeResponse(self.responses.pop(0)) class FakeResponse: def __init__(self, payload): self.payload = payload def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def read(self): return json.dumps(self.payload).encode("utf-8") def test_execute_node_starts_then_completes_with_runner_payload(): http = FakeHttp() client = StateCenterClient("http://state-center:8000", "dev-agent-token", opener=http) result = execute_node( client, "JOB-1", "ORDER_FETCH", lambda: {"summary": {"orderCount": 1}, "businessRefs": {}}, attempt_no=1, ) assert result["next_node"] == "PRODUCT_MATCH" assert [req["url"].rsplit("/", 1)[-1] for req in http.requests] == ["start", "complete"] assert http.requests[1]["body"]["summary"] == {"orderCount": 1} assert http.requests[1]["body"]["attemptNo"] == 1 def test_execute_node_maps_login_failure_to_fail_callback(): http = FakeHttp([{"ok": True}, {"ok": True, "job_status": "FAILED_RETRYABLE"}]) client = StateCenterClient("http://state-center:8000", "dev-agent-token", opener=http) def failing(): raise LoginFailed("登录失败") result = execute_node(client, "JOB-1", "ORDER_FETCH", failing) assert result["job_status"] == "FAILED_RETRYABLE" assert http.requests[1]["url"].endswith("/fail") assert http.requests[1]["body"]["errorCode"] == "SYS_LOGIN_FAIL" assert http.requests[1]["body"]["retryable"] is True assert http.requests[1]["body"]["routeRole"] == "OPS" def test_node_error_carries_default_ui_code(): exc = NodeError("元素找不到") assert exc.error_code == "UI_ELEMENT_NOT_FOUND" assert exc.retryable is False assert exc.route_role == "OPS"