MicroFish/backend/tests/test_locale.py

105 lines
3.7 KiB
Python

"""Unit tests for ``app.utils.locale``.
Covers the missing-key warning behavior introduced for ticket #6:
- Resolving a known key returns the translated value.
- Active locale falls back to ``zh`` when a key is only defined there.
- A missing key returns the raw key string and never raises.
- Each missing ``(locale, key)`` pair emits exactly one warning across the
process lifetime (deduplicated).
- The private ``_reset_missing_key_cache`` hook clears the dedup memoization
so successive tests can re-assert the warning behavior.
"""
import logging
import os
import pytest
from tests.conftest import load_module_directly
LOCALE_PATH = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"app",
"utils",
"locale.py",
)
locale_module = load_module_directly("mirofish_locale_under_test", LOCALE_PATH)
_reset_missing_key_cache = locale_module._reset_missing_key_cache
set_locale = locale_module.set_locale
t = locale_module.t
@pytest.fixture(autouse=True)
def _clear_dedup_cache():
"""Reset the missing-key dedup cache around every test."""
_reset_missing_key_cache()
yield
_reset_missing_key_cache()
def test_known_key_returns_active_locale_value():
set_locale("en")
# ``api.projectNotFound`` is a long-standing key in both en.json and zh.json.
assert t("api.projectNotFound", id="abc") != ""
assert t("api.projectNotFound", id="abc") != "api.projectNotFound"
def test_zh_fallback_when_active_locale_lacks_key():
set_locale("en")
# Inject a zh-only key for this test, then assert lookup falls back to it.
locale_module._translations.setdefault("zh", {})["__test_zh_only_key__"] = "中文回退"
try:
assert t("__test_zh_only_key__") == "中文回退"
finally:
locale_module._translations["zh"].pop("__test_zh_only_key__", None)
def test_missing_key_returns_raw_key_string():
set_locale("en")
assert t("definitely.not.a.real.key.path") == "definitely.not.a.real.key.path"
def test_missing_key_never_raises_for_invalid_path_segments():
set_locale("en")
# ``api.projectNotFound`` resolves to a string; descending into it would
# otherwise crash. The helper must guard against that.
assert t("api.projectNotFound.deeper") == "api.projectNotFound.deeper"
def test_missing_key_emits_exactly_one_warning_per_pair(caplog):
set_locale("en")
target_logger_name = "mirofish.locale"
with caplog.at_level(logging.WARNING, logger=target_logger_name):
t("definitely.not.a.real.key.path")
t("definitely.not.a.real.key.path")
t("definitely.not.a.real.key.path")
warnings = [r for r in caplog.records if r.name == target_logger_name and r.levelno == logging.WARNING]
assert len(warnings) == 1
assert "definitely.not.a.real.key.path" in warnings[0].getMessage()
assert "en" in warnings[0].getMessage()
def test_reset_hook_allows_warning_to_fire_again(caplog):
set_locale("en")
target_logger_name = "mirofish.locale"
with caplog.at_level(logging.WARNING, logger=target_logger_name):
t("another.missing.key")
_reset_missing_key_cache()
t("another.missing.key")
warnings = [r for r in caplog.records if r.name == target_logger_name and r.levelno == logging.WARNING]
assert len(warnings) == 2
def test_distinct_missing_keys_each_warn_once(caplog):
set_locale("en")
target_logger_name = "mirofish.locale"
with caplog.at_level(logging.WARNING, logger=target_logger_name):
t("missing.key.one")
t("missing.key.two")
t("missing.key.one")
t("missing.key.two")
warnings = [r for r in caplog.records if r.name == target_logger_name and r.levelno == logging.WARNING]
assert len(warnings) == 2