import json import sys import tempfile import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) from config import load class ConfigTests(unittest.TestCase): def test_defaults_when_file_missing(self): missing = Path(tempfile.gettempdir()) / "lel_missing_config_test.json" if missing.exists(): missing.unlink() cfg = load(str(missing)) self.assertEqual(cfg.daemon.hotkey, "Cmd+m") self.assertEqual(cfg.recording.input, "") self.assertEqual(cfg.stt.model, "base") self.assertEqual(cfg.stt.device, "cpu") self.assertEqual(cfg.injection.backend, "clipboard") self.assertFalse(cfg.injection.remove_transcription_from_clipboard) self.assertEqual(cfg.vocabulary.replacements, []) self.assertEqual(cfg.vocabulary.terms, []) self.assertTrue(cfg.domain_inference.enabled) def test_loads_nested_config(self): payload = { "daemon": {"hotkey": "Ctrl+space"}, "recording": {"input": 3}, "stt": {"model": "small", "device": "cuda"}, "injection": { "backend": "injection", "remove_transcription_from_clipboard": True, }, "vocabulary": { "replacements": [ {"from": "Martha", "to": "Marta"}, {"from": "docker", "to": "Docker"}, ], "terms": ["Systemd", "Kubernetes"], }, "domain_inference": {"enabled": True}, } with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") cfg = load(str(path)) self.assertEqual(cfg.daemon.hotkey, "Ctrl+space") self.assertEqual(cfg.recording.input, 3) self.assertEqual(cfg.stt.model, "small") self.assertEqual(cfg.stt.device, "cuda") self.assertEqual(cfg.injection.backend, "injection") self.assertTrue(cfg.injection.remove_transcription_from_clipboard) self.assertEqual(len(cfg.vocabulary.replacements), 2) self.assertEqual(cfg.vocabulary.replacements[0].source, "Martha") self.assertEqual(cfg.vocabulary.replacements[0].target, "Marta") self.assertEqual(cfg.vocabulary.terms, ["Systemd", "Kubernetes"]) self.assertTrue(cfg.domain_inference.enabled) def test_loads_legacy_keys(self): payload = { "hotkey": "Alt+m", "input": "Mic", "whisper_model": "tiny", "whisper_device": "cpu", "injection_backend": "clipboard", } with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") cfg = load(str(path)) self.assertEqual(cfg.daemon.hotkey, "Alt+m") self.assertEqual(cfg.recording.input, "Mic") self.assertEqual(cfg.stt.model, "tiny") self.assertEqual(cfg.stt.device, "cpu") self.assertEqual(cfg.injection.backend, "clipboard") self.assertFalse(cfg.injection.remove_transcription_from_clipboard) self.assertEqual(cfg.vocabulary.replacements, []) def test_invalid_injection_backend_raises(self): payload = {"injection": {"backend": "invalid"}} with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "injection.backend"): load(str(path)) def test_invalid_clipboard_remove_option_raises(self): payload = {"injection": {"remove_transcription_from_clipboard": "yes"}} with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "injection.remove_transcription_from_clipboard"): load(str(path)) def test_removed_ai_section_raises(self): payload = {"ai": {"enabled": True}} with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "ai section is no longer supported"): load(str(path)) def test_removed_legacy_ai_enabled_raises(self): payload = {"ai_enabled": True} with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "ai_enabled is no longer supported"): load(str(path)) def test_removed_logging_section_raises(self): payload = {"logging": {"log_transcript": True}} with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "no longer supported"): load(str(path)) def test_removed_legacy_log_transcript_raises(self): payload = {"log_transcript": True} with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "no longer supported"): load(str(path)) def test_conflicting_replacements_raise(self): payload = { "vocabulary": { "replacements": [ {"from": "Martha", "to": "Marta"}, {"from": "martha", "to": "Martha"}, ] } } with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "conflicting"): load(str(path)) def test_duplicate_rules_and_terms_are_deduplicated(self): payload = { "vocabulary": { "replacements": [ {"from": "docker", "to": "Docker"}, {"from": "DOCKER", "to": "Docker"}, ], "terms": ["Systemd", "systemd"], } } with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") cfg = load(str(path)) self.assertEqual(len(cfg.vocabulary.replacements), 1) self.assertEqual(cfg.vocabulary.replacements[0].source, "docker") self.assertEqual(cfg.vocabulary.replacements[0].target, "Docker") self.assertEqual(cfg.vocabulary.terms, ["Systemd"]) def test_wildcard_term_raises(self): payload = { "vocabulary": { "terms": ["Dock*"], } } with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "wildcard"): load(str(path)) def test_removed_domain_mode_raises(self): payload = {"domain_inference": {"mode": "heuristic"}} with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "domain_inference.mode is no longer supported"): load(str(path)) def test_removed_vocabulary_max_rules_raises(self): payload = {"vocabulary": {"max_rules": 100}} with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "vocabulary.max_rules is no longer supported"): load(str(path)) def test_removed_vocabulary_max_terms_raises(self): payload = {"vocabulary": {"max_terms": 100}} with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps(payload), encoding="utf-8") with self.assertRaisesRegex(ValueError, "vocabulary.max_terms is no longer supported"): load(str(path)) if __name__ == "__main__": unittest.main()