"""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 == []