Cover the balance entry path and three unguarded rules

Entering a parent's Försäkringskassan balances had no tests at all, despite
being how every number in the planner originates and despite it becoming the
first thing a new user does. The scraper endpoints are pinned per test rather
than read from the ambient configuration, because a local .env sets the flag
opposite to the shipped default and a test that trusts it would pass here and
fail in CI.

Also covered: the hard expiry at twelve, whose rejection path nothing reached
and whose boundary turns out to be inclusive; and the dubbeldagar count cap,
which now drives past the limit and asserts the days beyond it fall back to a
single parent. Two weak assertions were strengthened, including one that
derived its expectation from the same response it was checking and so could
not have failed on a shared error.
This commit is contained in:
2026-09-17 19:46:03 +02:00
parent 9d548da7d6
commit 0fc71101ae
6 changed files with 732 additions and 2 deletions
@@ -12,6 +12,9 @@ property this test file cares about most.
from datetime import date, datetime, timedelta
from uuid import uuid4
from dateutil.relativedelta import relativedelta
from app.config import settings
from app.entities.leave_wish import LeaveWish
from app.entities.parental_leave import ParentalLeaveFetch, TierData
from app.entities.planned_leave import PlannedLeaveDay
@@ -500,6 +503,135 @@ class TestApplyProposedDaysValidation:
assert rows == []
class TestApplyProposedDaysEligibleRange:
"""`PlanningService._validate_eligible_range` (planning_service.py:748-775)
rejects any planned day before the child's birth date or after
`birth_date + LeaveRules.hard_expiry_age_years` (the Swedish 12-year hard
expiry on FP days).
The upper bound is INCLUSIVE: the source only rejects `d.date >
hard_expiry`, so the exact 12th-birthday date itself is still eligible
-- confirmed by `test_hard_expiry_boundary_date_itself_is_inclusive_and_accepted`
below, which would fail if that boundary were ever tightened to `>=`.
`seeded_ids`' child has a fixed `birth_date` of 2024-01-01
(`tests/conftest.py`), which these tests rely on directly.
"""
_BIRTH_DATE = date(2024, 1, 1)
async def test_day_after_the_hard_expiry_is_rejected(
self,
planning_service: PlanningService,
planned_leave_repo: SQLitePlannedLeaveRepository,
seeded_ids: SeededIds,
) -> None:
ws, child, parent = (
seeded_ids.workspace_id,
seeded_ids.child_id,
seeded_ids.personal_data_uuid,
)
hard_expiry = self._BIRTH_DATE + relativedelta(years=settings.rules.hard_expiry_age_years)
day_after = hard_expiry + timedelta(days=1)
result = await planning_service.apply_proposed_days(
ws, [ProposedDayInput(day_after, parent, child, "sjukpenning")]
)
assert result.accepted is False
assert any(
r.message_key == "planApply.rejection.outsideEligibleWindow" for r in result.rejections
)
rows = await planned_leave_repo.list(parent_personal_data_uuid=parent)
assert rows == []
async def test_day_before_the_birth_date_is_rejected(
self,
planning_service: PlanningService,
planned_leave_repo: SQLitePlannedLeaveRepository,
seeded_ids: SeededIds,
) -> None:
ws, child, parent = (
seeded_ids.workspace_id,
seeded_ids.child_id,
seeded_ids.personal_data_uuid,
)
day_before = self._BIRTH_DATE - timedelta(days=1)
result = await planning_service.apply_proposed_days(
ws, [ProposedDayInput(day_before, parent, child, "sjukpenning")]
)
assert result.accepted is False
assert any(
r.message_key == "planApply.rejection.outsideEligibleWindow" for r in result.rejections
)
rows = await planned_leave_repo.list(parent_personal_data_uuid=parent)
assert rows == []
async def test_hard_expiry_boundary_date_itself_is_inclusive_and_accepted(
self,
planning_service: PlanningService,
seeded_ids: SeededIds,
) -> None:
"""The 12th-birthday date exactly equal to `birth_date +
hard_expiry_age_years` is still an eligible day -- the source rejects
strictly-greater dates only, so this is the INCLUSIVE boundary."""
ws, child, parent = (
seeded_ids.workspace_id,
seeded_ids.child_id,
seeded_ids.personal_data_uuid,
)
hard_expiry = self._BIRTH_DATE + relativedelta(years=settings.rules.hard_expiry_age_years)
result = await planning_service.apply_proposed_days(
ws, [ProposedDayInput(hard_expiry, parent, child, "sjukpenning")]
)
assert result.accepted is True, result.rejections
async def test_birth_date_itself_is_inclusive_and_accepted(
self,
planning_service: PlanningService,
seeded_ids: SeededIds,
) -> None:
ws, child, parent = (
seeded_ids.workspace_id,
seeded_ids.child_id,
seeded_ids.personal_data_uuid,
)
result = await planning_service.apply_proposed_days(
ws, [ProposedDayInput(self._BIRTH_DATE, parent, child, "sjukpenning")]
)
assert result.accepted is True, result.rejections
async def test_rejected_batch_writes_nothing_even_with_a_valid_day_included(
self,
planning_service: PlanningService,
planned_leave_repo: SQLitePlannedLeaveRepository,
seeded_ids: SeededIds,
) -> None:
"""All-or-nothing: one out-of-window day in the batch must sink the
whole batch, including the otherwise-valid day alongside it."""
ws, child, parent = (
seeded_ids.workspace_id,
seeded_ids.child_id,
seeded_ids.personal_data_uuid,
)
hard_expiry = self._BIRTH_DATE + relativedelta(years=settings.rules.hard_expiry_age_years)
day_after = hard_expiry + timedelta(days=1)
valid_day = ProposedDayInput(date(2026, 6, 15), parent, child, "sjukpenning")
invalid_day = ProposedDayInput(day_after, parent, child, "sjukpenning")
result = await planning_service.apply_proposed_days(ws, [valid_day, invalid_day])
assert result.accepted is False
rows = await planned_leave_repo.list(parent_personal_data_uuid=parent)
assert rows == []
class TestApplyProposedDaysDeleteKeys:
"""`delete_keys` folds explicit deletions into the same validated batch as