4512b6ecac
用户反馈后台:区分反馈类型(比价反馈/普通反馈)+ 审核可给用户留言;提现后台:按提现类型筛选。 - feedback 表加 source(profile/comparison)/scene/admin_reply + 迁移;提交接口 /api/v1/feedback 接收 source/scene,来源判定显式 source 优先、否则据 scene 有无派生(有=comparison); /records 带回 scene + admin_reply。 - admin 反馈:列表加「反馈类型」筛选;采纳/拒绝支持存 admin_reply(给用户的回复,用户端可见); FeedbackOut 带出 source/scene/admin_reply。 - admin 提现:WithdrawOut 暴露 source,列表加「提现类型」筛选(coin_cash=福利页提现 / invite_cash=邀请提现)。 - 补 4 项测试:来源派生、回复存取、反馈按类型筛选、提现按类型筛选。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: zzhyyyyy <2685922758@qq.com> Reviewed-on: #105 Co-authored-by: zhuzihao <zhuzihao@wonderable.ai> Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
115 lines
4.0 KiB
Python
115 lines
4.0 KiB
Python
"""用户反馈接口测试。"""
|
|
from __future__ import annotations
|
|
|
|
from app.db.session import SessionLocal
|
|
from app.models.feedback import Feedback
|
|
from app.repositories import user as user_repo
|
|
|
|
|
|
def _login(client, phone: str) -> str:
|
|
client.post("/api/v1/auth/sms/send", json={"phone": phone})
|
|
r = client.post("/api/v1/auth/sms/login", json={"phone": phone, "code": "123456"})
|
|
assert r.status_code == 200, r.text
|
|
return r.json()["access_token"]
|
|
|
|
|
|
def test_feedback_records_empty_returns_200(client) -> None:
|
|
token = _login(client, "13622000001")
|
|
|
|
r = client.get("/api/v1/feedback/records", headers={"Authorization": f"Bearer {token}"})
|
|
|
|
assert r.status_code == 200, r.text
|
|
assert r.json() == {
|
|
"records": [],
|
|
"counts": {"all": 0, "pending": 0, "adopted": 0, "rejected": 0},
|
|
}
|
|
|
|
|
|
def test_feedback_config_returns_default(client) -> None:
|
|
token = _login(client, "13622000002")
|
|
|
|
r = client.get("/api/v1/feedback/config", headers={"Authorization": f"Bearer {token}"})
|
|
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert body["enabled"] is True
|
|
assert body["group_name"] == "傻瓜比价官方群"
|
|
|
|
|
|
def test_submit_feedback_derives_source_from_scene(client) -> None:
|
|
"""比价结果页反馈带 scene → source=comparison 并存 scene;「我的」页不带 → profile;
|
|
客户端显式传 source 优先(即便没 scene 也判 comparison)。历史接口带回 scene。"""
|
|
token = _login(client, "13622000010")
|
|
h = {"Authorization": f"Bearer {token}"}
|
|
|
|
r = client.post("/api/v1/feedback", data={"content": "比价太慢了", "scene": "比价太慢"}, headers=h)
|
|
assert r.status_code == 200, r.text
|
|
fid_cmp = r.json()["id"]
|
|
|
|
r = client.post("/api/v1/feedback", data={"content": "普通建议"}, headers=h)
|
|
assert r.status_code == 200, r.text
|
|
fid_prof = r.json()["id"]
|
|
|
|
r = client.post(
|
|
"/api/v1/feedback", data={"content": "只填了描述", "source": "comparison"}, headers=h
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
fid_explicit = r.json()["id"]
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
cmp_fb = db.get(Feedback, fid_cmp)
|
|
assert cmp_fb.source == "comparison" and cmp_fb.scene == "比价太慢"
|
|
prof_fb = db.get(Feedback, fid_prof)
|
|
assert prof_fb.source == "profile" and prof_fb.scene is None
|
|
assert db.get(Feedback, fid_explicit).source == "comparison"
|
|
finally:
|
|
db.close()
|
|
|
|
r = client.get("/api/v1/feedback/records", headers=h)
|
|
assert r.status_code == 200, r.text
|
|
scenes = {item["id"]: item["scene"] for item in r.json()["records"]}
|
|
assert scenes[fid_cmp] == "比价太慢"
|
|
assert scenes[fid_prof] is None
|
|
|
|
|
|
def test_feedback_records_maps_existing_statuses(client) -> None:
|
|
phone = "13622000003"
|
|
token = _login(client, phone)
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
user = user_repo.get_user_by_phone(db, phone)
|
|
assert user is not None
|
|
db.add(Feedback(user_id=user.id, content="待处理反馈", contact="", status="pending"))
|
|
db.add(
|
|
Feedback(
|
|
user_id=user.id,
|
|
content="已采纳反馈",
|
|
contact="",
|
|
status="adopted",
|
|
reward_coins=800,
|
|
)
|
|
)
|
|
db.add(
|
|
Feedback(
|
|
user_id=user.id,
|
|
content="未采纳反馈",
|
|
contact="",
|
|
status="rejected",
|
|
reject_reason="暂未提供可复现信息",
|
|
)
|
|
)
|
|
db.commit()
|
|
finally:
|
|
db.close()
|
|
|
|
r = client.get("/api/v1/feedback/records", headers={"Authorization": f"Bearer {token}"})
|
|
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert body["counts"] == {"all": 3, "pending": 1, "adopted": 1, "rejected": 1}
|
|
by_status = {item["status"]: item for item in body["records"]}
|
|
assert by_status["adopted"]["reward_coins"] == 800
|
|
assert by_status["rejected"]["reject_reason"] == "暂未提供可复现信息"
|