test_health_plan.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. from app.models.health_plan import PlanTask, HealthPlanSection, HealthPlanResponse
  2. from app.api.adapter import _parse_plan_response, _fallback_extract_tasks
  3. def test_plan_task_valid():
  4. t = PlanTask(action_type="buy", title="购买维生素D3", dimension="wealth", frequency="once")
  5. assert t.frequency == "once"
  6. assert t.notes is None
  7. def test_plan_task_default_frequency_daily():
  8. t = PlanTask(action_type="exercise", title="每周跑步3次", dimension="body")
  9. assert t.frequency == "daily"
  10. def test_plan_task_invalid_action_type():
  11. import pytest
  12. from pydantic import ValidationError
  13. with pytest.raises(ValidationError):
  14. PlanTask(action_type="cook", title="做饭", dimension="body")
  15. def test_plan_task_invalid_dimension():
  16. import pytest
  17. from pydantic import ValidationError
  18. with pytest.raises(ValidationError):
  19. PlanTask(action_type="diet", title="少油少盐", dimension="earth")
  20. def test_plan_response_with_tasks():
  21. resp = HealthPlanResponse(
  22. overview="总览",
  23. sections=[HealthPlanSection(
  24. key="nutrition", title="营养补充", content="## 建议",
  25. tasks=[PlanTask(action_type="buy", title="购买鱼油", dimension="wealth", frequency="once")]
  26. )]
  27. )
  28. assert resp.sections[0].tasks[0].action_type == "buy"
  29. # 序列化后 tasks 字段存在
  30. data = resp.model_dump()
  31. assert "tasks" in data["sections"][0]
  32. def test_parse_plan_response_valid():
  33. answer = '{"overview":"o","sections":[{"key":"diet","title":"t","content":"c","items":[],"tasks":[{"action_type":"diet","title":"少油少盐","dimension":"body","frequency":"daily"}]}],"abnormal_indicators":[]}'
  34. res = _parse_plan_response(answer)
  35. assert res["success"] is True
  36. assert res["data"]["sections"][0]["tasks"][0]["action_type"] == "diet"
  37. def test_parse_plan_response_invalid_action_falls_back():
  38. # LLM 输出含非法 action_type("cook"),校验失败但可被 fallback 处理
  39. answer = '{"overview":"o","sections":[{"key":"diet","title":"t","content":"1. 少油少盐\\n2. 多吃蔬菜\\n3. 纯原理描述无动作","items":[],"tasks":[{"action_type":"cook","title":"做饭","dimension":"body"}]}],"abnormal_indicators":[]}'
  40. res = _parse_plan_response(answer)
  41. # fallback 从 content 提取动作行
  42. assert res["success"] is True
  43. tasks = res["data"]["sections"][0].get("tasks", [])
  44. assert len(tasks) >= 2
  45. def test_parse_plan_response_no_json():
  46. res = _parse_plan_response("这是纯文本没有 JSON")
  47. assert res["success"] is False
  48. def test_fallback_skips_when_tasks_exist():
  49. parsed = {"sections": [{"key": "diet", "title": "t", "content": "1. 少油少盐", "tasks": [{"action_type": "diet", "title": "已有", "dimension": "body"}]}]}
  50. assert _fallback_extract_tasks(parsed) is None
  51. def test_regenerate_tasks_validation_filter():
  52. raw_tasks = [
  53. {"action_type": "diet", "title": "少油少盐", "dimension": "body", "frequency": "daily"},
  54. {"action_type": "cook", "title": "非法", "dimension": "body"}, # 非法 action,应被丢弃
  55. ]
  56. valid = []
  57. for t in raw_tasks:
  58. try:
  59. valid.append(PlanTask.model_validate(t).model_dump())
  60. except Exception:
  61. continue
  62. assert len(valid) == 1
  63. assert valid[0]["action_type"] == "diet"