"""Tests for myagents.launcher.ensure_backend / guided auto-install.""" from __future__ import annotations from pathlib import Path from unittest.mock import patch from myagents import launcher as launcher_mod class TestEnsureBackend: def test_already_available_skips_install(self) -> None: with ( patch.object( launcher_mod, "_resolve_backend_binary", return_value="/usr/bin/claude", ), patch.object(launcher_mod, "_offer_install") as offer, ): assert launcher_mod.ensure_backend("claude") is True offer.assert_not_called() def test_noninteractive_auto_installs(self) -> None: with ( patch.object( launcher_mod, "_resolve_backend_binary", return_value=None ), patch.object(launcher_mod, "_is_interactive", return_value=False), patch.object( launcher_mod, "_run_install", return_value="/opt/bin/claude" ) as run_install, patch.object(launcher_mod, "click") as click_mod, ): assert launcher_mod.ensure_backend("claude") is True run_install.assert_called_once() click_mod.prompt.assert_not_called() def test_yes_true_skips_prompt(self) -> None: with ( patch.object( launcher_mod, "_resolve_backend_binary", return_value=None ), patch.object( launcher_mod, "_run_install", return_value="/opt/bin/claude" ) as run_install, patch.object(launcher_mod, "click") as click_mod, ): assert launcher_mod.ensure_backend("claude", yes=True) is True run_install.assert_called_once() click_mod.prompt.assert_not_called() def test_unknown_backend_false(self) -> None: assert launcher_mod.ensure_backend("nope") is False def test_hermes_cannot_auto_install(self) -> None: with ( patch.object( launcher_mod, "_resolve_backend_binary", return_value=None ), patch.object(launcher_mod, "_is_interactive", return_value=False), ): assert launcher_mod.ensure_backend("hermes") is False class TestResolveInstalledBinary: def test_falls_back_to_npm_global_bin(self, tmp_path: Path) -> None: bin_dir = tmp_path / "bin" bin_dir.mkdir() claude = bin_dir / "claude" claude.write_text("#!/bin/sh\n") claude.chmod(0o755) cfg = launcher_mod._BACKENDS["claude"] with ( patch.object( launcher_mod, "_resolve_backend_binary", return_value=None ), patch.object(launcher_mod, "_npm_global_bin", return_value=bin_dir), ): assert launcher_mod._resolve_installed_binary(cfg) == str(claude) class TestOfferInstallGuided: def test_interactive_default_yes_when_npm_present(self) -> None: cfg = launcher_mod._BACKENDS["claude"] with ( patch.object(launcher_mod, "_is_interactive", return_value=True), patch.object( launcher_mod.shutil, "which", return_value="/usr/bin/npm" ), patch.object( launcher_mod, "_run_install", return_value="/opt/bin/claude" ) as run_install, patch.object( launcher_mod.click, "prompt", return_value="y", ) as prompt, ): assert ( launcher_mod._offer_install(cfg, yes=None) == "/opt/bin/claude" ) prompt.assert_called_once() assert prompt.call_args.kwargs.get("default") == "y" run_install.assert_called_once()