mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 14:07:55 +00:00
The model catalog listed non-existent Qwen3.5 sizes (3B, 8B, 14B) and pointed to MLX community repos that don't exist, causing `jarvis init` to recommend models that cannot be downloaded on Apple Silicon. Replace with the actual Qwen3.5 model family sizes (0.8B, 2B, 9B, 27B) and verified mlx-community repo URLs from HuggingFace. Fixes #129 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
241 lines
9.6 KiB
Python
241 lines
9.6 KiB
Python
"""Tests for ``jarvis init`` next-steps guidance."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from click.testing import CliRunner
|
|
|
|
from openjarvis.cli import cli
|
|
from openjarvis.cli.init_cmd import _next_steps_text
|
|
|
|
_NO_DL = "--no-download"
|
|
|
|
|
|
class TestInitShowsNextSteps:
|
|
def test_init_shows_next_steps(self, tmp_path: Path) -> None:
|
|
"""Init command prints next-steps panel after writing config."""
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
|
):
|
|
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp", _NO_DL])
|
|
assert result.exit_code == 0
|
|
assert "Getting Started" in result.output
|
|
assert "jarvis ask" in result.output
|
|
assert "jarvis doctor" in result.output
|
|
|
|
def test_init_output_shows_toml_sections_literally(self, tmp_path: Path) -> None:
|
|
"""Init output should render TOML section headers like [engine] literally."""
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
|
):
|
|
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp", _NO_DL])
|
|
assert result.exit_code == 0
|
|
assert "[engine]" in result.output
|
|
assert "[intelligence]" in result.output
|
|
|
|
|
|
class TestNextStepsOllama:
|
|
def test_next_steps_ollama(self) -> None:
|
|
text = _next_steps_text("ollama")
|
|
assert "ollama serve" in text
|
|
assert "ollama pull" in text
|
|
assert "jarvis ask" in text
|
|
assert "jarvis doctor" in text
|
|
|
|
def test_next_steps_ollama_with_model(self) -> None:
|
|
text = _next_steps_text("ollama", "qwen3.5:27b")
|
|
assert "ollama pull qwen3.5:27b" in text
|
|
|
|
def test_next_steps_ollama_default_model(self) -> None:
|
|
text = _next_steps_text("ollama")
|
|
assert "ollama pull qwen3.5:2b" in text
|
|
|
|
|
|
class TestNextStepsVllm:
|
|
def test_next_steps_vllm(self) -> None:
|
|
text = _next_steps_text("vllm")
|
|
assert "pip install vllm" in text
|
|
assert "vllm serve" in text
|
|
assert "jarvis ask" in text
|
|
assert "jarvis doctor" in text
|
|
|
|
|
|
class TestNextStepsLlamacpp:
|
|
def test_next_steps_llamacpp(self) -> None:
|
|
text = _next_steps_text("llamacpp")
|
|
assert "brew install llama.cpp" in text
|
|
assert "llama-server" in text
|
|
assert "jarvis ask" in text
|
|
assert "jarvis doctor" in text
|
|
|
|
|
|
class TestNextStepsMlx:
|
|
def test_next_steps_mlx(self) -> None:
|
|
text = _next_steps_text("mlx")
|
|
assert "pip install mlx-lm" in text
|
|
assert "mlx_lm.server" in text
|
|
assert "jarvis ask" in text
|
|
assert "jarvis doctor" in text
|
|
|
|
|
|
class TestMinimalConfig:
|
|
def test_init_generates_minimal_by_default(self, tmp_path: Path) -> None:
|
|
"""Default jarvis init produces a short config."""
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
|
):
|
|
result = CliRunner().invoke(cli, ["init", "--engine", "ollama", _NO_DL])
|
|
assert result.exit_code == 0
|
|
content = config_path.read_text()
|
|
# Minimal config should be short
|
|
lines = [ln for ln in content.splitlines() if ln.strip()]
|
|
assert len(lines) <= 30
|
|
# Should have the reference hint
|
|
assert "jarvis init --full" in content
|
|
|
|
def test_init_full_generates_verbose_config(self, tmp_path: Path) -> None:
|
|
"""jarvis init --full produces the full reference config."""
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
|
):
|
|
result = CliRunner().invoke(
|
|
cli,
|
|
["init", "--full", "--engine", "ollama", _NO_DL],
|
|
)
|
|
assert result.exit_code == 0
|
|
content = config_path.read_text()
|
|
# Full config should have many sections
|
|
assert "[engine.ollama]" in content
|
|
assert "[server]" in content
|
|
assert "[security]" in content
|
|
|
|
|
|
class TestInitDownloadPrompt:
|
|
def test_init_shows_download_prompt(self, tmp_path: Path) -> None:
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
|
):
|
|
result = CliRunner().invoke(
|
|
cli, ["init", "--engine", "ollama"], input="n\n"
|
|
)
|
|
assert result.exit_code == 0
|
|
assert "Download" in result.output
|
|
assert "now?" in result.output
|
|
|
|
def test_init_no_download_flag_skips_prompt(self, tmp_path: Path) -> None:
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
|
):
|
|
result = CliRunner().invoke(cli, ["init", "--engine", "ollama", _NO_DL])
|
|
assert result.exit_code == 0
|
|
assert "Download" not in result.output
|
|
|
|
|
|
class TestInitEmptyModelFallback:
|
|
def test_init_no_model_shows_warning(self, tmp_path: Path) -> None:
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch("openjarvis.cli.init_cmd.recommend_model", return_value=""),
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
|
):
|
|
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp"])
|
|
assert result.exit_code == 0
|
|
assert (
|
|
"Not enough memory" in result.output or "not enough memory" in result.output
|
|
)
|
|
|
|
|
|
class TestNextStepsExoNexa:
|
|
def test_next_steps_exo(self) -> None:
|
|
text = _next_steps_text("exo")
|
|
assert "exo" in text.lower()
|
|
assert "jarvis ask" in text
|
|
assert "ollama" not in text.lower()
|
|
|
|
def test_next_steps_nexa(self) -> None:
|
|
text = _next_steps_text("nexa")
|
|
assert "nexa" in text.lower()
|
|
assert "jarvis ask" in text
|
|
assert "ollama" not in text.lower()
|
|
|
|
|
|
class TestInitDownloadDispatch:
|
|
def test_init_ollama_download_calls_ollama_pull(self, tmp_path: Path) -> None:
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch(
|
|
"openjarvis.cli.init_cmd.ollama_pull",
|
|
return_value=True,
|
|
) as mock_pull,
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
|
):
|
|
result = CliRunner().invoke(
|
|
cli, ["init", "--engine", "ollama"], input="y\n"
|
|
)
|
|
assert result.exit_code == 0
|
|
mock_pull.assert_called_once()
|
|
|
|
def test_init_vllm_shows_auto_download_message(self, tmp_path: Path) -> None:
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
|
):
|
|
result = CliRunner().invoke(cli, ["init", "--engine", "vllm"], input="y\n")
|
|
assert result.exit_code == 0
|
|
assert "automatically" in result.output
|
|
|
|
|
|
class TestInitPrivacyHook:
|
|
def test_init_shows_privacy_summary(self, tmp_path: Path) -> None:
|
|
config_dir = tmp_path / ".openjarvis"
|
|
config_path = config_dir / "config.toml"
|
|
with (
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_DIR", config_dir),
|
|
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
|
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner") as MockScanner,
|
|
):
|
|
from openjarvis.cli.scan_cmd import ScanResult
|
|
|
|
instance = MockScanner.return_value
|
|
instance.run_quick.return_value = [
|
|
ScanResult("FileVault", "ok", "FileVault enabled", "darwin"),
|
|
]
|
|
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp", _NO_DL])
|
|
assert result.exit_code == 0
|
|
assert "jarvis scan" in result.output
|