1a02bdcdbf
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>
104 lines
3.6 KiB
Python
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
|