"""Tests for the DayOverride repository + service. Follows the VacationDays reference pattern: arrange an entity, exercise the repo/service, assert on the result. See PLANNING task notes / AGENTS.md. """ from datetime import date, datetime from uuid import uuid4 import pytest from app.entities.day_override import DayOverride from app.entities.workspace import Workspace from app.repositories_impl.sqlite.day_override_repository import SQLiteDayOverrideRepository from app.repositories_impl.sqlite.workspace_repository import SQLiteWorkspaceRepository from app.services.day_override_service import DayOverrideService from app.utils.errors import NotFoundError, ValidationError from tests.conftest import SeededIds def _make(workspace_id=None, **overrides) -> DayOverride: base = dict( id=uuid4(), workspace_id=workspace_id or uuid4(), date=date(2027, 12, 24), is_working_day=True, note=None, created_at=datetime.now(), updated_at=datetime.now(), ) base.update(overrides) return DayOverride(**base) class TestSQLiteDayOverrideRepository: async def test_create_and_get_by_id( self, day_override_repo: SQLiteDayOverrideRepository, seeded_ids: SeededIds, ) -> None: entity = _make(workspace_id=seeded_ids.workspace_id) await day_override_repo.create(entity) fetched = await day_override_repo.get_by_id(entity.id) assert fetched is not None assert fetched.id == entity.id assert fetched.is_working_day is True assert fetched.date == date(2027, 12, 24) async def test_get_by_workspace_date( self, day_override_repo: SQLiteDayOverrideRepository, seeded_ids: SeededIds, ) -> None: entity = _make(workspace_id=seeded_ids.workspace_id) await day_override_repo.create(entity) found = await day_override_repo.get_by_workspace_date( seeded_ids.workspace_id, date(2027, 12, 24) ) assert found is not None assert found.id == entity.id missing = await day_override_repo.get_by_workspace_date(uuid4(), date(2027, 12, 24)) assert missing is None async def test_update( self, day_override_repo: SQLiteDayOverrideRepository, seeded_ids: SeededIds, ) -> None: entity = _make(workspace_id=seeded_ids.workspace_id, is_working_day=True) await day_override_repo.create(entity) entity.is_working_day = False entity.note = "Ledig enligt kollektivavtal" updated = await day_override_repo.update(entity.id, entity) assert updated.is_working_day is False assert updated.note == "Ledig enligt kollektivavtal" async def test_update_missing_raises( self, day_override_repo: SQLiteDayOverrideRepository ) -> None: with pytest.raises(NotFoundError): await day_override_repo.update(uuid4(), _make()) async def test_delete( self, day_override_repo: SQLiteDayOverrideRepository, seeded_ids: SeededIds, ) -> None: entity = _make(workspace_id=seeded_ids.workspace_id) await day_override_repo.create(entity) await day_override_repo.delete(entity.id) assert await day_override_repo.get_by_id(entity.id) is None async def test_delete_missing_raises( self, day_override_repo: SQLiteDayOverrideRepository ) -> None: with pytest.raises(NotFoundError): await day_override_repo.delete(uuid4()) async def test_list_by_workspace_filters_and_orders_by_date( self, day_override_repo: SQLiteDayOverrideRepository, seeded_ids: SeededIds, workspace_repo: SQLiteWorkspaceRepository, ) -> None: ws = seeded_ids.workspace_id ws2 = await workspace_repo.create( Workspace(id=uuid4(), name="WS2", created_at=datetime.now()) ) await day_override_repo.create(_make(workspace_id=ws, date=date(2027, 12, 31))) await day_override_repo.create(_make(workspace_id=ws, date=date(2027, 5, 7))) await day_override_repo.create(_make(workspace_id=ws2.id, date=date(2027, 12, 24))) rows = await day_override_repo.list_by_workspace(ws) assert [r.date for r in rows] == [date(2027, 5, 7), date(2027, 12, 31)] async def test_unique_constraint_per_workspace_date( self, day_override_repo: SQLiteDayOverrideRepository, seeded_ids: SeededIds, ) -> None: await day_override_repo.create(_make(workspace_id=seeded_ids.workspace_id)) with pytest.raises(Exception): # sqlite3.IntegrityError via aiosqlite await day_override_repo.create(_make(workspace_id=seeded_ids.workspace_id)) class TestDayOverrideService: async def test_upsert_creates_then_updates( self, day_override_service: DayOverrideService, seeded_ids: SeededIds, ) -> None: ws = seeded_ids.workspace_id created = await day_override_service.upsert(ws, date(2027, 12, 24), True) assert created.is_working_day is True assert created.note is None updated = await day_override_service.upsert( ws, date(2027, 12, 24), False, note="Ledigt i år" ) assert updated.id == created.id # same (workspace, date) row assert updated.is_working_day is False assert updated.note == "Ledigt i år" async def test_upsert_rejects_overlong_note( self, day_override_service: DayOverrideService, seeded_ids: SeededIds ) -> None: with pytest.raises(ValidationError): await day_override_service.upsert( seeded_ids.workspace_id, date(2027, 12, 24), True, note="x" * 281 ) async def test_get_missing_raises(self, day_override_service: DayOverrideService) -> None: with pytest.raises(NotFoundError): await day_override_service.get(uuid4()) async def test_list_for_workspace( self, day_override_service: DayOverrideService, seeded_ids: SeededIds ) -> None: ws = seeded_ids.workspace_id await day_override_service.upsert(ws, date(2027, 5, 7), False) await day_override_service.upsert(ws, date(2027, 12, 24), True) rows = await day_override_service.list_for_workspace(ws) assert len(rows) == 2 async def test_delete( self, day_override_service: DayOverrideService, seeded_ids: SeededIds ) -> None: created = await day_override_service.upsert( seeded_ids.workspace_id, date(2027, 12, 24), True ) await day_override_service.delete(created.id) with pytest.raises(NotFoundError): await day_override_service.get(created.id) async def test_get_effective_map( self, day_override_service: DayOverrideService, seeded_ids: SeededIds ) -> None: ws = seeded_ids.workspace_id await day_override_service.upsert(ws, date(2027, 12, 24), True) await day_override_service.upsert(ws, date(2027, 2, 2), False) effective = await day_override_service.get_effective_map(ws) assert effective == { date(2027, 12, 24): True, date(2027, 2, 2): False, }