"""Unit tests for the my-deepagent CLI (typer CliRunner).""" from __future__ import annotations from pathlib import Path import pytest from typer.testing import CliRunner import my_deepagent.keys as keys_module from my_deepagent.cli.main import app runner = CliRunner() class _FakeKeyring: def __init__(self) -> None: self.store: dict[tuple[str, str], str] = {} def get_password(self, service: str, username: str) -> str | None: return self.store.get((service, username)) def set_password(self, service: str, username: str, value: str) -> None: self.store[(service, username)] = value def delete_password(self, service: str, username: str) -> None: self.store.pop((service, username), None) @pytest.fixture def fake_keyring(monkeypatch: pytest.MonkeyPatch) -> _FakeKeyring: fake = _FakeKeyring() monkeypatch.setattr(keys_module.keyring, "get_password", fake.get_password) monkeypatch.setattr(keys_module.keyring, "set_password", fake.set_password) monkeypatch.setattr(keys_module.keyring, "delete_password", fake.delete_password) return fake def test_help_exit_zero() -> None: result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "mydeepagent" in result.output.lower() or "Usage" in result.output def test_no_subcommand_launches_repl_governance_check( monkeypatch: pytest.MonkeyPatch, ) -> None: """Without governance consent, the REPL exits 1 with an error.""" import my_deepagent.governance as gov_module monkeypatch.setattr(gov_module, "has_consent", lambda _: False) result = runner.invoke(app, []) # governance_not_accepted raises MyDeepAgentError which surfaces as exit 1 assert result.exit_code == 1 def test_doctor_exits_zero_normal_python(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: import sys import my_deepagent.cli.doctor as doctor_module # Ensure version is in valid range monkeypatch.setattr(sys, "version_info", (3, 12, 0, "final", 0)) # Patch has_consent inside the doctor module's namespace monkeypatch.setattr(doctor_module, "has_consent", lambda _: True) # Stub out async checks so doctor finishes without real DB / network monkeypatch.setattr( doctor_module, "_check_openrouter_api_key", lambda cfg: doctor_module.CheckResult("openrouter_api_key", "warn", "mocked"), ) async def _fake_ping(cfg: object) -> doctor_module.CheckResult: return doctor_module.CheckResult("openrouter_ping", "warn", "mocked") async def _fake_disk(cfg: object) -> doctor_module.CheckResult: return doctor_module.CheckResult("disk+db", "ok", "free=99.9GB, sqlite_integrity=ok") monkeypatch.setattr(doctor_module, "_check_openrouter_ping_and_upsert", _fake_ping) monkeypatch.setattr(doctor_module, "_check_disk_and_db", _fake_disk) result = runner.invoke(app, ["doctor"]) assert result.exit_code == 0 def test_doctor_exits_one_on_bad_python(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: import sys monkeypatch.setattr(sys, "version_info", (3, 10, 0, "final", 0)) monkeypatch.setattr(sys, "version", "3.10.0 (default, ...)") result = runner.invoke(app, ["doctor"]) assert result.exit_code == 1 def test_keys_empty_keyring(fake_keyring: _FakeKeyring) -> None: result = runner.invoke(app, ["keys"]) assert result.exit_code == 0 # Should show "none" message (Korean or English) assert "없음" in result.output or "none" in result.output.lower() def test_login_stores_key(fake_keyring: _FakeKeyring) -> None: result = runner.invoke(app, ["login", "openrouter"], input="sk-or-test-abc123\n") assert result.exit_code == 0 assert fake_keyring.store.get(("my-deepagent", "openrouter_api_key")) == "sk-or-test-abc123" def test_login_empty_input_exits_one(fake_keyring: _FakeKeyring) -> None: result = runner.invoke(app, ["login", "openrouter"], input="\n") assert result.exit_code == 1 def test_logout_after_login_removes_key(fake_keyring: _FakeKeyring) -> None: runner.invoke(app, ["login", "openrouter"], input="sk-or-test\n") result = runner.invoke(app, ["logout", "openrouter"]) assert result.exit_code == 0 assert fake_keyring.store.get(("my-deepagent", "openrouter_api_key")) is None def test_logout_not_found_shows_message(fake_keyring: _FakeKeyring) -> None: result = runner.invoke(app, ["logout", "openrouter"]) assert result.exit_code == 0 assert "keyring" in result.output or "없습니다" in result.output or "not_found" in result.output def test_keys_shows_entry_after_login(fake_keyring: _FakeKeyring) -> None: runner.invoke(app, ["login", "openrouter"], input="sk-or-v1-abcdefgh1234\n") result = runner.invoke(app, ["keys"]) assert result.exit_code == 0 assert "openrouter" in result.output assert "sk-or-v1" in result.output def test_init_governance_declined_exits_one( fake_keyring: _FakeKeyring, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: import my_deepagent.governance as gov_module monkeypatch.setattr(gov_module, "has_consent", lambda _: False) # Input: decline governance result = runner.invoke(app, ["init"], input="no\n") assert result.exit_code == 1 def test_init_governance_accepted_saves_key( fake_keyring: _FakeKeyring, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: import sys import my_deepagent.cli.doctor as doctor_module import my_deepagent.cli.init as init_module import my_deepagent.governance as gov_module recorded: list[Path] = [] def fake_record_consent(data_dir: Path) -> None: recorded.append(data_dir) monkeypatch.setattr(gov_module, "has_consent", lambda _: False) monkeypatch.setattr(init_module, "record_consent", fake_record_consent) # Ensure Python version check passes monkeypatch.setattr(sys, "version_info", (3, 12, 0, "final", 0)) # doctor_command() is called inside init — patch its async sub-checks so it # completes without network / DB access and passes governance in doctor's namespace. monkeypatch.setattr(doctor_module, "has_consent", lambda _: True) monkeypatch.setattr( doctor_module, "_check_openrouter_api_key", lambda cfg: doctor_module.CheckResult("openrouter_api_key", "warn", "mocked"), ) async def _fake_ping(cfg: object) -> doctor_module.CheckResult: return doctor_module.CheckResult("openrouter_ping", "warn", "mocked") async def _fake_disk(cfg: object) -> doctor_module.CheckResult: return doctor_module.CheckResult("disk+db", "ok", "free=99.9GB, sqlite_integrity=ok") monkeypatch.setattr(doctor_module, "_check_openrouter_ping_and_upsert", _fake_ping) monkeypatch.setattr(doctor_module, "_check_disk_and_db", _fake_disk) # Input: accept governance, then provide API key result = runner.invoke(app, ["init"], input="yes\nsk-or-init-test\n") assert result.exit_code == 0 assert len(recorded) == 1 assert fake_keyring.store.get(("my-deepagent", "openrouter_api_key")) == "sk-or-init-test"