kb-demo / backend /tests /test_feedback.py
RayLi-Git
fix: 檔案移至根目錄 + 套用示範 README
a7cd101
Raw
History Blame Contribute Delete
7.13 kB
"""§31 應用內回報 endpoint 測試。"""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from app.auth.passwords import hash_password
from app.db import _utcnow, get_conn
from app.main import app
PWD = "TestPw123!"
@pytest.fixture()
def client():
return TestClient(app)
def _mkuser(name, role="user"):
c = get_conn()
c.execute(
"INSERT INTO users (username, email, password_hash, role, department, "
"created_at, is_active) VALUES (?,?,?,?,?,?,1)",
(name, f"{name}@x.tw", hash_password(PWD), role, "業務部", _utcnow()),
)
return c.execute("SELECT id FROM users WHERE username=?", (name,)).fetchone()["id"]
def _login(c, u):
return c.post("/api/auth/login", json={"username": u, "password": PWD}).json()["access_token"]
def _auth(t):
return {"Authorization": f"Bearer {t}"}
def test_create_feedback(client):
_mkuser("u1")
tok = _login(client, "u1")
r = client.post("/api/feedback", headers=_auth(tok), json={
"category": "bug", "title": "登入頁卡住", "description": "點登入沒反應",
"page_url": "/login",
})
assert r.status_code == 201
assert r.json()["status"] == "open"
def test_create_feedback_invalid_category(client):
_mkuser("u1")
tok = _login(client, "u1")
r = client.post("/api/feedback", headers=_auth(tok), json={
"category": "spam", "title": "x", "description": "y",
})
assert r.status_code == 422
def test_create_feedback_title_too_long(client):
_mkuser("u1")
tok = _login(client, "u1")
r = client.post("/api/feedback", headers=_auth(tok), json={
"category": "bug", "title": "x" * 31, "description": "y",
})
assert r.status_code == 422
def test_feedback_notifies_admin(client):
aid = _mkuser("adm", "admin")
_mkuser("u1")
tok = _login(client, "u1")
client.post("/api/feedback", headers=_auth(tok), json={
"category": "feature", "title": "想要暗色模式", "description": "晚上看會刺眼",
})
rows = get_conn().execute(
"SELECT type FROM notifications WHERE user_id=? AND type='feedback'", (aid,),
).fetchall()
assert len(rows) >= 1
def test_admin_list_feedback(client):
_mkuser("adm", "admin")
uid = _mkuser("u1")
tok_u = _login(client, "u1")
client.post("/api/feedback", headers=_auth(tok_u), json={
"category": "bug", "title": "t1", "description": "d1",
})
tok_a = _login(client, "adm")
r = client.get("/api/admin/feedback", headers=_auth(tok_a))
assert r.status_code == 200
j = r.json()
assert j["count"] >= 1
assert j["items"][0]["username"] == "u1"
def test_admin_list_filter_status(client):
_mkuser("adm", "admin")
uid = _mkuser("u1")
tok_u = _login(client, "u1")
client.post("/api/feedback", headers=_auth(tok_u), json={
"category": "bug", "title": "t1", "description": "d1",
})
tok_a = _login(client, "adm")
r = client.get("/api/admin/feedback?status=open", headers=_auth(tok_a))
assert all(it["status"] == "open" for it in r.json()["items"])
def test_user_blocked_from_admin_list(client):
_mkuser("u1")
tok = _login(client, "u1")
r = client.get("/api/admin/feedback", headers=_auth(tok))
assert r.status_code in (401, 403)
def test_admin_reply_notifies_reporter(client):
_mkuser("adm", "admin")
uid = _mkuser("u1")
tok_u = _login(client, "u1")
fid = client.post("/api/feedback", headers=_auth(tok_u), json={
"category": "bug", "title": "t1", "description": "d1",
}).json()["id"]
tok_a = _login(client, "adm")
r = client.patch(f"/api/admin/feedback/{fid}", headers=_auth(tok_a), json={
"status": "resolved", "admin_reply": "已修復,感謝回報",
})
assert r.status_code == 200
# reporter 收到回覆通知
rows = get_conn().execute(
"SELECT type FROM notifications WHERE user_id=? AND type='feedback_reply'", (uid,),
).fetchall()
assert len(rows) >= 1
# status 更新
row = get_conn().execute("SELECT status, admin_reply FROM feedback WHERE id=?", (fid,)).fetchone()
assert row["status"] == "resolved"
assert "已修復" in row["admin_reply"]
def test_admin_reply_404(client):
_mkuser("adm", "admin")
tok_a = _login(client, "adm")
r = client.patch("/api/admin/feedback/99999", headers=_auth(tok_a), json={"status": "resolved"})
assert r.status_code == 404
def test_unauth_create_blocked(client):
r = client.post("/api/feedback", json={"category": "bug", "title": "t", "description": "d"})
assert r.status_code in (401, 403)
# ============ §50.3.2 文件報告錯誤 ============
def _mkdoc(title, author_id, dept="業務部"):
c = get_conn()
c.execute(
"INSERT INTO documents (title, category, tags, summary, body, author_id, "
"owner_department, visibility, allowed_departments, access_list, status, "
"created_at, updated_at) VALUES (?,?,'[]','','x',?,?,'public','[]','[]','active',?,?)",
(title, "知識/筆記", author_id, dept, _utcnow(), _utcnow()),
)
return c.execute("SELECT id FROM documents WHERE title=?", (title,)).fetchone()["id"]
def test_document_error_requires_doc_id(client):
_mkuser("u1")
tok = _login(client, "u1")
r = client.post("/api/feedback", headers=_auth(tok), json={
"category": "document_error", "title": "內容過時", "description": "配方比例錯了",
})
assert r.status_code == 400
assert r.json()["detail"]["error"] == "doc_id_required"
def test_document_error_nonexistent_doc(client):
_mkuser("u1")
tok = _login(client, "u1")
r = client.post("/api/feedback", headers=_auth(tok), json={
"category": "document_error", "title": "x", "description": "y", "doc_id": 999999,
})
assert r.status_code == 404
def test_document_error_report_notifies_owner_and_count(client):
owner_id = _mkuser("owner1")
reporter_id = _mkuser("reporter1")
other_id = _mkuser("other1")
did = _mkdoc("甘油配方文件", owner_id)
rtok = _login(client, "reporter1")
r = client.post("/api/feedback", headers=_auth(rtok), json={
"category": "document_error", "title": "比例錯誤",
"description": "甘油比例應為 8 成", "doc_id": did, "page_url": f"/documents/{did}",
})
assert r.status_code == 201
# owner 收到通知
c = get_conn()
n = c.execute(
"SELECT COUNT(*) AS n FROM notifications WHERE user_id=? AND type='feedback'", (owner_id,)
).fetchone()["n"]
assert n >= 1
# owner 看得到 error-count
otok = _login(client, "owner1")
rc = client.get(f"/api/feedback/document/{did}/error-count", headers=_auth(otok))
assert rc.status_code == 200 and rc.json()["open_count"] == 1 and rc.json()["visible"] is True
# 無關 user 看不到(visible False, count 0)
xtok = _login(client, "other1")
rx = client.get(f"/api/feedback/document/{did}/error-count", headers=_auth(xtok))
assert rx.json()["visible"] is False and rx.json()["open_count"] == 0