"""Tests for the PreschoolPlanningDays repository + service. Follows the pattern in `test_vacation_days_repository.py`: arrange an entity, exercise the repo/service, assert on the result. """ from datetime import date, datetime from uuid import uuid4 import pytest from app.entities.child import Child from app.entities.preschool_planning_days import PreschoolPlanningDays from app.repositories_impl.sqlite.child_repository import SQLiteChildRepository from app.repositories_impl.sqlite.preschool_planning_days_repository import ( SQLitePreschoolPlanningDaysRepository, ) from app.services.preschool_planning_days_service import PreschoolPlanningDaysService from app.utils.errors import NotFoundError, ValidationError from tests.conftest import SeededIds def _make(child_id=None, **overrides) -> PreschoolPlanningDays: base = dict( id=uuid4(), child_id=child_id or uuid4(), year=2026, planned_days=6, taken_days=None, created_at=datetime.now(), updated_at=datetime.now(), ) base.update(overrides) return PreschoolPlanningDays(**base) class TestSQLitePreschoolPlanningDaysRepository: async def test_create_and_get_by_id( self, preschool_planning_days_repo: SQLitePreschoolPlanningDaysRepository, seeded_ids: SeededIds, ) -> None: entity = _make(child_id=seeded_ids.child_id) await preschool_planning_days_repo.create(entity) fetched = await preschool_planning_days_repo.get_by_id(entity.id) assert fetched is not None assert fetched.id == entity.id assert fetched.planned_days == 6 assert fetched.taken_days is None async def test_get_by_child_year( self, preschool_planning_days_repo: SQLitePreschoolPlanningDaysRepository, seeded_ids: SeededIds, ) -> None: entity = _make(child_id=seeded_ids.child_id) await preschool_planning_days_repo.create(entity) found = await preschool_planning_days_repo.get_by_child_year(entity.child_id, 2026) assert found is not None assert found.id == entity.id missing = await preschool_planning_days_repo.get_by_child_year(uuid4(), 2026) assert missing is None async def test_update_preserves_null_taken_days_distinction( self, preschool_planning_days_repo: SQLitePreschoolPlanningDaysRepository, seeded_ids: SeededIds, ) -> None: """Not-recorded (None) must round-trip distinctly from recorded-as-zero.""" entity = _make(child_id=seeded_ids.child_id, taken_days=None) await preschool_planning_days_repo.create(entity) fetched = await preschool_planning_days_repo.get_by_id(entity.id) assert fetched is not None assert fetched.taken_days is None fetched.taken_days = 0 updated = await preschool_planning_days_repo.update(fetched.id, fetched) assert updated.taken_days == 0 async def test_update_missing_raises( self, preschool_planning_days_repo: SQLitePreschoolPlanningDaysRepository ) -> None: with pytest.raises(NotFoundError): await preschool_planning_days_repo.update(uuid4(), _make()) async def test_delete( self, preschool_planning_days_repo: SQLitePreschoolPlanningDaysRepository, seeded_ids: SeededIds, ) -> None: entity = _make(child_id=seeded_ids.child_id) await preschool_planning_days_repo.create(entity) await preschool_planning_days_repo.delete(entity.id) assert await preschool_planning_days_repo.get_by_id(entity.id) is None async def test_delete_missing_raises( self, preschool_planning_days_repo: SQLitePreschoolPlanningDaysRepository ) -> None: with pytest.raises(NotFoundError): await preschool_planning_days_repo.delete(uuid4()) async def test_list_filters_by_child( self, preschool_planning_days_repo: SQLitePreschoolPlanningDaysRepository, seeded_ids: SeededIds, child_repo: SQLiteChildRepository, ) -> None: other_child = await child_repo.create( Child( id=uuid4(), workspace_id=seeded_ids.workspace_id, name="Other Child", birth_date=date(2022, 1, 1), created_at=datetime.now(), ) ) await preschool_planning_days_repo.create(_make(child_id=seeded_ids.child_id, year=2025)) await preschool_planning_days_repo.create(_make(child_id=seeded_ids.child_id, year=2026)) await preschool_planning_days_repo.create(_make(child_id=other_child.id)) rows = await preschool_planning_days_repo.list(child_id=seeded_ids.child_id) assert len(rows) == 2 assert [r.year for r in rows] == [2026, 2025] # ordered year DESC async def test_list_rejects_unsupported_filter( self, preschool_planning_days_repo: SQLitePreschoolPlanningDaysRepository ) -> None: with pytest.raises(ValueError, match="invalid_filter"): await preschool_planning_days_repo.list(invalid_filter="value") class TestPreschoolPlanningDaysService: async def test_upsert_planned_creates_then_updates( self, preschool_planning_days_service: PreschoolPlanningDaysService, seeded_ids: SeededIds, child_repo: SQLiteChildRepository, ) -> None: child = await child_repo.get_by_id(seeded_ids.child_id) assert child is not None created = await preschool_planning_days_service.upsert_planned(child, 2026, 8) assert created.planned_days == 8 assert created.taken_days is None updated = await preschool_planning_days_service.upsert_planned(child, 2026, 10) assert updated.id == created.id assert updated.planned_days == 10 async def test_upsert_planned_rejects_negative( self, preschool_planning_days_service: PreschoolPlanningDaysService, child_repo: SQLiteChildRepository, seeded_ids: SeededIds, ) -> None: child = await child_repo.get_by_id(seeded_ids.child_id) assert child is not None with pytest.raises(ValidationError): await preschool_planning_days_service.upsert_planned(child, 2026, -1) async def test_record_taken_seeds_default_planned_days( self, preschool_planning_days_service: PreschoolPlanningDaysService, child_repo: SQLiteChildRepository, seeded_ids: SeededIds, ) -> None: """With no existing row, `planned_days` is seeded from the child's default.""" child = await child_repo.get_by_id(seeded_ids.child_id) assert child is not None assert child.preschool_planning_days_per_year == 6 # entity default recorded = await preschool_planning_days_service.record_taken(child, 2026, 4) assert recorded.planned_days == 6 assert recorded.taken_days == 4 async def test_record_taken_preserves_planned_days_on_existing_row( self, preschool_planning_days_service: PreschoolPlanningDaysService, child_repo: SQLiteChildRepository, seeded_ids: SeededIds, ) -> None: child = await child_repo.get_by_id(seeded_ids.child_id) assert child is not None await preschool_planning_days_service.upsert_planned(child, 2026, 12) recorded = await preschool_planning_days_service.record_taken(child, 2026, 3) assert recorded.planned_days == 12 assert recorded.taken_days == 3 async def test_record_taken_rejects_negative( self, preschool_planning_days_service: PreschoolPlanningDaysService, child_repo: SQLiteChildRepository, seeded_ids: SeededIds, ) -> None: child = await child_repo.get_by_id(seeded_ids.child_id) assert child is not None with pytest.raises(ValidationError): await preschool_planning_days_service.record_taken(child, 2026, -1) async def test_record_taken_zero_distinguishable_from_unrecorded( self, preschool_planning_days_service: PreschoolPlanningDaysService, child_repo: SQLiteChildRepository, seeded_ids: SeededIds, ) -> None: child = await child_repo.get_by_id(seeded_ids.child_id) assert child is not None unrecorded = await preschool_planning_days_service.get_by_child_year(child.id, 2026) assert unrecorded is None recorded_zero = await preschool_planning_days_service.record_taken(child, 2026, 0) assert recorded_zero.taken_days == 0 async def test_get_missing_raises( self, preschool_planning_days_service: PreschoolPlanningDaysService ) -> None: with pytest.raises(NotFoundError): await preschool_planning_days_service.get(uuid4()) async def test_list_for_child( self, preschool_planning_days_service: PreschoolPlanningDaysService, child_repo: SQLiteChildRepository, seeded_ids: SeededIds, ) -> None: child = await child_repo.get_by_id(seeded_ids.child_id) assert child is not None await preschool_planning_days_service.upsert_planned(child, 2025, 6) await preschool_planning_days_service.upsert_planned(child, 2026, 6) rows = await preschool_planning_days_service.list_for_child(child.id) assert [r.year for r in rows] == [2026, 2025]