diff --git a/lsfx-mock-server/routers/credit_api.py b/lsfx-mock-server/routers/credit_api.py new file mode 100644 index 00000000..3c53fc10 --- /dev/null +++ b/lsfx-mock-server/routers/credit_api.py @@ -0,0 +1,37 @@ +from typing import Optional + +from fastapi import APIRouter, File, Form, UploadFile + +from services.credit_debug_service import CreditDebugService +from services.credit_payload_service import CreditPayloadService + +router = APIRouter() +payload_service = CreditPayloadService("config/credit_feature_schema.json") +debug_service = CreditDebugService("config/credit_response_examples.json") + + +@router.post("/xfeature-mngs/conversation/htmlEval") +async def html_eval( + model: Optional[str] = Form(None), + hType: Optional[str] = Form(None), + file: Optional[UploadFile] = File(None), +): + error_response = debug_service.validate_request( + model=model, + h_type=hType, + file_present=file is not None, + ) + if error_response: + return error_response + + payload = payload_service.generate_payload( + model=model, + h_type=hType, + filename=file.filename or "credit.html", + ) + return debug_service.build_success_response(payload) + + +@router.get("/credit/health") +async def credit_health(): + return {"status": "healthy", "service": "credit-mock"} diff --git a/lsfx-mock-server/services/credit_debug_service.py b/lsfx-mock-server/services/credit_debug_service.py new file mode 100644 index 00000000..4ca9f2ff --- /dev/null +++ b/lsfx-mock-server/services/credit_debug_service.py @@ -0,0 +1,62 @@ +import copy +import json +import re +from pathlib import Path +from typing import Optional + + +class CreditDebugService: + """处理征信解析接口的调试标记、参数校验与响应封装。""" + + VALID_MODEL = "LXCUSTALL" + VALID_HTYPES = {"PERSON", "ENTERPRISE"} + + def __init__(self, template_path: str): + self.template_path = template_path + self.templates = self._load_templates() + + def validate_request(self, model: Optional[str], h_type: Optional[str], file_present: bool): + if not model: + return self.build_missing_param_response("model") + if not file_present: + return self.build_missing_param_response("file") + if not h_type: + return self.build_missing_param_response("hType") + + error_code = self.detect_error_marker(model) + if error_code: + return self.build_error_response(error_code) + + if model != self.VALID_MODEL: + return self.build_error_response("ERR_10002") + if h_type not in self.VALID_HTYPES: + return self.build_error_response("ERR_10003") + return None + + def build_success_response(self, payload: dict) -> dict: + response = copy.deepcopy(self.templates["success"]) + response["payload"] = payload + return response + + def build_missing_param_response(self, param_name: str) -> dict: + response = self.build_error_response("ERR_99999") + response["message"] = response["message"].replace("XX", param_name) + return response + + def build_error_response(self, error_code: str) -> dict: + return copy.deepcopy(self.templates["errors"][error_code]) + + def detect_error_marker(self, model: str) -> Optional[str]: + matched = re.search(r"error_(ERR_\d+)", model) + if not matched: + return None + error_code = matched.group(1) + if error_code in self.templates["errors"]: + return error_code + return None + + def _load_templates(self) -> dict: + template_file = Path(self.template_path) + if not template_file.is_absolute(): + template_file = Path(__file__).resolve().parent.parent / template_file + return json.loads(template_file.read_text(encoding="utf-8")) diff --git a/lsfx-mock-server/tests/conftest.py b/lsfx-mock-server/tests/conftest.py index 42ca014b..83a341ba 100644 --- a/lsfx-mock-server/tests/conftest.py +++ b/lsfx-mock-server/tests/conftest.py @@ -37,7 +37,21 @@ def reset_file_service_state(): @pytest.fixture def client(): """创建测试客户端""" - return TestClient(app) + original_routes = list(app.router.routes) + try: + from routers import credit_api + + if not any(route.path == "/xfeature-mngs/conversation/htmlEval" for route in app.routes): + app.include_router(credit_api.router, tags=["征信解析接口"]) + app.openapi_schema = None + except ModuleNotFoundError: + pass + + try: + yield TestClient(app) + finally: + app.router.routes[:] = original_routes + app.openapi_schema = None @pytest.fixture @@ -68,3 +82,9 @@ def sample_inner_flow_request(): "dataEndDateId": 20240131, "uploadUserId": 902001, } + + +@pytest.fixture +def sample_credit_html_file(): + """示例征信 HTML 文件。""" + return ("credit.html", b"", "text/html") diff --git a/lsfx-mock-server/tests/test_credit_api.py b/lsfx-mock-server/tests/test_credit_api.py new file mode 100644 index 00000000..924464e8 --- /dev/null +++ b/lsfx-mock-server/tests/test_credit_api.py @@ -0,0 +1,45 @@ +def test_html_eval_should_return_credit_payload(client, sample_credit_html_file): + response = client.post( + "/xfeature-mngs/conversation/htmlEval", + data={"model": "LXCUSTALL", "hType": "PERSON"}, + files={"file": sample_credit_html_file}, + ) + + assert response.status_code == 200 + data = response.json() + assert data["status_code"] == "0" + assert data["message"] == "成功" + assert "lx_header" in data["payload"] + + +def test_html_eval_should_return_err_99999_for_missing_model(client, sample_credit_html_file): + response = client.post( + "/xfeature-mngs/conversation/htmlEval", + data={"hType": "PERSON"}, + files={"file": sample_credit_html_file}, + ) + + assert response.status_code == 200 + assert response.json()["status_code"] == "ERR_99999" + + +def test_html_eval_should_return_err_10003_for_invalid_h_type(client, sample_credit_html_file): + response = client.post( + "/xfeature-mngs/conversation/htmlEval", + data={"model": "LXCUSTALL", "hType": "JSON"}, + files={"file": sample_credit_html_file}, + ) + + assert response.status_code == 200 + assert response.json()["status_code"] == "ERR_10003" + + +def test_html_eval_should_support_debug_error_marker(client, sample_credit_html_file): + response = client.post( + "/xfeature-mngs/conversation/htmlEval", + data={"model": "error_ERR_10001", "hType": "PERSON"}, + files={"file": sample_credit_html_file}, + ) + + assert response.status_code == 200 + assert response.json()["status_code"] == "ERR_10001"