fix(backend): CI war seit mehreren Commits rot - realer Bug + veraltete Tests
CI / backend-tests (push) Failing after 2m10s
CI / frontend-build (push) Failing after 14s

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-08 09:56:01 +02:00
co-authored by Claude Sonnet 5
parent 28d8b2957d
commit 7a3fd7acda
6 changed files with 22 additions and 11 deletions
+2 -2
View File
@@ -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-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-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-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-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) | | 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-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`) | | IDENT-002 | Identity | Inventarnummer manuell | P0 | S | LOW | IDENT-001 | ✅ (Objektposition-Code, `objektposition.py`) |
+1 -1
View File
@@ -47,7 +47,7 @@ async def suche(
.join(Objektposition, Objektposition.objekt_id == Objekt.id) .join(Objektposition, Objektposition.objekt_id == Objekt.id)
.join(Material, Material.id == Objektposition.material_id) .join(Material, Material.id == Objektposition.material_id)
.where( .where(
Objektposition.status == ObjektpositionStatus.aktiv, Objektposition.ist_status == ObjektpositionStatus.aktiv,
or_(Material.name.ilike(muster), aehnlichkeit_material > 0.2), or_(Material.name.ilike(muster), aehnlichkeit_material > 0.2),
) )
.order_by(aehnlichkeit_material.desc()) .order_by(aehnlichkeit_material.desc())
+9 -4
View File
@@ -204,8 +204,11 @@ async def test_offener_fehlbestand_macht_objekt_nicht_einsatzbereit(
body = response.json() body = response.json()
assert body["einsatzbereit"] == 0 assert body["einsatzbereit"] == 0
assert body["nicht_einsatzbereit"] == 1 assert body["nicht_einsatzbereit"] == 1
assert body["details"][0]["gruende"] == ["fehlbestand_offen", "noch_nie_kontrolliert"] # Die Kontrolle, die den Fehlbestand erzeugt hat, wurde per _fehlbestand()
assert body["nie_kontrolliert_gesamt"] == 1 # 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 @pytest.mark.asyncio
@@ -232,8 +235,10 @@ async def test_genehmigte_mindermenge_macht_objekt_eingeschraenkt_einsatzbereit(
assert body["einsatzbereit"] == 0 assert body["einsatzbereit"] == 0
assert body["eingeschraenkt_einsatzbereit"] == 1 assert body["eingeschraenkt_einsatzbereit"] == 1
assert body["nicht_einsatzbereit"] == 0 assert body["nicht_einsatzbereit"] == 0
assert body["details"][0]["gruende"] == ["mindermenge_genehmigt", "noch_nie_kontrolliert"] # Wie oben: die zugrundeliegende Kontrolle wurde abgeschlossen, Objekt gilt
assert body["nie_kontrolliert_gesamt"] == 1 # als kontrolliert.
assert body["details"][0]["gruende"] == ["mindermenge_genehmigt"]
assert body["nie_kontrolliert_gesamt"] == 0
@pytest.mark.asyncio @pytest.mark.asyncio
+6 -2
View File
@@ -77,7 +77,9 @@ async def test_in_wartung_macht_objekt_nicht_einsatzbereit(
) )
body = response.json() body = response.json()
assert body["nicht_einsatzbereit"] == 1 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 @pytest.mark.asyncio
@@ -96,7 +98,9 @@ async def test_ueberfaellige_hu_macht_objekt_nicht_einsatzbereit_und_erscheint_i
bereitschaft = await client.get( bereitschaft = await client.get(
"/api/v1/dashboard/einsatzbereitschaft", headers=auth_header(verantwortlicher_token) "/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)) prueftermine = await client.get("/api/v1/dashboard/prueftermine", headers=auth_header(verantwortlicher_token))
body = prueftermine.json() body = prueftermine.json()
+1 -1
View File
@@ -337,7 +337,7 @@ async def test_zweite_kontrolle_aktualisiert_offenen_fehlbestand_statt_duplikat(
assert len(fehlbestaende) == 1 assert len(fehlbestaende) == 1
assert float(fehlbestaende[0].istmenge) == 7 assert float(fehlbestaende[0].istmenge) == 7
assert float(fehlbestaende[0].fehlmenge) == 3 assert float(fehlbestaende[0].fehlmenge) == 3
assert fehlbestaende[0].kontrolle_id == erste_kontrolle assert str(fehlbestaende[0].kontrolle_id) == erste_kontrolle
@pytest.mark.asyncio @pytest.mark.asyncio
+3 -1
View File
@@ -93,7 +93,9 @@ async def test_kritischer_offener_mangel_macht_objekt_nicht_einsatzbereit(
) )
body = response.json() body = response.json()
assert body["nicht_einsatzbereit"] == 1 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 @pytest.mark.asyncio