import json import os import sys import tempfile import unittest from pathlib import Path from types import SimpleNamespace 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 aman_cli import aman_run from config import Config class _FakeDesktop: def __init__(self): self.hotkey = None self.hotkey_callback = None def start_hotkey_listener(self, hotkey, callback): self.hotkey = hotkey self.hotkey_callback = callback def stop_hotkey_listener(self): return def start_cancel_listener(self, callback): _ = callback return def stop_cancel_listener(self): return def validate_hotkey(self, hotkey): _ = hotkey return def inject_text(self, text, backend, *, remove_transcription_from_clipboard=False): _ = (text, backend, remove_transcription_from_clipboard) return def run_tray(self, _state_getter, on_quit, **_kwargs): on_quit() def request_quit(self): return class _HotkeyFailDesktop(_FakeDesktop): def start_hotkey_listener(self, hotkey, callback): _ = (hotkey, callback) raise RuntimeError("already in use") class _FakeDaemon: def __init__(self, cfg, _desktop, *, verbose=False, config_path=None): self.cfg = cfg self.verbose = verbose self.config_path = config_path self._paused = False def get_state(self): return "idle" def is_paused(self): return self._paused def toggle_paused(self): self._paused = not self._paused return self._paused def apply_config(self, cfg): self.cfg = cfg def toggle(self): return def shutdown(self, timeout=1.0): _ = timeout return True class _RetrySetupDesktop(_FakeDesktop): def __init__(self): super().__init__() self.settings_invocations = 0 def run_tray(self, _state_getter, on_quit, **kwargs): settings_cb = kwargs.get("on_open_settings") if settings_cb is not None and self.settings_invocations == 0: self.settings_invocations += 1 settings_cb() return on_quit() class AmanRunTests(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 = aman_run.lock_single_instance() try: with self.assertRaises(SystemExit) as ctx: aman_run.lock_single_instance() self.assertIn("already running", str(ctx.exception)) finally: first.close() def test_run_command_missing_config_uses_settings_ui_and_writes_file(self): with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" args = aman_cli.parse_cli_args(["run", "--config", str(path)]) desktop = _FakeDesktop() onboard_cfg = Config() onboard_cfg.daemon.hotkey = "Super+m" result = SimpleNamespace(saved=True, config=onboard_cfg, closed_reason="saved") with patch("aman_run.lock_single_instance", return_value=object()), patch( "aman_run.get_desktop_adapter", return_value=desktop ), patch("aman_run.run_config_ui", return_value=result) as config_ui_mock, patch( "aman_run.Daemon", _FakeDaemon ): exit_code = aman_run.run_command(args) self.assertEqual(exit_code, 0) self.assertTrue(path.exists()) self.assertEqual(desktop.hotkey, "Super+m") config_ui_mock.assert_called_once() def test_run_command_missing_config_cancel_returns_without_starting_daemon(self): with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" args = aman_cli.parse_cli_args(["run", "--config", str(path)]) desktop = _FakeDesktop() result = SimpleNamespace(saved=False, config=None, closed_reason="cancelled") with patch("aman_run.lock_single_instance", return_value=object()), patch( "aman_run.get_desktop_adapter", return_value=desktop ), patch("aman_run.run_config_ui", return_value=result), patch( "aman_run.Daemon" ) as daemon_cls: exit_code = aman_run.run_command(args) self.assertEqual(exit_code, 0) self.assertFalse(path.exists()) daemon_cls.assert_not_called() def test_run_command_missing_config_cancel_then_retry_settings(self): with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" args = aman_cli.parse_cli_args(["run", "--config", str(path)]) desktop = _RetrySetupDesktop() onboard_cfg = Config() config_ui_results = [ SimpleNamespace(saved=False, config=None, closed_reason="cancelled"), SimpleNamespace(saved=True, config=onboard_cfg, closed_reason="saved"), ] with patch("aman_run.lock_single_instance", return_value=object()), patch( "aman_run.get_desktop_adapter", return_value=desktop ), patch("aman_run.run_config_ui", side_effect=config_ui_results), patch( "aman_run.Daemon", _FakeDaemon ): exit_code = aman_run.run_command(args) self.assertEqual(exit_code, 0) self.assertTrue(path.exists()) self.assertEqual(desktop.settings_invocations, 1) def test_run_command_hotkey_failure_logs_actionable_issue(self): with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps({"config_version": 1}) + "\n", encoding="utf-8") args = aman_cli.parse_cli_args(["run", "--config", str(path)]) desktop = _HotkeyFailDesktop() with patch("aman_run.lock_single_instance", return_value=object()), patch( "aman_run.get_desktop_adapter", return_value=desktop ), patch("aman_run.load", return_value=Config()), patch( "aman_run.Daemon", _FakeDaemon ), self.assertLogs(level="ERROR") as logs: exit_code = aman_run.run_command(args) self.assertEqual(exit_code, 1) rendered = "\n".join(logs.output) self.assertIn("hotkey.parse: hotkey setup failed: already in use", rendered) self.assertIn("next_step: run `aman doctor --config", rendered) def test_run_command_daemon_init_failure_logs_self_check_next_step(self): with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps({"config_version": 1}) + "\n", encoding="utf-8") args = aman_cli.parse_cli_args(["run", "--config", str(path)]) desktop = _FakeDesktop() with patch("aman_run.lock_single_instance", return_value=object()), patch( "aman_run.get_desktop_adapter", return_value=desktop ), patch("aman_run.load", return_value=Config()), patch( "aman_run.Daemon", side_effect=RuntimeError("warmup boom") ), self.assertLogs(level="ERROR") as logs: exit_code = aman_run.run_command(args) self.assertEqual(exit_code, 1) rendered = "\n".join(logs.output) self.assertIn("startup.readiness: startup failed: warmup boom", rendered) self.assertIn("next_step: run `aman self-check --config", rendered) def test_run_command_logs_safe_config_payload(self): with tempfile.TemporaryDirectory() as td: path = Path(td) / "config.json" path.write_text(json.dumps({"config_version": 1}) + "\n", encoding="utf-8") custom_model_path = Path(td) / "custom-whisper.bin" custom_model_path.write_text("model\n", encoding="utf-8") args = aman_cli.parse_cli_args(["run", "--config", str(path)]) desktop = _FakeDesktop() cfg = Config() cfg.recording.input = "USB Mic" cfg.models.allow_custom_models = True cfg.models.whisper_model_path = str(custom_model_path) cfg.vocabulary.terms = ["SensitiveTerm"] with patch("aman_run.lock_single_instance", return_value=object()), patch( "aman_run.get_desktop_adapter", return_value=desktop ), patch("aman_run.load_runtime_config", return_value=cfg), patch( "aman_run.Daemon", _FakeDaemon ), self.assertLogs(level="INFO") as logs: exit_code = aman_run.run_command(args) self.assertEqual(exit_code, 0) rendered = "\n".join(logs.output) self.assertIn('"custom_whisper_path_configured": true', rendered) self.assertIn('"recording_input": "USB Mic"', rendered) self.assertNotIn(str(custom_model_path), rendered) self.assertNotIn("SensitiveTerm", rendered) if __name__ == "__main__": unittest.main()