test(i18n): add comprehensive i18n tests

- 34 tests covering all i18n functionality
- Test set_language(), get_language(), _detect_language()
- Test t() translation, fallback, interpolation
- Test get_language_directive() for English and Spanish
- Test locale key consistency between en.json and es.json
- Test Settings.language field with env var
This commit is contained in:
criss717 2026-08-10 17:56:53 +02:00
parent 0fd997613b
commit 2ed1a69672

208
tests/test_i18n.py Normal file
View file

@ -0,0 +1,208 @@
"""Tests for the i18n internationalization module."""
from __future__ import annotations
import json
import os
from pathlib import Path
from unittest.mock import patch
import pytest
import strix.i18n as mod
from strix.config.settings import Settings
from strix.i18n import (
SUPPORTED_LANGUAGES,
_detect_language,
_load_locale,
_normalize_lang,
get_language,
get_language_directive,
set_language,
t,
)
@pytest.fixture(autouse=True)
def _reset_i18n_state():
"""Reset module-level state between tests."""
mod._language = None
mod._locales.clear()
yield
mod._language = None
mod._locales.clear()
class TestSetLanguage:
def test_set_language_spanish(self):
set_language("es")
assert get_language() == "es"
def test_set_language_english(self):
set_language("en")
assert get_language() == "en"
def test_set_language_normalizes(self):
set_language("ES")
assert get_language() == "es"
def test_set_language_none_resets(self):
set_language("es")
set_language(None)
# After reset, should fall back to detection
assert get_language() == "en" # default
def test_set_language_unsupported_falls_back(self):
set_language("fr")
assert get_language() == "en"
class TestNormalizeLang:
def test_normalize_lowercase(self):
assert _normalize_lang("es") == "es"
def test_normalize_uppercase(self):
assert _normalize_lang("ES") == "es"
def test_normalize_strips(self):
assert _normalize_lang(" es ") == "es"
def test_normalize_unsupported(self):
assert _normalize_lang("fr") == "en"
def test_normalize_empty(self):
assert _normalize_lang("") == "en"
class TestDetectLanguage:
def test_default_is_english(self):
assert _detect_language() == "en"
def test_explicit_set_takes_priority(self):
mod._language = "es"
assert _detect_language() == "es"
def test_env_var_detected(self):
with patch.dict(os.environ, {"STRIX_LANGUAGE": "es"}):
assert _detect_language() == "es"
def test_env_var_overrides_config(self):
with patch.dict(os.environ, {"STRIX_LANGUAGE": "es"}):
assert _detect_language() == "es"
def test_system_locale_detected(self):
with patch.dict(os.environ, {"LANG": "es_ES.UTF-8"}, clear=False):
mod._language = None
assert _detect_language() == "es"
class TestLoadLocale:
def test_load_english(self):
locale = _load_locale("en")
assert "cli.description" in locale
assert isinstance(locale, dict)
def test_load_spanish(self):
locale = _load_locale("es")
assert "cli.description" in locale
def test_load_missing_returns_empty(self):
locale = _load_locale("nonexistent")
assert locale == {}
def test_cached_after_first_load(self):
_load_locale("en")
assert "en" in mod._locales
class TestTranslationFunction:
def test_t_returns_english_by_default(self):
result = t("cli.description")
assert "Strix" in result
assert "Penetration" in result
def test_t_returns_spanish_when_set(self):
set_language("es")
result = t("cli.description")
assert "Penetración" in result
def test_t_falls_back_to_english_for_missing_key(self):
set_language("es")
# Use a key that exists in en but not es
result = t("cli.description")
# Should still return something (English fallback)
assert result != "cli.description"
def test_t_returns_key_for_missing(self):
result = t("nonexistent.key.xyz")
assert result == "nonexistent.key.xyz"
def test_t_interpolates_placeholders(self):
result = t("cli.scan_started", target="example.com")
assert "example.com" in result
def test_t_interpolates_spanish(self):
set_language("es")
result = t("cli.scan_started", target="example.com")
assert "example.com" in result
assert "escaneo" in result.lower()
def test_t_handles_missing_placeholder_gracefully(self):
# Should not raise, just return with missing placeholder
result = t("cli.scan_started") # target not provided
assert isinstance(result, str)
class TestLanguageDirective:
def test_english_returns_empty(self):
set_language("en")
directive = get_language_directive()
assert directive == ""
def test_spanish_returns_directive(self):
set_language("es")
directive = get_language_directive()
assert "Spanish" in directive
assert "CVE" in directive # preservation rule
assert "CWE" in directive
def test_directive_preserves_identifiers(self):
set_language("es")
directive = get_language_directive()
assert "CVE identifiers" in directive
assert "CWE identifiers" in directive
assert "CVSS scores" in directive
assert "Source code" in directive
assert "Shell commands" in directive
class TestLocaleKeyConsistency:
def test_all_en_keys_exist_in_es(self):
en = _load_locale("en")
es = _load_locale("es")
missing = set(en.keys()) - set(es.keys())
assert missing == set(), f"Missing Spanish keys: {missing}"
def test_locale_files_are_valid_json(self):
locales_dir = Path(__file__).parent.parent / "strix" / "locales"
for json_file in locales_dir.glob("*.json"):
data = json.loads(json_file.read_text(encoding="utf-8"))
assert isinstance(data, dict), f"{json_file.name} is not a dict"
def test_supported_languages_match_files(self):
locales_dir = Path(__file__).parent.parent / "strix" / "locales"
for lang in SUPPORTED_LANGUAGES:
locale_file = locales_dir / f"{lang}.json"
assert locale_file.exists(), f"Missing locale file: {locale_file}"
class TestSettingsLanguageField:
def test_settings_has_language_field(self):
s = Settings()
assert hasattr(s, "language")
assert s.language == "en"
def test_settings_language_from_env(self):
with patch.dict(os.environ, {"STRIX_LANGUAGE": "es"}):
s = Settings()
assert s.language == "es"