"""Tests for the reference repository + service. Demonstrates the testing pattern: arrange an entity, exercise the repo/service, assert on the result. Aim for >=90% coverage on new code (ARCHITECTURE.md ยง10). """ from datetime import datetime from uuid import uuid4 import pytest from app.entities.personal_data import PersonalData from app.entities.vacation_days import VacationDays from app.entities.workspace import Workspace from app.repositories_impl.sqlite.personal_data_repository import SQLitePersonalDataRepository from app.repositories_impl.sqlite.vacation_days_repository import ( SQLiteVacationDaysRepository, ) from app.repositories_impl.sqlite.workspace_repository import SQLiteWorkspaceRepository from app.services.vacation_days_service import VacationDaysService from app.utils.errors import NotFoundError, ValidationError from tests.conftest import SeededIds def _make(workspace_id=None, personal_data_uuid=None, **overrides) -> VacationDays: base = dict( id=uuid4(), personal_data_uuid=personal_data_uuid or uuid4(), workspace_id=workspace_id or uuid4(), year=2026, total_days=25, saved_days_in=0, created_at=datetime.now(), updated_at=datetime.now(), ) base.update(overrides) return VacationDays(**base) class TestSQLiteVacationDaysRepository: async def test_create_and_get_by_id( self, vacation_repo: SQLiteVacationDaysRepository, seeded_ids: SeededIds, ) -> None: entity = _make( workspace_id=seeded_ids.workspace_id, personal_data_uuid=seeded_ids.personal_data_uuid ) await vacation_repo.create(entity) fetched = await vacation_repo.get_by_id(entity.id) assert fetched is not None assert fetched.id == entity.id assert fetched.total_days == 25 async def test_get_by_parent_year( self, vacation_repo: SQLiteVacationDaysRepository, seeded_ids: SeededIds, ) -> None: entity = _make( workspace_id=seeded_ids.workspace_id, personal_data_uuid=seeded_ids.personal_data_uuid ) await vacation_repo.create(entity) found = await vacation_repo.get_by_parent_year(entity.personal_data_uuid, 2026) assert found is not None assert found.id == entity.id missing = await vacation_repo.get_by_parent_year(uuid4(), 2026) assert missing is None async def test_update( self, vacation_repo: SQLiteVacationDaysRepository, seeded_ids: SeededIds, ) -> None: entity = _make( total_days=20, workspace_id=seeded_ids.workspace_id, personal_data_uuid=seeded_ids.personal_data_uuid, ) await vacation_repo.create(entity) entity.total_days = 30 updated = await vacation_repo.update(entity.id, entity) assert updated.total_days == 30 async def test_update_missing_raises(self, vacation_repo: SQLiteVacationDaysRepository) -> None: with pytest.raises(NotFoundError): await vacation_repo.update(uuid4(), _make()) async def test_delete( self, vacation_repo: SQLiteVacationDaysRepository, seeded_ids: SeededIds, ) -> None: entity = _make( workspace_id=seeded_ids.workspace_id, personal_data_uuid=seeded_ids.personal_data_uuid ) await vacation_repo.create(entity) await vacation_repo.delete(entity.id) assert await vacation_repo.get_by_id(entity.id) is None async def test_delete_missing_raises(self, vacation_repo: SQLiteVacationDaysRepository) -> None: with pytest.raises(NotFoundError): await vacation_repo.delete(uuid4()) async def test_list_filters_by_workspace( self, vacation_repo: SQLiteVacationDaysRepository, seeded_ids: SeededIds, workspace_repo: SQLiteWorkspaceRepository, personal_data_repo: SQLitePersonalDataRepository, ) -> None: ws = seeded_ids.workspace_id pd_uuid = seeded_ids.personal_data_uuid # Create a second workspace + personal_data for the "different workspace" row ws2 = await workspace_repo.create( Workspace(id=uuid4(), name="WS2", created_at=datetime.now()) ) pd2 = await personal_data_repo.create( PersonalData( uuid=uuid4(), workspace_id=ws2.id, name="Parent2", created_at=datetime.now() ) ) await vacation_repo.create(_make(workspace_id=ws, personal_data_uuid=pd_uuid, year=2025)) await vacation_repo.create(_make(workspace_id=ws, personal_data_uuid=pd_uuid, year=2026)) await vacation_repo.create(_make(workspace_id=ws2.id, personal_data_uuid=pd2.uuid)) rows = await vacation_repo.list(workspace_id=ws) assert len(rows) == 2 assert [r.year for r in rows] == [2026, 2025] # ordered year DESC async def test_list_rejects_unsupported_filter( self, vacation_repo: SQLiteVacationDaysRepository, ) -> None: """Passing an unsupported filter must raise ValueError.""" with pytest.raises(ValueError, match="invalid_filter"): await vacation_repo.list(invalid_filter="value") async def test_list_error_message_lists_supported_filters( self, vacation_repo: SQLiteVacationDaysRepository, ) -> None: """Error message should list all supported filters (sorted).""" with pytest.raises(ValueError, match="personal_data_uuid.*workspace_id.*year"): await vacation_repo.list(bad_filter="value") class TestVacationDaysService: async def test_upsert_creates_then_updates( self, vacation_service: VacationDaysService, seeded_ids: SeededIds, ) -> None: parent = seeded_ids.personal_data_uuid ws = seeded_ids.workspace_id created = await vacation_service.upsert(parent, ws, 2026, 25) assert created.total_days == 25 updated = await vacation_service.upsert(parent, ws, 2026, 30, saved_days_in=5) assert updated.id == created.id # same (parent, year) row assert updated.total_days == 30 assert updated.saved_days_in == 5 async def test_upsert_rejects_negative(self, vacation_service: VacationDaysService) -> None: with pytest.raises(ValidationError): await vacation_service.upsert(uuid4(), uuid4(), 2026, -1) async def test_get_missing_raises(self, vacation_service: VacationDaysService) -> None: with pytest.raises(NotFoundError): await vacation_service.get(uuid4())