0e9b20ec37
Three strands of work that grew together across the same settings screen and
API client, so they land as one change.
Per-year preschool planning days ("studiedagar"): new table, repository
(SQLite + Postgres), service, router and migration 007, plus the hooks and
Settings UI to plan and record them per child and year. Allocation now
reserves the recorded figure for a year where one exists and only falls back
to the child-level estimate otherwise, marking that fallback as assumed.
Partner invitations: workspaces issue and list invitations, registration
accepts an invite token to join the inviter's workspace instead of creating a
personal one, and a /join/:token page plus the settings invite panel drive it.
Vacation forfeiture: tips and the dashboard now distinguish days a parent
genuinely loses from days that merely age, so the warning stops firing on
balances that were never at risk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
3.3 KiB
Python
89 lines
3.3 KiB
Python
"""API-level tests for `/api/preschool-planning-days`.
|
|
|
|
Uses the shared `authed_client`/`seeded_ids` fixtures (see `tests/conftest.py`)
|
|
since these routes are scoped per child within the caller's own workspace.
|
|
"""
|
|
|
|
from uuid import uuid4
|
|
|
|
from httpx import AsyncClient
|
|
|
|
from tests.conftest import SeededIds
|
|
|
|
|
|
class TestPreschoolPlanningDaysRouter:
|
|
async def test_get_year_with_no_row_returns_child_default(
|
|
self, authed_client: AsyncClient, seeded_ids: SeededIds
|
|
) -> None:
|
|
resp = await authed_client.get(f"/api/preschool-planning-days/{seeded_ids.child_id}/2026")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body["is_default"] is True
|
|
assert body["id"] is None
|
|
assert body["planned_days"] == 6 # Child entity default
|
|
assert body["taken_days"] is None
|
|
|
|
async def test_upsert_planned_then_record_taken(
|
|
self, authed_client: AsyncClient, seeded_ids: SeededIds
|
|
) -> None:
|
|
put_planned = await authed_client.put(
|
|
f"/api/preschool-planning-days/{seeded_ids.child_id}/2026/planned",
|
|
json={"planned_days": 8},
|
|
)
|
|
assert put_planned.status_code == 200
|
|
body = put_planned.json()
|
|
assert body["is_default"] is False
|
|
assert body["planned_days"] == 8
|
|
assert body["taken_days"] is None
|
|
|
|
put_taken = await authed_client.put(
|
|
f"/api/preschool-planning-days/{seeded_ids.child_id}/2026/taken",
|
|
json={"taken_days": 5},
|
|
)
|
|
assert put_taken.status_code == 200
|
|
taken_body = put_taken.json()
|
|
assert taken_body["id"] == body["id"]
|
|
assert taken_body["planned_days"] == 8 # preserved
|
|
assert taken_body["taken_days"] == 5
|
|
|
|
async def test_record_taken_zero_distinguishable_from_default_none(
|
|
self, authed_client: AsyncClient, seeded_ids: SeededIds
|
|
) -> None:
|
|
resp = await authed_client.put(
|
|
f"/api/preschool-planning-days/{seeded_ids.child_id}/2026/taken",
|
|
json={"taken_days": 0},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.json()["taken_days"] == 0
|
|
assert resp.json()["is_default"] is False
|
|
|
|
async def test_list_years_for_child(
|
|
self, authed_client: AsyncClient, seeded_ids: SeededIds
|
|
) -> None:
|
|
await authed_client.put(
|
|
f"/api/preschool-planning-days/{seeded_ids.child_id}/2025/planned",
|
|
json={"planned_days": 6},
|
|
)
|
|
await authed_client.put(
|
|
f"/api/preschool-planning-days/{seeded_ids.child_id}/2026/planned",
|
|
json={"planned_days": 7},
|
|
)
|
|
|
|
resp = await authed_client.get(f"/api/preschool-planning-days/{seeded_ids.child_id}")
|
|
assert resp.status_code == 200
|
|
years = [row["year"] for row in resp.json()]
|
|
assert years == [2026, 2025]
|
|
|
|
async def test_unknown_child_returns_404(self, authed_client: AsyncClient) -> None:
|
|
resp = await authed_client.get(f"/api/preschool-planning-days/{uuid4()}/2026")
|
|
assert resp.status_code == 404
|
|
|
|
async def test_rejects_negative_planned_days(
|
|
self, authed_client: AsyncClient, seeded_ids: SeededIds
|
|
) -> None:
|
|
resp = await authed_client.put(
|
|
f"/api/preschool-planning-days/{seeded_ids.child_id}/2026/planned",
|
|
json={"planned_days": -1},
|
|
)
|
|
assert resp.status_code == 422
|