Refine config and runtime flow
This commit is contained in:
parent
85e082dd46
commit
b3be444625
16 changed files with 642 additions and 137 deletions
98
tests/test_config.py
Normal file
98
tests/test_config.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
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.assertTrue(cfg.ai.enabled)
|
||||
self.assertFalse(cfg.logging.log_transcript)
|
||||
|
||||
def test_loads_nested_config(self):
|
||||
payload = {
|
||||
"daemon": {"hotkey": "Ctrl+space"},
|
||||
"recording": {"input": 3},
|
||||
"stt": {"model": "small", "device": "cuda"},
|
||||
"injection": {"backend": "injection"},
|
||||
"ai": {"enabled": False},
|
||||
"logging": {"log_transcript": 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.assertFalse(cfg.ai.enabled)
|
||||
self.assertTrue(cfg.logging.log_transcript)
|
||||
|
||||
def test_loads_legacy_keys(self):
|
||||
payload = {
|
||||
"hotkey": "Alt+m",
|
||||
"input": "Mic",
|
||||
"whisper_model": "tiny",
|
||||
"whisper_device": "cpu",
|
||||
"injection_backend": "clipboard",
|
||||
"ai_enabled": False,
|
||||
"log_transcript": 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, "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.ai.enabled)
|
||||
self.assertTrue(cfg.logging.log_transcript)
|
||||
|
||||
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_logging_flag_raises(self):
|
||||
payload = {"logging": {"log_transcript": "yes"}}
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
path = Path(td) / "config.json"
|
||||
path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "logging.log_transcript"):
|
||||
load(str(path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
105
tests/test_leld.py
Normal file
105
tests/test_leld.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = ROOT / "src"
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
|
||||
import leld
|
||||
from config import Config
|
||||
|
||||
|
||||
class FakeDesktop:
|
||||
def __init__(self):
|
||||
self.inject_calls = []
|
||||
self.quit_calls = 0
|
||||
|
||||
def inject_text(self, text: str, backend: str) -> None:
|
||||
self.inject_calls.append((text, backend))
|
||||
|
||||
def request_quit(self) -> None:
|
||||
self.quit_calls += 1
|
||||
|
||||
|
||||
class FakeSegment:
|
||||
def __init__(self, text: str):
|
||||
self.text = text
|
||||
|
||||
|
||||
class FakeModel:
|
||||
def transcribe(self, _audio, language=None, vad_filter=None):
|
||||
return [FakeSegment("hello world")], {"language": language, "vad_filter": vad_filter}
|
||||
|
||||
|
||||
class FakeAudio:
|
||||
def __init__(self, size: int):
|
||||
self.size = size
|
||||
|
||||
|
||||
class DaemonTests(unittest.TestCase):
|
||||
def _config(self) -> Config:
|
||||
cfg = Config()
|
||||
cfg.ai.enabled = False
|
||||
cfg.logging.log_transcript = False
|
||||
return cfg
|
||||
|
||||
@patch("leld._build_whisper_model", return_value=FakeModel())
|
||||
@patch("leld.stop_audio_recording", return_value=FakeAudio(8))
|
||||
@patch("leld.start_audio_recording", return_value=(object(), object()))
|
||||
def test_toggle_start_stop_injects_text(self, _start_mock, _stop_mock, _model_mock):
|
||||
desktop = FakeDesktop()
|
||||
daemon = leld.Daemon(self._config(), desktop, verbose=False)
|
||||
daemon._start_stop_worker = (
|
||||
lambda stream, record, trigger, process_audio: daemon._stop_and_process(
|
||||
stream, record, trigger, process_audio
|
||||
)
|
||||
)
|
||||
|
||||
daemon.toggle()
|
||||
self.assertEqual(daemon.get_state(), leld.State.RECORDING)
|
||||
|
||||
daemon.toggle()
|
||||
|
||||
self.assertEqual(daemon.get_state(), leld.State.IDLE)
|
||||
self.assertEqual(desktop.inject_calls, [("hello world", "clipboard")])
|
||||
|
||||
@patch("leld._build_whisper_model", return_value=FakeModel())
|
||||
@patch("leld.stop_audio_recording", return_value=FakeAudio(8))
|
||||
@patch("leld.start_audio_recording", return_value=(object(), object()))
|
||||
def test_shutdown_stops_recording_without_injection(self, _start_mock, _stop_mock, _model_mock):
|
||||
desktop = FakeDesktop()
|
||||
daemon = leld.Daemon(self._config(), desktop, verbose=False)
|
||||
daemon._start_stop_worker = (
|
||||
lambda stream, record, trigger, process_audio: daemon._stop_and_process(
|
||||
stream, record, trigger, process_audio
|
||||
)
|
||||
)
|
||||
|
||||
daemon.toggle()
|
||||
self.assertEqual(daemon.get_state(), leld.State.RECORDING)
|
||||
|
||||
self.assertTrue(daemon.shutdown(timeout=0.2))
|
||||
self.assertEqual(daemon.get_state(), leld.State.IDLE)
|
||||
self.assertEqual(desktop.inject_calls, [])
|
||||
|
||||
|
||||
class LockTests(unittest.TestCase):
|
||||
def test_lock_rejects_second_instance(self):
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
with patch.dict(os.environ, {"XDG_RUNTIME_DIR": td}, clear=False):
|
||||
first = leld._lock_single_instance()
|
||||
try:
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
leld._lock_single_instance()
|
||||
self.assertIn("already running", str(ctx.exception))
|
||||
finally:
|
||||
first.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue