Files
upbeatBytes/tests/test_feedback.py
T
thejayman77 18707a50d2 Feedback admin: 404 on missing id for read/delete
Per audit: read-toggle and delete returned {"ok":true} even for a nonexistent
id. Return 404 when no row is affected, so the optimistic UI can distinguish a
stale/already-deleted row from a real success. (The postJSON/delJSON imports
flagged in the audit were already present — verified in source + built bundle.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 13:45:14 -04:00

97 lines
4.4 KiB
Python

import pytest
from fastapi.testclient import TestClient
def _app(tmp_path, monkeypatch, admin=""):
db = tmp_path / "t.sqlite3"
monkeypatch.setenv("GOODNEWS_DB", str(db))
monkeypatch.setenv("GOODNEWS_PUBLIC_BASE_URL", "http://testserver")
monkeypatch.setenv("GOODNEWS_ADMIN_EMAILS", admin)
import importlib
import goodnews.api as api
importlib.reload(api)
from goodnews.db import connect, init_db
connect(str(db)).close()
c = connect(str(db)); init_db(c); c.close()
return api.create_app(), api, db
def _count(db):
from goodnews.db import connect
c = connect(str(db)); n = c.execute("SELECT COUNT(*) FROM feedback").fetchone()[0]; c.close()
return n
def test_feedback_stored_and_validated(tmp_path, monkeypatch):
app, api, db = _app(tmp_path, monkeypatch)
monkeypatch.setattr(api.email_send, "send_feedback", lambda *a, **k: None)
tc = TestClient(app)
assert tc.post("/api/feedback", json={"message": " "}).status_code == 422 # empty
assert tc.post("/api/feedback", json={"category": "idea", "message": "Love it", "visitor": "v"}).json() == {"ok": True}
assert _count(db) == 1
# honeypot tripped → accepted but NOT stored
tc.post("/api/feedback", json={"message": "spam", "hp": "i am a bot", "visitor": "v"})
assert _count(db) == 1
def test_admin_feedback_gated(tmp_path, monkeypatch):
app, api, db = _app(tmp_path, monkeypatch, admin="boss@x.com")
monkeypatch.setattr(api.email_send, "send_feedback", lambda *a, **k: None)
TestClient(app).post("/api/feedback", json={"message": "hi", "visitor": "v"})
assert TestClient(app).get("/api/admin/feedback").status_code == 401 # anon
# sign in as admin
tc = TestClient(app); sent = {}
monkeypatch.setattr(api.email_send, "send_magic_link", lambda to, link: sent.update(link=link))
tc.post("/api/auth/email/start", json={"email": "boss@x.com"})
tc.post("/api/auth/email/verify", json={"token": sent["link"].split("token=")[1]})
fb = tc.get("/api/admin/feedback").json()
assert len(fb) == 1 and fb[0]["message"] == "hi"
stats = tc.get("/api/admin/stats").json()
assert {"funnel", "accounts", "retention", "emotional_mix", "paywall", "replace"} <= set(stats)
assert stats["accounts"]["total"] >= 1
def _admin_client(tmp_path, monkeypatch):
app, api, db = _app(tmp_path, monkeypatch, admin="boss@x.com")
monkeypatch.setattr(api.email_send, "send_feedback", lambda *a, **k: None)
TestClient(app).post("/api/feedback", json={"message": "hello there", "visitor": "v"})
tc = TestClient(app); sent = {}
monkeypatch.setattr(api.email_send, "send_magic_link", lambda to, link: sent.update(link=link))
tc.post("/api/auth/email/start", json={"email": "boss@x.com"})
tc.post("/api/auth/email/verify", json={"token": sent["link"].split("token=")[1]})
return tc
def test_feedback_read_unread_and_delete(tmp_path, monkeypatch):
tc = _admin_client(tmp_path, monkeypatch)
fb = tc.get("/api/admin/feedback").json()
fid = fb[0]["id"]
assert fb[0]["read_at"] is None # starts unread
assert tc.get("/api/admin/stats").json()["feedback_unread"] == 1
tc.post(f"/api/admin/feedback/{fid}/read", json={"read": True})
assert tc.get("/api/admin/feedback").json()[0]["read_at"] is not None
assert tc.get("/api/admin/stats").json()["feedback_unread"] == 0
tc.post(f"/api/admin/feedback/{fid}/read", json={"read": False}) # back to unread
assert tc.get("/api/admin/stats").json()["feedback_unread"] == 1
assert tc.delete(f"/api/admin/feedback/{fid}").json() == {"ok": True}
assert tc.get("/api/admin/feedback").json() == []
def test_feedback_admin_actions_gated(tmp_path, monkeypatch):
app, api, db = _app(tmp_path, monkeypatch, admin="boss@x.com")
monkeypatch.setattr(api.email_send, "send_feedback", lambda *a, **k: None)
TestClient(app).post("/api/feedback", json={"message": "hi", "visitor": "v"})
anon = TestClient(app)
assert anon.post("/api/admin/feedback/1/read", json={"read": True}).status_code == 401
assert anon.delete("/api/admin/feedback/1").status_code == 401
def test_feedback_actions_404_on_missing_id(tmp_path, monkeypatch):
tc = _admin_client(tmp_path, monkeypatch)
assert tc.post("/api/admin/feedback/9999/read", json={"read": True}).status_code == 404
assert tc.delete("/api/admin/feedback/9999").status_code == 404