ac3b6a41d7
Deleting a period left every day it had funded behind. The link from a day back to its period was set to null the instant the period was deleted, so the days became untraceable before anything could clean them up, and the next recalculation had no way to know they no longer belonged to anything. Undo had the same fault for the same reason: the period went, the days stayed, and a reload showed them still there. The days are now removed with the period, before the link can be broken. A day pinned by hand goes too — pinning keeps the planner from quietly retyping a day, not from being deleted when you delete the period it belongs to, which is already how an unplanned band behaves.
1214 lines
44 KiB
Python
1214 lines
44 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 UUID, uuid4
|
|
|
|
import pytest
|
|
|
|
from app.entities.leave_wish import LeaveWish
|
|
from app.entities.planned_leave import PlannedLeaveDay
|
|
from app.entities.workspace import Workspace
|
|
from app.repositories_impl.sqlite.leave_wish_repository import SQLiteLeaveWishRepository
|
|
from app.repositories_impl.sqlite.planned_leave_repository import SQLitePlannedLeaveRepository
|
|
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,
|
|
target_days=None,
|
|
recurrence="yearly",
|
|
first_year=2027,
|
|
last_year=None,
|
|
priority=1,
|
|
parent_mode="either",
|
|
parent_personal_data_uuid=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
|
|
|
|
# -- target_span_days() (calendar-drawn periods, day-granular) ---------
|
|
|
|
def test_target_span_days_falls_back_to_weeks_times_seven_when_unset(self) -> None:
|
|
wish = _make(target_weeks=6, target_days=None)
|
|
assert wish.target_span_days() == 42
|
|
|
|
def test_target_span_days_prefers_target_days_when_set(self) -> None:
|
|
# A calendar-drawn 10-day band, even though target_weeks still holds
|
|
# a stale/unrelated whole-week value.
|
|
wish = _make(target_weeks=6, target_days=10)
|
|
assert wish.target_span_days() == 10
|
|
|
|
|
|
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
|
|
|
|
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_and_get_round_trip_name_message_key(
|
|
self,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
entity = _make(
|
|
workspace_id=seeded_ids.workspace_id,
|
|
name="Jul",
|
|
name_message_key="wishEditor.seasonJul",
|
|
)
|
|
created = await leave_wish_repo.create(entity)
|
|
assert created.name_message_key == "wishEditor.seasonJul"
|
|
|
|
fetched = await leave_wish_repo.get_by_id(created.id)
|
|
assert fetched is not None
|
|
assert fetched.name_message_key == "wishEditor.seasonJul"
|
|
|
|
async def test_create_without_name_message_key_defaults_to_none(
|
|
self,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
"""Existing-row simulation: a wish created before this field existed
|
|
|
|
(or a free-typed custom name) has no key and must read back `None`,
|
|
not a synthesized or empty-string value.
|
|
"""
|
|
entity = _make(workspace_id=seeded_ids.workspace_id, name="Custom trip")
|
|
created = await leave_wish_repo.create(entity)
|
|
assert created.name_message_key is None
|
|
|
|
fetched = await leave_wish_repo.get_by_id(created.id)
|
|
assert fetched is not None
|
|
assert fetched.name_message_key is None
|
|
|
|
async def test_update_round_trips_name_message_key(
|
|
self,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
entity = _make(workspace_id=seeded_ids.workspace_id, name="Jul", name_message_key=None)
|
|
created = await leave_wish_repo.create(entity)
|
|
|
|
created.name_message_key = "wishEditor.seasonJul"
|
|
updated = await leave_wish_repo.update(created.id, created)
|
|
assert updated.name_message_key == "wishEditor.seasonJul"
|
|
|
|
fetched = await leave_wish_repo.get_by_id(created.id)
|
|
assert fetched is not None
|
|
assert fetched.name_message_key == "wishEditor.seasonJul"
|
|
|
|
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,
|
|
)
|
|
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
|
|
|
|
# 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
|
|
|
|
async def test_create_and_update_round_trip_target_days(
|
|
self,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
entity = _make(workspace_id=seeded_ids.workspace_id, target_weeks=6, target_days=10)
|
|
created = await leave_wish_repo.create(entity)
|
|
assert created.target_days == 10
|
|
|
|
fetched = await leave_wish_repo.get_by_id(created.id)
|
|
assert fetched is not None
|
|
assert fetched.target_days == 10
|
|
|
|
fetched.target_days = None
|
|
updated = await leave_wish_repo.update(fetched.id, fetched)
|
|
assert updated.target_days is None
|
|
|
|
refetched = await leave_wish_repo.get_by_id(created.id)
|
|
assert refetched is not None
|
|
assert refetched.target_days is None
|
|
|
|
async def test_create_without_target_days_defaults_to_none(
|
|
self,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
entity = _make(workspace_id=seeded_ids.workspace_id, target_days=None)
|
|
created = await leave_wish_repo.create(entity)
|
|
assert created.target_days is None
|
|
|
|
fetched = await leave_wish_repo.get_by_id(created.id)
|
|
assert fetched is not None
|
|
assert fetched.target_days is None
|
|
|
|
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
|
|
|
|
# Placeholder resolved to seeded_ids.personal_data_uuid at test time, since
|
|
# parametrize case values are collected before fixtures exist.
|
|
_MISSING_UUID = object()
|
|
|
|
_REJECTION_BASE_KWARGS: dict[str, object] = dict(
|
|
name="Wish",
|
|
window_start_month=1,
|
|
window_start_day=1,
|
|
window_end_month=1,
|
|
window_end_day=10,
|
|
target_weeks=1,
|
|
first_year=2027,
|
|
)
|
|
|
|
_REJECTION_CASES = [
|
|
pytest.param({"recurrence": "monthly"}, "recurrence", id="invalid_recurrence"),
|
|
pytest.param({"parent_mode": "everyone"}, "parent_mode", id="invalid_parent_mode"),
|
|
pytest.param(
|
|
{"parent_mode": "either", "parent_personal_data_uuid": _MISSING_UUID},
|
|
"parent_personal_data_uuid",
|
|
id="parent_uuid_forbidden_unless_specific",
|
|
),
|
|
pytest.param({"window_mode": "whenever"}, "window_mode", id="invalid_window_mode"),
|
|
pytest.param(
|
|
{
|
|
"window_start_month": 3,
|
|
"window_start_day": 1,
|
|
"window_end_month": 3,
|
|
"window_end_day": 10,
|
|
"target_weeks": 2,
|
|
"window_mode": "flexible",
|
|
},
|
|
"flexible",
|
|
id="flexible_bound_shorter_than_target_weeks",
|
|
),
|
|
*[
|
|
pytest.param(
|
|
{
|
|
"window_start_month": month,
|
|
"window_start_day": day,
|
|
"window_end_month": 6,
|
|
"window_end_day": 20,
|
|
},
|
|
None,
|
|
id=f"invalid_month_day_{month}_{day}",
|
|
)
|
|
for month, day in [(0, 15), (13, 15), (6, 0), (6, 32), (2, 30), (4, 31)]
|
|
],
|
|
pytest.param({"target_days": 0}, "target_days", id="target_days_below_one"),
|
|
pytest.param({"target_weeks": 0}, "target_weeks", id="zero_target_weeks"),
|
|
pytest.param(
|
|
{"first_year": 2030, "last_year": 2029},
|
|
"last_year",
|
|
id="last_year_before_first_year",
|
|
),
|
|
]
|
|
|
|
@pytest.mark.parametrize("overrides,match", _REJECTION_CASES)
|
|
async def test_create_rejects_invalid_input(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
seeded_ids: SeededIds,
|
|
overrides: dict[str, object],
|
|
match: str | None,
|
|
) -> None:
|
|
kwargs = {**self._REJECTION_BASE_KWARGS, **overrides}
|
|
if kwargs.get("parent_personal_data_uuid") is self._MISSING_UUID:
|
|
kwargs["parent_personal_data_uuid"] = seeded_ids.personal_data_uuid
|
|
|
|
with pytest.raises(ValidationError, match=match):
|
|
await leave_wish_service.create(workspace_id=seeded_ids.workspace_id, **kwargs)
|
|
|
|
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
|
|
|
|
# -- 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_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_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")
|
|
|
|
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
|
|
|
|
# -- target_days (calendar-drawn periods, day-granular) ----------------
|
|
|
|
async def test_create_with_target_days_persists_it(
|
|
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=31,
|
|
target_weeks=1,
|
|
target_days=10,
|
|
first_year=2027,
|
|
)
|
|
assert result.target_days == 10
|
|
assert result.target_span_days() == 10
|
|
|
|
async def test_create_without_target_days_defaults_to_none(
|
|
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=2,
|
|
first_year=2027,
|
|
)
|
|
assert result.target_days is None
|
|
assert result.target_span_days() == 14
|
|
|
|
async def test_create_flexible_wish_sized_by_target_days_rejects_short_bound(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
# Bound = 2027-03-01 -> 2027-03-10 (10 days); target_days=11 doesn't
|
|
# fit even though target_weeks=1 (7 days) would. target_days must be
|
|
# the value actually checked, not silently ignored in favor of weeks.
|
|
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=1,
|
|
target_days=11,
|
|
first_year=2027,
|
|
window_mode="flexible",
|
|
)
|
|
|
|
async def test_create_flexible_wish_sized_by_target_days_accepts_matching_bound(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
# Same 10-day bound, target_days=10 fits exactly.
|
|
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=3,
|
|
window_end_day=10,
|
|
target_weeks=1,
|
|
target_days=10,
|
|
first_year=2027,
|
|
window_mode="flexible",
|
|
)
|
|
assert result.target_days == 10
|
|
|
|
async def test_update_can_set_target_days(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
entity = _make(workspace_id=seeded_ids.workspace_id, target_days=None)
|
|
created = await leave_wish_repo.create(entity)
|
|
|
|
result = await leave_wish_service.update(leave_wish_id=created.id, target_days=10)
|
|
assert result.target_days == 10
|
|
|
|
async def test_update_can_clear_target_days(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
entity = _make(workspace_id=seeded_ids.workspace_id, target_days=10)
|
|
created = await leave_wish_repo.create(entity)
|
|
assert created.target_days == 10
|
|
|
|
result = await leave_wish_service.update(leave_wish_id=created.id, _clear_target_days=True)
|
|
assert result.target_days is None
|
|
|
|
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,
|
|
)
|
|
|
|
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
|
|
|
|
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_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
|
|
|
|
# -- name_message_key (fixes the "Jul"/"Höstlov" always-Swedish bug) ---
|
|
|
|
async def test_create_passes_through_name_message_key(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
result = await leave_wish_service.create(
|
|
workspace_id=seeded_ids.workspace_id,
|
|
name="Jul",
|
|
name_message_key="wishEditor.seasonJul",
|
|
window_start_month=12,
|
|
window_start_day=1,
|
|
window_end_month=1,
|
|
window_end_day=31,
|
|
target_weeks=4,
|
|
first_year=2027,
|
|
)
|
|
assert result.name_message_key == "wishEditor.seasonJul"
|
|
|
|
async def test_update_without_changing_name_preserves_name_message_key(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
entity = _make(
|
|
workspace_id=seeded_ids.workspace_id,
|
|
name="Jul",
|
|
name_message_key="wishEditor.seasonJul",
|
|
)
|
|
created = await leave_wish_repo.create(entity)
|
|
|
|
result = await leave_wish_service.update(leave_wish_id=created.id, target_weeks=5)
|
|
assert result.name_message_key == "wishEditor.seasonJul"
|
|
assert result.name == "Jul"
|
|
|
|
async def test_update_that_resends_the_same_name_preserves_name_message_key(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
"""The auto-save flow resends the unchanged `name` on every edit —
|
|
|
|
only an *actual* change to the text may clear the key.
|
|
"""
|
|
entity = _make(
|
|
workspace_id=seeded_ids.workspace_id,
|
|
name="Jul",
|
|
name_message_key="wishEditor.seasonJul",
|
|
)
|
|
created = await leave_wish_repo.create(entity)
|
|
|
|
result = await leave_wish_service.update(leave_wish_id=created.id, name="Jul")
|
|
assert result.name_message_key == "wishEditor.seasonJul"
|
|
|
|
async def test_update_renaming_clears_name_message_key_even_without_being_told_to(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
"""Backend enforcement, not caller-trust: renaming clears the key
|
|
|
|
even if the caller never mentions `name_message_key` at all — this
|
|
is the critical guard against a user's custom name silently
|
|
reverting to a stale preset label.
|
|
"""
|
|
entity = _make(
|
|
workspace_id=seeded_ids.workspace_id,
|
|
name="Jul",
|
|
name_message_key="wishEditor.seasonJul",
|
|
)
|
|
created = await leave_wish_repo.create(entity)
|
|
|
|
result = await leave_wish_service.update(leave_wish_id=created.id, name="Vinterresa")
|
|
assert result.name == "Vinterresa"
|
|
assert result.name_message_key is None
|
|
|
|
async def test_update_can_explicitly_clear_name_message_key(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
entity = _make(
|
|
workspace_id=seeded_ids.workspace_id,
|
|
name="Jul",
|
|
name_message_key="wishEditor.seasonJul",
|
|
)
|
|
created = await leave_wish_repo.create(entity)
|
|
|
|
result = await leave_wish_service.update(
|
|
leave_wish_id=created.id, _clear_name_message_key=True
|
|
)
|
|
assert result.name_message_key is None
|
|
assert result.name == "Jul" # unrelated to the clear
|
|
|
|
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())
|
|
|
|
|
|
class TestLeaveWishServiceDeleteCascade:
|
|
"""`LeaveWishService.delete()` must remove the `planned_leave_day` rows
|
|
|
|
the wish produced, not just null their `wish_id` out from under them
|
|
(the schema's `ON DELETE SET NULL` would otherwise leave them behind as
|
|
permanent, unattributable ad-hoc rows — see the docstring on
|
|
`LeaveWishService.delete()` and `PlannedLeaveRepository.delete_by_wish`).
|
|
"""
|
|
|
|
def _make_day(
|
|
self,
|
|
seeded_ids: SeededIds,
|
|
*,
|
|
wish_id: UUID | None = None,
|
|
is_pinned: bool = False,
|
|
day: str = "2027-02-03",
|
|
) -> PlannedLeaveDay:
|
|
return PlannedLeaveDay(
|
|
id=uuid4(),
|
|
workspace_id=seeded_ids.workspace_id,
|
|
child_id=seeded_ids.child_id,
|
|
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
|
|
date=date.fromisoformat(day),
|
|
day_type="sjukpenning",
|
|
is_dubbeldag=False,
|
|
wish_id=wish_id,
|
|
is_pinned=is_pinned,
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
async def test_delete_removes_the_planned_leave_days_it_produced(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
wish = await leave_wish_repo.create(_make(workspace_id=seeded_ids.workspace_id))
|
|
day1 = await planned_leave_repo.create(
|
|
self._make_day(seeded_ids, wish_id=wish.id, day="2027-02-03")
|
|
)
|
|
day2 = await planned_leave_repo.create(
|
|
self._make_day(seeded_ids, wish_id=wish.id, day="2027-02-04")
|
|
)
|
|
|
|
await leave_wish_service.delete(wish.id)
|
|
|
|
assert await planned_leave_repo.get_by_id(day1.id) is None
|
|
assert await planned_leave_repo.get_by_id(day2.id) is None
|
|
|
|
async def test_delete_removes_a_pinned_day_too(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
"""A pinned day dies with its wish rather than surviving as ad-hoc.
|
|
|
|
Pinning only protects a day from being silently retyped/reassigned
|
|
by a later `sync_plan` re-plan — it is not a signal that the day
|
|
should outlive a period the user explicitly chose to delete. This
|
|
also matches the ad-hoc-period delete path in `Calendar.tsx`
|
|
(`handleDeletePeriod`'s no-`wishId` branch), which already deletes
|
|
every day in the band regardless of `is_pinned`.
|
|
"""
|
|
wish = await leave_wish_repo.create(_make(workspace_id=seeded_ids.workspace_id))
|
|
pinned = await planned_leave_repo.create(
|
|
self._make_day(seeded_ids, wish_id=wish.id, is_pinned=True)
|
|
)
|
|
|
|
await leave_wish_service.delete(wish.id)
|
|
|
|
assert await planned_leave_repo.get_by_id(pinned.id) is None
|
|
|
|
async def test_delete_leaves_a_different_wishs_days_untouched(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
wish_a = await leave_wish_repo.create(_make(workspace_id=seeded_ids.workspace_id, name="A"))
|
|
wish_b = await leave_wish_repo.create(_make(workspace_id=seeded_ids.workspace_id, name="B"))
|
|
day_a = await planned_leave_repo.create(
|
|
self._make_day(seeded_ids, wish_id=wish_a.id, day="2027-02-03")
|
|
)
|
|
day_b = await planned_leave_repo.create(
|
|
self._make_day(seeded_ids, wish_id=wish_b.id, day="2027-02-04")
|
|
)
|
|
|
|
await leave_wish_service.delete(wish_a.id)
|
|
|
|
assert await planned_leave_repo.get_by_id(day_a.id) is None
|
|
survivor = await planned_leave_repo.get_by_id(day_b.id)
|
|
assert survivor is not None
|
|
assert survivor.wish_id == wish_b.id
|
|
|
|
async def test_delete_leaves_genuine_ad_hoc_days_untouched(
|
|
self,
|
|
leave_wish_service: LeaveWishService,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
wish = await leave_wish_repo.create(_make(workspace_id=seeded_ids.workspace_id))
|
|
ad_hoc = await planned_leave_repo.create(
|
|
self._make_day(seeded_ids, wish_id=None, day="2027-02-03")
|
|
)
|
|
|
|
await leave_wish_service.delete(wish.id)
|
|
|
|
survivor = await planned_leave_repo.get_by_id(ad_hoc.id)
|
|
assert survivor is not None
|
|
assert survivor.wish_id is None
|