import json from rpa_yisaiqi.callback_client import StateCenterClient from rpa_yisaiqi.contract_create_node import ( ContractCreateInput, LoginFailed, execute_contract_create, ) class FakeHttp: def __init__(self, responses=None): self.requests = [] self.responses = list(responses or [{"ok": True}, {"ok": True, "next_node": "CONTRACT_APPROVE_WAIT"}]) 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, "headers": dict(request.header_items()), "body": body, "timeout": timeout, } ) 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_state_center_client_sends_agent_token_and_json_body(): http = FakeHttp([{"ok": True}]) client = StateCenterClient("http://state-center:8000/", "dev-agent-token", opener=http) result = client.start_node("JOB-1", "CONTRACT_CREATE", attempt_no=2, operator="agent") assert result == {"ok": True} assert http.requests[0]["url"] == "http://state-center:8000/api/jobs/JOB-1/nodes/CONTRACT_CREATE/start" assert http.requests[0]["headers"]["X-agent-token"] == "dev-agent-token" assert http.requests[0]["body"] == { "attemptNo": 2, "executorPlatform": "iS-RPA", "operator": "agent", } def test_state_center_client_escapes_non_ascii_path_parts(): http = FakeHttp([{"ok": True}]) client = StateCenterClient("http://state-center:8000", "dev-agent-token", opener=http) client.start_node("YYC-合同节点联调-001", "CONTRACT_CREATE") assert http.requests[0]["url"] == ( "http://state-center:8000/api/jobs/" "YYC-%E5%90%88%E5%90%8C%E8%8A%82%E7%82%B9%E8%81%94%E8%B0%83-001" "/nodes/CONTRACT_CREATE/start" ) def test_contract_create_calls_start_then_complete_with_contract_number(): http = FakeHttp() client = StateCenterClient("http://state-center:8000", "dev-agent-token", opener=http) spec = ContractCreateInput( job_id="JOB-1", supplier_name="重庆亿昊药业", order_date="2026-08-11", source_type="YYC", contract_system_no="HT-20260811-001", ) result = execute_contract_create(spec, client) assert result["next_node"] == "CONTRACT_APPROVE_WAIT" assert [request["url"].split("/")[-1] for request in http.requests] == ["start", "complete"] assert http.requests[1]["body"]["businessRefs"] == {"contractSystemNo": "HT-20260811-001"} assert http.requests[1]["body"]["summary"]["supplierName"] == "重庆亿昊药业" assert http.requests[1]["body"]["summary"]["contractDate"] == "2026-08-11" def test_contract_create_reports_login_failure_to_state_center(): http = FakeHttp([{"ok": True}, {"ok": True, "job_status": "FAILED_RETRYABLE"}]) client = StateCenterClient("http://state-center:8000", "dev-agent-token", opener=http) spec = ContractCreateInput( job_id="JOB-1", supplier_name="重庆亿昊药业", order_date="2026-08-11", source_type="YYC", contract_system_no="HT-20260811-001", ) def failing_runner(_spec): raise LoginFailed("合同系统登录失败") result = execute_contract_create(spec, client, contract_runner=failing_runner) assert result["job_status"] == "FAILED_RETRYABLE" assert http.requests[1]["url"].endswith("/api/jobs/JOB-1/nodes/CONTRACT_CREATE/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"