Show preset wish names in the current UI language
Preset wishes were created with `name: t(preset.labelKey)`, so the translated string was persisted as the wish's name. A wish created while the UI was Swedish stayed "Jul" in English forever -- the Swedish text was the data, so no display-layer change could fix it. Wish names are user-editable free text, so `name` cannot simply be translated at render time. Migration 007 adds a nullable `leave_wish.name_message_key` recording where a name came from: preset wishes store the i18n key alongside the translated literal, and the UI renders the key when present. The key is cleared server-side the moment `name` actually changes, compared against the stored value rather than trusting the caller, so no client can forget to clear it and silently revert a user's own name to a preset label. An unknown key (a stale row, a renamed key) falls back to the stored literal via an isTranslationKey guard, so a bad key can never crash the page or leak a raw key to the user. Rows predating this column keep displaying their stored name. _FULL_SCHEMA in db/connection.py is updated in step with the migration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL
This commit is contained in:
@@ -81,6 +81,7 @@ CREATE TABLE IF NOT EXISTS leave_wish (
|
||||
workspace_id TEXT NOT NULL,
|
||||
child_id TEXT,
|
||||
name TEXT NOT NULL,
|
||||
name_message_key TEXT,
|
||||
window_start_month INTEGER NOT NULL DEFAULT 1
|
||||
CHECK (window_start_month >= 1 AND window_start_month <= 12),
|
||||
window_start_day INTEGER NOT NULL DEFAULT 1
|
||||
|
||||
@@ -24,7 +24,7 @@ class LeaveWish:
|
||||
id: UUID
|
||||
workspace_id: UUID
|
||||
child_id: UUID | None # None = family-wide; set = tied to that child's pools
|
||||
name: str # "Sommar", "Jullov"
|
||||
name: str # "Sommar", "Jullov" — persisted, possibly-translated display text
|
||||
|
||||
# Recurring anchor stored as month/day ints (SQLite-friendly, no custom type).
|
||||
window_start_month: int # 1-12
|
||||
@@ -55,6 +55,16 @@ class LeaveWish:
|
||||
# completely unmodified.
|
||||
window_mode: str = "fixed" # 'fixed' | 'flexible'
|
||||
|
||||
# Provenance of `name`: when a wish is created from a preset (Sommar,
|
||||
# Jullov, Höstlov, ...) this holds the i18n message key that produced the
|
||||
# translated string stored in `name` (e.g. "wishEditor.presetSommar").
|
||||
# Nullable — free-typed names, and every row created before this field
|
||||
# existed, have no key and just show `name` verbatim. Cleared by
|
||||
# `LeaveWishService.update` the moment the user edits `name`, since a
|
||||
# user-typed name is no longer a preset label and must never silently
|
||||
# revert to one.
|
||||
name_message_key: str | None = None
|
||||
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ class LeaveWishCreate(BaseModel):
|
||||
workspace_id: UUID
|
||||
child_id: UUID | None = None
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
# i18n message key when `name` came from a preset (e.g.
|
||||
# "wishEditor.presetSommar") — see app/entities/leave_wish.py. Stored
|
||||
# alongside the translated `name` so the frontend can re-translate it;
|
||||
# not validated against a key catalogue here (that's a frontend concern).
|
||||
name_message_key: str | None = Field(default=None, max_length=255)
|
||||
|
||||
window_start_month: int = Field(ge=1, le=12)
|
||||
window_start_day: int = Field(ge=1, le=31)
|
||||
@@ -46,6 +51,11 @@ class LeaveWishUpdate(BaseModel):
|
||||
|
||||
child_id: UUID | None = None
|
||||
name: str | None = Field(None, min_length=1, max_length=255)
|
||||
# Present + null in the payload means "clear it"; absent means "leave
|
||||
# unchanged" (see `model_fields_set` handling in the router). The service
|
||||
# also clears this automatically whenever `name` actually changes, so a
|
||||
# caller can never forget to and leave a stale key pointing at old text.
|
||||
name_message_key: str | None = Field(default=None, max_length=255)
|
||||
|
||||
window_start_month: int | None = Field(None, ge=1, le=12)
|
||||
window_start_day: int | None = Field(None, ge=1, le=31)
|
||||
@@ -71,6 +81,7 @@ class LeaveWishResponse(BaseModel):
|
||||
workspace_id: UUID
|
||||
child_id: UUID | None
|
||||
name: str
|
||||
name_message_key: str | None
|
||||
|
||||
window_start_month: int
|
||||
window_start_day: int
|
||||
@@ -100,6 +111,7 @@ class LeaveWishResponse(BaseModel):
|
||||
workspace_id=entity.workspace_id,
|
||||
child_id=entity.child_id,
|
||||
name=entity.name,
|
||||
name_message_key=entity.name_message_key,
|
||||
window_start_month=entity.window_start_month,
|
||||
window_start_day=entity.window_start_day,
|
||||
window_end_month=entity.window_end_month,
|
||||
|
||||
@@ -26,6 +26,7 @@ class SQLiteLeaveWishRepository(LeaveWishRepository):
|
||||
workspace_id=UUID(row["workspace_id"]),
|
||||
child_id=UUID(row["child_id"]) if row["child_id"] is not None else None,
|
||||
name=row["name"],
|
||||
name_message_key=row["name_message_key"],
|
||||
window_start_month=row["window_start_month"],
|
||||
window_start_day=row["window_start_day"],
|
||||
window_end_month=row["window_end_month"],
|
||||
@@ -54,18 +55,19 @@ class SQLiteLeaveWishRepository(LeaveWishRepository):
|
||||
await self._conn.execute(
|
||||
"""
|
||||
INSERT INTO leave_wish
|
||||
(id, workspace_id, child_id, name,
|
||||
(id, workspace_id, child_id, name, name_message_key,
|
||||
window_start_month, window_start_day, window_end_month, window_end_day,
|
||||
target_weeks, recurrence, first_year, last_year,
|
||||
priority, parent_mode, parent_personal_data_uuid, goal_override,
|
||||
window_mode, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
str(item.id),
|
||||
str(item.workspace_id),
|
||||
str(item.child_id) if item.child_id is not None else None,
|
||||
item.name,
|
||||
item.name_message_key,
|
||||
item.window_start_month,
|
||||
item.window_start_day,
|
||||
item.window_end_month,
|
||||
@@ -109,7 +111,7 @@ class SQLiteLeaveWishRepository(LeaveWishRepository):
|
||||
cursor = await self._conn.execute(
|
||||
"""
|
||||
UPDATE leave_wish
|
||||
SET child_id = ?, name = ?,
|
||||
SET child_id = ?, name = ?, name_message_key = ?,
|
||||
window_start_month = ?, window_start_day = ?,
|
||||
window_end_month = ?, window_end_day = ?,
|
||||
target_weeks = ?, recurrence = ?, first_year = ?, last_year = ?,
|
||||
@@ -120,6 +122,7 @@ class SQLiteLeaveWishRepository(LeaveWishRepository):
|
||||
(
|
||||
str(item.child_id) if item.child_id is not None else None,
|
||||
item.name,
|
||||
item.name_message_key,
|
||||
item.window_start_month,
|
||||
item.window_start_day,
|
||||
item.window_end_month,
|
||||
|
||||
@@ -29,6 +29,7 @@ async def create_leave_wish(
|
||||
workspace_id=payload.workspace_id,
|
||||
child_id=payload.child_id,
|
||||
name=payload.name,
|
||||
name_message_key=payload.name_message_key,
|
||||
window_start_month=payload.window_start_month,
|
||||
window_start_day=payload.window_start_day,
|
||||
window_end_month=payload.window_end_month,
|
||||
@@ -70,6 +71,7 @@ async def update_leave_wish(
|
||||
leave_wish_id=leave_wish_id,
|
||||
child_id=payload.child_id,
|
||||
name=payload.name,
|
||||
name_message_key=payload.name_message_key,
|
||||
window_start_month=payload.window_start_month,
|
||||
window_start_day=payload.window_start_day,
|
||||
window_end_month=payload.window_end_month,
|
||||
@@ -90,6 +92,9 @@ async def update_leave_wish(
|
||||
and payload.parent_personal_data_uuid is None
|
||||
),
|
||||
_clear_goal_override=("goal_override" in fields_set and payload.goal_override is None),
|
||||
_clear_name_message_key=(
|
||||
"name_message_key" in fields_set and payload.name_message_key is None
|
||||
),
|
||||
)
|
||||
except ValidationError as exc:
|
||||
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, str(exc)) from exc
|
||||
|
||||
@@ -37,6 +37,7 @@ class LeaveWishService:
|
||||
target_weeks: int,
|
||||
first_year: int,
|
||||
child_id: UUID | None = None,
|
||||
name_message_key: str | None = None,
|
||||
recurrence: str = "once",
|
||||
last_year: int | None = None,
|
||||
priority: int = 1,
|
||||
@@ -65,6 +66,7 @@ class LeaveWishService:
|
||||
workspace_id=workspace_id,
|
||||
child_id=child_id,
|
||||
name=name,
|
||||
name_message_key=name_message_key,
|
||||
window_start_month=window_start_month,
|
||||
window_start_day=window_start_day,
|
||||
window_end_month=window_end_month,
|
||||
@@ -97,6 +99,7 @@ class LeaveWishService:
|
||||
leave_wish_id: UUID,
|
||||
child_id: UUID | None = None,
|
||||
name: str | None = None,
|
||||
name_message_key: str | None = None,
|
||||
window_start_month: int | None = None,
|
||||
window_start_day: int | None = None,
|
||||
window_end_month: int | None = None,
|
||||
@@ -114,21 +117,35 @@ class LeaveWishService:
|
||||
_clear_last_year: bool = False,
|
||||
_clear_parent_personal_data_uuid: bool = False,
|
||||
_clear_goal_override: bool = False,
|
||||
_clear_name_message_key: bool = False,
|
||||
) -> LeaveWish:
|
||||
"""Partial update — only non-None fields are applied.
|
||||
|
||||
`_clear_*` flags exist because a nullable field's "no change" and
|
||||
"set to None" both look like `None` on the wire; the router passes
|
||||
them based on whether the field was present in the request payload.
|
||||
|
||||
`name_message_key` is additionally enforced here rather than left to
|
||||
callers: whenever `name` actually changes, the key is force-cleared
|
||||
regardless of what was passed for it, since a caller (frontend bug,
|
||||
future API client, ...) forgetting to clear it would silently make a
|
||||
user's edited name revert to a stale preset label.
|
||||
"""
|
||||
entity = await self.get(leave_wish_id)
|
||||
original_name = entity.name
|
||||
|
||||
if _clear_child_id:
|
||||
entity.child_id = None
|
||||
elif child_id is not None:
|
||||
entity.child_id = child_id
|
||||
if _clear_name_message_key:
|
||||
entity.name_message_key = None
|
||||
elif name_message_key is not None:
|
||||
entity.name_message_key = name_message_key
|
||||
if name is not None:
|
||||
entity.name = name
|
||||
if name != original_name:
|
||||
entity.name_message_key = None
|
||||
if window_start_month is not None:
|
||||
entity.window_start_month = window_start_month
|
||||
if window_start_day is not None:
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Add leave_wish.name_message_key (nullable i18n key for preset names).
|
||||
|
||||
Bug: `WishEditor.tsx` created preset wishes (Sommar/Jullov/Höstlov/...) with
|
||||
`name` set to the already-translated string (e.g. "Jul"), persisted as-is.
|
||||
Switching the UI language afterwards could never fix a wish's name, because
|
||||
the Swedish (or English) text was the data, not a rendering choice.
|
||||
|
||||
Fix: `name_message_key` stores the i18n key that produced `name` at creation
|
||||
time (e.g. "wishEditor.seasonJul"), alongside the translated `name` so
|
||||
anything reading `name` directly (backend, exports, ...) still gets a sane
|
||||
value. The frontend re-translates via the key when present and falls back to
|
||||
the literal `name` otherwise. Nullable and no default: every existing row
|
||||
(and any row created by a free-typed custom name) has no key and must keep
|
||||
showing its stored `name` unchanged — see `LeaveWishService.update`, which
|
||||
also force-clears the key the moment a user edits `name`.
|
||||
|
||||
Revision ID: 007
|
||||
Revises: 006
|
||||
Create Date: 2026-09-14
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "007"
|
||||
down_revision: str | None = "006"
|
||||
branch_labels: str | None = None
|
||||
depends_on: str | None = None
|
||||
|
||||
_UPGRADE_SQL = """
|
||||
ALTER TABLE leave_wish ADD COLUMN name_message_key TEXT;
|
||||
"""
|
||||
|
||||
_DOWNGRADE_SQL = """
|
||||
ALTER TABLE leave_wish DROP COLUMN name_message_key;
|
||||
"""
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
from sqlalchemy import text
|
||||
|
||||
for stmt in [s.strip() for s in _UPGRADE_SQL.split(";") if s.strip()]:
|
||||
op.execute(text(stmt))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
from sqlalchemy import text
|
||||
|
||||
for stmt in [s.strip() for s in _DOWNGRADE_SQL.split(";") if s.strip()]:
|
||||
op.execute(text(stmt))
|
||||
@@ -823,3 +823,81 @@ class TestWorkspaceParentsRenameAndRemove:
|
||||
# The blocked placeholder and the caller are both still members.
|
||||
assert placeholder_uuid in member_uuids
|
||||
assert str(pd_uuid) in member_uuids
|
||||
|
||||
|
||||
class TestLeaveWishNameMessageKeyRoundTrip:
|
||||
"""`name_message_key` (added to fix the "Jul"/"Höstlov" always-Swedish
|
||||
|
||||
bug — see `backend/app/entities/leave_wish.py`) must actually appear on
|
||||
the wire. The hand-written entity<->response mapping in this codebase
|
||||
has silently dropped new fields twice before, so this asserts the
|
||||
response *body* directly rather than only the service/repository layer.
|
||||
|
||||
All four scenarios share a single registered user/workspace (one
|
||||
`_register` call) rather than one each: `/api/auth/register` is rate
|
||||
limited to 10/minute (see `app/routers/auth.py`), and this file already
|
||||
registers a user per test class — four more registrations pushed the
|
||||
whole module over that limit when run together.
|
||||
"""
|
||||
|
||||
async def test_name_message_key_round_trip(
|
||||
self, client: AsyncClient, connection: aiosqlite.Connection
|
||||
) -> None:
|
||||
headers, ws_id, _ = await _register(client, "wish-key-roundtrip@example.com", "Alex")
|
||||
|
||||
# --- create returns the key it was given -----------------------
|
||||
wish = await _create_wish(
|
||||
client,
|
||||
headers,
|
||||
workspace_id=str(ws_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,
|
||||
recurrence="yearly",
|
||||
first_year=date.today().year,
|
||||
)
|
||||
assert wish["name_message_key"] == "wishEditor.seasonJul"
|
||||
assert wish["name"] == "Jul"
|
||||
|
||||
# --- a wish created without a key round-trips null, not a crash -
|
||||
# (pre-existing rows, or a free-typed custom name, have no key.)
|
||||
keyless_wish = await _create_wish(
|
||||
client,
|
||||
headers,
|
||||
workspace_id=str(ws_id),
|
||||
name="Custom trip",
|
||||
window_start_month=6,
|
||||
window_start_day=1,
|
||||
window_end_month=6,
|
||||
window_end_day=7,
|
||||
target_weeks=1,
|
||||
recurrence="once",
|
||||
first_year=date.today().year,
|
||||
)
|
||||
assert keyless_wish["name_message_key"] is None
|
||||
|
||||
# --- updating an unrelated field preserves the key --------------
|
||||
resp = await client.put(
|
||||
f"/api/leave-wishes/{wish['id']}", headers=headers, json={"target_weeks": 5}
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
body = resp.json()
|
||||
assert body["target_weeks"] == 5
|
||||
assert body["name"] == "Jul"
|
||||
assert body["name_message_key"] == "wishEditor.seasonJul"
|
||||
|
||||
# --- renaming clears the key even if the caller never mentions --
|
||||
# it: the backend enforces this, not just the frontend. Getting
|
||||
# this backwards would mean a user's custom name silently reverts
|
||||
# to the old preset label the next time the UI translates it.
|
||||
resp = await client.put(
|
||||
f"/api/leave-wishes/{wish['id']}", headers=headers, json={"name": "Vinterresa"}
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
body = resp.json()
|
||||
assert body["name"] == "Vinterresa"
|
||||
assert body["name_message_key"] is None
|
||||
|
||||
@@ -133,6 +133,57 @@ class TestSQLiteLeaveWishRepository:
|
||||
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,
|
||||
@@ -902,6 +953,105 @@ class TestLeaveWishService:
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user