"""Smoke tests pour les nouvelles routes /logs et /aide + journalisation présence.""" import sys import tempfile from pathlib import Path # Prépare un DATA_DIR temporaire AVANT l'import de models _TMP = Path(tempfile.mkdtemp()) import models models.DATA_DIR = _TMP models.CONFIG_FILE = models.DATA_DIR / "config.json" models.CONFIG_FILE.write_text('{"heures_jour": 7.8, "allowed_email_domain": "x.fr"}') # Recharge main dans un état propre sys.path.insert(0, ".") import main from fastapi.testclient import TestClient client = TestClient(main.app) def _login_as(uid: str): """Crée un user avec mdp puis se connecte (renvoie les cookies).""" import bcrypt models.save_auth(uid, email=f"{uid}@x.fr", password_hash=bcrypt.hashpw(b"x" * 12, bcrypt.gensalt()).decode()) r = client.post("/login/password", data={"email": f"{uid}@x.fr", "password": "x" * 12}, follow_redirects=False) assert r.status_code == 302, r.text return {"user_id": uid} COOKIES = _login_as("alice") # ── Route /logs ────────────────────────────────────────────────────────────── def test_logs_page_requires_auth(): # Client frais, sans cookie, doit être redirigé vers /login unauth = TestClient(main.app) r = unauth.get("/logs", follow_redirects=False) assert r.status_code == 302 assert "/login" in r.headers["location"] def test_logs_page_empty_for_new_user(): r = client.get("/logs", cookies=COOKIES) assert r.status_code == 200 assert "Journaux" in r.text assert "Aucun événement" in r.text # état initial def test_logs_page_shows_entries_after_presence(): token = models.load_notif_config("alice")["token"] # Simule une arrivée Tasker client.get(f"/presence/{token}/arrivee") # Simule un départ client.get(f"/presence/{token}/depart") r = client.get("/logs", cookies=COOKIES) assert r.status_code == 200 assert "arrivée" in r.text assert "départ" in r.text assert "Présence" in r.text assert "Aucun événement" not in r.text def test_logs_filter_query_param(): # Toutes les entrées sont de type "presence" à ce stade r_all = client.get("/logs", cookies=COOKIES) r_notif = client.get("/logs?filter=notif", cookies=COOKIES) r_pres = client.get("/logs?filter=presence", cookies=COOKIES) # Le filtre "notif" ne doit PAS montrer les entrées presence assert "arrivée" not in r_notif.text # Le filtre "presence" DOIT les montrer assert "arrivée" in r_pres.text assert "départ" in r_pres.text # Le total Tout inclut tout assert "arrivée" in r_all.text # ── Route /aide ────────────────────────────────────────────────────────────── def test_aide_page_requires_auth(): unauth = TestClient(main.app) r = unauth.get("/aide", follow_redirects=False) assert r.status_code == 302 assert "/login" in r.headers["location"] def test_aide_page_content(): r = client.get("/aide", cookies=COOKIES) assert r.status_code == 200 # Vérifie la présence des sections clés for needle in ["ntfy", "Tasker", "Aide", "topic", "géofence", "/logs"]: assert needle in r.text, f"missing {needle!r}" # ── Logging via notifications.send_ntfy (échec) ───────────────────────────── def test_send_ntfy_failure_logs_entry(): # Serveur injoignable → échec → entrée de log "échec" import notifications notifications.send_ntfy( "http://127.0.0.1:1", "topic-injoignable", "msg test", "titre test", user_id="alice", ) logs = models.load_logs("alice") assert any(l["event"] == "échec" and l["type"] == "notif" for l in logs) # ── Navigation ────────────────────────────────────────────────────────────── def test_nav_links_present(): r = client.get("/aide", cookies=COOKIES) assert 'href="/logs"' in r.text assert 'href="/aide"' in r.text assert 'href="/settings"' in r.text # ── Isolation par user ────────────────────────────────────────────────────── def test_logs_isolated_per_user(): # Bob ne doit pas voir les logs d'alice bob_cookies = _login_as("bob") r = client.get("/logs", cookies=bob_cookies) assert "arrivée" not in r.text assert "Aucun événement" in r.text # ── Rotation des logs ─────────────────────────────────────────────────────── def test_log_rotation_caps_at_max(): # Alice a déjà quelques entrées ; on en ajoute largement > MAX_LOGS for i in range(models.MAX_LOGS + 50): models.append_log("alice", "notif", "envoyée", f"msg {i}") logs = models.load_logs("alice") assert len(logs) == models.MAX_LOGS # Le plus récent est bien le dernier inséré assert logs[0]["message"] == f"msg {models.MAX_LOGS + 49}" def test_invalid_log_type_rejected(): try: models.append_log("alice", "invalid_type", "x", "y") assert False, "doit lever une AssertionError" except AssertionError: pass