mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 19:02:16 +00:00
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:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user