From eb122802b2eb7092136a422a52dd1a4921c20c17 Mon Sep 17 00:00:00 2001 From: patrick Date: Sat, 23 May 2026 22:51:50 +0200 Subject: [PATCH] fix: 8 pre-existing Test-Fehler behoben MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Krankmeldungen an Wochenenden erlaubt (working_days=0 für SICK) Medizinisch korrekt: Krankmeldungen können auch Wochenendtage umfassen. Behebt: test_create_absence_sick_auto_approved, test_quick_sick_*, test_pull_includes_today_absence_with_category, test_sick_stats_* 2. Self-Approval-Schutz in Tests berücksichtigt abs_approver_headers / time_approver_headers: zweiter Admin je Company. Behebt: test_approve_absence, test_balance_deducted_after_approve, test_approve_entry 3. test_export_invalid_format: "pdf" → "xml" (pdf ist jetzt valides Format) Ergebnis: 134/134 passed. Co-Authored-By: Claude Sonnet 4.6 --- backend/app/services/absence_service.py | 3 ++- backend/tests/test_absences.py | 30 ++++++++++++++++++++----- backend/tests/test_reports.py | 2 +- backend/tests/test_time.py | 25 ++++++++++++++++++--- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/backend/app/services/absence_service.py b/backend/app/services/absence_service.py index c8e7514..0ccce80 100644 --- a/backend/app/services/absence_service.py +++ b/backend/app/services/absence_service.py @@ -156,7 +156,8 @@ class AbsenceService: data.half_day_start, data.half_day_end ) - if working_days <= 0: + # Krankmeldungen dürfen auch Wochenenden/Feiertage umfassen (0 Arbeitstage erlaubt) + if working_days <= 0 and absence_type.category != AbsenceCategory.SICK: raise HTTPException(status_code=400, detail="Keine Arbeitstage im ausgewählten Zeitraum.") # Urlaubskonto prüfen wenn Urlaub abgezogen werden soll diff --git a/backend/tests/test_absences.py b/backend/tests/test_absences.py index 9921ded..ea6b53b 100644 --- a/backend/tests/test_absences.py +++ b/backend/tests/test_absences.py @@ -31,6 +31,25 @@ async def abs_headers(abs_company): return {"Authorization": f"Bearer {abs_company['tokens']['access_token']}"} +@pytest_asyncio.fixture(scope="session", loop_scope="session") +async def abs_approver_headers(client: AsyncClient, abs_headers): + """Zweiter Admin in Absence AG – kann Abwesenheiten anderer genehmigen.""" + resp = await client.post("/api/v1/users/invite", json={ + "first_name": "Approver", + "last_name": "Admin", + "email": "approver@absenceag.de", + "role": "COMPANY_ADMIN", + "initial_password": "Secret123", + }, headers=abs_headers) + assert resp.status_code == 201, resp.text + login = await client.post("/api/v1/auth/login", json={ + "email": "approver@absenceag.de", + "password": "Secret123", + }) + assert login.status_code == 200, login.text + return {"Authorization": f"Bearer {login.json()['access_token']}"} + + @pytest_asyncio.fixture(scope="session", loop_scope="session") async def vacation_type_id(client: AsyncClient, abs_headers): """Urlaubs-Typ der Company holen.""" @@ -163,7 +182,7 @@ async def test_get_absence_by_id(client: AsyncClient, abs_headers, vacation_type @pytest.mark.asyncio -async def test_approve_absence(client: AsyncClient, abs_headers, vacation_type_id): +async def test_approve_absence(client: AsyncClient, abs_headers, abs_approver_headers, vacation_type_id): next_monday = date.today() + timedelta(days=(7 - date.today().weekday()) + 14) create_resp = await client.post( "/api/v1/absences/", @@ -176,7 +195,7 @@ async def test_approve_absence(client: AsyncClient, abs_headers, vacation_type_i ) absence_id = create_resp.json()["id"] - resp = await client.post(f"/api/v1/absences/{absence_id}/approve", headers=abs_headers) + resp = await client.post(f"/api/v1/absences/{absence_id}/approve", headers=abs_approver_headers) assert resp.status_code == 200 assert resp.json()["status"] == "approved" @@ -248,7 +267,7 @@ async def test_own_balance(client: AsyncClient, abs_headers): @pytest.mark.asyncio -async def test_balance_deducted_after_approve(client: AsyncClient, abs_headers, vacation_type_id, abs_company): +async def test_balance_deducted_after_approve(client: AsyncClient, abs_headers, abs_approver_headers, vacation_type_id, abs_company): """Urlaubskonto wird nach Genehmigung abgezogen.""" user_id = abs_company["user"]["id"] @@ -270,8 +289,9 @@ async def test_balance_deducted_after_approve(client: AsyncClient, abs_headers, absence_id = create_resp.json()["id"] working_days = create_resp.json()["working_days"] - # Genehmigen - await client.post(f"/api/v1/absences/{absence_id}/approve", headers=abs_headers) + # Genehmigen (zweiter Admin, nicht self-approval) + approve_resp = await client.post(f"/api/v1/absences/{absence_id}/approve", headers=abs_approver_headers) + assert approve_resp.status_code == 200, approve_resp.text # Konto prüfen after = await client.get("/api/v1/absences/balance", params={"year": 2026}, headers=abs_headers) diff --git a/backend/tests/test_reports.py b/backend/tests/test_reports.py index 4c7e8f4..90ab6ce 100644 --- a/backend/tests/test_reports.py +++ b/backend/tests/test_reports.py @@ -269,7 +269,7 @@ async def test_export_invalid_format(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/time/export", - params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "pdf"}, + params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "xml"}, headers=report_headers, ) assert resp.status_code == 422 diff --git a/backend/tests/test_time.py b/backend/tests/test_time.py index a07faa6..8840069 100644 --- a/backend/tests/test_time.py +++ b/backend/tests/test_time.py @@ -28,6 +28,25 @@ async def manager_headers(client: AsyncClient): return {"Authorization": f"Bearer {token}"} +@pytest_asyncio.fixture(scope="session", loop_scope="session") +async def time_approver_headers(client: AsyncClient, manager_headers): + """Zweiter Admin in Time GmbH – kann Zeiteinträge anderer genehmigen.""" + resp = await client.post("/api/v1/users/invite", json={ + "first_name": "Time", + "last_name": "Approver", + "email": "approver@timegmbh.de", + "role": "COMPANY_ADMIN", + "initial_password": "Secret123", + }, headers=manager_headers) + assert resp.status_code == 201, resp.text + login = await client.post("/api/v1/auth/login", json={ + "email": "approver@timegmbh.de", + "password": "Secret123", + }) + assert login.status_code == 200, login.text + return {"Authorization": f"Bearer {login.json()['access_token']}"} + + # ── Stamp In / Out ───────────────────────────────────────────────────────────── @pytest.mark.asyncio @@ -175,8 +194,8 @@ async def test_update_entry(client: AsyncClient, auth_headers): @pytest.mark.asyncio -async def test_approve_entry(client: AsyncClient, manager_headers): - """Manager genehmigt einen Eintrag.""" +async def test_approve_entry(client: AsyncClient, manager_headers, time_approver_headers): + """Manager genehmigt einen Eintrag eines anderen Benutzers (kein Self-Approval).""" create_resp = await client.post( "/api/v1/time/entries", json={ @@ -193,7 +212,7 @@ async def test_approve_entry(client: AsyncClient, manager_headers): resp = await client.post( f"/api/v1/time/entries/{entry_id}/approve", - headers=manager_headers, + headers=time_approver_headers, ) assert resp.status_code == 200 assert resp.json()["status"] == "approved"