"""A32 — smoke tests : un par route des 4 routers à 0 test. `webhooks.py` (3), `notes.py` (2), `sidebar_config.py` (2), `github_routes.py` (2). Aucun accès réseau réel : le secret webhook est piloté par monkeypatch et `gitea.list_webhooks` est stubbé. """ from app.config import settings OWNER, REPO = "smoke-a32", "repo-x" # ── webhooks.py (0/3) ──────────────────────────────────────────────────────── def test_webhook_receive_ok_without_secret(client, monkeypatch): monkeypatch.setattr(settings, "gitea_webhook_secret", "") r = client.post("/api/webhook", json={"zen": "smoke"}) assert r.status_code == 200 assert r.json() == {"status": "ok"} def test_webhook_receive_rejects_bad_signature(client, monkeypatch): monkeypatch.setattr(settings, "gitea_webhook_secret", "s3cret") r = client.post("/api/webhook", json={}) # pas d'en-tête X-Gitea-Signature assert r.status_code == 401 assert r.json()["detail"] == "Invalid signature" def test_webhook_register_fails_fast_without_secret(client, monkeypatch): # secret vide → 400 AVANT tout appel réseau (gitea.list_webhooks) monkeypatch.setattr(settings, "gitea_webhook_secret", "") r = client.post(f"/api/webhook/register/{OWNER}/{REPO}") assert r.status_code == 400 assert "not configured" in r.json()["detail"] def test_webhook_status_stubbed(client, monkeypatch): from app.services import gitea_client async def _no_hooks(*_a, **_k): return [] monkeypatch.setattr(gitea_client.gitea, "list_webhooks", _no_hooks) r = client.get(f"/api/webhook/status/{OWNER}/{REPO}") assert r.status_code == 200 assert r.json() == {"registered": False} # ── notes.py (0/2) ─────────────────────────────────────────────────────────── def test_notes_get_renders_html(client): r = client.get(f"/notes/{OWNER}/{REPO}") assert r.status_code == 200 assert "text/html" in r.headers["content-type"] def test_notes_save_roundtrip(client): payload = "note de smoke A32" html_payload = "&co" r = client.post( f"/notes/{OWNER}/{REPO}", data={"content": f"{payload} {html_payload}"} ) assert r.status_code == 200 assert payload in r.text # contenu rendu assert "<b>&co</b>" in r.text # échappé (A10 — pas d'HTML cru) again = client.get(f"/notes/{OWNER}/{REPO}") assert payload in again.text # persistée en base (upsert) # ── sidebar_config.py (0/2) ────────────────────────────────────────────────── def test_sidebar_config_get(client): r = client.get("/api/sidebar/config") assert r.status_code == 200 cfg = r.json()["config"] assert isinstance(cfg, dict) and "meetings" in cfg def test_sidebar_config_put_persists_and_validates(client): r = client.put( "/api/sidebar/config", json={"config": {"meetings": {"visible": False, "order": 2, "show_count": 5}}}, ) assert r.status_code == 200 back = client.get("/api/sidebar/config").json()["config"] assert back["meetings"]["visible"] is False # relu depuis users.sidebar_config bad = client.put("/api/sidebar/config", json={}) assert bad.status_code == 400 # config dict requis # remise en état pour les autres tests restore = client.put( "/api/sidebar/config", json={"config": {"meetings": {"visible": True, "order": 2, "show_count": 5}}}, ) assert restore.status_code == 200 # ── github_routes.py (0/2) ─────────────────────────────────────────────────── def test_github_status_unlinked(client): r = client.get("/api/github/status") assert r.status_code == 200 assert r.json() == {"linked": False} def test_github_disconnect_ok(client): r = client.delete("/api/github/disconnect") assert r.status_code == 200 assert r.json() == {"status": "ok"}