Four papercuts met while using the CLI: the test menu returned a verdict nobody could find at the end of the output, the file browser never closed once a file was picked, a wrong KeePass password killed the whole CLI, and a configuration key had drifted from the code that reads it. --- FR --- Quatre irritants rencontrés à l'usage : le menu Test rendait un verdict introuvable en fin de sortie, le navigateur de fichiers ne se fermait jamais une fois le fichier choisi, un mauvais mot de passe KeePass tuait tout le CLI, et une clé de configuration avait dérivé du code qui la lit. Assisted-by: Claude Opus 5
236 lines
7.5 KiB
Python
236 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from script.todo import todo_i18n
|
|
|
|
|
|
class TestTranslations(unittest.TestCase):
|
|
"""Test TRANSLATIONS dictionary integrity."""
|
|
|
|
def test_all_entries_have_fr_and_en(self):
|
|
for key, entry in todo_i18n.TRANSLATIONS.items():
|
|
self.assertIn("fr", entry, f"Key '{key}' missing 'fr' translation")
|
|
self.assertIn("en", entry, f"Key '{key}' missing 'en' translation")
|
|
|
|
def test_no_empty_translations(self):
|
|
for key, entry in todo_i18n.TRANSLATIONS.items():
|
|
for lang in ("fr", "en"):
|
|
self.assertTrue(
|
|
len(entry[lang]) > 0,
|
|
f"Key '{key}' has empty '{lang}' translation",
|
|
)
|
|
|
|
def test_translations_not_empty(self):
|
|
self.assertGreater(len(todo_i18n.TRANSLATIONS), 0)
|
|
|
|
|
|
class TestT(unittest.TestCase):
|
|
"""Test t() translation function."""
|
|
|
|
def setUp(self):
|
|
todo_i18n._current_lang = None
|
|
|
|
def tearDown(self):
|
|
todo_i18n._current_lang = None
|
|
|
|
def test_returns_french_when_lang_fr(self):
|
|
todo_i18n._current_lang = "fr"
|
|
result = todo_i18n.t("Quit")
|
|
self.assertEqual(result, "Quitter")
|
|
|
|
def test_returns_english_when_lang_en(self):
|
|
todo_i18n._current_lang = "en"
|
|
result = todo_i18n.t("Quit")
|
|
self.assertEqual(result, "Quit")
|
|
|
|
def test_unknown_key_returns_key(self):
|
|
todo_i18n._current_lang = "fr"
|
|
result = todo_i18n.t("nonexistent_key_xyz")
|
|
self.assertEqual(result, "nonexistent_key_xyz")
|
|
|
|
def test_fallback_to_fr_if_lang_missing(self):
|
|
todo_i18n._current_lang = "de"
|
|
result = todo_i18n.t("Quit")
|
|
self.assertEqual(result, "Quitter")
|
|
|
|
|
|
class TestGetLang(unittest.TestCase):
|
|
"""Test get_lang() function."""
|
|
|
|
def setUp(self):
|
|
todo_i18n._current_lang = None
|
|
|
|
def tearDown(self):
|
|
todo_i18n._current_lang = None
|
|
|
|
def test_returns_cached_lang(self):
|
|
todo_i18n._current_lang = "en"
|
|
result = todo_i18n.get_lang()
|
|
self.assertEqual(result, "en")
|
|
|
|
def test_reads_from_env_var_sh(self):
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".sh", delete=False
|
|
) as f:
|
|
f.write('EL_LANG="en"\n')
|
|
f.flush()
|
|
try:
|
|
with patch.object(todo_i18n, "ENV_VAR_FILE", f.name):
|
|
result = todo_i18n.get_lang()
|
|
self.assertEqual(result, "en")
|
|
finally:
|
|
os.unlink(f.name)
|
|
|
|
def test_reads_unquoted_lang(self):
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".sh", delete=False
|
|
) as f:
|
|
f.write("EL_LANG=fr\n")
|
|
f.flush()
|
|
try:
|
|
with patch.object(todo_i18n, "ENV_VAR_FILE", f.name):
|
|
result = todo_i18n.get_lang()
|
|
self.assertEqual(result, "fr")
|
|
finally:
|
|
os.unlink(f.name)
|
|
|
|
def test_env_variable_fallback(self):
|
|
with patch.object(
|
|
todo_i18n,
|
|
"ENV_VAR_FILE",
|
|
"/nonexistent/path",
|
|
), patch.dict(os.environ, {"EL_LANG": "en"}):
|
|
result = todo_i18n.get_lang()
|
|
self.assertEqual(result, "en")
|
|
|
|
def test_default_is_fr(self):
|
|
with patch.object(
|
|
todo_i18n,
|
|
"ENV_VAR_FILE",
|
|
"/nonexistent/path",
|
|
), patch.dict(os.environ, {}, clear=True):
|
|
result = todo_i18n.get_lang()
|
|
self.assertEqual(result, "fr")
|
|
|
|
def test_invalid_lang_in_file_falls_through(self):
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".sh", delete=False
|
|
) as f:
|
|
f.write('EL_LANG="de"\n')
|
|
f.flush()
|
|
try:
|
|
with patch.object(
|
|
todo_i18n, "ENV_VAR_FILE", f.name
|
|
), patch.dict(os.environ, {}, clear=True):
|
|
result = todo_i18n.get_lang()
|
|
self.assertEqual(result, "fr")
|
|
finally:
|
|
os.unlink(f.name)
|
|
|
|
|
|
class TestSetLang(unittest.TestCase):
|
|
"""Test set_lang() function."""
|
|
|
|
def setUp(self):
|
|
todo_i18n._current_lang = None
|
|
|
|
def tearDown(self):
|
|
todo_i18n._current_lang = None
|
|
|
|
def test_sets_current_lang(self):
|
|
# Détourner ENV_VAR_FILE comme le font les trois tests suivants :
|
|
# `set_lang()` PERSISTE, et sans ce détournement celui-ci écrivait
|
|
# dans le ./env_var.sh du dépôt, suivi par git.
|
|
with patch.object(todo_i18n, "ENV_VAR_FILE", "/nonexistent/path"):
|
|
todo_i18n.set_lang("en")
|
|
self.assertEqual(todo_i18n._current_lang, "en")
|
|
|
|
def test_persists_to_file_update(self):
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".sh", delete=False
|
|
) as f:
|
|
f.write('EL_LANG="fr"\nOTHER=value\n')
|
|
f.flush()
|
|
try:
|
|
with patch.object(todo_i18n, "ENV_VAR_FILE", f.name):
|
|
todo_i18n.set_lang("en")
|
|
with open(f.name) as rf:
|
|
content = rf.read()
|
|
self.assertIn('EL_LANG="en"', content)
|
|
self.assertIn("OTHER=value", content)
|
|
finally:
|
|
os.unlink(f.name)
|
|
|
|
def test_persists_to_file_append(self):
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".sh", delete=False
|
|
) as f:
|
|
f.write("SOME_VAR=123\n")
|
|
f.flush()
|
|
try:
|
|
with patch.object(todo_i18n, "ENV_VAR_FILE", f.name):
|
|
todo_i18n.set_lang("en")
|
|
with open(f.name) as rf:
|
|
content = rf.read()
|
|
self.assertIn('EL_LANG="en"', content)
|
|
self.assertIn("SOME_VAR=123", content)
|
|
finally:
|
|
os.unlink(f.name)
|
|
|
|
def test_nonexistent_file_no_crash(self):
|
|
with patch.object(
|
|
todo_i18n,
|
|
"ENV_VAR_FILE",
|
|
"/nonexistent/path",
|
|
):
|
|
todo_i18n.set_lang("en")
|
|
self.assertEqual(todo_i18n._current_lang, "en")
|
|
|
|
|
|
class TestLangIsConfigured(unittest.TestCase):
|
|
"""Test lang_is_configured() function."""
|
|
|
|
def test_returns_true_when_configured(self):
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".sh", delete=False
|
|
) as f:
|
|
f.write('EL_LANG="fr"\n')
|
|
f.flush()
|
|
try:
|
|
with patch.object(todo_i18n, "ENV_VAR_FILE", f.name):
|
|
result = todo_i18n.lang_is_configured()
|
|
self.assertTrue(result)
|
|
finally:
|
|
os.unlink(f.name)
|
|
|
|
def test_returns_false_when_not_configured(self):
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".sh", delete=False
|
|
) as f:
|
|
f.write("SOME_VAR=123\n")
|
|
f.flush()
|
|
try:
|
|
with patch.object(todo_i18n, "ENV_VAR_FILE", f.name):
|
|
result = todo_i18n.lang_is_configured()
|
|
self.assertFalse(result)
|
|
finally:
|
|
os.unlink(f.name)
|
|
|
|
def test_returns_false_when_file_missing(self):
|
|
with patch.object(
|
|
todo_i18n,
|
|
"ENV_VAR_FILE",
|
|
"/nonexistent/path",
|
|
):
|
|
result = todo_i18n.lang_is_configured()
|
|
self.assertFalse(result)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|