"""Ingest-API (Plan §6.1): Auth, dry_run, Idempotenz, Nachrechnung.""" from sqlalchemy import select from oamc.models import Ergebnis, Start, Turnier from oamc.models.enums import VeranstaltungStatus from oamc.seeds import seed_saison_2026 from tests import factories as f HEAD = {"X-API-Key": "test-key"} def _turnier(client): db = client.db seed_saison_2026(db) v = f.veranstaltung(db, status=VeranstaltungStatus.LAUFEND) t = f.turnier(db, v) db.flush() return t.id def test_ohne_api_key_401(client): r = client.post("/api/v1/turniere/1/ergebnisse", json={"quelle": "x", "idempotenz_schluessel": "k", "ergebnisse": []}) assert r.status_code == 401 def test_dry_run_meldet_neue_fahrer_ohne_zu_schreiben(client): tid = _turnier(client) payload = { "quelle": "excel-auswertung-v3", "idempotenz_schluessel": "2026-05-31-reinheim-final", "ergebnisse": [ { "startnummer": 32, "fahrer": {"nachname": "Gál-Szász", "vorname": "Péter", "geburtsjahr": 2015, "geschlecht": "M", "verein": "OAMC Reinheim"}, "klasse": "J 2", "summe_zeit": "159.16", "summe_fehler": 10, } ], } r = client.post(f"/api/v1/turniere/{tid}/ergebnisse?dry_run=true", json=payload, headers=HEAD) assert r.status_code == 200 body = r.json() assert body["dry_run"] is True assert body["neue_fahrer"] == 1 # nichts geschrieben assert client.db.scalars(select(Start).where(Start.turnier_id == tid)).first() is None def test_echter_upload_dann_idempotent(client): tid = _turnier(client) payload = { "quelle": "excel-auswertung-v3", "idempotenz_schluessel": "abc-final", "ergebnisse": [ {"startnummer": 1, "fahrer": {"nachname": "Bernius", "vorname": "Meik", "geburtsjahr": 1985}, "klasse": "S 6", "wertungsklasse": "S 5, S 9, S 3, S 6, S 7", "summe_zeit": "105.43", "summe_fehler": 0, "gesamt_erwartet": "105.43", "platzierung_erwartet": 1}, {"startnummer": 2, "fahrer": {"nachname": "Schmunk", "vorname": "Christian", "geburtsjahr": 1988}, "klasse": "S 9", "wertungsklasse": "S 5, S 9, S 3, S 6, S 7", "summe_zeit": "103.26", "summe_fehler": 10, "gesamt_erwartet": "113.26", "platzierung_erwartet": 2}, ], } r1 = client.post(f"/api/v1/turniere/{tid}/ergebnisse", json=payload, headers=HEAD) assert r1.status_code == 200, r1.text b1 = r1.json() assert b1["angelegt"] == 2 assert b1["abweichungen"] == [] # Nachrechnung deckt sich mit gesamt_erwartet r2 = client.post(f"/api/v1/turniere/{tid}/ergebnisse", json=payload, headers=HEAD) assert r2.json()["idempotent_uebersprungen"] is True assert client.db.scalars(select(Start).where(Start.turnier_id == tid)).all().__len__() == 2 def test_abweichung_zur_erwartung_wird_gemeldet(client): tid = _turnier(client) payload = { "quelle": "excel", "idempotenz_schluessel": "abw-1", "ergebnisse": [ {"startnummer": 1, "fahrer": {"nachname": "A", "vorname": "A", "geburtsjahr": 1985}, "klasse": "S 6", "summe_zeit": "100.00", "summe_fehler": 0, "gesamt_erwartet": "999.00", "platzierung_erwartet": 1}, ], } r = client.post(f"/api/v1/turniere/{tid}/ergebnisse", json=payload, headers=HEAD) body = r.json() assert any(a["feld"] == "gesamt" for a in body["abweichungen"]) assert body["warnungen"] def test_finalisieren_setzt_status_final(client): tid = _turnier(client) client.post(f"/api/v1/turniere/{tid}/ergebnisse", json={ "quelle": "x", "idempotenz_schluessel": "f1", "ergebnisse": [ {"startnummer": 1, "fahrer": {"nachname": "A", "vorname": "A", "geburtsjahr": 1985}, "klasse": "S 6", "summe_zeit": "100.00", "summe_fehler": 0}, ], }, headers=HEAD) r = client.post(f"/api/v1/turniere/{tid}/finalisieren", headers=HEAD) assert r.status_code == 200 assert client.db.get(Turnier, tid).status.value == "final" def test_turnier_wird_an_jahresmeisterschaft_gehaengt(client): db = client.db seed_saison_2026(db) v = f.veranstaltung(db) db.flush() r = client.post("/api/v1/turniere", headers=HEAD, json={ "veranstaltung_id": v.id, "name": "Reinheim", "zaehlt_meisterschaft": True, }) assert r.status_code == 201, r.text m = f.meisterschaft(db) assert r.json()["meisterschaft_id"] == m.id # o. M. -> keine Verknuepfung r2 = client.post("/api/v1/turniere", headers=HEAD, json={ "veranstaltung_id": v.id, "name": "Pokal o. M.", "zaehlt_meisterschaft": False, }) assert r2.json()["meisterschaft_id"] is None def test_meisterschaft_stand_liefert_alle_wertungen(client): db = client.db seed_saison_2026(db) m = f.meisterschaft(db) r = client.get(f"/api/v1/meisterschaften/{m.id}/stand") assert r.status_code == 200 codes = {w["wertung"] for w in r.json()["wertungen"]} assert "erwachsene" in codes # eine einzelne Wertung gezielt abrufen r2 = client.get(f"/api/v1/meisterschaften/{m.id}/stand", params={"wertung": "J 3"}) assert r2.status_code == 200 assert r2.json()["wertung"] == "J 3"