test_demand_feedback.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. from __future__ import annotations
  2. from datetime import datetime
  3. from types import SimpleNamespace
  4. import pytest
  5. from pydantic import ValidationError
  6. from api.schemas.demand_feedback import CreateDemandFeedbackBody
  7. from api.services import demand_feedback as feedback_service
  8. from supply_infra.db.models.demand_feedback import DemandFeedback
  9. def _body(**changes) -> CreateDemandFeedbackBody:
  10. values = {
  11. "client_request_id": "request-123",
  12. "target_type": "demand",
  13. "demand_grade_id": 10,
  14. "feedback_action": "support",
  15. }
  16. values.update(changes)
  17. return CreateDemandFeedbackBody(**values)
  18. def test_feedback_body_validates_target_fields() -> None:
  19. with pytest.raises(ValidationError):
  20. _body(target_type="video")
  21. with pytest.raises(ValidationError):
  22. _body(
  23. target_type="hit_content",
  24. video_id="vid-1",
  25. )
  26. with pytest.raises(ValidationError):
  27. _body(video_id="vid-1")
  28. body = _body(target_type="video", video_id="vid-1")
  29. assert body.video_id == "vid-1"
  30. def test_feedback_body_validates_action_content_and_reason() -> None:
  31. with pytest.raises(ValidationError):
  32. _body(feedback_action="oppose")
  33. with pytest.raises(ValidationError):
  34. _body(feedback_action="correct")
  35. with pytest.raises(ValidationError):
  36. _body(reason_code="irrelevant")
  37. body = _body(
  38. feedback_action="correct",
  39. reason_code="wrong_reason",
  40. content=" 正确原因应引用后验样本。 ",
  41. )
  42. assert body.content == "正确原因应引用后验样本。"
  43. def test_feedback_model_has_person_but_no_review_or_boolean_state() -> None:
  44. columns = DemandFeedback.__table__.columns
  45. assert "feedback_user_id" in columns
  46. assert "feedback_user_name_snapshot" in columns
  47. assert "feedback_username_snapshot" in columns
  48. assert "review_status" not in columns
  49. assert "has_feedback" not in columns
  50. assert "is_feedback" not in columns
  51. def test_create_feedback_uses_server_target_and_current_user(monkeypatch) -> None:
  52. grade = SimpleNamespace(
  53. id=10,
  54. biz_dt="20260730",
  55. demand_name="老年人智能手机使用",
  56. grade="A",
  57. reason="需求有明确内容意图",
  58. video_list=None,
  59. )
  60. expansion = SimpleNamespace(
  61. id=30,
  62. biz_dt="20260730",
  63. source_demand_grade_id=10,
  64. video_id="vid-1",
  65. point_type="purpose",
  66. expanded_text="降低老人使用智能设备的门槛",
  67. point_desc="解释基础操作",
  68. reason="覆盖当前需求",
  69. )
  70. detail = SimpleNamespace(title="教父母使用手机")
  71. saved: list[DemandFeedback] = []
  72. class Session:
  73. def scalar(self, _statement):
  74. return None
  75. def refresh(self, row) -> None:
  76. row.id = 99
  77. row.created_at = datetime(2026, 7, 30, 15, 20)
  78. def rollback(self) -> None:
  79. raise AssertionError("unexpected rollback")
  80. class SessionContext:
  81. def __enter__(self):
  82. return Session()
  83. def __exit__(self, *_args):
  84. return False
  85. class GradeRepo:
  86. def __init__(self, _session):
  87. pass
  88. def get_by_id(self, grade_id):
  89. return grade if grade_id == 10 else None
  90. class ExpansionRepo:
  91. def __init__(self, _session):
  92. pass
  93. def has_video(self, **kwargs):
  94. return kwargs["video_id"] == "vid-1"
  95. def get_active_by_id(self, expansion_id):
  96. return expansion if expansion_id == 30 else None
  97. class DetailRepo:
  98. def __init__(self, _session):
  99. pass
  100. def list_by_vids(self, _video_ids):
  101. return {"vid-1": detail}
  102. class FeedbackRepo:
  103. def __init__(self, _session):
  104. pass
  105. def get_by_client_request_id(self, _request_id):
  106. return None
  107. def add(self, row):
  108. saved.append(row)
  109. return row
  110. def list_for_target(self, **_kwargs):
  111. return saved, len(saved)
  112. monkeypatch.setattr(feedback_service, "get_session", lambda: SessionContext())
  113. monkeypatch.setattr(feedback_service, "DemandGradeRepository", GradeRepo)
  114. monkeypatch.setattr(feedback_service, "DemandVideoExpansionRepository", ExpansionRepo)
  115. monkeypatch.setattr(feedback_service, "MultiDemandVideoDetailRepository", DetailRepo)
  116. monkeypatch.setattr(feedback_service, "DemandFeedbackRepository", FeedbackRepo)
  117. result = feedback_service.create_demand_feedback(
  118. _body(
  119. target_type="hit_content",
  120. video_id="vid-1",
  121. demand_video_expansion_id=30,
  122. feedback_action="oppose",
  123. reason_code="not_hit",
  124. content="没有覆盖老人学习使用的需求。",
  125. ),
  126. {
  127. "id": 7,
  128. "username": "zhangsan",
  129. "display_name": "张三",
  130. },
  131. )
  132. assert result["feedback_summary"] == {"count": 1}
  133. assert result["item"]["feedback_user"] == {
  134. "id": 7,
  135. "username": "zhangsan",
  136. "display_name": "张三",
  137. }
  138. assert result["item"]["target_snapshot"]["expanded_text"] == "降低老人使用智能设备的门槛"
  139. assert saved[0].feedback_user_id == 7
  140. assert saved[0].feedback_user_name_snapshot == "张三"
  141. assert saved[0].feedback_username_snapshot == "zhangsan"