Files
myagents/tests/test_ensure_backend.py
T
Zhengshou Lai 6702f67462 feat: guided auto-install for missing agent CLIs
Offer install with default Yes on TTY; auto-install when non-interactive
(install/CI). Resolve binaries from npm global prefix after install.
2026-08-09 15:54:23 +08:00

106 lines
3.7 KiB
Python

"""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()