from datetime import date, timedelta import pytest from tests.conftest import auth_header, login @pytest.mark.asyncio async def test_einheit_anlegen_und_hierarchie(client, admin_user): token = await login(client, "admin1") zug = await client.post("/api/v1/einheiten", json={"name": "Sanitätszug 1"}, headers=auth_header(token)) assert zug.status_code == 201 zug_id = zug.json()["id"] gruppe = await client.post( "/api/v1/einheiten", json={"name": "Gruppe 1", "uebergeordnete_einheit_id": zug_id}, headers=auth_header(token), ) assert gruppe.status_code == 201 assert gruppe.json()["uebergeordnete_einheit_id"] == zug_id @pytest.mark.asyncio async def test_mitarbeiter_darf_einheit_nicht_anlegen(client, mitarbeiter_user): token = await login(client, "mitarbeiter1") response = await client.post("/api/v1/einheiten", json={"name": "X"}, headers=auth_header(token)) assert response.status_code == 403 @pytest.mark.asyncio async def test_qualifikationstyp_anlegen(client, admin_user): token = await login(client, "admin1") response = await client.post( "/api/v1/qualifikationstypen", json={"name": "Führerschein C1", "kategorie": "fuehrerschein"}, headers=auth_header(token), ) assert response.status_code == 201 assert response.json()["gueltigkeit_monate"] is None @pytest.mark.asyncio async def test_wer_darf_fahrzeug_fahren( client, db_session, objekttyp_factory, standort_factory, admin_user, mitarbeiter_user, hauptserver_id ): """Nutzer-Beispiel Personal-Modul: Objekttyp verlangt Führerschein C1, Benutzer ohne gültigen Nachweis darf nicht, mit gültigem Nachweis schon.""" from app.services.vorlagen import erstelle_vorlage from app.services.objekte import objekt_aus_vorlage objekttyp = await objekttyp_factory("GW-San", ist_zugfahrzeug=True) standort = await standort_factory("Wache-Personal") vorlage = await erstelle_vorlage(db_session, objekttyp_id=objekttyp.id, name="GW-San Standard", positionen=[]) fahrzeug = await objekt_aus_vorlage( db_session, code="GW-SAN-01", name="GW-San 01", standort_id=standort.id, vorlage=vorlage, zustaendiger_server_id=hauptserver_id, ) token = await login(client, "admin1") quali = await client.post( "/api/v1/qualifikationstypen", json={"name": "Führerschein C1", "kategorie": "fuehrerschein"}, headers=auth_header(token), ) quali_id = quali.json()["id"] await client.post( f"/api/v1/objekttypen/{objekttyp.id}/qualifikationsanforderungen", json={"objekttyp_id": objekttyp.id, "qualifikationstyp_id": quali_id}, headers=auth_header(token), ) mitarbeiter_id = mitarbeiter_user.id # ohne Nachweis: nicht berechtigt pruefung = await client.get( f"/api/v1/objekte/{fahrzeug.id}/berechtigung/{mitarbeiter_id}", headers=auth_header(token) ) assert pruefung.json()["berechtigt"] is False assert len(pruefung.json()["fehlende_qualifikationstypen"]) == 1 # abgelaufener Nachweis zählt nicht await client.post( f"/api/v1/benutzer/{mitarbeiter_id}/qualifikationen", json={ "benutzer_id": mitarbeiter_id, "qualifikationstyp_id": quali_id, "erworben_am": (date.today() - timedelta(days=1000)).isoformat(), "gueltig_bis": (date.today() - timedelta(days=1)).isoformat(), }, headers=auth_header(token), ) abgelaufen = await client.get( f"/api/v1/objekte/{fahrzeug.id}/berechtigung/{mitarbeiter_id}", headers=auth_header(token) ) assert abgelaufen.json()["berechtigt"] is False # gültiger Nachweis: berechtigt await client.post( f"/api/v1/benutzer/{mitarbeiter_id}/qualifikationen", json={ "benutzer_id": mitarbeiter_id, "qualifikationstyp_id": quali_id, "erworben_am": date.today().isoformat(), "gueltig_bis": None, }, headers=auth_header(token), ) berechtigt = await client.get( f"/api/v1/objekte/{fahrzeug.id}/berechtigung/{mitarbeiter_id}", headers=auth_header(token) ) assert berechtigt.json()["berechtigt"] is True assert berechtigt.json()["fehlende_qualifikationstypen"] == [] @pytest.mark.asyncio async def test_benutzer_einheit_zuordnung(client, admin_user): token = await login(client, "admin1") einheit = await client.post("/api/v1/einheiten", json={"name": "Zug 2"}, headers=auth_header(token)) einheit_id = einheit.json()["id"] benutzer = await client.post( "/api/v1/benutzer", json={"name": "Neuer Helfer", "login": "helfer1", "passwort": "test-passwort-123", "einheit_id": einheit_id}, headers=auth_header(token), ) assert benutzer.status_code == 201 assert benutzer.json()["einheit_id"] == einheit_id benutzer_id = benutzer.json()["id"] geloest = await client.patch( f"/api/v1/benutzer/{benutzer_id}", json={"einheit_id": None}, headers=auth_header(token) ) assert geloest.json()["einheit_id"] is None @pytest.mark.asyncio async def test_einsatzfunktion_anlegen_und_benutzer_zuordnen(client, admin_user): """PERS-004: Einsatzfunktion unabhängig von der technischen App-Rolle.""" token = await login(client, "admin1") funktion = await client.post( "/api/v1/einsatzfunktionen", json={"name": "Gruppenführer"}, headers=auth_header(token) ) assert funktion.status_code == 201 funktion_id = funktion.json()["id"] duplikat = await client.post( "/api/v1/einsatzfunktionen", json={"name": "Gruppenführer"}, headers=auth_header(token) ) assert duplikat.status_code == 409 benutzer = await client.post( "/api/v1/benutzer", json={ "name": "Neuer Gruppenführer", "login": "gf1", "passwort": "test-passwort-123", "einsatzfunktion_id": funktion_id, }, headers=auth_header(token), ) assert benutzer.status_code == 201 assert benutzer.json()["einsatzfunktion_id"] == funktion_id benutzer_id = benutzer.json()["id"] geloest = await client.patch( f"/api/v1/benutzer/{benutzer_id}", json={"einsatzfunktion_id": None}, headers=auth_header(token) ) assert geloest.json()["einsatzfunktion_id"] is None @pytest.mark.asyncio async def test_mitarbeiter_darf_einsatzfunktion_nicht_anlegen(client, mitarbeiter_user): token = await login(client, "mitarbeiter1") response = await client.post( "/api/v1/einsatzfunktionen", json={"name": "Melder"}, headers=auth_header(token) ) assert response.status_code == 403 @pytest.mark.asyncio async def test_bald_ablaufende_qualifikation_erscheint_im_dashboard( client, admin_user, mitarbeiter_user ): from datetime import date, timedelta token = await login(client, "admin1") quali = await client.post( "/api/v1/qualifikationstypen", json={"name": "Führerschein C1", "kategorie": "fuehrerschein"}, headers=auth_header(token), ) quali_id = quali.json()["id"] await client.post( f"/api/v1/benutzer/{mitarbeiter_user.id}/qualifikationen", json={ "benutzer_id": mitarbeiter_user.id, "qualifikationstyp_id": quali_id, "erworben_am": (date.today() - timedelta(days=365)).isoformat(), "gueltig_bis": (date.today() + timedelta(days=10)).isoformat(), }, headers=auth_header(token), ) response = await client.get("/api/v1/dashboard/qualifikationsablaeufe", headers=auth_header(token)) body = response.json() assert len(body) == 1 assert body[0]["status"] == "bald_ablaufend" assert body[0]["benutzer_id"] == mitarbeiter_user.id @pytest.mark.asyncio async def test_unbefristete_qualifikation_erscheint_nicht_im_dashboard(client, admin_user, mitarbeiter_user): from datetime import date, timedelta token = await login(client, "admin1") quali = await client.post( "/api/v1/qualifikationstypen", json={"name": "Sanitätshelfer", "kategorie": "lehrgang"}, headers=auth_header(token), ) quali_id = quali.json()["id"] await client.post( f"/api/v1/benutzer/{mitarbeiter_user.id}/qualifikationen", json={ "benutzer_id": mitarbeiter_user.id, "qualifikationstyp_id": quali_id, "erworben_am": (date.today() - timedelta(days=365)).isoformat(), "gueltig_bis": None, }, headers=auth_header(token), ) response = await client.get("/api/v1/dashboard/qualifikationsablaeufe", headers=auth_header(token)) assert response.json() == [] @pytest.mark.asyncio async def test_person_ohne_benutzer_anlegbar(client, admin_user): """PERS-001: Person kann ohne jeden Systemzugang existieren (z.B. Jugendgruppe).""" token = await login(client, "admin1") person = await client.post( "/api/v1/personen", json={"name": "Max Mustermann (Jugendgruppe)", "geburtsdatum": "2012-04-01"}, headers=auth_header(token), ) assert person.status_code == 201 body = person.json() assert body["name"] == "Max Mustermann (Jugendgruppe)" assert body["geburtsdatum"] == "2012-04-01" liste = await client.get("/api/v1/personen", headers=auth_header(token)) assert any(p["id"] == body["id"] for p in liste.json()) geaendert = await client.patch( f"/api/v1/personen/{body['id']}", json={"kontakt": "Erziehungsberechtigter: 0170..."}, headers=auth_header(token) ) assert geaendert.json()["kontakt"] == "Erziehungsberechtigter: 0170..." @pytest.mark.asyncio async def test_mitarbeiter_darf_person_nicht_anlegen(client, mitarbeiter_user): token = await login(client, "mitarbeiter1") response = await client.post("/api/v1/personen", json={"name": "Jemand"}, headers=auth_header(token)) assert response.status_code == 403 @pytest.mark.asyncio async def test_benutzer_bekommt_automatisch_verknuepfte_person(client, admin_user): """PERS-002: bestehender Flow (Benutzer ohne explizite person_id anlegen) bleibt unverändert - Person wird automatisch mitangelegt.""" token = await login(client, "admin1") benutzer = await client.post( "/api/v1/benutzer", json={"name": "Auto-Person Helfer", "login": "autoperson1", "passwort": "test-passwort-123"}, headers=auth_header(token), ) assert benutzer.status_code == 201 person_id = benutzer.json()["person_id"] assert person_id is not None personen = await client.get("/api/v1/personen", headers=auth_header(token)) assert any(p["id"] == person_id and p["name"] == "Auto-Person Helfer" for p in personen.json()) @pytest.mark.asyncio async def test_benutzer_mit_bestehender_person_verknuepfen(client, admin_user): """PERS-002: bereits erfasste Person (z.B. aus der Jugendgruppe) bekommt nachträglich einen Login, ohne dass eine zweite Person entsteht.""" token = await login(client, "admin1") person = await client.post("/api/v1/personen", json={"name": "Bestehende Person"}, headers=auth_header(token)) person_id = person.json()["id"] benutzer = await client.post( "/api/v1/benutzer", json={ "name": "Bestehende Person", "login": "bestehendeperson1", "passwort": "test-passwort-123", "person_id": person_id, }, headers=auth_header(token), ) assert benutzer.status_code == 201 assert benutzer.json()["person_id"] == person_id personen = await client.get("/api/v1/personen", headers=auth_header(token)) assert len([p for p in personen.json() if p["id"] == person_id]) == 1 @pytest.mark.asyncio async def test_benutzer_mit_unbekannter_person_id_abgelehnt(client, admin_user): token = await login(client, "admin1") response = await client.post( "/api/v1/benutzer", json={ "name": "X", "login": "unbekanntperson1", "passwort": "test-passwort-123", "person_id": 999999, }, headers=auth_header(token), ) assert response.status_code == 404 @pytest.mark.asyncio async def test_qualifikation_erfassen_steht_in_historie(client, admin_user, mitarbeiter_user): from datetime import date token = await login(client, "admin1") quali = await client.post( "/api/v1/qualifikationstypen", json={"name": "Maschinist", "kategorie": "lehrgang"}, headers=auth_header(token), ) quali_id = quali.json()["id"] erfasst = await client.post( f"/api/v1/benutzer/{mitarbeiter_user.id}/qualifikationen", json={ "benutzer_id": mitarbeiter_user.id, "qualifikationstyp_id": quali_id, "erworben_am": date.today().isoformat(), }, headers=auth_header(token), ) qualifikation_id = erfasst.json()["id"] historie = await client.get( f"/api/v1/historie?entitaet_typ=benutzer_qualifikation&entitaet_id={qualifikation_id}", headers=auth_header(token), ) body = historie.json() assert len(body) == 1 assert body[0]["ereignistyp"] == "qualifikation_erfasst"