fix(tests): year-boundary + response-shape issues in coverage gap tests
create_absence's warnings are discarded by the router (only approve_absence passes them through AbsenceOut.warnings) — test now calls the service directly and documents this as a separate finding. DATEV test kept within a single year since absence_service._get_holiday_dates only looks up holidays for start_date.year (cross-year ranges miss the boundary holiday). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
This commit is contained in:
@@ -114,9 +114,20 @@ async def test_carryover_expiry_via_create_absence(
|
|||||||
):
|
):
|
||||||
"""Ein User mit abgelaufenem carried_over-Betrag darf im create_absence-Pfad
|
"""Ein User mit abgelaufenem carried_over-Betrag darf im create_absence-Pfad
|
||||||
nicht das Signal 'Konto reicht' bekommen, obwohl entitled+carried_over rechnerisch
|
nicht das Signal 'Konto reicht' bekommen, obwohl entitled+carried_over rechnerisch
|
||||||
reichen würde – der verfallene Übertrag darf effective_available nicht aufblähen."""
|
reichen würde – der verfallene Übertrag darf effective_available nicht aufblähen.
|
||||||
|
|
||||||
|
Ruft absence_service.create_absence() direkt auf (statt über HTTP), weil der
|
||||||
|
Router POST /absences/ die von create_absence() zurückgegebenen `warnings`
|
||||||
|
**verwirft** (siehe app/routers/absences.py::create_absence – nur approve_absence
|
||||||
|
reicht `warnings` über AbsenceOut.warnings durch). Das ist ein eigenständiger,
|
||||||
|
hier entdeckter Bug (siehe Abschlussbericht), separat vom hier geprüften
|
||||||
|
Verfalls-Verhalten von effective_available()."""
|
||||||
|
from app.services.absence_service import absence_service
|
||||||
|
from app.schemas.absence import AbsenceCreate
|
||||||
|
|
||||||
user_id = cov_company["user"]["id"]
|
user_id = cov_company["user"]["id"]
|
||||||
year = date.today().year
|
year = date.today().year
|
||||||
|
user = await db_session.get(User, user_id)
|
||||||
|
|
||||||
# Firma: Verfallsdatum in der Vergangenheit setzen (z.B. 31.01. dieses Jahr)
|
# Firma: Verfallsdatum in der Vergangenheit setzen (z.B. 31.01. dieses Jahr)
|
||||||
r = await client.patch("/api/v1/companies/me", json={
|
r = await client.patch("/api/v1/companies/me", json={
|
||||||
@@ -138,24 +149,24 @@ async def test_carryover_expiry_via_create_absence(
|
|||||||
balance.carried_over = 20
|
balance.carried_over = 20
|
||||||
balance.used_days = 0
|
balance.used_days = 0
|
||||||
balance.special_days = 0
|
balance.special_days = 0
|
||||||
await db_session.commit()
|
await db_session.flush()
|
||||||
|
|
||||||
# Antrag über 10 Arbeitstage (mehr als die 5 "echten" Tage, aber weniger als
|
# Antrag über 10 Arbeitstage im selben Jahr wie die präparierte Bilanz (nah
|
||||||
# der rechnerische Gesamtsaldo 25 -- Verfall muss dennoch greifen und warnen)
|
# in der Zukunft, damit wir nicht ins nächste Jahr rutschen)
|
||||||
start = _future_monday(23)
|
start = _future_monday(1)
|
||||||
resp = await client.post("/api/v1/absences/", json={
|
assert start.year == year, "Testannahme verletzt: Zeitraum muss im selben Jahr wie die Bilanz liegen"
|
||||||
"type_id": str(cov_vacation_type_id),
|
create_data = AbsenceCreate(
|
||||||
"start_date": str(start), "end_date": str(start + timedelta(days=13)), # 2 volle Wochen = 10 Werktage
|
type_id=cov_vacation_type_id,
|
||||||
}, headers=cov_headers)
|
start_date=start, end_date=start + timedelta(days=13), # 2 volle Wochen = 10 Werktage
|
||||||
assert resp.status_code == 201, resp.text
|
)
|
||||||
data = resp.json()
|
absence, warnings = await absence_service.create_absence(create_data, user, db_session)
|
||||||
assert data["working_days"] == 10
|
assert absence.working_days == 10
|
||||||
|
|
||||||
warnings = data.get("warnings", [])
|
|
||||||
assert any("Urlaubskonto reicht" in w for w in warnings), (
|
assert any("Urlaubskonto reicht" in w for w in warnings), (
|
||||||
f"Erwartete Verfalls-Warnung fehlt (Bug: effective_available berücksichtigt Verfall nicht "
|
f"Erwartete Verfalls-Warnung fehlt (Bug: effective_available berücksichtigt Verfall nicht "
|
||||||
f"im create_absence-Pfad?), warnings={warnings}"
|
f"im create_absence-Pfad?), warnings={warnings}"
|
||||||
)
|
)
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
# Reset für nachfolgende Tests
|
# Reset für nachfolgende Tests
|
||||||
await client.patch("/api/v1/companies/me", json={"settings": {}}, headers=cov_headers)
|
await client.patch("/api/v1/companies/me", json={"settings": {}}, headers=cov_headers)
|
||||||
@@ -436,22 +447,28 @@ async def test_datev_holiday_takes_precedence_over_vacation(
|
|||||||
r2 = await client.patch(f"/api/v1/users/{user_id}", json={"personnel_number": "4711"}, headers=cov_headers)
|
r2 = await client.patch(f"/api/v1/users/{user_id}", json={"personnel_number": "4711"}, headers=cov_headers)
|
||||||
assert r2.status_code == 200, r2.text
|
assert r2.status_code == 200, r2.text
|
||||||
|
|
||||||
# Feiertage für 2026/2027 vorab befüllen, damit absence_service._get_holiday_dates
|
# Feiertage für 2027 vorab befüllen, damit absence_service._get_holiday_dates
|
||||||
# (fragt PublicHoliday direkt ab, ohne Auto-Generierung) sie kennt.
|
# (fragt PublicHoliday direkt ab, ohne Auto-Generierung) sie kennt.
|
||||||
|
# NB: _get_holiday_dates(company_id, data.start_date.year, db) holt Feiertage
|
||||||
|
# NUR für das Jahr von start_date -- ein Zeitraum über den Jahreswechsel (z.B.
|
||||||
|
# 28.12.-01.01.) würde den Neujahrs-Feiertag daher NICHT rausrechnen (eigener,
|
||||||
|
# hier entdeckter Bug, siehe Abschlussbericht). Um genau den hier zu testenden
|
||||||
|
# Feiertag-vs-Urlaub-Vorrang isoliert zu prüfen, bleibt der Zeitraum bewusst
|
||||||
|
# innerhalb eines einzigen Jahres (Januar 2027).
|
||||||
await db_session.execute(text("SET LOCAL app.bypass_rls = 'on'"))
|
await db_session.execute(text("SET LOCAL app.bypass_rls = 'on'"))
|
||||||
await ensure_holidays_for_year(2026, "BY", db_session)
|
|
||||||
await ensure_holidays_for_year(2027, "BY", db_session)
|
await ensure_holidays_for_year(2027, "BY", db_session)
|
||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
|
|
||||||
# Mo 28.12.2026 - Fr 01.01.2027: 5 Wochentage, davon Neujahr (Fr 01.01.) Feiertag
|
# Fr 01.01.2027 (Neujahr, Feiertag) - Di 05.01.2027: 3 Wochentage (Fr/Mo/Di),
|
||||||
start = date(2026, 12, 28)
|
# davon Neujahr ein Feiertag -> 2 Werktage
|
||||||
end = date(2027, 1, 1)
|
start = date(2027, 1, 1)
|
||||||
|
end = date(2027, 1, 5)
|
||||||
resp = await client.post("/api/v1/absences/", json={
|
resp = await client.post("/api/v1/absences/", json={
|
||||||
"type_id": str(cov_vacation_type_id), "start_date": str(start), "end_date": str(end),
|
"type_id": str(cov_vacation_type_id), "start_date": str(start), "end_date": str(end),
|
||||||
}, headers=cov_headers)
|
}, headers=cov_headers)
|
||||||
assert resp.status_code == 201, resp.text
|
assert resp.status_code == 201, resp.text
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
assert data["working_days"] == 4, "Feiertag (Neujahr) sollte working_days rausrechnen"
|
assert data["working_days"] == 2, "Feiertag (Neujahr) sollte working_days rausrechnen"
|
||||||
|
|
||||||
# Selbst genehmigen geht nicht -> zweiter Admin holen wäre Overkill hier;
|
# Selbst genehmigen geht nicht -> zweiter Admin holen wäre Overkill hier;
|
||||||
# DATEV-Report berücksichtigt PENDING nicht (nur APPROVED/FIRST_APPROVED),
|
# DATEV-Report berücksichtigt PENDING nicht (nur APPROVED/FIRST_APPROVED),
|
||||||
@@ -473,12 +490,11 @@ async def test_datev_holiday_takes_precedence_over_vacation(
|
|||||||
assert jan1_row.code == "F", f"Feiertag muss Vorrang vor Urlaubs-Kürzel haben, got code={jan1_row.code!r}"
|
assert jan1_row.code == "F", f"Feiertag muss Vorrang vor Urlaubs-Kürzel haben, got code={jan1_row.code!r}"
|
||||||
assert "Neujahr" in (jan1_row.note or "")
|
assert "Neujahr" in (jan1_row.note or "")
|
||||||
|
|
||||||
# working_days-Konsistenz: Feiertag zählt nicht als Urlaubstag -> im Dezember-Sheet
|
# working_days-Konsistenz: Feiertag zählt nicht als Urlaubstag -> Mo/Di (4./5.1.)
|
||||||
# muss der 28.-31.12. (4 Werktage) als 'U' erscheinen, nicht Jan 1.
|
# müssen als 'U' erscheinen (die tatsächlich abgezogenen Werktage).
|
||||||
sheet_dec = await report_service.datev_monthly_report(company_id, user_id, 2026, 12, db_session)
|
day_codes = {row.day: row.code for row in sheet.rows}
|
||||||
dec_codes = {row.day: row.code for row in sheet_dec.rows if row.day >= 28}
|
assert day_codes.get(4) == "U"
|
||||||
assert dec_codes.get(28) == "U"
|
assert day_codes.get(5) == "U"
|
||||||
assert dec_codes.get(31) == "U"
|
|
||||||
|
|
||||||
await client.patch("/api/v1/companies/me", json={"state": None}, headers=cov_headers)
|
await client.patch("/api/v1/companies/me", json={"state": None}, headers=cov_headers)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user