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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user