Files
microfish/backend/tests/test_language_policy.py
Kunthawat Greethong 8b84378fe1 feat: SaaS foundation for CrowdSight
Elevate MiroFish/CrowdSight from single-container dev to a SaaS foundation:

- Local memory backend (Zep-compatible): memory services/models, local graph
  builder + updater, AgentActivity seam, import-boundary isolation; Zep stays
  default, local is opt-in behind MEMORY_BACKEND. Semantic parity not yet proven.
- Durable product persistence: projects/simulations/reports schema (migration
  0007) + tenant/owner-scoped ProductRepository + dual-write + scoped_project
  read-first + ArtifactStore abstraction; durable JobQueue + worker.py.
- SaaS hardening: durable RateLimiter (wired to login), UsageService (LLM
  accounting), redacted AuditService, idempotency, CORS allowlist, safe API
  errors, single-use PasswordResetService + endpoints (covers invite-pending).
- Exactly 3 roles (super_admin/admin/user) with tenant authz policy.
- Admin UI: GET/POST/PATCH /api/admin/users + GET/PUT /api/admin/settings
  (super-admin only, encrypted/masked); AdminView.vue + SettingsView.vue with
  admin/super-admin route guards, th/en i18n.
- Production deploy topology: multi-stage Dockerfile (frontend build + gunicorn
  wsgi + nginx SPA-proxy + supervisord worker), backend/wsgi.py, gunicorn dep.

Backend 197 passed; frontend 10 tests + build green. ruff unavailable (gap).
No commit of credentials; secrets handled via env/.env.example.
Deferred: Zep semantic A/B parity, object storage cutover, mobile QA, EasyPanel
container build of deploy topology.
2026-08-31 13:05:21 +07:00

48 lines
1.9 KiB
Python

import importlib.util
from pathlib import Path
import sys
import unittest
_MODULE_PATH = Path(__file__).parents[1] / "app" / "utils" / "language_policy.py"
_SPEC = importlib.util.spec_from_file_location("language_policy_under_test", _MODULE_PATH)
assert _SPEC is not None and _SPEC.loader is not None
_LANGUAGE_POLICY = importlib.util.module_from_spec(_SPEC)
sys.modules[_SPEC.name] = _LANGUAGE_POLICY
_SPEC.loader.exec_module(_LANGUAGE_POLICY)
DEFAULT_LOCALE = _LANGUAGE_POLICY.DEFAULT_LOCALE
SUPPORTED_LOCALES = _LANGUAGE_POLICY.SUPPORTED_LOCALES
locale_from_accept_language = _LANGUAGE_POLICY.locale_from_accept_language
normalize_locale = _LANGUAGE_POLICY.normalize_locale
class LanguagePolicyTests(unittest.TestCase):
def test_supported_locales_are_thai_and_english(self):
self.assertEqual(SUPPORTED_LOCALES, ("th", "en"))
self.assertEqual(DEFAULT_LOCALE, "th")
def test_normalize_locale_accepts_language_region_values(self):
self.assertEqual(normalize_locale("en-US"), "en")
self.assertEqual(normalize_locale("TH_th"), "th")
def test_normalize_locale_migrates_legacy_chinese_to_default(self):
self.assertEqual(normalize_locale("zh"), "th")
self.assertEqual(normalize_locale("zh-CN"), "th")
def test_normalize_locale_falls_back_for_unknown_values(self):
self.assertEqual(normalize_locale(None), "th")
self.assertEqual(normalize_locale("fr"), "th")
self.assertEqual(normalize_locale(""), "th")
def test_accept_language_uses_quality_and_skips_zero_quality(self):
header = "zh-CN;q=1, fr-FR;q=0.9, en-US;q=0.8, th;q=0"
self.assertEqual(locale_from_accept_language(header), "en")
def test_accept_language_returns_default_when_no_supported_language_exists(self):
self.assertEqual(locale_from_accept_language("ja-JP, de;q=0.8"), "th")
if __name__ == "__main__":
unittest.main()