Files
F-r-ldraledigplannerare/backend/tests/unit/test_analytics_router.py
T
jacobh2 1a02bdcdbf Let vacation years go back to the child's birth year
Settings only offered vacation-day rows from the current year forward,
while analytics ran its balance series from the child's birth year. Every
year in the gap had no vacation_days row, so the series assumed a full
statutory 25 earned with no use and banked earned - floor of it forward —
phantom saved days the user had no row to correct.

Settings now offers editable rows back to the birth year, and a past year
with no row assumes only the floor was earned, so it banks nothing. A past
year the user did enter is untouched by that rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:49:56 +02:00

104 lines
3.6 KiB
Python

"""API-level tests for `GET /api/analytics/{workspace_id}/overview` and the
per-parent age4 endpoint — specifically that the new per-year balance series
(`year_balances`/`year_totals`) survives the wire boundary (§4.2's
service-computed-but-dropped-on-the-wire gotcha), matching the
`test_calendar_router.py` pattern.
"""
from datetime import date, datetime
from uuid import uuid4
from httpx import AsyncClient
from app.entities.child import Child
from app.repositories_impl.sqlite.child_repository import SQLiteChildRepository
from tests.conftest import SeededIds
class TestGetOverviewYearBalances:
async def test_wire_shape_includes_year_balances_and_year_totals(
self,
authed_client: AsyncClient,
child_repo: SQLiteChildRepository,
seeded_ids: SeededIds,
) -> None:
ws = seeded_ids.workspace_id
pd_uuid = seeded_ids.personal_data_uuid
birth = date(2025, 1, 1)
child = await child_repo.create(
Child(
id=uuid4(),
workspace_id=ws,
name="Kid",
birth_date=birth,
created_at=datetime.now(),
)
)
response = await authed_client.get(
f"/api/analytics/{ws}/overview", params={"child_id": str(child.id)}
)
assert response.status_code == 200
body = response.json()
assert "year_totals" in body
assert [yt["year"] for yt in body["year_totals"]] == [2025, 2026, 2027, 2028, 2029]
for yt in body["year_totals"]:
assert set(yt) == {"year", "pl_left", "vacation_left"}
assert len(body["parent_projections"]) == 1
projection = body["parent_projections"][0]
assert projection["personal_data_uuid"] == str(pd_uuid)
assert "year_balances" in projection
assert [yb["year"] for yb in projection["year_balances"]] == [
2025,
2026,
2027,
2028,
2029,
]
birth_year = projection["year_balances"][0]
assert birth_year["personal_data_uuid"] == str(pd_uuid)
assert birth_year["child_id"] == str(child.id)
vacation = birth_year["vacation"]
assert set(vacation) == {
"year",
"earned",
"is_assumed",
"carried_in",
"planned_use",
"left",
"carried_out",
"expiring",
"expiring_age",
"expiring_over_cap",
}
# `birth` (2025-01-01) is a PAST year relative to "today" in this
# test run — no `vacation_days` row was entered for it, so
# `_vacation_year_series`'s past-year rule applies: `earned` is the
# parent's floor (`PersonalData.min_vacation_days_per_year`, default
# 20), not the statutory default, so a phantom past year can't bank
# `25 - floor` days forward the user never actually saved. See
# `ParentYearBalance`'s docstring.
assert vacation["earned"] == 20
assert vacation["is_assumed"] is True
assert vacation["carried_in"] == 0
assert vacation["planned_use"] == 0
assert vacation["left"] == 20
parental_leave = birth_year["parental_leave"]
assert set(parental_leave) == {
"year",
"sjukpenning_used",
"lagsta_used",
"sjukpenning_left",
"lagsta_left",
}
# `seeded_ids`'s workspace has exactly one member -> full pool.
assert parental_leave["sjukpenning_left"] == 390
assert parental_leave["lagsta_left"] == 90