merge: resolve conflicts with main — keep both DeepResearch tools + MCP functions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jon Saad-Falcon
2026-03-27 13:09:12 -07:00
co-authored by Claude Opus 4.6
108 changed files with 9316 additions and 2046 deletions
+154
View File
@@ -0,0 +1,154 @@
"""Tests for ``jarvis config set`` command."""
from __future__ import annotations
import os
from pathlib import Path
from unittest import mock
from click.testing import CliRunner
from openjarvis.cli import cli
class TestConfigSet:
def test_set_creates_config_file(self, tmp_path: Path) -> None:
"""config set creates config.toml if it doesn't exist."""
config_file = tmp_path / "config.toml"
env = {"OPENJARVIS_CONFIG": str(config_file)}
with mock.patch.dict(os.environ, env):
result = CliRunner().invoke(
cli, ["config", "set", "engine.default", "vllm"]
)
assert result.exit_code == 0
assert config_file.exists()
content = config_file.read_text()
assert "vllm" in content
def test_set_engine_ollama_host(self, tmp_path: Path) -> None:
"""config set writes engine.ollama.host correctly."""
config_file = tmp_path / "config.toml"
config_file.write_text('[engine]\ndefault = "ollama"\n')
env = {"OPENJARVIS_CONFIG": str(config_file)}
with (
mock.patch.dict(os.environ, env),
mock.patch("openjarvis.cli.config_cmd.httpx"),
):
result = CliRunner().invoke(
cli,
["config", "set", "engine.ollama.host", "http://192.168.1.50:11434"],
)
assert result.exit_code == 0
content = config_file.read_text()
assert "http://192.168.1.50:11434" in content
def test_set_preserves_existing_keys(self, tmp_path: Path) -> None:
"""config set preserves other keys in the file."""
config_file = tmp_path / "config.toml"
config_file.write_text(
'[engine]\ndefault = "ollama"\n\n'
"[intelligence]\n"
'default_model = "qwen2.5:3b"\n'
)
env = {"OPENJARVIS_CONFIG": str(config_file)}
with mock.patch.dict(os.environ, env):
result = CliRunner().invoke(
cli, ["config", "set", "engine.default", "vllm"]
)
assert result.exit_code == 0
content = config_file.read_text()
assert "vllm" in content
assert "qwen2.5:3b" in content
def test_set_invalid_key_rejected(self, tmp_path: Path) -> None:
"""config set rejects unknown keys."""
config_file = tmp_path / "config.toml"
config_file.write_text("")
env = {"OPENJARVIS_CONFIG": str(config_file)}
with mock.patch.dict(os.environ, env):
result = CliRunner().invoke(
cli, ["config", "set", "engine.olllama.host", "http://x:1234"]
)
assert result.exit_code != 0
assert "Unknown config key" in result.output
def test_set_engine_host_probes_reachable(self, tmp_path: Path) -> None:
"""config set probes engine host and reports reachability."""
config_file = tmp_path / "config.toml"
config_file.write_text("")
env = {"OPENJARVIS_CONFIG": str(config_file)}
with (
mock.patch.dict(os.environ, env),
mock.patch("openjarvis.cli.config_cmd.httpx") as mock_httpx,
):
mock_httpx.get.return_value = mock.Mock(status_code=200)
result = CliRunner().invoke(
cli,
["config", "set", "engine.ollama.host", "http://myserver:11434"],
)
assert result.exit_code == 0
assert "Reachable" in result.output
def test_set_engine_host_probes_unreachable(self, tmp_path: Path) -> None:
"""config set warns when engine host is unreachable, but still writes."""
config_file = tmp_path / "config.toml"
config_file.write_text("")
env = {"OPENJARVIS_CONFIG": str(config_file)}
with (
mock.patch.dict(os.environ, env),
mock.patch("openjarvis.cli.config_cmd.httpx") as mock_httpx,
):
mock_httpx.get.side_effect = Exception("Connection refused")
result = CliRunner().invoke(
cli,
["config", "set", "engine.ollama.host", "http://myserver:11434"],
)
assert result.exit_code == 0
output_lower = result.output.lower()
assert "unreachable" in output_lower or "warning" in output_lower
content = config_file.read_text()
assert "http://myserver:11434" in content
def test_set_integer_value(self, tmp_path: Path) -> None:
"""config set coerces integer values."""
config_file = tmp_path / "config.toml"
config_file.write_text("")
env = {"OPENJARVIS_CONFIG": str(config_file)}
with mock.patch.dict(os.environ, env):
result = CliRunner().invoke(
cli, ["config", "set", "intelligence.max_tokens", "2048"]
)
assert result.exit_code == 0
content = config_file.read_text()
assert "2048" in content
def test_set_float_value(self, tmp_path: Path) -> None:
"""config set coerces float values."""
config_file = tmp_path / "config.toml"
config_file.write_text("")
env = {"OPENJARVIS_CONFIG": str(config_file)}
with mock.patch.dict(os.environ, env):
result = CliRunner().invoke(
cli, ["config", "set", "intelligence.temperature", "0.9"]
)
assert result.exit_code == 0
content = config_file.read_text()
assert "0.9" in content
def test_set_missing_args(self) -> None:
"""config set with missing args shows usage error."""
result = CliRunner().invoke(cli, ["config", "set"])
assert result.exit_code != 0
def test_set_shows_confirmation(self, tmp_path: Path) -> None:
"""config set prints a confirmation message."""
config_file = tmp_path / "config.toml"
config_file.write_text("")
env = {"OPENJARVIS_CONFIG": str(config_file)}
with mock.patch.dict(os.environ, env):
result = CliRunner().invoke(
cli, ["config", "set", "engine.default", "vllm"]
)
assert result.exit_code == 0
assert "Set" in result.output
assert "engine.default" in result.output
+10
View File
@@ -35,3 +35,13 @@ class TestHintFunctions:
def test_hint_no_model_with_name(self):
msg = hint_no_model("qwen3:8b")
assert "qwen3:8b" in msg
def test_hint_no_engine_includes_remote_tip(self):
msg = hint_no_engine()
assert "config set" in msg
assert "OLLAMA_HOST" in msg
def test_hint_no_engine_with_name_includes_remote_tip(self):
msg = hint_no_engine("vllm")
assert "config set" in msg
assert "engine.vllm.host" in msg
+13 -29
View File
@@ -23,9 +23,7 @@ class TestInitShowsNextSteps:
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]
)
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
@@ -40,9 +38,7 @@ class TestInitShowsNextSteps:
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]
)
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp", _NO_DL])
assert result.exit_code == 0
assert "[engine]" in result.output
assert "[intelligence]" in result.output
@@ -57,12 +53,12 @@ class TestNextStepsOllama:
assert "jarvis doctor" in text
def test_next_steps_ollama_with_model(self) -> None:
text = _next_steps_text("ollama", "qwen3.5:14b")
assert "ollama pull qwen3.5:14b" in text
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:3b" in text
assert "ollama pull qwen3.5:2b" in text
class TestNextStepsVllm:
@@ -102,9 +98,7 @@ class TestMinimalConfig:
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]
)
result = CliRunner().invoke(cli, ["init", "--engine", "ollama", _NO_DL])
assert result.exit_code == 0
content = config_path.read_text()
# Minimal config should be short
@@ -158,9 +152,7 @@ class TestInitDownloadPrompt:
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]
)
result = CliRunner().invoke(cli, ["init", "--engine", "ollama", _NO_DL])
assert result.exit_code == 0
assert "Download" not in result.output
@@ -175,13 +167,10 @@ class TestInitEmptyModelFallback:
mock.patch("openjarvis.cli.init_cmd.recommend_model", return_value=""),
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
):
result = CliRunner().invoke(
cli, ["init", "--engine", "llamacpp"]
)
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
"Not enough memory" in result.output or "not enough memory" in result.output
)
@@ -200,9 +189,7 @@ class TestNextStepsExoNexa:
class TestInitDownloadDispatch:
def test_init_ollama_download_calls_ollama_pull(
self, tmp_path: Path
) -> None:
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 (
@@ -228,9 +215,7 @@ class TestInitDownloadDispatch:
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"
)
result = CliRunner().invoke(cli, ["init", "--engine", "vllm"], input="y\n")
assert result.exit_code == 0
assert "automatically" in result.output
@@ -245,12 +230,11 @@ class TestInitPrivacyHook:
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]
)
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp", _NO_DL])
assert result.exit_code == 0
assert "jarvis scan" in result.output
+117
View File
@@ -0,0 +1,117 @@
"""Tests for ``jarvis init --host`` option."""
from __future__ import annotations
from pathlib import Path
from unittest import mock
from click.testing import CliRunner
from openjarvis.cli import cli
from openjarvis.core.config import generate_default_toml, generate_minimal_toml
_NO_DL = "--no-download"
class TestInitHost:
def test_init_host_writes_to_config(self, tmp_path: Path) -> None:
"""jarvis init --host writes the host into config.toml."""
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",
"--host",
"http://192.168.1.50:11434",
_NO_DL,
],
)
assert result.exit_code == 0
content = config_path.read_text()
assert "http://192.168.1.50:11434" in content
def test_init_host_with_vllm(self, tmp_path: Path) -> None:
"""jarvis init --host applies to the selected engine."""
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", "--host", "http://10.0.0.5:8000", _NO_DL],
)
assert result.exit_code == 0
content = config_path.read_text()
assert "http://10.0.0.5:8000" in content
def test_init_host_probes_and_reports(self, tmp_path: Path) -> None:
"""jarvis init --host shows reachability status."""
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"),
mock.patch("openjarvis.cli.init_cmd.httpx") as mock_httpx,
):
mock_httpx.get.side_effect = Exception("Connection refused")
result = CliRunner().invoke(
cli,
["init", "--engine", "ollama", "--host", "http://bad:11434", _NO_DL],
)
assert result.exit_code == 0
output_lower = result.output.lower()
assert "unreachable" in output_lower or "warning" in output_lower
def test_init_without_host_still_works(self, tmp_path: Path) -> None:
"""jarvis init without --host still produces valid 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()
assert "[engine]" in content
class TestGenerateTomlHost:
def test_minimal_toml_with_host(self) -> None:
from openjarvis.core.config import HardwareInfo
hw = HardwareInfo()
toml_str = generate_minimal_toml(
hw, engine="ollama", host="http://remote:11434"
)
assert "http://remote:11434" in toml_str
assert "[engine.ollama]" in toml_str
def test_minimal_toml_without_host_has_comment(self) -> None:
from openjarvis.core.config import HardwareInfo
hw = HardwareInfo()
toml_str = generate_minimal_toml(hw, engine="ollama")
assert "# host" in toml_str
def test_default_toml_with_host(self) -> None:
from openjarvis.core.config import HardwareInfo
hw = HardwareInfo()
toml_str = generate_default_toml(
hw, engine="ollama", host="http://remote:11434"
)
assert "http://remote:11434" in toml_str
+8 -7
View File
@@ -15,6 +15,7 @@ class TestOllamaPull:
def test_ollama_pull_success(self) -> None:
import io
console = Console(file=io.StringIO())
mock_lines = [
'{"status": "pulling manifest"}',
@@ -28,7 +29,7 @@ class TestOllamaPull:
mock_resp.__exit__ = mock.MagicMock(return_value=False)
with mock.patch("httpx.stream", return_value=mock_resp):
result = ollama_pull("http://localhost:11434", "qwen3.5:3b", console)
result = ollama_pull("http://localhost:11434", "qwen3.5:2b", console)
assert result is True
def test_ollama_pull_connect_error(self) -> None:
@@ -38,7 +39,7 @@ class TestOllamaPull:
console = Console(file=io.StringIO())
with mock.patch("httpx.stream", side_effect=httpx.ConnectError("refused")):
result = ollama_pull("http://localhost:11434", "qwen3.5:3b", console)
result = ollama_pull("http://localhost:11434", "qwen3.5:2b", console)
assert result is False
@@ -58,14 +59,14 @@ class TestPullCliMultiEngine:
mock_run.return_value = mock.MagicMock(returncode=0)
result = runner.invoke(
cli, ["model", "pull", "qwen3.5:8b", "--engine", "llamacpp"]
cli, ["model", "pull", "qwen3.5:9b", "--engine", "llamacpp"]
)
assert result.exit_code == 0
mock_run.assert_called_once()
call_args = mock_run.call_args[0][0]
assert "huggingface-cli" in call_args
assert "qwen3.5-8b-q4_k_m.gguf" in call_args
assert "qwen3.5-9b-q4_k_m.gguf" in call_args
def test_pull_mlx_uses_huggingface_cli(self) -> None:
from openjarvis.cli import cli
@@ -80,14 +81,14 @@ class TestPullCliMultiEngine:
mock_run.return_value = mock.MagicMock(returncode=0)
result = runner.invoke(
cli, ["model", "pull", "qwen3.5:8b", "--engine", "mlx"]
cli, ["model", "pull", "qwen3.5:9b", "--engine", "mlx"]
)
assert result.exit_code == 0
mock_run.assert_called_once()
call_args = mock_run.call_args[0][0]
assert "huggingface-cli" in call_args
assert "mlx-community/Qwen3.5-8B-4bit" in call_args
assert "mlx-community/Qwen3.5-9B-MLX-4bit" in call_args
def test_pull_llamacpp_huggingface_cli_not_found(self) -> None:
from openjarvis.cli import cli
@@ -101,7 +102,7 @@ class TestPullCliMultiEngine:
mock_cfg.return_value.engine.ollama_host = None
result = runner.invoke(
cli, ["model", "pull", "qwen3.5:8b", "--engine", "llamacpp"]
cli, ["model", "pull", "qwen3.5:9b", "--engine", "llamacpp"]
)
assert result.exit_code != 0
+134 -21
View File
@@ -31,9 +31,7 @@ def _make_proc(
class TestScanResultDataclass:
def test_fields_exist(self) -> None:
r = ScanResult(
name="test", status="ok", message="all good", platform="all"
)
r = ScanResult(name="test", status="ok", message="all good", platform="all")
assert r.name == "test"
assert r.status == "ok"
assert r.message == "all good"
@@ -246,15 +244,9 @@ class TestPlatformFiltering:
other_plat = "linux" if current_plat == "darwin" else "darwin"
with patch.object(scanner, "_get_all_checks") as mock_get:
darwin_ck = MagicMock(
return_value=ScanResult("d", "ok", "msg", "darwin")
)
linux_ck = MagicMock(
return_value=ScanResult("l", "ok", "msg", "linux")
)
all_ck = MagicMock(
return_value=ScanResult("a", "ok", "msg", "all")
)
darwin_ck = MagicMock(return_value=ScanResult("d", "ok", "msg", "darwin"))
linux_ck = MagicMock(return_value=ScanResult("l", "ok", "msg", "linux"))
all_ck = MagicMock(return_value=ScanResult("a", "ok", "msg", "all"))
mock_get.return_value = [darwin_ck, linux_ck, all_ck]
results = scanner.run_all()
@@ -275,23 +267,19 @@ class TestRemoteAccess:
def test_no_remote_access(self) -> None:
scanner = PrivacyScanner()
with patch.object(scanner, "_run") as mock_run:
mock_run.return_value = CompletedProcess(
[], 1, stdout="", stderr=""
)
mock_run.return_value = CompletedProcess([], 1, stdout="", stderr="")
result = scanner.check_remote_access()
assert result.status == "ok"
def test_xrdp_running(self) -> None:
scanner = PrivacyScanner()
with patch.object(scanner, "_run") as mock_run:
def side_effect(cmd, **kw):
if any("xrdp" in str(c) for c in cmd):
return CompletedProcess(
cmd, 0, stdout="12345", stderr=""
)
return CompletedProcess(
cmd, 1, stdout="", stderr=""
)
return CompletedProcess(cmd, 0, stdout="12345", stderr="")
return CompletedProcess(cmd, 1, stdout="", stderr="")
mock_run.side_effect = side_effect
result = scanner.check_remote_access()
assert result.status == "warn"
@@ -348,3 +336,128 @@ class TestRunQuick:
names = {r.name for r in results}
assert "Network Exposure" not in names
assert "Screen Recording" not in names
# ---------------------------------------------------------------------------
# TestDNS
# ---------------------------------------------------------------------------
class TestDNS:
"""Tests for check_dns (macOS)."""
def test_encrypted_dns_detected(self) -> None:
scanner = PrivacyScanner()
scutil_out = (
"resolver #1\n nameserver[0] : 127.0.0.1\n flags : dns-over-https\n"
)
with (
patch("sys.platform", "darwin"),
patch("subprocess.run", return_value=_make_proc(stdout=scutil_out)),
):
result = scanner.check_dns()
assert result.status == "ok"
assert "Encrypted" in result.message or "DoH" in result.message
def test_plain_dns_detected(self) -> None:
scanner = PrivacyScanner()
scutil_out = (
"resolver #1\n nameserver[0] : 8.8.8.8\n nameserver[1] : 8.8.4.4\n"
)
with (
patch("sys.platform", "darwin"),
patch("subprocess.run", return_value=_make_proc(stdout=scutil_out)),
):
result = scanner.check_dns()
assert result.status == "warn"
assert "8.8.8.8" in result.message
def test_private_dns(self) -> None:
scanner = PrivacyScanner()
scutil_out = "resolver #1\n nameserver[0] : 192.168.1.1\n"
with (
patch("sys.platform", "darwin"),
patch("subprocess.run", return_value=_make_proc(stdout=scutil_out)),
):
result = scanner.check_dns()
assert result.status == "ok"
assert "192.168.1.1" in result.message
def test_skip_on_linux(self) -> None:
scanner = PrivacyScanner()
with patch("sys.platform", "linux"):
result = scanner.check_dns()
assert result.status == "skip"
def test_scutil_not_available(self) -> None:
scanner = PrivacyScanner()
with (
patch("sys.platform", "darwin"),
patch("subprocess.run", side_effect=FileNotFoundError),
):
result = scanner.check_dns()
assert result.status == "skip"
# ---------------------------------------------------------------------------
# TestExpandedRemoteAccess
# ---------------------------------------------------------------------------
class TestExpandedRemoteAccess:
"""Verify expanded remote-access process list."""
def test_ngrok_detected(self) -> None:
scanner = PrivacyScanner()
with patch.object(scanner, "_run") as mock_run:
def side_effect(cmd, **kw):
if any("ngrok" in str(c) for c in cmd):
return CompletedProcess(cmd, 0, stdout="99999", stderr="")
return CompletedProcess(cmd, 1, stdout="", stderr="")
mock_run.side_effect = side_effect
result = scanner.check_remote_access()
assert result.status == "warn"
assert "ngrok" in result.message
def test_tailscaled_detected(self) -> None:
scanner = PrivacyScanner()
with patch.object(scanner, "_run") as mock_run:
def side_effect(cmd, **kw):
if any("tailscaled" in str(c) for c in cmd):
return CompletedProcess(cmd, 0, stdout="88888", stderr="")
return CompletedProcess(cmd, 1, stdout="", stderr="")
mock_run.side_effect = side_effect
result = scanner.check_remote_access()
assert result.status == "warn"
# ---------------------------------------------------------------------------
# TestJsonOutput
# ---------------------------------------------------------------------------
class TestJsonOutput:
"""Test --json output flag."""
def test_json_output_structure(self) -> None:
from click.testing import CliRunner
from openjarvis.cli.scan_cmd import scan
runner = CliRunner()
with patch(
"openjarvis.cli.scan_cmd.PrivacyScanner.run_all",
return_value=[
ScanResult("Test", "ok", "all good", "all"),
],
):
result = runner.invoke(scan, ["--json"])
assert result.exit_code == 0
data = json.loads(result.output)
assert isinstance(data, list)
assert data[0]["name"] == "Test"
assert data[0]["status"] == "ok"