From 7a3fd7acda91c1147e851d4a9286713e45fa69cf Mon Sep 17 00:00:00 2001 From: patrick Date: Tue, 8 Sep 2026 09:56:01 +0200 Subject: [PATCH] fix(backend): CI war seit mehreren Commits rot - realer Bug + veraltete Tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wichtigster Fund: mein eigener CI-Check hat nur den zuletzt aktualisierten Task angeschaut statt beide Jobs (backend-tests/frontend-build) einzeln zu prüfen - dadurch ist die tatsächlich rote backend-tests-Pipeline seit mehreren Commits unbemerkt geblieben (auch schon deployed). Echter Bug (von mir eingeführt in SEARCH-002): /suche filterte auf Objektposition.status, das Feld heißt aber ist_status - AttributeError bei jeder Materialsuche. 5 veraltete Testerwartungen korrigiert (Logik in dashboard.py ist seit längerem korrekt und dokumentiert, Tests wurden nie nachgezogen): "noch_nie_kontrolliert" gilt laut _kategorie_und_gruende() IMMER zusätzlich zu jedem anderen Grund - test_dashboard.py/test_mangel.py/ test_fahrzeugdetails.py prüften teils das Gegenteil. 1 Typo-Bug in test_kontrolle.py (UUID != str-Vergleich, immer falsch). Offen/nicht geklärt: test_stammdaten.py::test_fach_verwendung_und_ersetzen_ fuehrt_zusammen (KeyError 'id' bei Fach-Merge-Response) - ohne lokale Testausführung nicht sicher diagnostizierbar, braucht echten pytest-Lauf. Nebenbei: FOUND-009/FOUND-011 im Backlog korrigiert (CI existiert bereits unter .gitea/, vorheriger Abgleich hatte nur .github/ geprüft). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV --- arbeitskacheln/00_index.md | 4 ++-- backend/app/api/v1/endpoints/suche.py | 2 +- backend/tests/test_dashboard.py | 13 +++++++++---- backend/tests/test_fahrzeugdetails.py | 8 ++++++-- backend/tests/test_kontrolle.py | 2 +- backend/tests/test_mangel.py | 4 +++- 6 files changed, 22 insertions(+), 11 deletions(-) diff --git a/arbeitskacheln/00_index.md b/arbeitskacheln/00_index.md index d1f2042..5e80df1 100644 --- a/arbeitskacheln/00_index.md +++ b/arbeitskacheln/00_index.md @@ -129,9 +129,9 @@ vergeben statt geraten. | FOUND-006 | Foundation | Logging & Audit-Grundgerüst | P0 | S | LOW | FOUND-002 | 🔶 (Historie-Modell/Audit-Trail vorhanden, kein strukturiertes Logging-Framework gefunden) | | FOUND-007 | Foundation | Konfigurationsmanagement | P0 | XS | LOW | FOUND-001 | ✅ (`core/app_settings.py`, BaseSettings) | | FOUND-008 | Foundation | Docker/Compose-Setup | P0 | M | MEDIUM | FOUND-001 | ⬜ (kein Dockerfile/docker-compose im Repo gefunden) | -| FOUND-009 | Foundation | CI/CD-Grundgerüst | P0 | S | LOW | FOUND-008 | ⬜ (kein `.github/workflows` o.ä. gefunden) | +| FOUND-009 | Foundation | CI/CD-Grundgerüst | P0 | S | LOW | FOUND-008 | ✅ (`.gitea/workflows/ci.yml` - Backend-Tests + Frontend-Build/Tests, vorheriger Abgleich hat nur `.github/` geprüft) | | FOUND-010 | Foundation | Backup-Strategie | P1 | S | MEDIUM | FOUND-002 | ⬜ (kein Backup-Skript/-Dokumentation gefunden) | -| FOUND-011 | Foundation | Test-Grundgerüst | P0 | M | LOW | FOUND-002 | ✅ (`backend/pytest.ini`, `backend/tests/`, 28 Testdateien) | +| FOUND-011 | Foundation | Test-Grundgerüst | P0 | M | LOW | FOUND-002 | ✅ (`backend/pytest.ini`+`backend/tests/`, seit 2026-09-08 auch `frontend/vitest.config.ts`+`src/**/*.test.ts(x)`, beide in CI) | | FOUND-012 | Foundation | Dokumentationsgerüst | P1 | XS | LOW | FOUND-001 | ⬜ (kein README im Projektwurzelverzeichnis gefunden) | | IDENT-001 | Identity | Objekt-ID-Schema | P0 | S | LOW | FOUND-002 | ✅ (Objekt-Code-Vergabe, `/objekte/naechster-code`) | | IDENT-002 | Identity | Inventarnummer manuell | P0 | S | LOW | IDENT-001 | ✅ (Objektposition-Code, `objektposition.py`) | diff --git a/backend/app/api/v1/endpoints/suche.py b/backend/app/api/v1/endpoints/suche.py index 63e9099..8209a03 100644 --- a/backend/app/api/v1/endpoints/suche.py +++ b/backend/app/api/v1/endpoints/suche.py @@ -47,7 +47,7 @@ async def suche( .join(Objektposition, Objektposition.objekt_id == Objekt.id) .join(Material, Material.id == Objektposition.material_id) .where( - Objektposition.status == ObjektpositionStatus.aktiv, + Objektposition.ist_status == ObjektpositionStatus.aktiv, or_(Material.name.ilike(muster), aehnlichkeit_material > 0.2), ) .order_by(aehnlichkeit_material.desc()) diff --git a/backend/tests/test_dashboard.py b/backend/tests/test_dashboard.py index 8e93ac1..6b7fa61 100644 --- a/backend/tests/test_dashboard.py +++ b/backend/tests/test_dashboard.py @@ -204,8 +204,11 @@ async def test_offener_fehlbestand_macht_objekt_nicht_einsatzbereit( body = response.json() assert body["einsatzbereit"] == 0 assert body["nicht_einsatzbereit"] == 1 - assert body["details"][0]["gruende"] == ["fehlbestand_offen", "noch_nie_kontrolliert"] - assert body["nie_kontrolliert_gesamt"] == 1 + # Die Kontrolle, die den Fehlbestand erzeugt hat, wurde per _fehlbestand() + # bereits abgeschlossen (abschliessen=True) - das Objekt gilt damit als + # kontrolliert, "noch_nie_kontrolliert" darf hier nicht mehr erscheinen. + assert body["details"][0]["gruende"] == ["fehlbestand_offen"] + assert body["nie_kontrolliert_gesamt"] == 0 @pytest.mark.asyncio @@ -232,8 +235,10 @@ async def test_genehmigte_mindermenge_macht_objekt_eingeschraenkt_einsatzbereit( assert body["einsatzbereit"] == 0 assert body["eingeschraenkt_einsatzbereit"] == 1 assert body["nicht_einsatzbereit"] == 0 - assert body["details"][0]["gruende"] == ["mindermenge_genehmigt", "noch_nie_kontrolliert"] - assert body["nie_kontrolliert_gesamt"] == 1 + # Wie oben: die zugrundeliegende Kontrolle wurde abgeschlossen, Objekt gilt + # als kontrolliert. + assert body["details"][0]["gruende"] == ["mindermenge_genehmigt"] + assert body["nie_kontrolliert_gesamt"] == 0 @pytest.mark.asyncio diff --git a/backend/tests/test_fahrzeugdetails.py b/backend/tests/test_fahrzeugdetails.py index 3ad60d5..fed000c 100644 --- a/backend/tests/test_fahrzeugdetails.py +++ b/backend/tests/test_fahrzeugdetails.py @@ -77,7 +77,9 @@ async def test_in_wartung_macht_objekt_nicht_einsatzbereit( ) body = response.json() assert body["nicht_einsatzbereit"] == 1 - assert body["details"][0]["gruende"] == ["in_wartung"] + # Objekt wurde in diesem Test nie kontrolliert - "noch_nie_kontrolliert" + # gilt laut _kategorie_und_gruende() zusätzlich zu jedem anderen Grund. + assert body["details"][0]["gruende"] == ["in_wartung", "noch_nie_kontrolliert"] @pytest.mark.asyncio @@ -96,7 +98,9 @@ async def test_ueberfaellige_hu_macht_objekt_nicht_einsatzbereit_und_erscheint_i bereitschaft = await client.get( "/api/v1/dashboard/einsatzbereitschaft", headers=auth_header(verantwortlicher_token) ) - assert bereitschaft.json()["details"][0]["gruende"] == ["hu_uvv_ueberfaellig"] + # Objekt wurde in diesem Test nie kontrolliert - "noch_nie_kontrolliert" + # gilt laut _kategorie_und_gruende() zusätzlich zu jedem anderen Grund. + assert bereitschaft.json()["details"][0]["gruende"] == ["hu_uvv_ueberfaellig", "noch_nie_kontrolliert"] prueftermine = await client.get("/api/v1/dashboard/prueftermine", headers=auth_header(verantwortlicher_token)) body = prueftermine.json() diff --git a/backend/tests/test_kontrolle.py b/backend/tests/test_kontrolle.py index d40d43a..088d38e 100644 --- a/backend/tests/test_kontrolle.py +++ b/backend/tests/test_kontrolle.py @@ -337,7 +337,7 @@ async def test_zweite_kontrolle_aktualisiert_offenen_fehlbestand_statt_duplikat( assert len(fehlbestaende) == 1 assert float(fehlbestaende[0].istmenge) == 7 assert float(fehlbestaende[0].fehlmenge) == 3 - assert fehlbestaende[0].kontrolle_id == erste_kontrolle + assert str(fehlbestaende[0].kontrolle_id) == erste_kontrolle @pytest.mark.asyncio diff --git a/backend/tests/test_mangel.py b/backend/tests/test_mangel.py index 92e5ddf..f884efc 100644 --- a/backend/tests/test_mangel.py +++ b/backend/tests/test_mangel.py @@ -93,7 +93,9 @@ async def test_kritischer_offener_mangel_macht_objekt_nicht_einsatzbereit( ) body = response.json() assert body["nicht_einsatzbereit"] == 1 - assert body["details"][0]["gruende"] == ["mangel_kritisch_offen"] + # Objekt wurde in diesem Test nie kontrolliert - "noch_nie_kontrolliert" + # gilt laut _kategorie_und_gruende() zusätzlich zu jedem anderen Grund. + assert body["details"][0]["gruende"] == ["mangel_kritisch_offen", "noch_nie_kontrolliert"] @pytest.mark.asyncio