antragstracker/backend/tests/test_api.py
Dotty Dotter 17606ab237 feat: Initial commit — Antragstracker Hagen
Vollständige Pipeline zur Analyse kommunaler Vorlagen aus ALLRIS:
- OParl-Import: 20.149 Vorlagen
- PDF-Extraktion: 10.045 Volltexte (adaptives Throttling)
- KI-Zusammenfassungen: 10.026 via Qwen Plus (parallelisiert)
- Beratungsfolge-Scraper: Beschlusstexte + Wortprotokolle
- Abstimmungs-Analyse mit Koalitionsmatrix
- Georeferenzierung (Nominatim)

Stack: FastAPI + SvelteKit + SQLite
Deployment: Docker + Traefik auf VServer

Daten (DB, Logs) nicht im Repo — siehe Restic-Backup.
Repo-Setup: scripts/setup.sh für Neuaufbau aus OParl-API.
2026-03-30 16:37:58 +02:00

114 lines
3.4 KiB
Python

"""Tests for API endpoints against the real database."""
import pytest
from fastapi.testclient import TestClient
from tracker.main import app
from tracker.db.session import get_connection
@pytest.fixture
def client():
return TestClient(app)
@pytest.fixture
def db():
conn = get_connection()
yield conn
conn.close()
# --- Sanity checks on the DB ---
class TestDatabaseSanity:
def test_vorlagen_count(self, db):
count = db.execute("SELECT COUNT(*) as cnt FROM vorlagen").fetchone()["cnt"]
assert count >= 6000, f"Expected >=6000 Vorlagen, got {count}"
def test_vorlagen_types(self, db):
types = db.execute(
"SELECT DISTINCT typ FROM vorlagen WHERE typ IS NOT NULL"
).fetchall()
type_names = {r["typ"] for r in types}
assert "antrag" in type_names
assert "anfrage" in type_names
def test_beratungen_exist(self, db):
count = db.execute("SELECT COUNT(*) as cnt FROM beratungen").fetchone()["cnt"]
assert count > 0, "No Beratungen in DB"
def test_suffix_vorlagen_exist(self, db):
count = db.execute(
"SELECT COUNT(*) as cnt FROM vorlagen WHERE aktenzeichen_suffix IS NOT NULL"
).fetchone()["cnt"]
assert count > 0, "No suffix Vorlagen in DB"
# --- API: Health ---
class TestHealth:
def test_health(self, client):
resp = client.get("/api/health")
assert resp.status_code == 200
assert resp.json()["status"] == "ok"
# --- API: Vorlagen ---
class TestVorlagenAPI:
def test_list_vorlagen(self, client):
resp = client.get("/api/vorlagen?page=1&page_size=10")
assert resp.status_code == 200
data = resp.json()
assert data["total"] >= 6000
assert len(data["items"]) == 10
assert data["page"] == 1
def test_list_vorlagen_filter_typ(self, client):
resp = client.get("/api/vorlagen?typ=antrag&page_size=5")
assert resp.status_code == 200
data = resp.json()
assert data["total"] > 0
for item in data["items"]:
assert item["typ"] == "antrag"
def test_list_vorlagen_filter_suche(self, client):
resp = client.get("/api/vorlagen?suche=Klimaschutz&page_size=5")
assert resp.status_code == 200
# May or may not find results, but should not error
def test_get_vorlage_detail(self, client, db):
# Get first vorlage with aktenzeichen
row = db.execute(
"SELECT id FROM vorlagen WHERE aktenzeichen IS NOT NULL LIMIT 1"
).fetchone()
assert row is not None
resp = client.get(f"/api/vorlagen/{row['id']}")
assert resp.status_code == 200
data = resp.json()
assert data["id"] == row["id"]
assert data["aktenzeichen"] is not None
def test_get_vorlage_not_found(self, client):
resp = client.get("/api/vorlagen/999999")
assert resp.status_code == 404
# --- API: Ketten ---
class TestKettenAPI:
def test_list_ketten_empty_initially(self, client):
"""Before building chains, the list may be empty."""
resp = client.get("/api/ketten?page_size=5")
assert resp.status_code == 200
def test_list_ketten_filter(self, client):
resp = client.get("/api/ketten?status=eingereicht&page_size=5")
assert resp.status_code == 200
def test_get_kette_not_found(self, client):
resp = client.get("/api/ketten/999999")
assert resp.status_code == 404