"""Tests for the public-landing locale loader and language detection.""" from __future__ import annotations import pytest from app.services.locales import ( ACTIVE_PUBLIC_LANGS, DEFAULT_LANG, detect_public_lang, get_locale, load_locales, ) # --------------------------------------------------------------------------- # YAML loading # --------------------------------------------------------------------------- def test_locales_load_all_active_languages(): """Every code in ACTIVE_PUBLIC_LANGS must have a YAML file and return a usable dict — keeps deploys honest when a new language is added to the list but the YAML is missing.""" load_locales() for lang in ACTIVE_PUBLIC_LANGS: t = get_locale(lang) assert t, f"no copy loaded for {lang}" def test_locale_dotted_access(): """Templates use {{ t.hero.subhead }} syntax — the wrapper must expose nested dotted access through the whole tree.""" t = get_locale("en") assert isinstance(t.hero.tagline, str) assert isinstance(t.features.news.title, str) assert isinstance(t.not_strip.items, list) assert t.not_strip.items[0] # non-empty def test_unknown_locale_falls_back_to_default(): t = get_locale("zz") # Same shape as the default — the lookup should hand back the # default locale, not raise. assert hasattr(t, "hero") or t == {} def test_en_and_it_have_matching_top_level_keys(): """Translation parity check — both languages must define the same top-level sections. Lets a missing IT section show up in CI rather than as a 500 on the live page.""" en_keys = set(iter(get_locale("en"))) it_keys = set(iter(get_locale("it"))) assert en_keys == it_keys # --------------------------------------------------------------------------- # detect_public_lang precedence chain # --------------------------------------------------------------------------- def test_detect_user_lang_wins_over_everything(): """A logged-in user's stored preference is the highest-priority signal — never overridden by detection on a public surface.""" lang = detect_public_lang( cookie_lang="it", accept_language="fr,fr-FR;q=0.9", cf_country="DE", user_lang="en", ) assert lang == "en" def test_detect_cookie_wins_over_header_and_geo(): lang = detect_public_lang( cookie_lang="it", accept_language="en-US", cf_country="DE", user_lang=None, ) assert lang == "it" def test_detect_accept_language_first_subtag(): """Accept-Language is parsed to its base subtag — 'en-GB' resolves to 'en'. Falls through cookie (None) to header.""" lang = detect_public_lang( cookie_lang=None, accept_language="en-GB,en;q=0.9,it;q=0.5", cf_country=None, user_lang=None, ) assert lang == "en" def test_detect_geolocation_when_no_other_signal(): lang = detect_public_lang( cookie_lang=None, accept_language=None, cf_country="IT", user_lang=None, ) assert lang == "it" def test_detect_default_when_nothing_matches(): """Falls through to DEFAULT_LANG when no signal returns a code in ACTIVE_PUBLIC_LANGS. e.g. a French browser hitting from a German IP — neither lang is in our active set, default wins.""" lang = detect_public_lang( cookie_lang=None, accept_language="fr-FR,fr;q=0.9", cf_country="DE", user_lang=None, ) assert lang == DEFAULT_LANG def test_detect_invalid_cookie_ignored(): """A cookie pointing at a language we don't support shouldn't derail detection — fall through to the next signal.""" lang = detect_public_lang( cookie_lang="zz", accept_language="it", cf_country=None, user_lang=None, ) assert lang == "it" def test_detect_empty_inputs_default(): lang = detect_public_lang( cookie_lang=None, accept_language=None, cf_country=None, user_lang=None, ) assert lang == DEFAULT_LANG