test_report_parse.py 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import os
  2. import pytest
  3. from unittest.mock import patch, AsyncMock
  4. from httpx import AsyncClient, ASGITransport
  5. from app.main import app
  6. # B-66:真样本 PDF 路径改为可移植解析(原写死容器内绝对路径 /app/cfc/...,
  7. # 任何非容器宿主机都会 400 失败)。仓库根 = tests/<-> cfc-langgraph/<-> cfc。
  8. _REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  9. SAMPLE_PDF = os.path.join(_REPO_ROOT, "docs", "参考资料", "501999942-某人.pdf")
  10. requires_sample_pdf = pytest.mark.skipif(
  11. not os.path.exists(SAMPLE_PDF),
  12. reason=f"真样本 PDF 不存在(分发副本/容器内未挂载): {SAMPLE_PDF}",
  13. )
  14. @pytest.mark.asyncio
  15. async def test_report_parse_nonexistent_file():
  16. transport = ASGITransport(app=app)
  17. async with AsyncClient(transport=transport, base_url="http://test") as client:
  18. resp = await client.post("/api/v1/report/parse", json={
  19. "file_path": "/nonexistent.pdf",
  20. "family_id": 1,
  21. "user_id": 1,
  22. })
  23. assert resp.status_code == 400
  24. data = resp.json()
  25. assert "文件不存在" in data["detail"]
  26. @pytest.mark.asyncio
  27. @requires_sample_pdf
  28. async def test_report_parse_with_real_pdf_algorithm_path():
  29. transport = ASGITransport(app=app)
  30. async with AsyncClient(transport=transport, base_url="http://test") as client:
  31. resp = await client.post("/api/v1/report/parse", json={
  32. "file_path": SAMPLE_PDF,
  33. "family_id": 1,
  34. "user_id": 1,
  35. })
  36. assert resp.status_code == 200
  37. data = resp.json()
  38. assert data["code"] == 200
  39. result = data["data"]
  40. assert "format" in result
  41. assert "overview" in result
  42. assert "disease_risks" in result
  43. assert "nutrition" in result
  44. assert "amino_acids" in result
  45. assert isinstance(result["overview"], dict)
  46. assert isinstance(result["disease_risks"], list)
  47. assert isinstance(result["amino_acids"], list)
  48. @pytest.mark.asyncio
  49. @requires_sample_pdf
  50. async def test_report_parse_invalid_json_response():
  51. transport = ASGITransport(app=app)
  52. async with AsyncClient(transport=transport, base_url="http://test") as client:
  53. with patch("app.agents.report_parse_agent.ReportParseAgent._parse_with_llm",
  54. new_callable=AsyncMock, return_value=[1, 2, 3]):
  55. with patch("app.agents.report_parse_agent.parse_report_pdf_with_fallback",
  56. return_value={"format": "inline", "overview": {},
  57. "_parse_incomplete": True}):
  58. resp = await client.post("/api/v1/report/parse", json={
  59. "file_path": SAMPLE_PDF,
  60. "family_id": 1,
  61. "user_id": 1,
  62. })
  63. assert resp.status_code == 200
  64. data = resp.json()
  65. assert data["code"] == 500
  66. assert "解析失败" in data["message"]
  67. def test_indicator_constraint_contains_names():
  68. from app.agents.report_parse_agent import _indicator_constraint
  69. s = _indicator_constraint(["总分", "菌群健康"])
  70. assert "总分" in s and "菌群健康" in s
  71. assert _indicator_constraint(None) == ""
  72. assert _indicator_constraint([]) == ""
  73. @pytest.mark.asyncio
  74. async def test_report_parse_accepts_indicators_field():
  75. transport = ASGITransport(app=app)
  76. async with AsyncClient(transport=transport, base_url="http://test") as client:
  77. resp = await client.post("/api/v1/report/parse", json={
  78. "file_path": "/nonexistent.pdf",
  79. "family_id": 1,
  80. "user_id": 1,
  81. "indicators": ["总分", "菌群健康"],
  82. })
  83. # 字段被接受(非 422 校验错误),仅文件不存在
  84. assert resp.status_code == 400
  85. assert "文件不存在" in resp.json()["detail"]