Files
F-r-ldraledigplannerare/backend/tests/unit/test_services/test_fetch_data_service.py
T
jacobh2 0fc71101ae 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.
2026-09-17 19:46:03 +02:00

164 lines
6.0 KiB
Python

"""Tests for `ManualFetchService` (backend/app/services/fetch_data_service.py).
This is the manual-entry path a parent's Försäkringskassan balances enter the
app through — now the primary onboarding action. `git grep` found zero
existing coverage for this service or its router before this file.
`test_fk_scraper.py::test_reserved_days_derived` already proves the
`reserved_days_left <= reserved_days_total` domain rule holds for the
scraper-side derivation; this file proves the equivalent manual-entry
boundary — the `ManualFetchService.save_manual_entry` validation itself —
which was previously untested.
"""
from uuid import uuid4
import pytest
from app.repositories_impl.sqlite.parental_leave_repository import SQLiteParentalLeaveRepository
from app.services.fetch_data_service import ManualFetchService
from app.utils.errors import ValidationError
from tests.conftest import SeededIds
def _valid_kwargs(seeded_ids: SeededIds, **overrides: object) -> dict:
base = dict(
personal_data_uuid=seeded_ids.personal_data_uuid,
workspace_id=seeded_ids.workspace_id,
child_id=seeded_ids.child_id,
total_days=480,
days_used=100,
reserved_days_total=90,
reserved_days_left=90,
transferable_days_left=150,
dubbeldagar_left=60,
sjukpenning_total=390,
sjukpenning_used=80,
lagsta_niva_total=90,
lagsta_niva_used=20,
)
base.update(overrides)
return base
@pytest.fixture
def manual_fetch_service(
parental_leave_repo: SQLiteParentalLeaveRepository,
) -> ManualFetchService:
return ManualFetchService(parental_leave_repo)
class TestSaveManualEntryValidation:
async def test_days_used_exceeding_total_days_is_rejected(
self,
manual_fetch_service: ManualFetchService,
seeded_ids: SeededIds,
) -> None:
kwargs = _valid_kwargs(seeded_ids, total_days=100, days_used=101)
with pytest.raises(ValidationError, match="days_used cannot exceed total_days"):
await manual_fetch_service.save_manual_entry(**kwargs)
async def test_days_used_equal_to_total_days_is_accepted(
self,
manual_fetch_service: ManualFetchService,
seeded_ids: SeededIds,
) -> None:
"""The boundary itself (0 days left) is valid, not an off-by-one reject."""
kwargs = _valid_kwargs(seeded_ids, total_days=100, days_used=100)
fetch = await manual_fetch_service.save_manual_entry(**kwargs)
assert fetch.days_left == 0
async def test_reserved_days_left_exceeding_total_is_rejected(
self,
manual_fetch_service: ManualFetchService,
seeded_ids: SeededIds,
) -> None:
kwargs = _valid_kwargs(seeded_ids, reserved_days_total=90, reserved_days_left=91)
with pytest.raises(
ValidationError, match="reserved_days_left cannot exceed reserved_days_total"
):
await manual_fetch_service.save_manual_entry(**kwargs)
async def test_reserved_days_left_equal_to_total_is_accepted(
self,
manual_fetch_service: ManualFetchService,
seeded_ids: SeededIds,
) -> None:
kwargs = _valid_kwargs(seeded_ids, reserved_days_total=90, reserved_days_left=90)
fetch = await manual_fetch_service.save_manual_entry(**kwargs)
assert fetch.reserved_days_left == 90
async def test_valid_payload_is_accepted_and_derived_fields_are_correct(
self,
manual_fetch_service: ManualFetchService,
seeded_ids: SeededIds,
) -> None:
kwargs = _valid_kwargs(
seeded_ids,
total_days=480,
days_used=100,
sjukpenning_total=390,
sjukpenning_used=80,
lagsta_niva_total=90,
lagsta_niva_used=20,
)
fetch = await manual_fetch_service.save_manual_entry(**kwargs)
assert fetch.source == "manual"
assert fetch.days_left == 380
assert fetch.tier_data.sjukpenning_left == 310
assert fetch.tier_data.lagsta_niva_left == 70
assert fetch.id is not None
assert fetch.created_at is not None
assert fetch.updated_at is not None
class TestSaveManualEntryPersistence:
async def test_snapshot_persists_and_is_readable_via_get_latest(
self,
manual_fetch_service: ManualFetchService,
seeded_ids: SeededIds,
) -> None:
kwargs = _valid_kwargs(seeded_ids)
saved = await manual_fetch_service.save_manual_entry(**kwargs)
latest = await manual_fetch_service.get_latest(
seeded_ids.personal_data_uuid, seeded_ids.child_id
)
assert latest is not None
assert latest.id == saved.id
assert latest.total_days == saved.total_days
assert latest.tier_data.sjukpenning_left == saved.tier_data.sjukpenning_left
async def test_get_latest_with_no_snapshot_returns_none(
self,
manual_fetch_service: ManualFetchService,
seeded_ids: SeededIds,
) -> None:
result = await manual_fetch_service.get_latest(uuid4(), uuid4())
assert result is None
async def test_list_history_returns_all_snapshots_newest_first(
self,
manual_fetch_service: ManualFetchService,
seeded_ids: SeededIds,
) -> None:
await manual_fetch_service.save_manual_entry(**_valid_kwargs(seeded_ids, days_used=50))
await manual_fetch_service.save_manual_entry(**_valid_kwargs(seeded_ids, days_used=60))
await manual_fetch_service.save_manual_entry(**_valid_kwargs(seeded_ids, days_used=70))
history = await manual_fetch_service.list_history(
seeded_ids.personal_data_uuid, seeded_ids.child_id
)
assert len(history) == 3
assert [h.days_used for h in history] == [70, 60, 50]
async def test_list_history_with_no_snapshots_returns_empty_list(
self,
manual_fetch_service: ManualFetchService,
seeded_ids: SeededIds,
) -> None:
history = await manual_fetch_service.list_history(uuid4(), uuid4())
assert history == []