"""Integration tests for ``PATCH /auth/me/profile``. Covers the new self-service preferred_language endpoint added 2026-04-27. The endpoint is the source-of-truth for the user's UI / notification locale; the auth store reads it on every boot via /auth/me to seed ``i18n.changeLanguage``, and ``NotificationService`` consults it on every email + Feishu dispatch. Persistence is verified by re-issuing ``GET /auth/me`` rather than poking the test session directly — that way we exercise the same read path the frontend uses on boot, and we sidestep the cross-session greenlet quirks that arise when a test's db_session and the API request's session live on different async loops. """ from __future__ import annotations import pytest @pytest.mark.asyncio async def test_patch_profile_sets_preferred_language( client, employee_headers, ) -> None: resp = await client.patch( "/api/v1/auth/me/profile", headers=employee_headers, json={"preferred_language": "en_US"}, ) assert resp.status_code == 200, resp.text assert resp.json()["data"]["preferred_language"] == "en_US" # Re-fetch via /auth/me — same read path the frontend uses on # boot to seed i18n. Confirms the value survives across requests. me = await client.get("/api/v1/auth/me", headers=employee_headers) assert me.json()["data"]["preferred_language"] == "en_US" @pytest.mark.asyncio async def test_patch_profile_clear_with_empty_string( client, employee_headers, ) -> None: """Empty string is the documented sentinel for "use platform default". The endpoint must persist it as NULL so the NotificationService falls back to ``app.default_locale``.""" # Seed via PATCH to avoid the cross-session quirks; this is also # the path the frontend exercises. seed = await client.patch( "/api/v1/auth/me/profile", headers=employee_headers, json={"preferred_language": "zh_CN"}, ) assert seed.status_code == 200, seed.text resp = await client.patch( "/api/v1/auth/me/profile", headers=employee_headers, json={"preferred_language": ""}, ) assert resp.status_code == 200, resp.text assert resp.json()["data"]["preferred_language"] is None me = await client.get("/api/v1/auth/me", headers=employee_headers) assert me.json()["data"]["preferred_language"] is None @pytest.mark.asyncio async def test_patch_profile_rejects_unknown_locale( client, employee_headers, ) -> None: """Anything outside the ``zh_CN`` / ``en_US`` whitelist is a 422 — silently saving "fr" would render every notification in Chinese (anything not starting with "en" lands in the zh branch), which a French user would find very surprising.""" resp = await client.patch( "/api/v1/auth/me/profile", headers=employee_headers, json={"preferred_language": "fr_FR"}, ) assert resp.status_code == 422 body = resp.json() assert any( "PREFERRED_LANGUAGE_INVALID" in (e.get("code") or "") for e in body.get("errors") or [] ), body @pytest.mark.asyncio async def test_patch_profile_omitted_field_is_noop( client, employee_headers, ) -> None: """``{}`` body must not clear an existing preference — only explicitly passing the field counts as a write. This matches what the frontend does on initial form submit when the user only changed unrelated settings (when those land).""" seed = await client.patch( "/api/v1/auth/me/profile", headers=employee_headers, json={"preferred_language": "zh_CN"}, ) assert seed.status_code == 200, seed.text resp = await client.patch( "/api/v1/auth/me/profile", headers=employee_headers, json={}, ) assert resp.status_code == 200, resp.text assert resp.json()["data"]["preferred_language"] == "zh_CN" me = await client.get("/api/v1/auth/me", headers=employee_headers) assert me.json()["data"]["preferred_language"] == "zh_CN" @pytest.mark.asyncio async def test_get_me_returns_preferred_language( client, employee_headers, ) -> None: """/auth/me must return ``preferred_language`` so the frontend can seed i18n on app boot. Pre-2026-04-27 it was missing — that's why the language toggle on the profile page was in-memory only.""" await client.patch( "/api/v1/auth/me/profile", headers=employee_headers, json={"preferred_language": "en_US"}, ) resp = await client.get("/api/v1/auth/me", headers=employee_headers) assert resp.status_code == 200 data = resp.json()["data"] assert data["preferred_language"] == "en_US"