banyaro/tests/test_partner_profile.py
rene a40aa183ec Admin: offene Partner-Profil-Freigaben in 'Zu erledigen'-Leiste + ADMIN_EMAIL-Befund
Rene reichte ein Partner-Profil ein und sah als Admin nirgends einen Hinweis:
1. Action-Items kannten Partner-Profile nicht — partner_profiles_pending
   (submitted_at gesetzt, approved=0) jetzt im Endpoint + Chip im Admin-Kopf
   (Klick -> Partner-Tab). Test ergänzt (7 passed).
2. ADMIN_EMAIL fehlte in BEIDEN .env auf der DS (Prod+Staging) — damit wurden
   auch Upgrade-Anfragen-Mails still verschluckt (bekanntes Silent-Skip-Muster).
   Auf der DS nachgetragen; greift je beim nächsten Deploy.
2026-06-07 17:34:56 +02:00

155 lines
6.1 KiB
Python

"""Smoke-Tests fuer Partner-Profile (Editor + Freigabe-Workflow + oeffentlicher Showcase)."""
import io
def _make_partner(user_email: str):
"""Setzt is_partner=1 direkt in der Test-DB."""
from database import db
with db() as conn:
conn.execute("UPDATE users SET is_partner=1 WHERE email=?", (user_email,))
def test_my_profile_requires_partner(client, user):
"""GET /api/partner/my-profile -> 403 fuer normale User."""
r = client.get("/api/partner/my-profile", headers=user["headers"])
assert r.status_code == 403
def test_partner_profile_full_flow(client, user, admin):
"""Texte speichern -> einreichen -> Admin gibt frei -> oeffentlich sichtbar."""
_make_partner(user["email"])
# Leeres Profil mit Storage-Infos
r = client.get("/api/partner/my-profile", headers=user["headers"])
assert r.status_code == 200, r.text
assert r.json()["storage_limit_mb"] == 200
# Texte speichern (Website ohne Schema wird normalisiert)
r = client.put("/api/partner/my-profile", headers=user["headers"], json={
"display_name": "Hundeblog Test",
"tagline": "Testkanal",
"bio": "Wir testen Ban Yaro.",
"website": "hundeblog-test.de",
"instagram": "@hundeblogtest",
})
assert r.status_code == 200, r.text
p = r.json()["profile"]
assert p["display_name"] == "Hundeblog Test"
assert p["website"] == "https://hundeblog-test.de"
# Vor Freigabe nicht oeffentlich
r = client.get("/api/partners/public")
assert all(x.get("display_name") != "Hundeblog Test" for x in r.json()["partners"])
# Einreichen
r = client.post("/api/partner/my-profile/submit", headers=user["headers"], json={})
assert r.status_code == 200, r.text
assert r.json()["profile"]["submitted_at"]
# Admin sieht das Profil und gibt frei
r = client.get("/api/admin/partner/profiles", headers=admin["headers"])
assert r.status_code == 200
mine = [x for x in r.json() if x.get("display_name") == "Hundeblog Test"]
assert mine, "Profil fehlt in der Admin-Liste"
uid = mine[0]["user_id"]
r = client.post(f"/api/admin/partner/profiles/{uid}/review",
headers=admin["headers"], json={"approved": 1})
assert r.status_code == 200
# Jetzt oeffentlich (ohne Login)
r = client.get("/api/partners/public")
names = [x["display_name"] for x in r.json()["partners"]]
assert "Hundeblog Test" in names
# Ablehnen entfernt es wieder von der oeffentlichen Seite
r = client.post(f"/api/admin/partner/profiles/{uid}/review",
headers=admin["headers"], json={"approved": -1})
assert r.status_code == 200
r = client.get("/api/partners/public")
assert "Hundeblog Test" not in [x["display_name"] for x in r.json()["partners"]]
def test_submit_requires_display_name(client, user):
"""Einreichen ohne Anzeigename -> 400."""
_make_partner(user["email"])
r = client.post("/api/partner/my-profile/submit", headers=user["headers"], json={})
assert r.status_code == 400
def test_logo_and_photo_upload(client, user):
"""Logo + Foto hochladen, Foto wieder loeschen."""
from PIL import Image
_make_partner(user["email"])
def _png(size=(64, 64), color="red"):
buf = io.BytesIO()
Image.new("RGB", size, color).save(buf, format="PNG")
buf.seek(0)
return buf
# Logo
r = client.post("/api/partner/my-profile/logo", headers=user["headers"],
files={"file": ("logo.png", _png(), "image/png")})
assert r.status_code == 200, r.text
assert r.json()["logo_url"].startswith("/media/partner/")
# Foto
r = client.post("/api/partner/my-profile/photos", headers=user["headers"],
files={"file": ("foto.png", _png(color="blue"), "image/png")})
assert r.status_code == 200, r.text
photos = r.json()["photos"]
assert len(photos) == 1 and photos[0].endswith(".webp")
# Speicher belegt
r = client.get("/api/partner/my-profile", headers=user["headers"])
assert r.json()["storage_mb"] > 0
# Foto loeschen
r = client.post("/api/partner/my-profile/photos/0/delete", headers=user["headers"], json={})
assert r.status_code == 200
assert r.json()["photos"] == []
def test_heic_uploads_convert(client, user):
"""HEIC (iPhone-Format) wird bei Logo UND Foto nach WebP konvertiert."""
import pillow_heif
from PIL import Image
_make_partner(user["email"])
pillow_heif.register_heif_opener()
buf = io.BytesIO()
Image.new("RGB", (64, 64), "green").save(buf, format="HEIF")
heic_bytes = buf.getvalue()
# Logo als HEIC
r = client.post("/api/partner/my-profile/logo", headers=user["headers"],
files={"file": ("IMG_0001.HEIC", io.BytesIO(heic_bytes), "image/heic")})
assert r.status_code == 200, r.text
assert r.json()["logo_url"].endswith(".webp")
# Foto als HEIC
r = client.post("/api/partner/my-profile/photos", headers=user["headers"],
files={"file": ("IMG_0002.heic", io.BytesIO(heic_bytes), "image/heic")})
assert r.status_code == 200, r.text
assert r.json()["photos"][0].endswith(".webp")
def test_submit_appears_in_admin_action_items(client, user, admin):
"""Eingereichtes Profil taucht im Admin-'Zu erledigen'-Zaehler auf."""
_make_partner(user["email"])
client.put("/api/partner/my-profile", headers=user["headers"],
json={"display_name": "Action-Item-Test"})
before = client.get("/api/admin/action-items", headers=admin["headers"]).json()
r = client.post("/api/partner/my-profile/submit", headers=user["headers"], json={})
assert r.status_code == 200
after = client.get("/api/admin/action-items", headers=admin["headers"]).json()
assert after["partner_profiles_pending"] == before.get("partner_profiles_pending", 0) + 1
def test_partner_has_pro_access(client, user):
"""is_partner=1 -> has_pro_access True (Pro gratis fuer Partner)."""
from auth import has_pro_access
assert has_pro_access({"rolle": "user", "subscription_tier": "standard", "is_partner": 1})
assert not has_pro_access({"rolle": "user", "subscription_tier": "standard", "is_partner": 0})