"""GET/PATCH /auth/me — the interface language choice and what it resolves to.
The override lives in its own column, so a user who never touched the picker
must still get a usable language (the account's), and "auto" must put them back
there. The endpoint pair is stubbed at the DB boundary — the SQL itself is
covered by tests/unit/auth/test_deps.py.
"""
import pytest
from fastapi.testclient import TestClient
import navi.api.routes.auth as auth_routes
from navi.auth import User
from navi.auth.deps import require_user
@pytest.fixture
def api(monkeypatch):
"""TestClient over a signed-in user (account language: uk) with a stubbed store."""
from navi.main import app
user = User(id="u1", email="u1@navi.test", display_name="U", locale="uk")
app.dependency_overrides[require_user] = lambda: user
stored: dict[str, str | None] = {"value": None}
written: list[tuple[str, str | None]] = []
async def _get(_user_id):
return stored["value"]
async def _set(user_id, value):
written.append((user_id, value))
stored["value"] = value
monkeypatch.setattr(auth_routes, "get_locale_override", _get)
monkeypatch.setattr(auth_routes, "set_locale_override", _set)
try:
yield TestClient(app), stored, written
finally:
app.dependency_overrides.pop(require_user, None)
def test_me_follows_the_account_language_when_nothing_was_chosen(api):
client, _stored, _written = api
body = client.get("/auth/me").json()
assert body["locale"] is None
assert body["locale_effective"] == "uk"
# The rest of the profile is unchanged by the new fields.
assert body["id"] == "u1"
assert body["email"] == "u1@navi.test"
assert "profile_url" in body
def test_me_reports_the_chosen_language(api):
client, stored, _written = api
stored["value"] = "ru"
body = client.get("/auth/me").json()
assert body["locale"] == "ru"
assert body["locale_effective"] == "ru"
def test_me_falls_back_to_english_for_an_unknown_account_tag(monkeypatch):
"""Accounts can carry tags Navi has no dictionary for — the interface must
still come up in a language it can speak."""
from navi.main import app
app.dependency_overrides[require_user] = lambda: User(id="u2", email="u2@navi.test", locale="de")
async def _no_override(_user_id):
return None
monkeypatch.setattr(auth_routes, "get_locale_override", _no_override)
try:
body = TestClient(app).get("/auth/me").json()
finally:
app.dependency_overrides.pop(require_user, None)
assert body["locale_effective"] == "en"
def test_patch_stores_the_choice_and_answers_like_me(api):
client, _stored, written = api
resp = client.patch("/auth/me", json={"locale": "ru"})
assert resp.status_code == 200
assert written == [("u1", "ru")]
assert resp.json()["locale"] == "ru"
assert resp.json()["locale_effective"] == "ru"
@pytest.mark.parametrize("payload", [{"locale": "auto"}, {"locale": ""}, {}])
def test_patch_clears_the_choice_back_to_the_account(api, payload):
"""Empty and "auto" are the same value: follow the account again."""
client, stored, written = api
stored["value"] = "ru"
resp = client.patch("/auth/me", json=payload)
assert resp.status_code == 200
assert written == [("u1", None)]
assert resp.json()["locale"] is None
assert resp.json()["locale_effective"] == "uk"
def test_patch_normalizes_a_regional_tag(api):
client, _stored, written = api
client.patch("/auth/me", json={"locale": "en-US"})
assert written == [("u1", "en")]
def test_patch_refuses_a_language_we_do_not_ship(api):
client, _stored, written = api
resp = client.patch("/auth/me", json={"locale": "de"})
assert resp.status_code == 422
assert written == []
def test_patch_reports_a_failed_write(api, monkeypatch):
"""A choice that cannot be stored must not be reported as saved."""
client, _stored, _written = api
async def _boom(_user_id, _value):
raise RuntimeError("column is gone")
monkeypatch.setattr(auth_routes, "set_locale_override", _boom)
assert client.patch("/auth/me", json={"locale": "ru"}).status_code == 503