Files
F-r-ldraledigplannerare/backend/tests/unit/test_leave_wish_repository.py
T
jacobh2 76b1e712b9 Complete NextSteps roadmap: planning engine UI, calendar cluster, flexible wishes
Works through every numbered item in NextSteps260913.md section 3, plus the
three known verification issues in section 4.1.

Surface what the backend already computed:
- 3.1 render surplus_advice in PlanResults, mirroring the shortfall remedies
- 3.2 surface VacationBalance.is_assumed so assumed statutory years are visible
- 3.3 age-4 expiry given the proposed plan, via a new Age4Projection reusing
  the existing _age4_projections arithmetic (Dashboard stat verified, not rebuilt)

Calendar cluster (one pass over the holiday data):
- new GET /api/calendar/days exposing roda dagar, klamdagar and day overrides,
  paginated four years at a time
- 3.4 shade roda dagar/klamdagar, with R/K badges so colour is not the only signal
- 3.6 click-to-override behind an explicit mode toggle, leaving drag multi-select intact
- 3.7 click month/year to advance
- 3.8 per-year red-days list in Settings, statutory days plus overrides

i18n:
- 3.5 five backend-generated Swedish surfaces converted to message_key + data,
  following the tips_service pattern; old string fields replaced, not kept

Flexible wishes (3.9):
- PLANNING_ENGINE_SPEC section 10: window_mode, candidate search, cost function,
  deterministic tie-break, two-pass ordering, test matrix rows 15-24
- backend: entity, migration 006, _FULL_SCHEMA sync, repository, allocator
- frontend: season-preset toggle in WishEditor, unplaced outcome rendering

Fixes (4.1):
- pin typecheck to the local tsc rather than npx resolving a newer TypeScript
- ignore generated coverage/ and dist/ in the ESLint flat config
- fix the flaky usePlanApply test properly, with waitFor rather than a retry

Tests: backend 361 -> 404, frontend 540 -> 704. Coverage 89.47% -> 96.52% lines.

Not yet verified in a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL
2026-09-14 02:33:03 +02:00

891 lines
31 KiB
Python

"""Tests for the LeaveWish repository and service.
Demonstrates the testing pattern for entity-specific repos and services.
Aim for >=90% coverage on new code (ARCHITECTURE.md §10).
"""
from datetime import date, datetime
from uuid import uuid4
import pytest
from app.entities.leave_wish import LeaveWish
from app.entities.workspace import Workspace
from app.repositories_impl.sqlite.leave_wish_repository import SQLiteLeaveWishRepository
from app.repositories_impl.sqlite.workspace_repository import SQLiteWorkspaceRepository
from app.services.leave_wish_service import LeaveWishService
from app.utils.errors import NotFoundError, ValidationError
from tests.conftest import SeededIds
def _make(workspace_id=None, **overrides) -> LeaveWish:
"""Helper to create a test LeaveWish entity."""
base = dict(
id=uuid4(),
workspace_id=workspace_id or uuid4(),
child_id=None,
name="Sommar",
window_start_month=6,
window_start_day=15,
window_end_month=8,
window_end_day=15,
target_weeks=6,
recurrence="yearly",
first_year=2027,
last_year=None,
priority=1,
parent_mode="either",
parent_personal_data_uuid=None,
goal_override=None,
created_at=datetime.now(),
updated_at=datetime.now(),
)
base.update(overrides)
return LeaveWish(**base)
class TestLeaveWishWindowLogic:
"""Direct tests of LeaveWish's recurring-window helpers.
These are the rules the allocator (Phase B) depends on most — especially
year-wrapping, which is the easiest thing in this feature to get backwards.
"""
def test_window_wraps_false_when_end_after_start(self) -> None:
wish = _make(
window_start_month=6, window_start_day=15, window_end_month=8, window_end_day=15
)
assert wish.window_wraps() is False
def test_window_wraps_true_when_end_before_start(self) -> None:
# Jullov: Dec 20 -> Jan 7 (end sorts before start -> wraps)
wish = _make(
window_start_month=12, window_start_day=20, window_end_month=1, window_end_day=7
)
assert wish.window_wraps() is True
def test_window_wraps_false_when_start_equals_end(self) -> None:
wish = _make(window_start_month=6, window_start_day=1, window_end_month=6, window_end_day=1)
assert wish.window_wraps() is False
def test_window_for_year_no_wrap_stays_in_same_year(self) -> None:
wish = _make(
window_start_month=6, window_start_day=15, window_end_month=8, window_end_day=15
)
start, end = wish.window_for_year(2027)
assert start == date(2027, 6, 15)
assert end == date(2027, 8, 15)
def test_window_for_year_wraps_end_into_next_year(self) -> None:
wish = _make(
window_start_month=12, window_start_day=20, window_end_month=1, window_end_day=7
)
start, end = wish.window_for_year(2027)
assert start == date(2027, 12, 20)
assert end == date(2028, 1, 7)
def test_applies_to_year_before_first_year_is_false(self) -> None:
wish = _make(recurrence="yearly", first_year=2027, last_year=None)
assert wish.applies_to_year(2026) is False
def test_applies_to_year_once_matches_only_first_year(self) -> None:
wish = _make(recurrence="once", first_year=2027, last_year=2027)
assert wish.applies_to_year(2027) is True
assert wish.applies_to_year(2028) is False
def test_applies_to_year_yearly_with_no_last_year_recurs_indefinitely(self) -> None:
wish = _make(recurrence="yearly", first_year=2027, last_year=None)
assert wish.applies_to_year(2027) is True
assert wish.applies_to_year(2050) is True
def test_applies_to_year_yearly_stops_after_last_year(self) -> None:
wish = _make(recurrence="yearly", first_year=2027, last_year=2029)
assert wish.applies_to_year(2029) is True
assert wish.applies_to_year(2030) is False
class TestSQLiteLeaveWishRepository:
"""Test LeaveWishRepository SQLite implementation."""
async def test_create_and_get_by_id(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id)
created = await leave_wish_repo.create(entity)
fetched = await leave_wish_repo.get_by_id(created.id)
assert fetched is not None
assert fetched.id == created.id
assert fetched.name == "Sommar"
assert fetched.target_weeks == 6
assert fetched.recurrence == "yearly"
assert fetched.parent_mode == "either"
assert fetched.child_id is None
assert fetched.parent_personal_data_uuid is None
assert fetched.goal_override is None
async def test_get_by_id_missing(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
) -> None:
result = await leave_wish_repo.get_by_id(uuid4())
assert result is None
async def test_create_preserves_all_fields(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(
workspace_id=seeded_ids.workspace_id,
child_id=seeded_ids.child_id,
name="Jullov",
window_start_month=12,
window_start_day=20,
window_end_month=1,
window_end_day=7,
target_weeks=4,
recurrence="yearly",
first_year=2027,
last_year=2031,
priority=2,
parent_mode="specific",
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
goal_override="max_time",
)
created = await leave_wish_repo.create(entity)
assert created.name == "Jullov"
assert created.child_id == seeded_ids.child_id
assert created.window_start_month == 12
assert created.window_start_day == 20
assert created.window_end_month == 1
assert created.window_end_day == 7
assert created.target_weeks == 4
assert created.recurrence == "yearly"
assert created.first_year == 2027
assert created.last_year == 2031
assert created.priority == 2
assert created.parent_mode == "specific"
assert created.parent_personal_data_uuid == seeded_ids.personal_data_uuid
assert created.goal_override == "max_time"
# Round-trip through the DB, not just the in-memory object.
fetched = await leave_wish_repo.get_by_id(created.id)
assert fetched is not None
assert fetched.child_id == seeded_ids.child_id
assert fetched.last_year == 2031
assert fetched.parent_personal_data_uuid == seeded_ids.personal_data_uuid
assert fetched.goal_override == "max_time"
async def test_get_by_workspace(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
workspace_repo: SQLiteWorkspaceRepository,
) -> None:
ws1 = seeded_ids.workspace_id
ws2 = await workspace_repo.create(
Workspace(id=uuid4(), name="WS2", created_at=datetime.now())
)
w1 = await leave_wish_repo.create(_make(workspace_id=ws1, name="W1"))
w2 = await leave_wish_repo.create(_make(workspace_id=ws1, name="W2"))
w3 = await leave_wish_repo.create(_make(workspace_id=ws2.id, name="W3"))
rows = await leave_wish_repo.get_by_workspace(ws1)
assert len(rows) == 2
ids = {r.id for r in rows}
assert w1.id in ids
assert w2.id in ids
assert w3.id not in ids
async def test_get_by_workspace_empty(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
) -> None:
result = await leave_wish_repo.get_by_workspace(uuid4())
assert result == []
async def test_update(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id, name="Sommar", target_weeks=6)
created = await leave_wish_repo.create(entity)
created.name = "Sommarlov"
created.target_weeks = 8
created.priority = 3
updated = await leave_wish_repo.update(created.id, created)
assert updated.name == "Sommarlov"
assert updated.target_weeks == 8
assert updated.priority == 3
assert updated.id == created.id
async def test_update_missing_raises(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
) -> None:
with pytest.raises(NotFoundError):
await leave_wish_repo.update(uuid4(), _make())
async def test_delete(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id)
created = await leave_wish_repo.create(entity)
await leave_wish_repo.delete(created.id)
result = await leave_wish_repo.get_by_id(created.id)
assert result is None
async def test_delete_missing_raises(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
) -> None:
with pytest.raises(NotFoundError):
await leave_wish_repo.delete(uuid4())
async def test_list(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
ws = seeded_ids.workspace_id
w1 = await leave_wish_repo.create(_make(workspace_id=ws, name="W1"))
w2 = await leave_wish_repo.create(_make(workspace_id=ws, name="W2"))
rows = await leave_wish_repo.list()
assert len(rows) >= 2
ids = {r.id for r in rows}
assert w1.id in ids
assert w2.id in ids
async def test_list_filters_by_child_id(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
ws = seeded_ids.workspace_id
tied = await leave_wish_repo.create(
_make(workspace_id=ws, child_id=seeded_ids.child_id, name="Tied")
)
family_wide = await leave_wish_repo.create(
_make(workspace_id=ws, child_id=None, name="FamilyWide")
)
rows = await leave_wish_repo.list(child_id=seeded_ids.child_id)
ids = {r.id for r in rows}
assert tied.id in ids
assert family_wide.id not in ids
class TestLeaveWishService:
"""Test LeaveWishService business logic (validation)."""
async def test_create_with_valid_fields(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
result = await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Sommar",
window_start_month=6,
window_start_day=15,
window_end_month=8,
window_end_day=15,
target_weeks=6,
first_year=2027,
)
assert result.name == "Sommar"
assert result.target_weeks == 6
assert result.recurrence == "once" # default
assert result.parent_mode == "either" # default
@pytest.mark.parametrize("recurrence", ["once", "yearly"])
async def test_create_accepts_valid_recurrence(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
recurrence: str,
) -> None:
result = await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
recurrence=recurrence,
)
assert result.recurrence == recurrence
async def test_create_rejects_invalid_recurrence(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
with pytest.raises(ValidationError, match="recurrence"):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
recurrence="monthly",
)
async def test_create_rejects_invalid_parent_mode(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
with pytest.raises(ValidationError, match="parent_mode"):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
parent_mode="everyone",
)
async def test_create_specific_parent_mode_requires_uuid(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
with pytest.raises(ValidationError, match="parent_personal_data_uuid"):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
parent_mode="specific",
parent_personal_data_uuid=None,
)
async def test_create_specific_parent_mode_with_uuid_succeeds(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
result = await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
parent_mode="specific",
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
)
assert result.parent_mode == "specific"
assert result.parent_personal_data_uuid == seeded_ids.personal_data_uuid
async def test_create_rejects_parent_uuid_when_not_specific(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
with pytest.raises(ValidationError, match="parent_personal_data_uuid"):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
parent_mode="either",
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
)
@pytest.mark.parametrize("goal_override", ["max_time", "max_money", "balanced", None])
async def test_create_accepts_valid_goal_override(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
goal_override: str | None,
) -> None:
result = await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
goal_override=goal_override,
)
assert result.goal_override == goal_override
async def test_create_rejects_invalid_goal_override(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
with pytest.raises(ValidationError, match="goal_override"):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
goal_override="minimize_stress",
)
# -- window_mode (§10.1) ------------------------------------------------
async def test_create_defaults_to_fixed_window_mode(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
result = await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
)
assert result.window_mode == "fixed"
async def test_create_rejects_invalid_window_mode(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
with pytest.raises(ValidationError, match="window_mode"):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
window_mode="whenever",
)
async def test_create_accepts_flexible_window_mode_with_wide_enough_bound(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
# Bound = 2027-03-01 -> 2027-05-31 (92 days), target_weeks=1 (7 days).
result = await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Vårlov",
window_start_month=3,
window_start_day=1,
window_end_month=5,
window_end_day=31,
target_weeks=1,
first_year=2027,
window_mode="flexible",
)
assert result.window_mode == "flexible"
async def test_create_rejects_flexible_wish_whose_bound_is_shorter_than_target_weeks(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
# Bound = 2027-03-01 -> 2027-03-10 (10 days), target_weeks=2 (14
# days) -- a legitimate future shortfall must not be silently
# accepted; this is a user-input error (§10.1).
with pytest.raises(ValidationError, match="flexible"):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Vårlov",
window_start_month=3,
window_start_day=1,
window_end_month=3,
window_end_day=10,
target_weeks=2,
first_year=2027,
window_mode="flexible",
)
async def test_update_can_switch_window_mode_to_flexible(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
entity = _make(
workspace_id=seeded_ids.workspace_id,
window_start_month=3,
window_start_day=1,
window_end_month=5,
window_end_day=31,
target_weeks=1,
)
created = await leave_wish_service._repo.create(entity)
assert created.window_mode == "fixed"
updated = await leave_wish_service.update(created.id, window_mode="flexible")
assert updated.window_mode == "flexible"
async def test_update_to_flexible_still_validates_bound_length(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
entity = _make(
workspace_id=seeded_ids.workspace_id,
window_start_month=3,
window_start_day=1,
window_end_month=3,
window_end_day=10, # 10-day bound
target_weeks=2, # needs 14 days
)
created = await leave_wish_service._repo.create(entity)
with pytest.raises(ValidationError, match="flexible"):
await leave_wish_service.update(created.id, window_mode="flexible")
@pytest.mark.parametrize(
"month,day",
[(0, 15), (13, 15), (6, 0), (6, 32), (2, 30), (4, 31)],
)
async def test_create_rejects_invalid_month_day(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
month: int,
day: int,
) -> None:
with pytest.raises(ValidationError):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=month,
window_start_day=day,
window_end_month=6,
window_end_day=20,
target_weeks=1,
first_year=2027,
)
async def test_create_accepts_leap_day(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
result = await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=2,
window_start_day=29,
window_end_month=3,
window_end_day=1,
target_weeks=1,
first_year=2027,
)
assert result.window_start_day == 29
async def test_create_rejects_zero_target_weeks(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
with pytest.raises(ValidationError, match="target_weeks"):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=0,
first_year=2027,
)
async def test_create_rejects_last_year_before_first_year(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
with pytest.raises(ValidationError, match="last_year"):
await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2030,
last_year=2029,
)
async def test_create_allows_last_year_equal_first_year(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
result = await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
last_year=2027,
)
assert result.last_year == 2027
async def test_create_allows_null_last_year(
self,
leave_wish_service: LeaveWishService,
seeded_ids: SeededIds,
) -> None:
result = await leave_wish_service.create(
workspace_id=seeded_ids.workspace_id,
name="Wish",
window_start_month=1,
window_start_day=1,
window_end_month=1,
window_end_day=10,
target_weeks=1,
first_year=2027,
last_year=None,
)
assert result.last_year is None
async def test_get(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id)
created = await leave_wish_repo.create(entity)
fetched = await leave_wish_service.get(created.id)
assert fetched.id == created.id
assert fetched.name == "Sommar"
async def test_get_missing_raises(
self,
leave_wish_service: LeaveWishService,
) -> None:
with pytest.raises(NotFoundError):
await leave_wish_service.get(uuid4())
async def test_list_for_workspace(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
ws = seeded_ids.workspace_id
w1 = await leave_wish_repo.create(_make(workspace_id=ws, name="W1"))
w2 = await leave_wish_repo.create(_make(workspace_id=ws, name="W2"))
result = await leave_wish_service.list_for_workspace(ws)
assert len(result) == 2
ids = {r.id for r in result}
assert w1.id in ids
assert w2.id in ids
async def test_update_partial_only_touches_given_fields(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id, name="Sommar", target_weeks=6)
created = await leave_wish_repo.create(entity)
result = await leave_wish_service.update(
leave_wish_id=created.id,
name="Sommarlov",
)
assert result.name == "Sommarlov"
assert result.target_weeks == 6 # untouched
assert result.window_start_month == 6 # untouched
async def test_update_can_set_all_optional_fields(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
"""Every non-clear-flag optional field, set to a real value in one call."""
entity = _make(workspace_id=seeded_ids.workspace_id, recurrence="once", last_year=None)
created = await leave_wish_repo.create(entity)
result = await leave_wish_service.update(
leave_wish_id=created.id,
child_id=seeded_ids.child_id,
window_start_month=3,
window_start_day=10,
window_end_month=4,
window_end_day=20,
target_weeks=9,
recurrence="yearly",
first_year=2028,
last_year=2030,
priority=5,
parent_mode="specific",
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
goal_override="balanced",
)
assert result.child_id == seeded_ids.child_id
assert result.window_start_month == 3
assert result.window_start_day == 10
assert result.window_end_month == 4
assert result.window_end_day == 20
assert result.target_weeks == 9
assert result.recurrence == "yearly"
assert result.first_year == 2028
assert result.last_year == 2030
assert result.priority == 5
assert result.parent_mode == "specific"
assert result.parent_personal_data_uuid == seeded_ids.personal_data_uuid
assert result.goal_override == "balanced"
async def test_update_rejects_invalid_values(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id)
created = await leave_wish_repo.create(entity)
with pytest.raises(ValidationError):
await leave_wish_service.update(leave_wish_id=created.id, target_weeks=0)
async def test_update_can_clear_goal_override(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id, goal_override="max_time")
created = await leave_wish_repo.create(entity)
assert created.goal_override == "max_time"
result = await leave_wish_service.update(
leave_wish_id=created.id,
_clear_goal_override=True,
)
assert result.goal_override is None
async def test_update_can_clear_last_year(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id, last_year=2030)
created = await leave_wish_repo.create(entity)
result = await leave_wish_service.update(
leave_wish_id=created.id,
_clear_last_year=True,
)
assert result.last_year is None
async def test_update_can_clear_child_id(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id, child_id=seeded_ids.child_id)
created = await leave_wish_repo.create(entity)
result = await leave_wish_service.update(
leave_wish_id=created.id,
_clear_child_id=True,
)
assert result.child_id is None
async def test_update_can_clear_parent_personal_data_uuid(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(
workspace_id=seeded_ids.workspace_id,
parent_mode="specific",
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
)
created = await leave_wish_repo.create(entity)
result = await leave_wish_service.update(
leave_wish_id=created.id,
parent_mode="either",
_clear_parent_personal_data_uuid=True,
)
assert result.parent_mode == "either"
assert result.parent_personal_data_uuid is None
async def test_update_missing_raises(
self,
leave_wish_service: LeaveWishService,
) -> None:
with pytest.raises(NotFoundError):
await leave_wish_service.update(leave_wish_id=uuid4(), name="Name")
async def test_delete(
self,
leave_wish_service: LeaveWishService,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
entity = _make(workspace_id=seeded_ids.workspace_id)
created = await leave_wish_repo.create(entity)
await leave_wish_service.delete(created.id)
with pytest.raises(NotFoundError):
await leave_wish_service.get(created.id)
async def test_delete_missing_raises(
self,
leave_wish_service: LeaveWishService,
) -> None:
with pytest.raises(NotFoundError):
await leave_wish_service.delete(uuid4())