API-Test-Suite live: 165 Tests als Rewrite-Versicherung (08.06.2026 früh)
Executive Summary
Die in der Stack-Recherche empfohlene Black-Box-API-Suite ist gebaut und grün (ADR-51): 150 fast-Tests in ~1,5 Sekunden (Gate gesamt ~15 s inkl. Image-Build und Kandidaten-Start) plus 15 Integrationstests mit echten 3rd-Party-Calls. Alle ~120 Endpoints sind abgedeckt — Statuscodes, 85 Golden-Snapshots der vollständigen Antworten und die Zustands-Ketten (Vorrats-Kreislauf, Plan↔Tagebuch, Teilverbrauch). Dennis' Prämisse ist baulich verankert: Backend nach Belieben neu schreiben (Spring Boot, Postgres, Redis …) — solange ./app/tests/run.sh grün ist, merkt das Frontend nichts.
In einfachen Worten
Vor jedem Deploy startet ein Skript eine Wegwerf-Kopie des Backends mit bekannten Testdaten und stellt ihr ~150 Fragen — von „was liegt im Vorrat?" bis „wenn ich ein geloggtes Essen lösche, kommt der Hack zurück ins Fach?". Die Antworten werden Byte für Byte mit eingefrorenen Soll-Antworten verglichen. Antwortet ein künftiges, komplett neu gebautes Backend irgendwo anders, fällt das Gate um, bevor irgendetwas live geht.
Bausteine
- Stack: pytest + httpx + syrupy (wie recherchiert) — pytest ist nur der Runner; die Suite spricht ausschließlich HTTP, importiert nie Backend-Code und überlebt so jeden Rewrite.
- Seed als Vertrag:
POST /api/test/reset(nurTESTMODE=1) stellt Seed v1 her — 2 Profile, 12 Artikel quer durch alle Sonderfälle (Packung/kg/ohne Nährwerte/geflaggt/Non-Food), 2 Bons inkl. Rabatt + Pfand + Gewichtsartikel, Vorrat mit angebrochener Packung, 2 Gerichte (gerechnet vs. HF-Festwerte), Plan, Einkaufsliste, KI-Job, eGYM/Garmin-Daten. Feste IDs ⇒ deterministische Snapshots ohne ID-Maskierung. Eine DB-Fixture-Kopie hätte SQLite zementiert — der Endpoint ist die DB-agnostische Antwort. - Marker-Split:
-m "not integration"= der Sekunden-Lauf.@integration= OFF, HelloFresh, eGYM, Garmin, Gemini, Lidl-Sync, OIDC-Browser-Login (macht Discovery = Netz-Call). KI-Endpoints queuen nur in die DB → fast. - Gate:
./app/tests/run.shbaut das Image, startet den Kandidaten auf :9301 mit leerer DB, testet, räumt ab. Exit-Code entscheidet.
Beifang (der Wert von Tests, Folge 1)
db.init() crashte auf jeder frischen Datenbank: die hf_name-Migration alterte dish_ingredients, bevor die Tabelle erzeugt war. In Produktion nie aufgefallen — dort existierte die Tabelle immer. Der allererste Kandidaten-Start fand ihn. Außerdem fixiert: ai-resolve-Trigger/Log sind Dateisystem-Zustand und werden vom Reset mit abgeräumt.
Verifikation
2× komplettes Gate gegen je frische Kandidaten: identisch grün (Determinismus über Container-Leben hinweg) · Integration: 165/165 · Zyklen-Flows decken ADR-48/49-Logik grammgenau (inkl. Server-Rundung je Abzug).
Nutzung & Pflege
./app/tests/run.sh # vor jedem Backend-Deploy
./app/tests/run.sh --integration # NUR auf Dennis' Anweisung (Gemini/OpenRouter = echtes Geld)
Neue Endpoints ⇒ Contract-Test zuerst. Gewollte API-Änderungen ⇒ --snapshot-update + Diff reviewen. Seed-Änderungen = neue Seed-Version (bewusster Akt). Details: app/tests/README.md.
Bezug: Stack-Recherche · ADR-51 in app/ARCHITEKTUR.md