Files
F-r-ldraledigplannerare/backend/tests/unit/test_leave_wish_repository.py
T
jacobh2 68d982bb94 Make repository list() reject unrecognised filter names
Repository.list(**filters) let each implementation pick out the filter
names it understood and silently ignore the rest, so a typo or an
unsupported filter returned every row in the table instead of an error.

This was hit for real: LeaveWishRepository.list(parent_personal_data_uuid
=...) returns every wish in the database, because that implementation
only recognises workspace_id and child_id. A guard written on top of it
would have passed while checking nothing.

All ten implementations now validate against their supported set and
raise ValueError naming the offending key. ValueError rather than a
domain error because passing a filter that does not exist is a
programming bug to fix, not a runtime condition to handle -- it must not
become an HTTP 4xx.

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

932 lines
32 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
async def test_list_rejects_unsupported_filter(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
"""Passing an unsupported filter (like a typo) must raise ValueError, not return all rows."""
with pytest.raises(ValueError, match="parent_personal_data_uuid"):
await leave_wish_repo.list(parent_personal_data_uuid=seeded_ids.personal_data_uuid)
# Verify the error message lists what filters ARE supported
with pytest.raises(ValueError, match="child_id.*workspace_id"):
await leave_wish_repo.list(parent_personal_data_uuid=seeded_ids.personal_data_uuid)
async def test_list_with_supported_filter_still_works(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
"""Verify filter validation doesn't break existing filtering behavior."""
ws = seeded_ids.workspace_id
created_1 = await leave_wish_repo.create(_make(workspace_id=ws, name="W1"))
created_2 = await leave_wish_repo.create(_make(workspace_id=ws, name="W2"))
rows = await leave_wish_repo.list(workspace_id=ws)
assert len(rows) >= 2
ids = {r.id for r in rows}
assert created_1.id in ids
assert created_2.id in ids
async def test_list_rejects_multiple_unsupported_filters(
self,
leave_wish_repo: SQLiteLeaveWishRepository,
seeded_ids: SeededIds,
) -> None:
"""Error message should list all unsupported filters."""
with pytest.raises(ValueError, match="parent_personal_data_uuid.*typo_filter"):
await leave_wish_repo.list(
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
typo_filter="value",
)
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())